added set time via ntp as command and retrieve all cron jobs

This commit is contained in:
Emanuele Trabattoni
2025-07-30 15:24:11 +02:00
parent 581eca124e
commit 1110648978
6 changed files with 128 additions and 43 deletions

View File

@@ -232,7 +232,7 @@ private:
m_mqttLoopTime = mqtt["loopTime"].as<uint16_t>();
m_mqttKeepalive = mqtt["keepalive"].as<uint8_t>();
m_mqttRetries = mqtt["retries"].as<uint8_t>();
auto subscribe = mqtt["subsribe"].as<ArduinoJson::JsonObject>();
auto subscribe = mqtt["subscribe"].as<ArduinoJson::JsonObject>();
for (auto v : subscribe)
{
m_mqttSubscribe[v.key().c_str()] = v.value().as<std::string>();
@@ -287,6 +287,7 @@ public:
std::map<const std::string, std::string> m_mqttSubscribe = {
{"commands", "etcontroller/hw/commands"}};
std::map<const std::string, std::string> m_mqttPublish = {
{"cronjobs", "etcontroller/hw/cronjobs"},
{"answers", "etcontroller/hw/answers"},
{"heatpump", "etcontroller/hw/heatpump"},
{"temperatures", "etcontroller/hw/temperatures"},

View File

@@ -100,8 +100,30 @@ namespace commands
response["cmd"] = "getCronJob";
auto &cron = Cron::getInstance(dev);
auto eventName = params["name"].as<std::string>();
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 &params)
{
ArduinoJson::JsonDocument response;
response["cmd"] = "setTimeNTP";
auto &eth = 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<std::string>().c_str(), "]");
return response;
}
// SETTERS //
// SETTERS //
@@ -350,8 +399,8 @@ namespace commands
const ArduinoJson::JsonDocument Commands::getHPpower(const devices_t &dev, const ArduinoJson::JsonDocument &params)
{
ArduinoJson::JsonDocument response;
const auto pinfo = dev.seneca.getAll();
response["cmd"] = "getHPpower";
const auto pinfo = dev.seneca.getAll();
auto values = response["values"].to<JsonObject>();
values["power"] = pinfo.pAct;
values["current"] = pinfo.a;
@@ -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;
}
@@ -435,9 +485,11 @@ namespace commands
auto timeDiff = std::chrono::duration_cast<std::chrono::seconds>(ntpTimePoint - rtcTimePoint);
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 //

View File

@@ -79,6 +79,7 @@ namespace commands
static const ArduinoJson::JsonDocument setHPlimit(const devices_t &dev, const ArduinoJson::JsonDocument &params);
static const ArduinoJson::JsonDocument setHeating(const devices_t &dev, const ArduinoJson::JsonDocument &params);
static const ArduinoJson::JsonDocument setIrrigation(const devices_t &dev, const ArduinoJson::JsonDocument &params);
static const ArduinoJson::JsonDocument setTimeNTP(const devices_t &dev, const ArduinoJson::JsonDocument &params);
// GETTERS //
static const ArduinoJson::JsonDocument getHPpower(const devices_t &dev, const ArduinoJson::JsonDocument &params);
@@ -92,7 +93,7 @@ namespace commands
static const ArduinoJson::JsonDocument getTimeDrift(const devices_t &dev, const ArduinoJson::JsonDocument &params);
};
static const std::map<const std::string, Command> commandMap = {
static const std::map<const std::string, Command> 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},
};
}

View File

@@ -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<std::string>();
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;

View File

@@ -8,6 +8,7 @@
#include <PCF85063_Driver.h>
#include <commands.h>
#include <mqtt.h>
#include <filesystem>
#include <croncpp.h>
@@ -16,6 +17,8 @@ class Cron
{
public: // eventName cronExpression nextExec command parameters
using CronEvent = std::tuple<std::string, cron::cronexpr, std::tm, ArduinoJson::JsonDocument>;
using CronEventMap = std::map<std::string, CronEvent>;
using CronCallback = std::function<void(const ArduinoJson::JsonDocument &)>;
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<std::string, CronEvent> m_cronMap;
std::mutex m_mutex;
};

View File

@@ -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<void(const ArduinoJson::JsonDocument &)> commandsCallback =
[&mqtt, &devices](const ArduinoJson::JsonDocument &doc)
@@ -73,10 +67,10 @@ void loop()
}
const std::string cmd = doc["cmd"].as<std::string>();
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();
}