Files
ETcontroller_PRO/src/cronjobs.cpp
2025-07-31 16:16:06 +02:00

239 lines
7.3 KiB
C++

#include <cronjobs.h>
#include <chrono>
#include <fsmount.h>
#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<JsonArray>();
LOG_INFO("Cron loadEvents loaded [", cronjobList.size(), "] events");
for (const auto &job : cronjobList)
{
const auto &eventName = job["name"].as<std::string>();
const auto &cronExpr = job["cronExpr"].as<std::string>();
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<std::mutex> 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<JsonArray>();
for (const auto &job : m_cronMap) // convert cron events map to json file
{
const auto &eventName = job.first;
const auto &params = 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<std::mutex> 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<std::string>();
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<std::mutex> 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<std::mutex> 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<std::mutex> 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;
}