added time drift check command

This commit is contained in:
Emanuele Trabattoni
2025-07-30 10:15:13 +02:00
parent 1d1eb6fbfa
commit 581eca124e
8 changed files with 137 additions and 41 deletions

54
esp32-s3-waveshare8.json Normal file
View File

@@ -0,0 +1,54 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"partitions": "app3M_fat9M_16MB.csv"
},
"core": "esp32",
"extra_flags": [
"-DARDUINO_ESP32S3_DEV",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1",
"-DARDUINO_USB_CDC_ON_BOOT=1"
],
"partitions": "app3M_fat9M_16MB.csv",
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"hwids": [
[
"0x303A",
"0x1001"
]
],
"mcu": "esp32s3",
"variant": "esp32s3"
},
"connectivity": [
"bluetooth",
"wifi",
"ethernet"
],
"debug": {
"default_tool": "esp-builtin",
"onboard_tools": [
"esp-builtin"
],
"openocd_target": "esp32s3.cfg"
},
"frameworks": [
"arduino",
"espidf"
],
"name": "Espressif ESP32-S3-Waveshare_8RO-8DI",
"upload": {
"flash_size": "16MB",
"maximum_ram_size": 327680,
"maximum_size": 16777216,
"require_upload_port": true,
"speed": 921600
},
"url": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/hw-reference/esp32s3/user-guide-devkitm-1.html",
"vendor": "Espressif"
}

View File

@@ -5,6 +5,7 @@
#include <DebugLog.h> #include <DebugLog.h>
#include "I2C_Driver.h" #include "I2C_Driver.h"
#include <string> #include <string>
#include <time.h>
// PCF85063_ADDRESS // PCF85063_ADDRESS
#define PCF85063_ADDRESS (0x51) #define PCF85063_ADDRESS (0x51)

View File

@@ -51,11 +51,13 @@ namespace commands
// CRONJOBS // // CRONJOBS //
// CRONJOBS // // CRONJOBS //
const ArduinoJson::JsonDocument Commands::loadCronjob(const devices_t &dev, const ArduinoJson::JsonDocument &params){ const ArduinoJson::JsonDocument Commands::loadCronJob(const devices_t &dev, const ArduinoJson::JsonDocument &params)
{
ArduinoJson::JsonDocument response; ArduinoJson::JsonDocument response;
response["cmd"] = "loadCronjob"; response["cmd"] = "loadCronJob";
auto& cron = Cron::getInstance(dev); auto &cron = Cron::getInstance(dev);
if(!cron.loadEvents()){ if (!cron.loadEvents())
{
LOG_ERROR("loadCronJob failed to load events from flash"); LOG_ERROR("loadCronJob failed to load events from flash");
response["values"]["status"] = "invalid"; response["values"]["status"] = "invalid";
return response; return response;
@@ -64,10 +66,10 @@ namespace commands
return response; return response;
} }
const ArduinoJson::JsonDocument Commands::setCronjob(const devices_t &dev, const ArduinoJson::JsonDocument &params) const ArduinoJson::JsonDocument Commands::setCronJob(const devices_t &dev, const ArduinoJson::JsonDocument &params)
{ {
ArduinoJson::JsonDocument response; ArduinoJson::JsonDocument response;
response["cmd"] = "setCronjob"; response["cmd"] = "setCronJob";
const auto &jobName = params["name"].as<std::string>(); const auto &jobName = params["name"].as<std::string>();
const auto &timeStr = params["cronExpr"].as<std::string>(); const auto &timeStr = params["cronExpr"].as<std::string>();
@@ -92,16 +94,16 @@ namespace commands
response["value"]["status"] = "valid"; response["value"]["status"] = "valid";
return response; return response;
} }
const ArduinoJson::JsonDocument Commands::getCronjob(const devices_t &dev, const ArduinoJson::JsonDocument &params) const ArduinoJson::JsonDocument Commands::getCronJob(const devices_t &dev, const ArduinoJson::JsonDocument &params)
{ {
ArduinoJson::JsonDocument response; ArduinoJson::JsonDocument response;
response["cmd"] = "getCronjob"; response["cmd"] = "getCronJob";
auto &cron = Cron::getInstance(dev); auto &cron = Cron::getInstance(dev);
auto eventName = params["name"].as<std::string>(); auto eventName = params["name"].as<std::string>();
Cron::CronEvent event; Cron::CronEvent event;
if (eventName.empty() || !cron.getEvent(eventName, event)) if (eventName.empty() || !cron.getEvent(eventName, event))
{ {
LOG_ERROR("delCronjob failed to get job [", eventName.c_str(), "]"); LOG_ERROR("getCronJob failed to get job [", eventName.c_str(), "]");
response["values"]["status"] = "invalid"; response["values"]["status"] = "invalid";
return response; return response;
} }
@@ -117,18 +119,18 @@ namespace commands
response["values"]["cronExpr"] = cron::to_cronstr(cronExpr); response["values"]["cronExpr"] = cron::to_cronstr(cronExpr);
response["values"]["action"] = action; response["values"]["action"] = action;
LOG_INFO("getCronjob get job [", eventName.c_str(), "]"); LOG_INFO("getCronJob get job [", eventName.c_str(), "]");
return response; return response;
} }
const ArduinoJson::JsonDocument Commands::delCronjob(const devices_t &dev, const ArduinoJson::JsonDocument &params) const ArduinoJson::JsonDocument Commands::delCronJob(const devices_t &dev, const ArduinoJson::JsonDocument &params)
{ {
ArduinoJson::JsonDocument response; ArduinoJson::JsonDocument response;
response["cmd"] = "delCronjob"; response["cmd"] = "delCronJob";
auto &cron = Cron::getInstance(dev); auto &cron = Cron::getInstance(dev);
auto eventName = params["name"].as<std::string>(); auto eventName = params["name"].as<std::string>();
if (eventName.empty() || !cron.delEvent(eventName)) if (eventName.empty() || !cron.delEvent(eventName))
{ {
LOG_ERROR("delCronjob failed to delete job [", eventName.c_str(), "]"); LOG_ERROR("delCronJob failed to delete job [", eventName.c_str(), "]");
response["values"]["status"] = "invalid"; response["values"]["status"] = "invalid";
return response; return response;
} }
@@ -136,12 +138,13 @@ namespace commands
return response; return response;
} }
const ArduinoJson::JsonDocument Commands::storeCronjob(const devices_t &dev, const ArduinoJson::JsonDocument &params) const ArduinoJson::JsonDocument Commands::storeCronJob(const devices_t &dev, const ArduinoJson::JsonDocument &params)
{ {
ArduinoJson::JsonDocument response; ArduinoJson::JsonDocument response;
response["cmd"] = "storeCronjob"; response["cmd"] = "storeCronJob";
auto& cron = Cron::getInstance(dev); auto &cron = Cron::getInstance(dev);
if(!cron.storeEvents()){ if (!cron.storeEvents())
{
LOG_ERROR("storeCronJob failed to store events in flash"); LOG_ERROR("storeCronJob failed to store events in flash");
response["values"]["status"] = "invalid"; response["values"]["status"] = "invalid";
return response; return response;
@@ -406,6 +409,37 @@ namespace commands
LOG_WARN("Comand not yet implemented"); LOG_WARN("Comand not yet implemented");
return response; return response;
} }
const ArduinoJson::JsonDocument Commands::getTimeDrift(const devices_t &dev, const ArduinoJson::JsonDocument &params)
{
ArduinoJson::JsonDocument response;
response["cmd"] = "getTimeDrift";
auto& eth= dev.eth;
auto& rtc = dev.rtc;
time_t ntpTime;
auto ntpOk = eth.getNtpTime(ntpTime);
drivers::PCF85063::datetime_t rtcTime;
auto rtcOk = rtc.readDatetime(rtcTime);
auto rtcTimeTm = drivers::PCF85063::datetime2tm(rtcTime);
if (!rtcOk || !ntpOk) {
response["value"]["status"] = "unable to get time";
return response;
}
auto ntpTimePoint = std::chrono::system_clock::from_time_t(ntpTime);
auto rtcTimePoint = std::chrono::system_clock::from_time_t(std::mktime(&rtcTimeTm));
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"]["direction"] = "RTC is [" + std::string(direction) + "] NTP time";
return response;
}
// GETTERS // // GETTERS //
// GETTERS // // GETTERS //

View File

@@ -69,11 +69,11 @@ namespace commands
static const ArduinoJson::JsonDocument getConfig(const devices_t &dev, const ArduinoJson::JsonDocument &params); static const ArduinoJson::JsonDocument getConfig(const devices_t &dev, const ArduinoJson::JsonDocument &params);
// CRONJOBS // // CRONJOBS //
static const ArduinoJson::JsonDocument loadCronjob(const devices_t &dev, const ArduinoJson::JsonDocument &params); static const ArduinoJson::JsonDocument loadCronJob(const devices_t &dev, const ArduinoJson::JsonDocument &params);
static const ArduinoJson::JsonDocument setCronjob(const devices_t &dev, const ArduinoJson::JsonDocument &params); static const ArduinoJson::JsonDocument setCronJob(const devices_t &dev, const ArduinoJson::JsonDocument &params);
static const ArduinoJson::JsonDocument getCronjob(const devices_t &dev, const ArduinoJson::JsonDocument &params); static const ArduinoJson::JsonDocument getCronJob(const devices_t &dev, const ArduinoJson::JsonDocument &params);
static const ArduinoJson::JsonDocument delCronjob(const devices_t &dev, const ArduinoJson::JsonDocument &params); static const ArduinoJson::JsonDocument delCronJob(const devices_t &dev, const ArduinoJson::JsonDocument &params);
static const ArduinoJson::JsonDocument storeCronjob(const devices_t &dev, const ArduinoJson::JsonDocument &params); static const ArduinoJson::JsonDocument storeCronJob(const devices_t &dev, const ArduinoJson::JsonDocument &params);
// SETTERS // // SETTERS //
static const ArduinoJson::JsonDocument setHPlimit(const devices_t &dev, const ArduinoJson::JsonDocument &params); static const ArduinoJson::JsonDocument setHPlimit(const devices_t &dev, const ArduinoJson::JsonDocument &params);
@@ -89,17 +89,18 @@ namespace commands
static const ArduinoJson::JsonDocument getTankInfo(const devices_t &dev, const ArduinoJson::JsonDocument &params); static const ArduinoJson::JsonDocument getTankInfo(const devices_t &dev, const ArduinoJson::JsonDocument &params);
static const ArduinoJson::JsonDocument getRainInfo(const devices_t &dev, const ArduinoJson::JsonDocument &params); static const ArduinoJson::JsonDocument getRainInfo(const devices_t &dev, const ArduinoJson::JsonDocument &params);
static const ArduinoJson::JsonDocument getIrrigation(const devices_t &dev, const ArduinoJson::JsonDocument &params); static const ArduinoJson::JsonDocument getIrrigation(const devices_t &dev, const ArduinoJson::JsonDocument &params);
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> commandMap = {
{"setConfig", Commands::setConfig}, {"setConfig", Commands::setConfig},
{"getConfig", Commands::getConfig}, {"getConfig", Commands::getConfig},
{"loadCronjob", Commands::loadCronjob}, {"loadCronJob", Commands::loadCronJob},
{"setCronjob", Commands::setCronjob}, {"setCronJob", Commands::setCronJob},
{"getCronjob", Commands::getCronjob}, {"getCronJob", Commands::getCronJob},
{"delCronjob", Commands::delCronjob}, {"delCronJob", Commands::delCronJob},
{"storeCronjob", Commands::storeCronjob}, {"storeCronJob", Commands::storeCronJob},
{"setHPlimit", Commands::setHPlimit}, {"setHPlimit", Commands::setHPlimit},
{"setHeating", Commands::setHeating}, {"setHeating", Commands::setHeating},
@@ -107,6 +108,7 @@ namespace commands
{"getHPpower", Commands::getHPpower}, {"getHPpower", Commands::getHPpower},
{"setHeating", Commands::setHeating}, {"setHeating", Commands::setHeating},
{"getTimeDrift", Commands::getTimeDrift},
}; };
} }

View File

@@ -10,6 +10,7 @@
typedef struct typedef struct
{ {
drivers::Ethernet &eth;
drivers::PCF85063 &rtc; drivers::PCF85063 &rtc;
drivers::R4DCB08 &tmp; drivers::R4DCB08 &tmp;
drivers::S50140 &seneca; drivers::S50140 &seneca;

View File

@@ -48,7 +48,7 @@ void loop()
LOG_INFO("Temperature sensors connected ->", sensors); LOG_INFO("Temperature sensors connected ->", sensors);
// Create device structure to pass all devices in the callbacks as needed // Create device structure to pass all devices in the callbacks as needed
devices_t devices(rtc, tmp, seneca, buzzer, led, io); devices_t devices(eth, rtc, tmp, seneca, buzzer, led, io);
//////////////// DEVICES //////////////// //////////////// DEVICES ////////////////
//////////////// MQTT //////////////// //////////////// MQTT ////////////////
@@ -72,11 +72,11 @@ void loop()
return; return;
} }
const std::string cmd = doc["cmd"].as<std::string>(); const std::string cmd = doc["cmd"].as<std::string>();
ArduinoJson::JsonDocument params = doc["params"]; const ArduinoJson::JsonDocument params = doc["params"];
if (commands::commandMap.contains(cmd)) if (commands::commandMap.contains(cmd))
{ // call command from command map in this same thread (the MQTT thread) { // call command from command map in this same thread (the MQTT thread)
LOG_INFO("Executing command", cmd.c_str()); LOG_INFO("Executing command", cmd.c_str());
auto answer = std::move(commands::commandMap.at(cmd)(devices, params)); // here the magic happens const auto answer = std::move(commands::commandMap.at(cmd)(devices, params)); // here the magic happens
if (answer.isNull()) if (answer.isNull())
return; return;
mqtt.publish(conf.m_mqttPublish["answers"], answer); mqtt.publish(conf.m_mqttPublish["answers"], answer);
@@ -104,12 +104,13 @@ void loop()
uint8_t mqttRetries(0); uint8_t mqttRetries(0);
while (timeRetries++ < conf.m_ntpRetries) while (timeRetries++ < conf.m_ntpRetries)
{ {
if (eth.getNtpTime(ntpTime) && rtc.setDatetime(drivers::PCF85063::fromEpoch(ntpTime))) if (eth.getNtpTime(ntpTime))
{ { // skip NTP update for drift testing
buzzer.beep(250, NOTE_A); buzzer.beep(250, NOTE_A);
led.setColor(led.COLOR_ORANGE); led.setColor(led.COLOR_ORANGE);
// rtc.setDatetime(drivers::PCF85063::fromEpoch(ntpTime));
const drivers::PCF85063::datetime_t dt(drivers::PCF85063::fromEpoch(ntpTime)); const drivers::PCF85063::datetime_t dt(drivers::PCF85063::fromEpoch(ntpTime));
LOG_INFO("NTP Time Update: ", drivers::PCF85063::datetime2str(dt).c_str()); LOG_INFO("NTP Time: ", drivers::PCF85063::datetime2str(dt).c_str());
break; break;
} }
delay(100); delay(100);
@@ -118,6 +119,7 @@ void loop()
{ {
if (mqtt.connect()) if (mqtt.connect())
{ {
buzzer.beep(250, NOTE_B);
led.setColor(led.COLOR_GREEN); led.setColor(led.COLOR_GREEN);
mqtt.subscribe(conf.m_mqttSubscribe["commands"], commandsCallback); mqtt.subscribe(conf.m_mqttSubscribe["commands"], commandsCallback);
break; break;
@@ -133,8 +135,10 @@ void loop()
while (true) while (true)
{ {
const uint32_t start(millis()); const uint32_t start(millis());
const std::string timeStr(rtc.getTimeStr()); drivers::PCF85063::datetime_t datetime;
LOG_INFO("[", k++, "] Loop - Current Datetime", timeStr.c_str()); rtc.readDatetime(datetime);
const std::string timeStr(drivers::PCF85063::datetime2str(datetime));
LOG_INFO("[", k++, "] Loop - Current Datetime UTC", timeStr.c_str());
{ {
ArduinoJson::JsonDocument poll; ArduinoJson::JsonDocument poll;

View File

@@ -44,7 +44,7 @@ const bool MQTTwrapper::disconnect()
return true; return true;
} }
const bool MQTTwrapper::subscribe(topic_t topic, action_t action) const bool MQTTwrapper::subscribe(const topic_t &topic, const action_t action)
{ {
if (m_actionMap.contains(topic)) if (m_actionMap.contains(topic))
{ {
@@ -61,7 +61,7 @@ const bool MQTTwrapper::subscribe(topic_t topic, action_t action)
return false; return false;
} }
const bool MQTTwrapper::unsubscribe(topic_t topic) const bool MQTTwrapper::unsubscribe(const topic_t &topic)
{ {
if (!m_actionMap.contains(topic)) if (!m_actionMap.contains(topic))
{ {
@@ -83,7 +83,7 @@ const bool MQTTwrapper::connected()
return m_loopHandle != NULL; return m_loopHandle != NULL;
} }
const bool MQTTwrapper::publish(topic_t topic, const ArduinoJson::JsonDocument obj) const bool MQTTwrapper::publish(const topic_t &topic, const ArduinoJson::JsonDocument obj)
{ {
std::string message; std::string message;
if (!m_client.connected()) if (!m_client.connected())

View File

@@ -54,10 +54,10 @@ public:
const bool disconnect(); const bool disconnect();
const bool connected(); const bool connected();
const bool subscribe(topic_t topic, action_t action); const bool subscribe(const topic_t &topic, const action_t action);
const bool unsubscribe(topic_t topic); const bool unsubscribe(const topic_t &topic);
const bool publish(topic_t topic, const ArduinoJson::JsonDocument obj); const bool publish(const topic_t &topic, const ArduinoJson::JsonDocument obj);
private: private:
static void callback(char *topic, uint8_t *payload, unsigned int length); // C-style callback only to invoke onMessage static void callback(char *topic, uint8_t *payload, unsigned int length); // C-style callback only to invoke onMessage