diff --git a/lib/GPIO/LED_Driver.cpp b/lib/GPIO/LED_Driver.cpp index 84a63e9..9d00023 100644 --- a/lib/GPIO/LED_Driver.cpp +++ b/lib/GPIO/LED_Driver.cpp @@ -10,8 +10,8 @@ namespace drivers { LOG_INFO("Inizializing RGB Led"); pinMode(c_ledPin, OUTPUT); - m_lp.pin = c_ledPin; - m_lp.blinkTask = NULL; + m_blinkTask = NULL; + m_flashTimer = NULL; } Led::~Led() @@ -22,54 +22,85 @@ namespace drivers void Led::setColor(const color_t color) { + std::lock_guard lock(m_ledMutex); blinkStop(); + m_colorDefault = color; rgbLedWrite(c_ledPin, color.g, color.r, color.b); } + void Led::flashHandle(TimerHandle_t th) + { + Led *led = (Led *)pvTimerGetTimerID(th); + std::lock_guard lock(led->m_ledMutex); + rgbLedWrite(led->c_ledPin, led->m_colorDefault.g, led->m_colorDefault.r, led->m_colorDefault.b); // reset color to saved color + LOG_DEBUG("Led Flash timer expired"); + xTimerDelete(th, 0); + led->m_flashTimer = NULL; + } + + void Led::flashColor(const uint16_t tOn, const color_t color) + { + std::lock_guard lock(m_ledMutex); + if (m_flashTimer == NULL) + { + blinkStop(); + rgbLedWrite(c_ledPin, color.g, color.r, color.b); // set color to flash + m_flashTimer = xTimerCreate("flasher", pdMS_TO_TICKS(tOn), pdFALSE, this, flashHandle); + xTimerStart(m_flashTimer, 0); + LOG_DEBUG("Led Flash timer created"); + } + } + void Led::blinkColor(const uint16_t tOn, const uint16_t tOff, const color_t color) { + std::lock_guard lock(m_ledMutex); blinkStop(); - m_lp.color1 = color; - m_lp.color2 = {0, 0, 0}; - m_lp.tOn = tOn; - m_lp.tOff = tOff; - xTaskCreate(blinkTask, "blinker", TASK_STACK, static_cast(&m_lp), TASK_PRIORITY, &m_lp.blinkTask); + m_color1 = color; + m_color2 = {0, 0, 0}; + m_tOn = tOn; + m_tOff = tOff; + xTaskCreate(blinkTask, "blinker", TASK_STACK, this, TASK_PRIORITY, &m_blinkTask); } void Led::blinkAlternate(const uint16_t tOn, const uint16_t tOff, const color_t color1, const color_t color2) { - { - blinkStop(); - m_lp.color1 = color1; - m_lp.color2 = color2; - m_lp.tOn = tOn; - m_lp.tOff = tOff; - xTaskCreate(blinkTask, "blinker", TASK_STACK, static_cast(&m_lp), TASK_PRIORITY, &m_lp.blinkTask); - } + std::lock_guard lock(m_ledMutex); + blinkStop(); + m_color1 = color1; + m_color2 = color2; + m_tOn = tOn; + m_tOff = tOff; + xTaskCreate(blinkTask, "blinker", TASK_STACK, this, TASK_PRIORITY, &m_blinkTask); } void Led::blinkStop() { - if (m_lp.blinkTask != NULL) - vTaskDelete(m_lp.blinkTask); - m_lp.blinkTask = NULL; + if (m_blinkTask != NULL) + vTaskDelete(m_blinkTask); + m_blinkTask = NULL; } void Led::blinkTask(void *params) { + Led *led = static_cast(params); LOG_DEBUG("Blinker Task Created"); - led_params_t *lPar = static_cast(params); while (true) { - rgbLedWrite(lPar->pin, lPar->color1.g, lPar->color1.r, lPar->color1.b); - delay(lPar->tOn); - rgbLedWrite(lPar->pin, lPar->color2.g, lPar->color2.r, lPar->color2.b); // off - if (lPar->tOff == 0) + { + std::lock_guard lock(led->m_ledMutex); + rgbLedWrite(led->c_ledPin, led->m_color1.g, led->m_color1.r, led->m_color1.b); + } + delay(led->m_tOn); + { + std::lock_guard lock(led->m_ledMutex); + rgbLedWrite(led->c_ledPin, led->m_color2.g, led->m_color2.r, led->m_color2.b); // off + } + if (led->m_tOff == 0) break; - delay(lPar->tOff); + delay(led->m_tOff); } LOG_DEBUG("Blinker Task Ended"); - lPar->blinkTask = NULL; + led->m_blinkTask = NULL; vTaskDelete(NULL); } } diff --git a/lib/GPIO/LED_Driver.h b/lib/GPIO/LED_Driver.h index 5c5f185..8be0ef0 100644 --- a/lib/GPIO/LED_Driver.h +++ b/lib/GPIO/LED_Driver.h @@ -5,21 +5,22 @@ #define DEBUGLOG_DEFAULT_LOG_LEVEL_INFO #include +#include + namespace drivers { class Led { - const uint8_t c_ledPin = 38; - - public: + + public: typedef struct { uint8_t r; uint8_t g; uint8_t b; } color_t; - + const color_t COLOR_RED = {255, 0, 0}; const color_t COLOR_ORANGE = {255, 127, 0}; const color_t COLOR_YELLOW = {255, 255, 0}; @@ -30,32 +31,35 @@ namespace drivers const color_t COLOR_BLUE = {0, 0, 255}; const color_t COLOR_VIOLET = {127, 0, 255}; const color_t COLOR_MAGENTA = {255, 0, 255}; - - private: - typedef struct - { - color_t color1; - color_t color2; - uint8_t pin; - uint16_t tOn; - uint16_t tOff; - TaskHandle_t blinkTask; - } led_params_t; - - public: + + public: Led(); ~Led(); - + void setColor(const color_t color); + void flashColor(const uint16_t tOn, const color_t color); void blinkColor(const uint16_t tOn, const uint16_t tOff, const color_t color); void blinkAlternate(const uint16_t tOn, const uint16_t tOff, const color_t color1, const color_t color2); void blinkStop(); - - private: + + private: + static void flashHandle(TimerHandle_t th); static void blinkTask(void *params); + + private: + const uint8_t c_ledPin = 38; + + color_t m_color1; + color_t m_color2; + color_t m_colorDefault; - private: - led_params_t m_lp; + uint16_t m_tOn; + uint16_t m_tOff; + + TaskHandle_t m_blinkTask; + TimerHandle_t m_flashTimer; + + std::mutex m_ledMutex; }; } diff --git a/src/commands.cpp b/src/commands.cpp index 9f382cd..76a82cd 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -78,27 +78,28 @@ namespace commands ArduinoJson::JsonDocument response; response["cmd"] = "setCronJob"; - const auto &jobName = params["name"].as(); + const auto &eventName = params["name"].as(); const auto &timeStr = params["cronExpr"].as(); const auto &actionStr = params["action"].as(); + response["values"]["name"] = eventName; ArduinoJson::JsonDocument action; if (ArduinoJson::deserializeJson(action, actionStr) != ArduinoJson::DeserializationError::Ok) { LOG_ERROR("setCronJob unable to deserialize cron job [", actionStr.c_str(), "]"); - response["value"]["status"] = "invalid"; + response["values"]["status"] = "invalid"; return response; } auto &cron = Cron::getInstance(dev); - if (!cron.addEvent(jobName, timeStr, action)) + if (!cron.addEvent(eventName, timeStr, action)) { LOG_ERROR("setCronJob unable to add job [", actionStr.c_str(), "]"); - response["value"]["status"] = "invalid"; + response["values"]["status"] = "invalid"; return response; } LOG_INFO("setCronJob added job [", actionStr.c_str(), "]"); - response["value"]["status"] = "valid"; + response["values"]["status"] = "valid"; return response; } const ArduinoJson::JsonDocument Commands::getCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) @@ -107,6 +108,7 @@ namespace commands response["cmd"] = "getCronJob"; auto &cron = Cron::getInstance(dev); auto eventName = params["name"].as(); + response["values"]["name"] = eventName; if (eventName.empty()) { @@ -144,7 +146,6 @@ namespace commands ArduinoJson::JsonDocument action; action["cmd"] = cmd; action["params"] = cmdParams; - response["values"]["name"] = eventName; response["values"]["cronExpr"] = cron::to_cronstr(cronExpr); response["values"]["action"] = action; @@ -157,6 +158,7 @@ namespace commands response["cmd"] = "delCronJob"; auto &cron = Cron::getInstance(dev); auto eventName = params["name"].as(); + response["values"]["name"] = eventName; if (eventName.empty() || !cron.delEvent(eventName)) { LOG_ERROR("delCronJob failed to delete job [", eventName.c_str(), "]"); @@ -189,20 +191,18 @@ namespace commands const ArduinoJson::JsonDocument Commands::setHPlimit(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) { ArduinoJson::JsonDocument response; - { - std::string msg; - serializeJson(params, msg); - LOG_INFO("setHPlimit params ->", msg.c_str()); - }; + response["cmd"] = "setHPlimit"; if (!params["level"].is()) { LOG_ERROR("setHPlimit incorrect parameters"); return response; } const auto level = params["level"].as(); + response["values"]["level"] = level; if (!c_hpLimitsMap.contains(level)) { LOG_ERROR("setHPlimit invalid level", level.c_str()); + response["values"]["status"] = "invalid"; return response; } for (const auto [lvl, ro] : c_hpLimitsMap) @@ -213,17 +213,14 @@ namespace commands dev.io.digitalOutWrite(ro, false); } LOG_INFO("setHPlimit -> level", level.c_str()); + response["values"]["status"] = "valid"; return response; } const ArduinoJson::JsonDocument Commands::setHeating(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) { ArduinoJson::JsonDocument response; - { - std::string msg; - serializeJson(params, msg); - LOG_INFO("setHeating params ->", msg.c_str()); - }; + response["cmd"] = "setHeating"; if (params.isNull()) { LOG_ERROR("setHeating incorrect paramaters"); @@ -236,15 +233,20 @@ namespace commands if (params[lvl] == "ON") { dev.io.digitalOutWrite(ro, true); + response["values"][lvl] = "ON"; LOG_INFO("setHeating -> ", lvl.c_str(), "ON"); } else if (params[lvl] == "OFF") { dev.io.digitalOutWrite(ro, false); + response["values"][lvl] = "OFF"; LOG_INFO("setHeating -> ", lvl.c_str(), "OFF"); } else + { + response["values"][lvl] = "invalid"; LOG_ERROR("setHeating invalid valve state"); + } } return response; } @@ -277,11 +279,6 @@ namespace commands { ArduinoJson::JsonDocument response; auto &conf = Config::getInstance(); - { - std::string msg; - serializeJson(params, msg); - LOG_INFO("setIrrigation params ->", msg.c_str()); - }; response["cmd"] = "setIrrigation"; if (params.isNull()) { @@ -292,6 +289,8 @@ namespace commands const uint16_t tOn(params["timeOn"].as()); const uint16_t tPause(params["timePause"].as()); + response["values"]["zone"] = zone; + if (zone == "stop") { // stop all zones and reset timers LOG_INFO("setIrrigation stop all zones"); @@ -321,6 +320,8 @@ namespace commands return response; } + response["values"]["timeOn"] = tOn; + response["values"]["timePause"] = tPause; if (!c_irrigationValveMap.contains(zone) || tOn <= 0 || tPause <= 0) // verify if zone is a valid map key { LOG_ERROR("setIrrigation incorrect zone[", zone.c_str(), "] or time values tOn[", tOn, "] tPause[", tPause, "]"); @@ -366,7 +367,7 @@ namespace commands dev.io.digitalOutWrite(zoneIoNumber, true); xTimerStart(shTimer, 0); timerHandle = shTimer; - response["values"]["status"] = "ok"; + response["values"]["status"] = "valid"; LOG_INFO("setIrrigation zone [", timerName, "] tOn[", tOn, "] tPause[", tPause, "]"); } return response; @@ -387,15 +388,13 @@ namespace commands if (!rtcOk || !ntpOk) { - response["values"]["status"] = "unable to get time"; + response["values"]["status"] = "invalid"; return response; } response["values"]["status"] = "valid"; response["values"]["time"] = rtc.getTimeStr(); - LOG_INFO("setTimeNTP -> RTC is [", response["status"]["time"].as().c_str(), "]"); - return response; } // SETTERS // @@ -482,7 +481,7 @@ namespace commands if (!rtcOk || !ntpOk) { - response["values"]["status"] = "unable to get time"; + response["values"]["status"] = "invalid"; return response; } @@ -492,7 +491,8 @@ namespace commands auto timeDiff = std::chrono::duration_cast(ntpTimePoint - rtcTimePoint); auto direction = timeDiff.count() >= 0 ? "BEYOND" : "AHEAD"; - response["values"]["drift"] = (int32_t)timeDiff.count(); + response["values"]["status"] = "valid"; + response["values"]["drift"] = (uint32_t)timeDiff.count(); response["values"]["direction"] = "RTC is [" + std::string(direction) + "] NTP time"; LOG_INFO("getTimeDrift -> RTC is [", (int32_t)timeDiff.count(), "] sec, [", std::string(direction).c_str(), "] NTP time"); diff --git a/src/cronjobs.cpp b/src/cronjobs.cpp index 4e013ff..3301123 100644 --- a/src/cronjobs.cpp +++ b/src/cronjobs.cpp @@ -222,7 +222,13 @@ const bool Cron::processEvents() next = cron::cron_next(cronexrp, nowTm); // update next execution time only if event was executed // otherwise time tracking is lost LOG_INFO("Cron running event [", eventName.c_str(), "] next execution time [", drivers::PCF85063::tm2str(next).c_str(), "]"); - auto resp = commands::s_commandMap.at(cmd)(m_dev, cmdParams); // here the magic happens + auto action = commands::s_commandMap.at(cmd)(m_dev, cmdParams); // here the magic happens + ArduinoJson::JsonDocument resp; + resp["cmd"] = "logCronJob"; + resp["values"]["name"] = eventName; + resp["values"]["now"] = drivers::PCF85063::tm2str(nowTm).c_str(); + resp["values"]["next"] = drivers::PCF85063::tm2str(next).c_str(); + resp["values"]["action"] = action; if (m_callback) { m_callback(resp); diff --git a/src/main.cpp b/src/main.cpp index ff61ea9..a2609a2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -57,7 +57,7 @@ void loop() //////////////// MQTT ////////////// /////////////// CALLBACK ////////////// - std::function commandsCallback = + MQTTwrapper::ActionCallback commandsCallback = [&mqtt, &devices](const ArduinoJson::JsonDocument &doc) { if (!doc["cmd"].is()) @@ -81,6 +81,18 @@ void loop() } }; + MQTTwrapper::MessageCallback onMessage = [&devices](const MQTTwrapper::Topic &topic, const MQTTwrapper::Message &message) + { + LOG_INFO("onMessage callback [", topic.c_str(),"]"); + devices.led.flashColor(250, devices.led.COLOR_YELLOW); + }; + + MQTTwrapper::MessageCallback onPublish = [&devices](const MQTTwrapper::Topic &topic, const MQTTwrapper::Message &message) + { + LOG_INFO("onPublish callback [", topic.c_str(),"]"); + devices.led.flashColor(250, devices.led.COLOR_BLUE); + }; + ///////////// CRONJOB ////////////// /////////////// CALLBACK ////////////// Cron::CronCallback cronCallback = [&mqtt](const ArduinoJson::JsonDocument &resp) @@ -132,6 +144,8 @@ void loop() buzzer.beep(250, NOTE_B); led.setColor(led.COLOR_GREEN); mqtt.subscribe(conf.m_mqttSubscribe["commands"], commandsCallback); + mqtt.setOnMessageCb(onMessage); + mqtt.setOnPublishCb(onPublish); break; } delay(250); diff --git a/src/mqtt.cpp b/src/mqtt.cpp index 63d4f6a..ca5216f 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -44,7 +44,7 @@ const bool MQTTwrapper::disconnect() return true; } -const bool MQTTwrapper::subscribe(const topic_t &topic, const action_t action) +const bool MQTTwrapper::subscribe(const Topic &topic, const ActionCallback action) { if (m_actionMap.contains(topic)) { @@ -61,7 +61,7 @@ const bool MQTTwrapper::subscribe(const topic_t &topic, const action_t action) return false; } -const bool MQTTwrapper::unsubscribe(const topic_t &topic) +const bool MQTTwrapper::unsubscribe(const Topic &topic) { if (!m_actionMap.contains(topic)) { @@ -83,7 +83,7 @@ const bool MQTTwrapper::connected() return m_loopHandle != NULL; } -const bool MQTTwrapper::publish(const topic_t &topic, const ArduinoJson::JsonDocument obj) +const bool MQTTwrapper::publish(const Topic &topic, const ArduinoJson::JsonDocument obj) { std::string message; if (!m_client.connected()) @@ -99,12 +99,32 @@ const bool MQTTwrapper::publish(const topic_t &topic, const ArduinoJson::JsonDoc if (m_client.publish(topic.c_str(), message.c_str())) { LOG_DEBUG("MQTT published topic [", topic.c_str(), "] - message [", message.c_str(), "]"); + if (m_onPublish) + { + m_onPublish(topic, message); + } return true; } LOG_ERROR("MQTT failed to publish topic [", topic.c_str(), "] - message [", message.c_str(), "]"); return false; } +void MQTTwrapper::setOnMessageCb(MessageCallback cb) +{ + if (cb) + m_onReceive = cb; + else + LOG_ERROR("MQTT invalid onReceive Callback"); +} + +void MQTTwrapper::setOnPublishCb(MessageCallback cb) +{ + if (cb) + m_onPublish = cb; + else + LOG_ERROR("MQTT invalid onPublish Callback"); +} + void MQTTwrapper::callback(char *topic, uint8_t *payload, unsigned int length) { std::string pl; @@ -120,13 +140,15 @@ void MQTTwrapper::callback(char *topic, uint8_t *payload, unsigned int length) return; } -void MQTTwrapper::onMessage(const std::string topic, const std::string message) +void MQTTwrapper::onMessage(const Topic topic, const Message message) { ArduinoJson::JsonDocument obj; LOG_DEBUG("MQTT received topic [", topic.c_str(), "] - message [", message.c_str(), "]"); if (ArduinoJson::deserializeJson(obj, message) == ArduinoJson::DeserializationError::Ok) { m_actionMap[topic](obj); + if (m_onReceive) + m_onReceive(topic, message); return; } LOG_ERROR("MQTT failed to deserialize message\n", message.c_str()); diff --git a/src/mqtt.h b/src/mqtt.h index f199ac5..be90107 100644 --- a/src/mqtt.h +++ b/src/mqtt.h @@ -13,12 +13,16 @@ #include #include -typedef std::string topic_t; -typedef std::function action_t; // the actions receive a JsonObject containing the received message -typedef std::map action_map_t; - class MQTTwrapper { +public: + using Topic = std::string; + using Message = std::string; + using MessageCallback = std::function; + using ActionCallback = std::function; // the actions receive a JsonObject containing the received message + using StateChangeCallback = std::function; + + using ActionMap = std::map; private: const std::map stateMap = { @@ -31,8 +35,7 @@ private: {2, "MQTT_CONNECT_BAD_CLIENT_ID"}, {3, "MQTT_CONNECT_UNAVAILABLE"}, {4, "MQTT_CONNECT_BAD_CREDENTIALS"}, - {5, "MQTT_CONNECT_UNAUTHORIZED"} - }; + {5, "MQTT_CONNECT_UNAUTHORIZED"}}; private: static MQTTwrapper * @@ -54,10 +57,13 @@ public: const bool disconnect(); const bool connected(); - const bool subscribe(const topic_t &topic, const action_t action); - const bool unsubscribe(const topic_t &topic); + const bool subscribe(const Topic &topic, const ActionCallback action); + const bool unsubscribe(const Topic &topic); - const bool publish(const topic_t &topic, const ArduinoJson::JsonDocument obj); + const bool publish(const Topic &topic, const ArduinoJson::JsonDocument obj); + + void setOnMessageCb(MessageCallback cb); + void setOnPublishCb(MessageCallback cb); private: static void callback(char *topic, uint8_t *payload, unsigned int length); // C-style callback only to invoke onMessage @@ -68,8 +74,11 @@ private: private: const Config &m_config; - action_map_t m_actionMap; + ActionMap m_actionMap; NetworkClient m_tcp; PubSubClient m_client; TaskHandle_t m_loopHandle; + + MessageCallback m_onPublish; + MessageCallback m_onReceive; };