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 "I2C_Driver.h"
#include <string>
#include <time.h>
// PCF85063_ADDRESS
#define PCF85063_ADDRESS (0x51)

View File

@@ -51,11 +51,13 @@ namespace commands
// 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;
response["cmd"] = "loadCronjob";
auto& cron = Cron::getInstance(dev);
if(!cron.loadEvents()){
response["cmd"] = "loadCronJob";
auto &cron = Cron::getInstance(dev);
if (!cron.loadEvents())
{
LOG_ERROR("loadCronJob failed to load events from flash");
response["values"]["status"] = "invalid";
return response;
@@ -64,10 +66,10 @@ namespace commands
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;
response["cmd"] = "setCronjob";
response["cmd"] = "setCronJob";
const auto &jobName = params["name"].as<std::string>();
const auto &timeStr = params["cronExpr"].as<std::string>();
@@ -92,16 +94,16 @@ namespace commands
response["value"]["status"] = "valid";
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;
response["cmd"] = "getCronjob";
response["cmd"] = "getCronJob";
auto &cron = Cron::getInstance(dev);
auto eventName = params["name"].as<std::string>();
Cron::CronEvent 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";
return response;
}
@@ -117,18 +119,18 @@ namespace commands
response["values"]["cronExpr"] = cron::to_cronstr(cronExpr);
response["values"]["action"] = action;
LOG_INFO("getCronjob get job [", eventName.c_str(), "]");
LOG_INFO("getCronJob get job [", eventName.c_str(), "]");
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;
response["cmd"] = "delCronjob";
response["cmd"] = "delCronJob";
auto &cron = Cron::getInstance(dev);
auto eventName = params["name"].as<std::string>();
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";
return response;
}
@@ -136,12 +138,13 @@ namespace commands
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;
response["cmd"] = "storeCronjob";
auto& cron = Cron::getInstance(dev);
if(!cron.storeEvents()){
response["cmd"] = "storeCronJob";
auto &cron = Cron::getInstance(dev);
if (!cron.storeEvents())
{
LOG_ERROR("storeCronJob failed to store events in flash");
response["values"]["status"] = "invalid";
return response;
@@ -406,6 +409,37 @@ namespace commands
LOG_WARN("Comand not yet implemented");
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 //

View File

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

View File

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

View File

@@ -48,7 +48,7 @@ void loop()
LOG_INFO("Temperature sensors connected ->", sensors);
// 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 ////////////////
//////////////// MQTT ////////////////
@@ -72,11 +72,11 @@ void loop()
return;
}
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))
{ // call command from command map in this same thread (the MQTT thread)
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())
return;
mqtt.publish(conf.m_mqttPublish["answers"], answer);
@@ -104,12 +104,13 @@ void loop()
uint8_t mqttRetries(0);
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);
led.setColor(led.COLOR_ORANGE);
// rtc.setDatetime(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;
}
delay(100);
@@ -118,6 +119,7 @@ void loop()
{
if (mqtt.connect())
{
buzzer.beep(250, NOTE_B);
led.setColor(led.COLOR_GREEN);
mqtt.subscribe(conf.m_mqttSubscribe["commands"], commandsCallback);
break;
@@ -133,8 +135,10 @@ void loop()
while (true)
{
const uint32_t start(millis());
const std::string timeStr(rtc.getTimeStr());
LOG_INFO("[", k++, "] Loop - Current Datetime", timeStr.c_str());
drivers::PCF85063::datetime_t datetime;
rtc.readDatetime(datetime);
const std::string timeStr(drivers::PCF85063::datetime2str(datetime));
LOG_INFO("[", k++, "] Loop - Current Datetime UTC", timeStr.c_str());
{
ArduinoJson::JsonDocument poll;

View File

@@ -44,7 +44,7 @@ const bool MQTTwrapper::disconnect()
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))
{
@@ -61,7 +61,7 @@ const bool MQTTwrapper::subscribe(topic_t topic, action_t action)
return false;
}
const bool MQTTwrapper::unsubscribe(topic_t topic)
const bool MQTTwrapper::unsubscribe(const topic_t &topic)
{
if (!m_actionMap.contains(topic))
{
@@ -83,7 +83,7 @@ const bool MQTTwrapper::connected()
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;
if (!m_client.connected())

View File

@@ -54,10 +54,10 @@ public:
const bool disconnect();
const bool connected();
const bool subscribe(topic_t topic, action_t action);
const bool unsubscribe(topic_t topic);
const bool subscribe(const topic_t &topic, const action_t action);
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:
static void callback(char *topic, uint8_t *payload, unsigned int length); // C-style callback only to invoke onMessage