From 11106489789b536f19a3e7d92c56f228546bd5e2 Mon Sep 17 00:00:00 2001 From: Emanuele Trabattoni Date: Wed, 30 Jul 2025 15:24:11 +0200 Subject: [PATCH] added set time via ntp as command and retrieve all cron jobs --- include/config.h | 3 +- src/commands.cpp | 94 +++++++++++++++++++++++++++++++++++++----------- src/commands.h | 5 ++- src/cronjobs.cpp | 21 +++++++---- src/cronjobs.h | 14 ++++++-- src/main.cpp | 34 +++++++++++------- 6 files changed, 128 insertions(+), 43 deletions(-) diff --git a/include/config.h b/include/config.h index 1bb2f3f..041ce3a 100644 --- a/include/config.h +++ b/include/config.h @@ -232,7 +232,7 @@ private: m_mqttLoopTime = mqtt["loopTime"].as(); m_mqttKeepalive = mqtt["keepalive"].as(); m_mqttRetries = mqtt["retries"].as(); - auto subscribe = mqtt["subsribe"].as(); + auto subscribe = mqtt["subscribe"].as(); for (auto v : subscribe) { m_mqttSubscribe[v.key().c_str()] = v.value().as(); @@ -287,6 +287,7 @@ public: std::map m_mqttSubscribe = { {"commands", "etcontroller/hw/commands"}}; std::map m_mqttPublish = { + {"cronjobs", "etcontroller/hw/cronjobs"}, {"answers", "etcontroller/hw/answers"}, {"heatpump", "etcontroller/hw/heatpump"}, {"temperatures", "etcontroller/hw/temperatures"}, diff --git a/src/commands.cpp b/src/commands.cpp index f62db08..3f6bf6a 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -100,8 +100,30 @@ namespace commands response["cmd"] = "getCronJob"; auto &cron = Cron::getInstance(dev); auto eventName = params["name"].as(); + + if (eventName.empty()) + { + LOG_ERROR("getCronJob empty job name"); + response["values"]["status"] = "invalid"; + return response; + } + + if (eventName == "all") + { + const auto &eventMap = cron.getAllEvents(); + uint8_t eventNum(0); + for (const auto &[name, event] : eventMap) + { + const auto cmd = std::get<0>(event); + response["values"]["name"] = cmd; + eventNum++; + } + LOG_INFO("getCronJob got [", eventNum, "] events"); + return response; + } + Cron::CronEvent event; - if (eventName.empty() || !cron.getEvent(eventName, event)) + if (!cron.getEvent(eventName, event)) { LOG_ERROR("getCronJob failed to get job [", eventName.c_str(), "]"); response["values"]["status"] = "invalid"; @@ -176,12 +198,12 @@ namespace commands LOG_ERROR("setHPlimit invalid level", level.c_str()); return response; } - for (auto k : c_hpLimitsMap) + for (const auto [lvl, ro] : c_hpLimitsMap) { - if (level == k.first && level != "UNLIMITED") - dev.io.digitalOutWrite(k.second, true); + if (level == lvl && level != "UNLIMITED") + dev.io.digitalOutWrite(ro, true); else - dev.io.digitalOutWrite(k.second, false); + dev.io.digitalOutWrite(ro, false); } LOG_INFO("setHPlimit -> level", level.c_str()); return response; @@ -200,19 +222,19 @@ namespace commands LOG_ERROR("setHeating incorrect paramaters"); return response; } - for (auto v : c_heatingValveMap) + for (const auto [lvl, ro] : c_heatingValveMap) { - if (params[v.first].isNull()) + if (params[lvl].isNull()) continue; - if (params[v.first] == "ON") + if (params[lvl] == "ON") { - dev.io.digitalOutWrite(v.second, true); - LOG_INFO("setHeating -> ", v.first.c_str(), "ON"); + dev.io.digitalOutWrite(ro, true); + LOG_INFO("setHeating -> ", lvl.c_str(), "ON"); } - else if (params[v.first] == "OFF") + else if (params[lvl] == "OFF") { - dev.io.digitalOutWrite(v.second, false); - LOG_INFO("setHeating -> ", v.first.c_str(), "OFF"); + dev.io.digitalOutWrite(ro, false); + LOG_INFO("setHeating -> ", lvl.c_str(), "OFF"); } else LOG_ERROR("setHeating invalid valve state"); @@ -342,6 +364,33 @@ namespace commands } return response; } + + const ArduinoJson::JsonDocument Commands::setTimeNTP(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) + { + ArduinoJson::JsonDocument response; + response["cmd"] = "setTimeNTP"; + auto ð = dev.eth; + auto &rtc = dev.rtc; + + time_t ntpTime; + auto ntpOk = eth.getNtpTime(ntpTime); + + drivers::PCF85063::datetime_t rtcTime(drivers::PCF85063::fromEpoch(ntpTime)); + auto rtcOk = rtc.setDatetime(rtcTime); + + if (!rtcOk || !ntpOk) + { + response["values"]["status"] = "unable to get time"; + return response; + } + + response["values"]["status"] = "valid"; + response["status"]["time"] = rtc.getTimeStr(); + + LOG_INFO("setTimeNTP -> RTC is [", response["status"]["time"].as().c_str(), "]"); + + return response; + } // SETTERS // // SETTERS // @@ -350,8 +399,8 @@ namespace commands const ArduinoJson::JsonDocument Commands::getHPpower(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) { ArduinoJson::JsonDocument response; - const auto pinfo = dev.seneca.getAll(); response["cmd"] = "getHPpower"; + const auto pinfo = dev.seneca.getAll(); auto values = response["values"].to(); values["power"] = pinfo.pAct; values["current"] = pinfo.a; @@ -409,13 +458,13 @@ namespace commands LOG_WARN("Comand not yet implemented"); return response; } - + const ArduinoJson::JsonDocument Commands::getTimeDrift(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) { ArduinoJson::JsonDocument response; response["cmd"] = "getTimeDrift"; - auto& eth= dev.eth; - auto& rtc = dev.rtc; + auto ð = dev.eth; + auto &rtc = dev.rtc; time_t ntpTime; auto ntpOk = eth.getNtpTime(ntpTime); @@ -424,8 +473,9 @@ namespace commands auto rtcOk = rtc.readDatetime(rtcTime); auto rtcTimeTm = drivers::PCF85063::datetime2tm(rtcTime); - if (!rtcOk || !ntpOk) { - response["value"]["status"] = "unable to get time"; + if (!rtcOk || !ntpOk) + { + response["values"]["status"] = "unable to get time"; return response; } @@ -433,11 +483,13 @@ namespace commands auto rtcTimePoint = std::chrono::system_clock::from_time_t(std::mktime(&rtcTimeTm)); auto timeDiff = std::chrono::duration_cast(ntpTimePoint - rtcTimePoint); - auto direction = timeDiff.count() >= 0 ? "BEYOND" : "AHEAD"; + auto direction = timeDiff.count() >= 0 ? "BEYOND" : "AHEAD"; - response["values"]["drift"] = timeDiff.count(); + response["values"]["drift"] = (uint32_t)timeDiff.count(); response["values"]["direction"] = "RTC is [" + std::string(direction) + "] NTP time"; + LOG_INFO("getTimeDrift -> RTC is [", (uint32_t)timeDiff.count(), "] sec, [", std::string(direction).c_str(), "] NTP time"); + return response; } // GETTERS // diff --git a/src/commands.h b/src/commands.h index 03ac8f3..fcca973 100644 --- a/src/commands.h +++ b/src/commands.h @@ -79,6 +79,7 @@ namespace commands static const ArduinoJson::JsonDocument setHPlimit(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); static const ArduinoJson::JsonDocument setHeating(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); static const ArduinoJson::JsonDocument setIrrigation(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); + static const ArduinoJson::JsonDocument setTimeNTP(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); // GETTERS // static const ArduinoJson::JsonDocument getHPpower(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); @@ -92,7 +93,7 @@ namespace commands static const ArduinoJson::JsonDocument getTimeDrift(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); }; - static const std::map commandMap = { + static const std::map s_commandMap = { {"setConfig", Commands::setConfig}, {"getConfig", Commands::getConfig}, @@ -108,7 +109,9 @@ namespace commands {"getHPpower", Commands::getHPpower}, {"setHeating", Commands::setHeating}, + {"getTimeDrift", Commands::getTimeDrift}, + {"setTimeNTP", Commands::setTimeNTP}, }; } \ No newline at end of file diff --git a/src/cronjobs.cpp b/src/cronjobs.cpp index 8c69fe9..4e013ff 100644 --- a/src/cronjobs.cpp +++ b/src/cronjobs.cpp @@ -67,7 +67,7 @@ const bool Cron::storeEvents() ArduinoJson::JsonDocument thisJob; thisJob["name"] = eventName; - thisJob["cronExpr"] = cron::to_cronstr(cronExpr); + thisJob["cronExpr"] = cron::to_cronstr(cronExpr); thisJob["action"]["cmd"] = cmd; thisJob["action"]["params"] = cmdParams; @@ -102,7 +102,7 @@ const bool Cron::addEvent(const std::string &name, const std::string &expr, cons const auto eventExpr(cron::make_cron(expr)); const auto cmd = action["cmd"].as(); const auto params = action["params"]; - if (!commands::commandMap.contains(cmd)) + if (!commands::s_commandMap.contains(cmd)) { LOG_ERROR("Cron unknown command [", cmd.c_str(), "]"); return false; @@ -153,9 +153,14 @@ const bool Cron::delEvent(const std::string &name) return true; } -void cronLoop(void *dev) +const Cron::CronEventMap &Cron::getAllEvents() { - auto &cron = Cron::getInstance(*(devices_t *)dev); + return m_cronMap; +} + +void cronLoop(void *cronPtr) +{ + auto &cron = *(Cron *)cronPtr; while (true) { cron.processEvents(); @@ -168,7 +173,7 @@ void Cron::startCron() if (!m_cronTaskHandle) { LOG_INFO("Cron starting loop"); - xTaskCreate(cronLoop, "cronLoop", STACK_DEPTH, (void *)&m_dev, PRIORITY, &m_cronTaskHandle); + xTaskCreate(cronLoop, "cronLoop", STACK_DEPTH, this, PRIORITY, &m_cronTaskHandle); } } @@ -217,7 +222,11 @@ 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(), "]"); - commands::commandMap.at(cmd)(m_dev, cmdParams); // here the magic happens + auto resp = commands::s_commandMap.at(cmd)(m_dev, cmdParams); // here the magic happens + if (m_callback) + { + m_callback(resp); + } } } return true; diff --git a/src/cronjobs.h b/src/cronjobs.h index 714529c..4dc8e40 100644 --- a/src/cronjobs.h +++ b/src/cronjobs.h @@ -8,14 +8,17 @@ #include #include +#include #include #include class Cron { -public: // eventName cronExpression nextExec command parameters +public: // eventName cronExpression nextExec command parameters using CronEvent = std::tuple; + using CronEventMap = std::map; + using CronCallback = std::function; public: static Cron &getInstance(const devices_t &dev) @@ -30,11 +33,17 @@ private: Cron &operator=(const Cron &) = delete; public: + void setResponseCallback(CronCallback &cb) + { + m_callback = cb; + } + const bool loadEvents(); const bool storeEvents(); const bool addEvent(const std::string &name, const std::string &expr, const ArduinoJson::JsonDocument action); const bool getEvent(const std::string &name, CronEvent &event); const bool delEvent(const std::string &name); + const CronEventMap &getAllEvents(); void startCron(); void stopCron(); @@ -42,7 +51,8 @@ public: private: const devices_t &m_dev; + CronCallback m_callback; + CronEventMap m_cronMap; TaskHandle_t m_cronTaskHandle; - std::map m_cronMap; std::mutex m_mutex; }; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 6d2883d..ff61ea9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -55,13 +55,7 @@ void loop() auto mqtt = MQTTwrapper(); //////////////// MQTT //////////////// - //////////////// CRONJOB //////////////// - auto &cron = Cron::getInstance(devices); - cron.loadEvents(); - cron.startCron(); - //////////////// CRONJOB //////////////// - - //////////////// MQTT //////////////// + //////////////// MQTT ////////////// /////////////// CALLBACK ////////////// std::function commandsCallback = [&mqtt, &devices](const ArduinoJson::JsonDocument &doc) @@ -73,10 +67,10 @@ void loop() } const std::string cmd = doc["cmd"].as(); const ArduinoJson::JsonDocument params = doc["params"]; - if (commands::commandMap.contains(cmd)) + if (commands::s_commandMap.contains(cmd)) { // call command from command map in this same thread (the MQTT thread) LOG_INFO("Executing command", cmd.c_str()); - const auto answer = std::move(commands::commandMap.at(cmd)(devices, params)); // here the magic happens + const auto answer = std::move(commands::s_commandMap.at(cmd)(devices, params)); // here the magic happens if (answer.isNull()) return; mqtt.publish(conf.m_mqttPublish["answers"], answer); @@ -87,6 +81,22 @@ void loop() } }; + ///////////// CRONJOB ////////////// + /////////////// CALLBACK ////////////// + Cron::CronCallback cronCallback = [&mqtt](const ArduinoJson::JsonDocument &resp) + { + if (resp.isNull()) + return; + mqtt.publish(conf.m_mqttPublish["cronjobs"], resp); + }; + + //////////////// CRONJOB //////////////// + auto &cron = Cron::getInstance(devices); + cron.setResponseCallback(cronCallback); + cron.loadEvents(); + cron.startCron(); + //////////////// CRONJOB //////////////// + //////////////// NETWORK //////////////// /////////////// CALLBACK //////////////// Network.onEvent( @@ -113,7 +123,7 @@ void loop() LOG_INFO("NTP Time: ", drivers::PCF85063::datetime2str(dt).c_str()); break; } - delay(100); + delay(250); } while (mqttRetries++ < conf.m_mqttRetries) { @@ -124,7 +134,7 @@ void loop() mqtt.subscribe(conf.m_mqttSubscribe["commands"], commandsCallback); break; } - delay(100); + delay(250); } }); @@ -170,7 +180,7 @@ void loop() { LOG_WARN("RESTART!"); buzzer.beep(450, NOTE_D); - delay(100); + delay(450); esp_restart(); }