#include #include #include #define STACK_DEPTH 4096 #define PRIORITY 3 const bool Cron::loadEvents() { FSmount fs; File cronFile = FFat.open("/cronjobs.json", FILE_READ, false); if (!cronFile) { LOG_ERROR("Cron failed to open cronjobs.json"); return false; } ArduinoJson::JsonDocument cronFileContent; if (ArduinoJson::deserializeJson(cronFileContent, cronFile) != ArduinoJson::DeserializationError::Ok) { LOG_ERROR("Cron unable to deserialize cronjobs.json"); return false; } std::string buf; ArduinoJson::serializeJsonPretty(cronFileContent, buf); LOG_INFO("Cron loadEvents loaded cronjobs.json\n", buf.c_str()); ArduinoJson::JsonArray cronjobList = cronFileContent.as(); LOG_INFO("Cron loadEvents loaded [", cronjobList.size(), "] events"); for (const auto &job : cronjobList) { 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, status)) LOG_ERROR("Cron failed to load event [", eventName.c_str(), "]"); else LOG_INFO("Cron loaded event [", eventName.c_str(), "]"); delay(10); } cronFile.close(); return true; } const bool Cron::storeEvents() { FSmount fs; std::lock_guard lock(m_mutex); File cronFile = FFat.open("/cronjobs.json", FILE_WRITE, true); if (!cronFile) { LOG_ERROR("Cron failed to open cronjobs.json"); return false; } ArduinoJson::JsonDocument cronFileContent; ArduinoJson::JsonArray cronFileArray = cronFileContent.to(); for (const auto &[eventName, eventParams] : m_cronMap) // convert cron events map to json file { ArduinoJson::JsonDocument thisJob; thisJob["name"] = eventName; 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); } std::string buf; ArduinoJson::serializeJsonPretty(cronFileContent, buf); LOG_INFO("Cron storeEvents generated cronjobs.json\n", buf.c_str()); ArduinoJson::serializeJson(cronFileContent, cronFile); cronFile.close(); return true; } 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)) { LOG_ERROR("Cron event [", name.c_str(), "] already scheduled"); return false; } if (name.empty() || expr.empty() || action.isNull()) { LOG_ERROR("Cron event invalid parameters"); return false; } try { 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(), "]"); return false; } drivers::PCF85063::datetime_t now; if (!m_dev.rtc.readDatetime(now)) { LOG_ERROR("Cron unable to update current time"); return false; } 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] = CronEvent(cmd, cmdParams, cronExpr, next, status); } catch (cron::bad_cronexpr const &ex) { LOG_ERROR("Cron failed to parse expression [", expr.c_str(), "] ->", ex.what()); return false; } 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); if (!m_cronMap.contains(name)) { LOG_ERROR("Cron event [", name.c_str(), "] does not exist"); return false; } LOG_INFO("Cron get event [", name.c_str(), "]"); event = m_cronMap.at(name); return true; } const bool Cron::delEvent(const std::string &name) { std::lock_guard lock(m_mutex); if (!m_cronMap.contains(name)) { LOG_WARN("Cron event [", name.c_str(), "] does not exist"); return false; } m_cronMap.erase(name); LOG_INFO("Cron removed event [", name.c_str(), "]"); return true; } const Cron::CronEventMap &Cron::getAllEvents() { return m_cronMap; } void cronLoop(void *cronPtr) { auto &cron = *(Cron *)cronPtr; while (true) { cron.processEvents(); delay(1000); } } void Cron::startCron() { if (!m_cronTaskHandle) { LOG_INFO("Cron starting loop"); xTaskCreate(cronLoop, "cronLoop", STACK_DEPTH, this, PRIORITY, &m_cronTaskHandle); } } void Cron::stopCron() { if (m_cronTaskHandle) { LOG_WARN("Cron stopping loop"); vTaskDelete(m_cronTaskHandle); m_cronTaskHandle = NULL; } } const bool Cron::processEvents() { std::lock_guard lock(m_mutex); LOG_DEBUG("Cron processEvents [", m_cronMap.size(), "]"); drivers::PCF85063::datetime_t now; if (!m_dev.rtc.readDatetime(now)) { LOG_ERROR("Cron unable to update current time"); return false; } std::tm nowTm = drivers::PCF85063::datetime2tm(now); for (auto &[eventName, eventParams] : m_cronMap) { 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(&eventParams.next)); LOG_DEBUG("Cron current time [", std::asctime(&nowTm), "]"); 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 { 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(eventParams.next).c_str(); resp["values"]["status"] = c_statusEnum2Str.at(eventParams.status).c_str(); switch (eventParams.status) { 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; }