From fc2316b0f21fb9e823f5b33d7b408599b6aafe33 Mon Sep 17 00:00:00 2001 From: Emanuele Trabattoni Date: Thu, 31 Jul 2025 16:15:36 +0200 Subject: [PATCH 1/4] refactor tyoes + added callbacks --- src/mqtt.cpp | 30 ++++++++++++++++++++++++++---- src/mqtt.h | 29 +++++++++++++++++++---------- 2 files changed, 45 insertions(+), 14 deletions(-) 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; }; From abe0cb08396e843845fee25e4ff0657e2a409cf2 Mon Sep 17 00:00:00 2001 From: Emanuele Trabattoni Date: Thu, 31 Jul 2025 16:16:06 +0200 Subject: [PATCH 2/4] improved responses content for commands and cronjobs --- src/commands.cpp | 50 ++++++++++++++++++++++++------------------------ src/cronjobs.cpp | 8 +++++++- src/main.cpp | 14 +++++++++++++- 3 files changed, 45 insertions(+), 27 deletions(-) diff --git a/src/commands.cpp b/src/commands.cpp index 3f6bf6a..1e0556b 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -71,9 +71,10 @@ 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["value"]["name"] = eventName; ArduinoJson::JsonDocument action; if (ArduinoJson::deserializeJson(action, actionStr) != ArduinoJson::DeserializationError::Ok) @@ -84,7 +85,7 @@ namespace commands } 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"; @@ -100,6 +101,7 @@ namespace commands response["cmd"] = "getCronJob"; auto &cron = Cron::getInstance(dev); auto eventName = params["name"].as(); + response["values"]["name"] = eventName; if (eventName.empty()) { @@ -115,7 +117,7 @@ namespace commands for (const auto &[name, event] : eventMap) { const auto cmd = std::get<0>(event); - response["values"]["name"] = cmd; + response["values"][name] = cmd; eventNum++; } LOG_INFO("getCronJob got [", eventNum, "] events"); @@ -137,7 +139,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; @@ -150,6 +151,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(), "]"); @@ -182,20 +184,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) @@ -206,17 +206,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"); @@ -229,15 +226,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; } @@ -270,11 +272,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()) { @@ -285,6 +282,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"); @@ -314,6 +313,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, "]"); @@ -359,7 +360,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; @@ -380,15 +381,13 @@ namespace commands if (!rtcOk || !ntpOk) { - response["values"]["status"] = "unable to get time"; + response["values"]["status"] = "invalid"; return response; } response["values"]["status"] = "valid"; - response["status"]["time"] = rtc.getTimeStr(); - + response["values"]["time"] = rtc.getTimeStr(); LOG_INFO("setTimeNTP -> RTC is [", response["status"]["time"].as().c_str(), "]"); - return response; } // SETTERS // @@ -475,7 +474,7 @@ namespace commands if (!rtcOk || !ntpOk) { - response["values"]["status"] = "unable to get time"; + response["values"]["status"] = "invalid"; return response; } @@ -485,6 +484,7 @@ namespace commands auto timeDiff = std::chrono::duration_cast(ntpTimePoint - rtcTimePoint); auto direction = timeDiff.count() >= 0 ? "BEYOND" : "AHEAD"; + response["values"]["status"] = "valid"; response["values"]["drift"] = (uint32_t)timeDiff.count(); response["values"]["direction"] = "RTC is [" + std::string(direction) + "] 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..1c20a88 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,16 @@ void loop() } }; + MQTTwrapper::MessageCallback onMessage = [&devices](const MQTTwrapper::Topic &topic, const MQTTwrapper::Message &message) + { + // devices.led.flashColor(250, devices.led.COLOR_BLUE); + }; + + MQTTwrapper::MessageCallback onPublish = [&devices](const MQTTwrapper::Topic &topic, const MQTTwrapper::Message &message) + { + // devices.led.flashColor(250, devices.led.COLOR_ORANGE); + }; + ///////////// CRONJOB ////////////// /////////////// CALLBACK ////////////// Cron::CronCallback cronCallback = [&mqtt](const ArduinoJson::JsonDocument &resp) @@ -132,6 +142,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); From eaa643bf3ceec6d4e780e2b670c797368fe8a519 Mon Sep 17 00:00:00 2001 From: Emanuele Trabattoni Date: Fri, 1 Aug 2025 10:38:29 +0200 Subject: [PATCH 3/4] fixed typo "values" --- src/commands.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/commands.cpp b/src/commands.cpp index 1e0556b..78989c3 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -74,13 +74,13 @@ namespace commands const auto &eventName = params["name"].as(); const auto &timeStr = params["cronExpr"].as(); const auto &actionStr = params["action"].as(); - response["value"]["name"] = eventName; + 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; } @@ -88,11 +88,11 @@ namespace commands 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) From 25aa2d6cb6ebc25ae9b522a4fba1af2661dcc4be Mon Sep 17 00:00:00 2001 From: Emanuele Trabattoni Date: Fri, 1 Aug 2025 10:38:41 +0200 Subject: [PATCH 4/4] led class refactor --- lib/GPIO/LED_Driver.cpp | 81 ++++++++++++++++++++++++++++------------- lib/GPIO/LED_Driver.h | 48 +++++++++++++----------- src/main.cpp | 8 ++-- 3 files changed, 87 insertions(+), 50 deletions(-) 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/main.cpp b/src/main.cpp index 1c20a88..a2609a2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -83,12 +83,14 @@ void loop() MQTTwrapper::MessageCallback onMessage = [&devices](const MQTTwrapper::Topic &topic, const MQTTwrapper::Message &message) { - // devices.led.flashColor(250, devices.led.COLOR_BLUE); + 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) { - // devices.led.flashColor(250, devices.led.COLOR_ORANGE); + LOG_INFO("onPublish callback [", topic.c_str(),"]"); + devices.led.flashColor(250, devices.led.COLOR_BLUE); }; ///////////// CRONJOB //////////////