#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(); ArduinoJson::JsonDocument action(job["action"]); if (!addEvent(eventName, cronExpr, action)) 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 &job : 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; 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) { 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 eventExpr(cron::make_cron(expr)); const auto cmd = action["cmd"].as(); const auto params = action["params"]; 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; } std::tm nowTm = drivers::PCF85063::datetime2tm(now); auto next = cron::cron_next(eventExpr, nowTm); JsonDocument act(params); 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); } 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::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 &event : 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)); LOG_DEBUG("Cron current time [", std::asctime(&nowTm), "]"); LOG_DEBUG("Cron checking event [", eventName.c_str(), "] executionTime [", drivers::PCF85063::tm2str(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; 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); } } } return true; }