From f9c5ab86eff8f905547d4105f726246dc13988ff Mon Sep 17 00:00:00 2001 From: Emanuele Trabattoni Date: Fri, 29 Aug 2025 19:30:41 +0200 Subject: [PATCH] refactor cronjobs --- .vscode/settings.json | 72 ++++++++++++++++++++++++++++++++++- src/commands.cpp | 76 ++++++++++++++++++++++++++++--------- src/commands.h | 7 ++++ src/cronjobs.cpp | 88 ++++++++++++++++++++++++------------------- src/cronjobs.h | 32 ++++++++++++++-- 5 files changed, 216 insertions(+), 59 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 2ce7a63..51e7c3e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,75 @@ { "files.associations": { - "esp32-hal.h": "c" + "*.h": "cpp", + "esp32-hal.h": "c", + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "charconv": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "codecvt": "cpp", + "compare": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "list": "cpp", + "map": "cpp", + "set": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "netfwd": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "ratio": "cpp", + "source_location": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "format": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "mutex": "cpp", + "new": "cpp", + "numbers": "cpp", + "ostream": "cpp", + "semaphore": "cpp", + "span": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "text_encoding": "cpp", + "thread": "cpp", + "cinttypes": "cpp", + "typeinfo": "cpp", + "variant": "cpp" } } \ No newline at end of file diff --git a/src/commands.cpp b/src/commands.cpp index baa0b19..e5e29ba 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -71,10 +71,10 @@ namespace commands response["values"]["status"] = "valid"; return response; } - const ArduinoJson::JsonDocument Commands::setCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) + const ArduinoJson::JsonDocument Commands::addCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) { ArduinoJson::JsonDocument response; - response["cmd"] = "setCronJob"; + response["cmd"] = "addCronJob"; const auto &eventName = params["name"].as(); const auto &timeStr = params["cronExpr"].as(); @@ -84,7 +84,7 @@ namespace commands ArduinoJson::JsonDocument action; if (ArduinoJson::deserializeJson(action, actionStr) != ArduinoJson::DeserializationError::Ok) { - LOG_ERROR("setCronJob unable to deserialize cron job [", actionStr.c_str(), "]"); + LOG_ERROR("addCronJob unable to deserialize cron job [", actionStr.c_str(), "]"); response["values"]["status"] = "invalid"; return response; } @@ -92,11 +92,31 @@ namespace commands auto &cron = Cron::getInstance(dev); if (!cron.addEvent(eventName, timeStr, action)) { - LOG_ERROR("setCronJob unable to add job [", actionStr.c_str(), "]"); + LOG_ERROR("addCronJob unable to add job [", actionStr.c_str(), "]"); response["values"]["status"] = "invalid"; return response; } - LOG_INFO("setCronJob added job [", actionStr.c_str(), "]"); + LOG_INFO("addCronJob added job [", actionStr.c_str(), "]"); + response["values"]["status"] = "valid"; + return response; + } + const ArduinoJson::JsonDocument Commands::setCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) + { + ArduinoJson::JsonDocument response; + response["cmd"] = "setCronJob"; + const auto &eventName = params["name"].as(); + const auto &statusStr = params["status"].as(); + response["values"]["name"] = eventName; + + auto &cron = Cron::getInstance(dev); + if (!cron.c_statusStr2Enum.contains(statusStr)) + { + LOG_ERROR("setCronJob invalid status [", statusStr.c_str(), "]"); + response["values"]["status"] = "invalid"; + return response; + } + cron.setEvent(eventName, cron.c_statusStr2Enum.at(statusStr)); + LOG_INFO("setCronJob set job [", eventName.c_str(), "] to [", statusStr.c_str(), "]"); response["values"]["status"] = "valid"; return response; } @@ -120,8 +140,7 @@ namespace commands uint8_t eventNum(0); for (const auto &[name, event] : eventMap) { - const auto cmd = std::get<0>(event); - response["values"][name] = cmd; + response["values"][name] = event.cmd; eventNum++; } LOG_INFO("getCronJob got [", eventNum, "] events"); @@ -137,16 +156,11 @@ namespace commands return response; } - auto cmd = std::get<0>(event); - auto cronExpr = std::get<1>(event); - auto cmdParams = std::get<3>(event); - ArduinoJson::JsonDocument action; - action["cmd"] = cmd; - action["params"] = cmdParams; - response["values"]["cronExpr"] = cron::to_cronstr(cronExpr); + action["cmd"] = event.cmd; + action["params"] = event.cmdParams; + response["values"]["cronExpr"] = cron::to_cronstr(event.cronExpr); response["values"]["action"] = action; - LOG_INFO("getCronJob get job [", eventName.c_str(), "]"); return response; } @@ -283,7 +297,6 @@ namespace commands const std::string zone(params["zone"].as()); const uint16_t tOn(params["timeOn"].as()); const uint16_t tPause(params["timePause"].as()); - response["values"]["zone"] = zone; if (zone == "stop") @@ -304,7 +317,7 @@ namespace commands } } LOG_INFO("setIrrigation closing", zoneName.c_str()); - dev.io.digitalOutWrite(c_irrigationValveMap.at(zoneName), false); // shuto down the valve + dev.io.digitalOutWrite(c_irrigationValveMap.at(zoneName), false); // shutdown the valve } if (s_irrigationPumpTimer) { @@ -315,6 +328,13 @@ namespace commands return response; } + if (!s_rainOverride && !dev.io.digitalInRead(DI::RAIN)) // verify rain sensor and override value (rain sensor input is inverted) + { + LOG_WARN("setIrrigation skipping zone [", zone.c_str(), "] because its raining"); + response["values"]["status"] = "rain"; + 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 @@ -360,6 +380,7 @@ namespace commands if (shTimer) { dev.io.digitalOutWrite(zoneIoNumber, true); + // controllare riempimento serbatoio con controllo del pressostato, magari in un timer xTimerStart(shTimer, 0); timerHandle = shTimer; response["values"]["status"] = "valid"; @@ -367,6 +388,20 @@ namespace commands } return response; } + const ArduinoJson::JsonDocument Commands::setRainOverride(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) + { + ArduinoJson::JsonDocument response; + response["cmd"] = "setRainOverride"; + if (params.isNull()) + { + LOG_ERROR("setRainOverride incorrect paramaters"); + return response; + } + s_rainOverride = params["rainOverride"].as() == "True" ? true : false; + response["values"]["status"] = "valid"; + LOG_INFO("setRainOverride [", s_rainOverride ? "True]" : "False]"); + return response; + } const ArduinoJson::JsonDocument Commands::setTimeNTP(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) { ArduinoJson::JsonDocument response; @@ -477,6 +512,13 @@ namespace commands LOG_WARN("Comand not yet implemented"); return response; } + const ArduinoJson::JsonDocument Commands::getRainOverride(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) + { + ArduinoJson::JsonDocument response; + response["cmd"] = "getRainOverride"; + response["values"]["rainOverride"] = s_rainOverride ? "True" : "False"; + return response; + } const ArduinoJson::JsonDocument Commands::getTimeDrift(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) { ArduinoJson::JsonDocument response; diff --git a/src/commands.h b/src/commands.h index 0fc3d3e..c8a6910 100644 --- a/src/commands.h +++ b/src/commands.h @@ -35,6 +35,7 @@ namespace commands {"rubinetti", {"rubinetti", NULL}}}; static TimerHandle_t s_irrigationPumpTimer = NULL; + static bool s_rainOverride = false; // define command callback type using Command = std::function; @@ -53,6 +54,7 @@ namespace commands // CRONJOBS // static const ArduinoJson::JsonDocument loadCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); + static const ArduinoJson::JsonDocument addCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); static const ArduinoJson::JsonDocument setCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); static const ArduinoJson::JsonDocument getCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); static const ArduinoJson::JsonDocument delCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); @@ -62,6 +64,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 setRainOverride(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); static const ArduinoJson::JsonDocument setTimeNTP(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); // GETTERS // @@ -73,6 +76,7 @@ namespace commands static const ArduinoJson::JsonDocument getTankInfo(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); static const ArduinoJson::JsonDocument getRainInfo(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); static const ArduinoJson::JsonDocument getIrrigation(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); + static const ArduinoJson::JsonDocument getRainOverride(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); static const ArduinoJson::JsonDocument getTimeDrift(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); }; @@ -84,6 +88,7 @@ namespace commands {"getConfig", Commands::getConfig}, // CRONJOBS {"loadCronJob", Commands::loadCronJob}, + {"addCronJob", Commands::addCronJob}, {"setCronJob", Commands::setCronJob}, {"getCronJob", Commands::getCronJob}, {"delCronJob", Commands::delCronJob}, @@ -92,10 +97,12 @@ namespace commands {"setHPlimit", Commands::setHPlimit}, {"setHeating", Commands::setHeating}, {"setIrrigation", Commands::setIrrigation}, + {"setRainOverride", Commands::setRainOverride}, // GETTERS {"getHPpower", Commands::getHPpower}, {"getInputStatus", Commands::getInputStatus}, {"getOutputStatus", Commands::getOutputStatus}, + {"getRainOverride", Commands::getRainOverride}, // NTP and Time {"getTimeDrift", Commands::getTimeDrift}, {"setTimeNTP", Commands::setTimeNTP}, diff --git a/src/cronjobs.cpp b/src/cronjobs.cpp index 3301123..67723c2 100644 --- a/src/cronjobs.cpp +++ b/src/cronjobs.cpp @@ -31,8 +31,9 @@ const bool Cron::loadEvents() { const auto &eventName = job["name"].as(); const auto &cronExpr = job["cronExpr"].as(); + const auto status = c_statusStr2Enum.at(job["status"].as()); ArduinoJson::JsonDocument action(job["action"]); - if (!addEvent(eventName, cronExpr, action)) + if (!addEvent(eventName, cronExpr, action, status)) LOG_ERROR("Cron failed to load event [", eventName.c_str(), "]"); else LOG_INFO("Cron loaded event [", eventName.c_str(), "]"); @@ -56,21 +57,14 @@ const bool Cron::storeEvents() ArduinoJson::JsonDocument cronFileContent; ArduinoJson::JsonArray cronFileArray = cronFileContent.to(); - for (const auto &job : m_cronMap) // convert cron events map to json file + for (const auto &[eventName, eventParams] : m_cronMap) // convert cron events map to json file { - const auto &eventName = job.first; - const auto ¶ms = job.second; - - const auto &cmd = std::get<0>(params); - const auto &cronExpr = std::get<1>(params); - const auto &cmdParams = std::get<3>(params); - ArduinoJson::JsonDocument thisJob; thisJob["name"] = eventName; - thisJob["cronExpr"] = cron::to_cronstr(cronExpr); - thisJob["action"]["cmd"] = cmd; - thisJob["action"]["params"] = cmdParams; - + thisJob["cronExpr"] = cron::to_cronstr(eventParams.cronExpr); + thisJob["status"] = c_statusEnum2Str.at(eventParams.status); + thisJob["action"]["cmd"] = eventParams.cmd; + thisJob["action"]["params"] = eventParams.cmdParams; cronFileArray.add(thisJob); } @@ -83,7 +77,7 @@ const bool Cron::storeEvents() return true; } -const bool Cron::addEvent(const std::string &name, const std::string &expr, const ArduinoJson::JsonDocument action) +const bool Cron::addEvent(const std::string &name, const std::string &expr, const ArduinoJson::JsonDocument action, const CronStatus status) { std::lock_guard lock(m_mutex); if (m_cronMap.contains(name)) @@ -99,9 +93,9 @@ const bool Cron::addEvent(const std::string &name, const std::string &expr, cons try { - const auto eventExpr(cron::make_cron(expr)); const auto cmd = action["cmd"].as(); const auto params = action["params"]; + const auto cronExpr(cron::make_cron(expr)); if (!commands::s_commandMap.contains(cmd)) { LOG_ERROR("Cron unknown command [", cmd.c_str(), "]"); @@ -113,11 +107,11 @@ const bool Cron::addEvent(const std::string &name, const std::string &expr, cons LOG_ERROR("Cron unable to update current time"); return false; } - std::tm nowTm = drivers::PCF85063::datetime2tm(now); - auto next = cron::cron_next(eventExpr, nowTm); - JsonDocument act(params); + const std::tm nowTm = drivers::PCF85063::datetime2tm(now); + const std::tm next = cron::cron_next(cronExpr, nowTm); + JsonDocument cmdParams(params); // create a copy of command parameters LOG_INFO("Cron adding event [", name.c_str(), "] next execution [", drivers::PCF85063::tm2str(next).c_str(), "]"); - m_cronMap[name] = std::make_tuple(cmd, eventExpr, next, act); + m_cronMap[name] = CronEvent(cmd, cmdParams, cronExpr, next, status); } catch (cron::bad_cronexpr const &ex) { @@ -127,6 +121,19 @@ const bool Cron::addEvent(const std::string &name, const std::string &expr, cons return true; } +const bool Cron::setEvent(const std::string &name, const CronStatus status) +{ + std::lock_guard lock(m_mutex); + if (!m_cronMap.contains(name)) + { + LOG_ERROR("Cron event [", name.c_str(), "] does not exist"); + return false; + } + LOG_INFO("Cron set event [", name.c_str(), "] status [", c_statusEnum2Str.at(status).c_str(), "]"); + m_cronMap.at(name).status = status; + return true; +} + const bool Cron::getEvent(const std::string &name, CronEvent &event) { std::lock_guard lock(m_mutex); @@ -201,38 +208,43 @@ const bool Cron::processEvents() std::tm nowTm = drivers::PCF85063::datetime2tm(now); - for (auto &event : m_cronMap) + for (auto &[eventName, eventParams] : m_cronMap) { - auto &eventName = event.first; - auto &eventAction = event.second; - - auto &cmd = std::get<0>(eventAction); - auto &cronexrp = std::get<1>(eventAction); - auto &next = std::get<2>(eventAction); - auto &cmdParams = std::get<3>(eventAction); - const auto nowPoint = std::chrono::system_clock::from_time_t(std::mktime(&nowTm)); - const auto nextEventPoint = std::chrono::system_clock::from_time_t(std::mktime(&next)); + const auto nextEventPoint = std::chrono::system_clock::from_time_t(std::mktime(&eventParams.next)); LOG_DEBUG("Cron current time [", std::asctime(&nowTm), "]"); - LOG_DEBUG("Cron checking event [", eventName.c_str(), "] executionTime [", drivers::PCF85063::tm2str(next).c_str(), "]"); + LOG_DEBUG("Cron checking event [", eventName.c_str(), "] executionTime [", drivers::PCF85063::tm2str(eventParams.next).c_str(), "]"); if (nextEventPoint <= nowPoint) // execution time hs passed, run event { - 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 action = commands::s_commandMap.at(cmd)(m_dev, cmdParams); // here the magic happens ArduinoJson::JsonDocument resp; + ArduinoJson::JsonDocument action; + eventParams.next = cron::cron_next(eventParams.cronExpr, nowTm); // update next execution time only if event was executed, otherwise time tracking is lost 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) + resp["values"]["next"] = drivers::PCF85063::tm2str(eventParams.next).c_str(); + resp["values"]["status"] = c_statusEnum2Str.at(eventParams.status).c_str(); + switch (eventParams.status) { - m_callback(resp); + case CronStatus::ACTIVE: + LOG_INFO("Cron running ACTIVE event [", eventName.c_str(), "] next execution time [", drivers::PCF85063::tm2str(eventParams.next).c_str(), "]"); + action = commands::s_commandMap.at(eventParams.cmd)(m_dev, eventParams.cmdParams); // here the magic happens + resp["values"]["action"] = action; + break; + case CronStatus::INACTIVE: + LOG_INFO("Cron skipping INACTIVE event [", eventName.c_str(), "] next execution time [", drivers::PCF85063::tm2str(eventParams.next).c_str(), "]"); + break; + case CronStatus::SKIP: + LOG_INFO("Cron skipping 1 time ACTIVE event [", eventName.c_str(), "] next execution time [", drivers::PCF85063::tm2str(eventParams.next).c_str(), "]"); + eventParams.status = CronStatus::ACTIVE; + break; + default: + break; } + if (m_callback) + m_callback(resp); // execute cronLog callback action } } return true; diff --git a/src/cronjobs.h b/src/cronjobs.h index 4dc8e40..19e42f8 100644 --- a/src/cronjobs.h +++ b/src/cronjobs.h @@ -15,8 +15,33 @@ class Cron { -public: // eventName cronExpression nextExec command parameters - using CronEvent = std::tuple; +public: + enum class CronStatus + { + ACTIVE, + INACTIVE, + SKIP + }; + + const std::map c_statusEnum2Str = { + {CronStatus::ACTIVE, "ACTIVE"}, + {CronStatus::INACTIVE, "INACTIVE"}, + {CronStatus::SKIP, "SKIP"}}; + + const std::map c_statusStr2Enum = { + {"ACTIVE", CronStatus::ACTIVE}, + {"INACTIVE", CronStatus::INACTIVE}, + {"SKIP", CronStatus::SKIP}}; + + struct CronEvent + { + std::string cmd; + ArduinoJson::JsonDocument cmdParams; + cron::cronexpr cronExpr; + std::tm next; + CronStatus status; + }; + using CronEventMap = std::map; using CronCallback = std::function; @@ -40,7 +65,8 @@ public: const bool loadEvents(); const bool storeEvents(); - const bool addEvent(const std::string &name, const std::string &expr, const ArduinoJson::JsonDocument action); + const bool addEvent(const std::string &name, const std::string &expr, const ArduinoJson::JsonDocument action, const CronStatus status = CronStatus::ACTIVE); + const bool setEvent(const std::string &name, const CronStatus status); const bool getEvent(const std::string &name, CronEvent &event); const bool delEvent(const std::string &name); const CronEventMap &getAllEvents();