From 581eca124e58a5d4c193349e385bae63e65088ba Mon Sep 17 00:00:00 2001 From: Emanuele Trabattoni Date: Wed, 30 Jul 2025 10:15:13 +0200 Subject: [PATCH] added time drift check command --- esp32-s3-waveshare8.json | 54 +++++++++++++++++++++++++++++++ lib/RTC/PCF85063_Driver.h | 1 + src/commands.cpp | 68 +++++++++++++++++++++++++++++---------- src/commands.h | 22 +++++++------ src/devices.h | 1 + src/main.cpp | 20 +++++++----- src/mqtt.cpp | 6 ++-- src/mqtt.h | 6 ++-- 8 files changed, 137 insertions(+), 41 deletions(-) create mode 100644 esp32-s3-waveshare8.json diff --git a/esp32-s3-waveshare8.json b/esp32-s3-waveshare8.json new file mode 100644 index 0000000..33c3cb1 --- /dev/null +++ b/esp32-s3-waveshare8.json @@ -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" +} \ No newline at end of file diff --git a/lib/RTC/PCF85063_Driver.h b/lib/RTC/PCF85063_Driver.h index 9276926..c166e05 100644 --- a/lib/RTC/PCF85063_Driver.h +++ b/lib/RTC/PCF85063_Driver.h @@ -5,6 +5,7 @@ #include #include "I2C_Driver.h" #include +#include // PCF85063_ADDRESS #define PCF85063_ADDRESS (0x51) diff --git a/src/commands.cpp b/src/commands.cpp index 56bbed1..f62db08 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -51,11 +51,13 @@ namespace commands // CRONJOBS // // CRONJOBS // - const ArduinoJson::JsonDocument Commands::loadCronjob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms){ + const ArduinoJson::JsonDocument Commands::loadCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) + { 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 ¶ms) + const ArduinoJson::JsonDocument Commands::setCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) { ArduinoJson::JsonDocument response; - response["cmd"] = "setCronjob"; + response["cmd"] = "setCronJob"; const auto &jobName = params["name"].as(); const auto &timeStr = params["cronExpr"].as(); @@ -92,16 +94,16 @@ namespace commands response["value"]["status"] = "valid"; return response; } - const ArduinoJson::JsonDocument Commands::getCronjob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) + const ArduinoJson::JsonDocument Commands::getCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) { ArduinoJson::JsonDocument response; - response["cmd"] = "getCronjob"; + response["cmd"] = "getCronJob"; auto &cron = Cron::getInstance(dev); auto eventName = params["name"].as(); 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 ¶ms) + const ArduinoJson::JsonDocument Commands::delCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) { ArduinoJson::JsonDocument response; - response["cmd"] = "delCronjob"; + response["cmd"] = "delCronJob"; auto &cron = Cron::getInstance(dev); auto eventName = params["name"].as(); 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 ¶ms) + const ArduinoJson::JsonDocument Commands::storeCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms) { 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 ¶ms) + { + 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(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 // diff --git a/src/commands.h b/src/commands.h index cf617f2..03ac8f3 100644 --- a/src/commands.h +++ b/src/commands.h @@ -69,11 +69,11 @@ namespace commands static const ArduinoJson::JsonDocument getConfig(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); // CRONJOBS // - static const ArduinoJson::JsonDocument loadCronjob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); - static const ArduinoJson::JsonDocument setCronjob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); - static const ArduinoJson::JsonDocument getCronjob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); - static const ArduinoJson::JsonDocument delCronjob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); - static const ArduinoJson::JsonDocument storeCronjob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); + static const ArduinoJson::JsonDocument loadCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); + static const ArduinoJson::JsonDocument setCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); + static const ArduinoJson::JsonDocument getCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); + static const ArduinoJson::JsonDocument delCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); + static const ArduinoJson::JsonDocument storeCronJob(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); // SETTERS // static const ArduinoJson::JsonDocument setHPlimit(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); @@ -89,17 +89,18 @@ namespace commands static const ArduinoJson::JsonDocument getTankInfo(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); static const ArduinoJson::JsonDocument getRainInfo(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); static const ArduinoJson::JsonDocument getIrrigation(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); + static const ArduinoJson::JsonDocument getTimeDrift(const devices_t &dev, const ArduinoJson::JsonDocument ¶ms); }; static const std::map 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}, }; } \ No newline at end of file diff --git a/src/devices.h b/src/devices.h index cf6decf..52ad2b9 100644 --- a/src/devices.h +++ b/src/devices.h @@ -10,6 +10,7 @@ typedef struct { + drivers::Ethernet ð drivers::PCF85063 &rtc; drivers::R4DCB08 &tmp; drivers::S50140 &seneca; diff --git a/src/main.cpp b/src/main.cpp index 87444a1..6d2883d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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(); - 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; diff --git a/src/mqtt.cpp b/src/mqtt.cpp index 2f59559..63d4f6a 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -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()) diff --git a/src/mqtt.h b/src/mqtt.h index ad2df4f..f199ac5 100644 --- a/src/mqtt.h +++ b/src/mqtt.h @@ -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