From cc6031dba79a762f44758e93cd0d7bc0b250dff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Ma=C5=82ecki?= Date: Sat, 9 May 2026 00:58:50 +0200 Subject: [PATCH 1/3] add hardware RTC startup sync and periodic system clock correction AutoDiscoverRTCClock now syncs the fallback system clock from the hardware RTC immediately after discovery in begin(), and re-syncs every hour via tick() to correct drift. Invalid RTC readings (before 2024-01-01) are ignored to handle unpowered DS3231 gracefully. --- src/helpers/AutoDiscoverRTCClock.cpp | 17 +++++++++++++++++ src/helpers/AutoDiscoverRTCClock.h | 17 ++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/helpers/AutoDiscoverRTCClock.cpp b/src/helpers/AutoDiscoverRTCClock.cpp index 49a72893d9..53afcfcaa1 100644 --- a/src/helpers/AutoDiscoverRTCClock.cpp +++ b/src/helpers/AutoDiscoverRTCClock.cpp @@ -26,6 +26,17 @@ bool AutoDiscoverRTCClock::i2c_probe(TwoWire& wire, uint8_t addr) { return (error == 0); } +void AutoDiscoverRTCClock::syncSystemClock() { + uint32_t hw_time = getCurrentTime(); + if (hw_time <= 1704067200UL) return; // DS3231 not set or lost power (before 2024-01-01) + + uint32_t sys_time = _fallback->getCurrentTime(); + int32_t drift = (int32_t)(hw_time - sys_time); + if (drift > 2 || drift < -2) { + _fallback->setCurrentTime(hw_time); + } +} + void AutoDiscoverRTCClock::begin(TwoWire& wire) { if (i2c_probe(wire, DS3231_ADDRESS)) { ds3231_success = rtc_3231.begin(&wire); @@ -49,6 +60,12 @@ void AutoDiscoverRTCClock::begin(TwoWire& wire) { rtc_8130_success = true; MESH_DEBUG_PRINTLN("RX8130CE: Initialized"); } + + _has_hw_rtc = ds3231_success || rv3028_success || rtc_8563_success || rtc_8130_success; + if (_has_hw_rtc) { + syncSystemClock(); + _last_sync_ms = millis(); + } } uint32_t AutoDiscoverRTCClock::getCurrentTime() { diff --git a/src/helpers/AutoDiscoverRTCClock.h b/src/helpers/AutoDiscoverRTCClock.h index 11364cd811..21b2ef6e71 100644 --- a/src/helpers/AutoDiscoverRTCClock.h +++ b/src/helpers/AutoDiscoverRTCClock.h @@ -4,12 +4,20 @@ #include #include +#ifndef RTC_RESYNC_INTERVAL_MS + #define RTC_RESYNC_INTERVAL_MS 3600000UL // re-sync system clock from hardware RTC once per hour +#endif + class AutoDiscoverRTCClock : public mesh::RTCClock { mesh::RTCClock* _fallback; + bool _has_hw_rtc; + unsigned long _last_sync_ms; bool i2c_probe(TwoWire& wire, uint8_t addr); + void syncSystemClock(); public: - AutoDiscoverRTCClock(mesh::RTCClock& fallback) : _fallback(&fallback) { } + AutoDiscoverRTCClock(mesh::RTCClock& fallback) + : _fallback(&fallback), _has_hw_rtc(false), _last_sync_ms(0) { } void begin(TwoWire& wire); uint32_t getCurrentTime() override; @@ -17,5 +25,12 @@ class AutoDiscoverRTCClock : public mesh::RTCClock { void tick() override { _fallback->tick(); // is typically VolatileRTCClock, which now needs tick() + if (_has_hw_rtc) { + unsigned long now = millis(); + if ((unsigned long)(now - _last_sync_ms) >= RTC_RESYNC_INTERVAL_MS) { + syncSystemClock(); + _last_sync_ms = now; + } + } } }; From 56eb87d05af06deb51dc4df4ad61d84cac4c1535 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Ma=C5=82ecki?= Date: Sat, 9 May 2026 01:01:16 +0200 Subject: [PATCH 2/3] add 'get rtcclock' CLI command to report RTC source and current time Returns the hardware RTC type (DS3231, RV3028, PCF8563, RX8130CE) or 'internal' when no external module was detected, alongside the current date and time in UTC. Useful for verifying that an external RTC is active and providing correct time to the node. --- src/MeshCore.h | 2 ++ src/helpers/AutoDiscoverRTCClock.cpp | 5 +++++ src/helpers/AutoDiscoverRTCClock.h | 5 ++++- src/helpers/CommonCLI.cpp | 7 +++++++ 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/MeshCore.h b/src/MeshCore.h index 2db1d4c3ec..3cf60a3497 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -92,6 +92,8 @@ class RTCClock { */ virtual void tick() { /* no op */} + virtual const char* getSourceName() const { return "internal"; } + uint32_t getCurrentTimeUnique() { uint32_t t = getCurrentTime(); if (t <= last_unique) { diff --git a/src/helpers/AutoDiscoverRTCClock.cpp b/src/helpers/AutoDiscoverRTCClock.cpp index 53afcfcaa1..b870cefd37 100644 --- a/src/helpers/AutoDiscoverRTCClock.cpp +++ b/src/helpers/AutoDiscoverRTCClock.cpp @@ -61,6 +61,11 @@ void AutoDiscoverRTCClock::begin(TwoWire& wire) { MESH_DEBUG_PRINTLN("RX8130CE: Initialized"); } + if (ds3231_success) _source_name = "DS3231"; + else if (rv3028_success) _source_name = "RV3028"; + else if (rtc_8563_success) _source_name = "PCF8563"; + else if (rtc_8130_success) _source_name = "RX8130CE"; + _has_hw_rtc = ds3231_success || rv3028_success || rtc_8563_success || rtc_8130_success; if (_has_hw_rtc) { syncSystemClock(); diff --git a/src/helpers/AutoDiscoverRTCClock.h b/src/helpers/AutoDiscoverRTCClock.h index 21b2ef6e71..a52ac372db 100644 --- a/src/helpers/AutoDiscoverRTCClock.h +++ b/src/helpers/AutoDiscoverRTCClock.h @@ -12,12 +12,15 @@ class AutoDiscoverRTCClock : public mesh::RTCClock { mesh::RTCClock* _fallback; bool _has_hw_rtc; unsigned long _last_sync_ms; + const char* _source_name; bool i2c_probe(TwoWire& wire, uint8_t addr); void syncSystemClock(); public: AutoDiscoverRTCClock(mesh::RTCClock& fallback) - : _fallback(&fallback), _has_hw_rtc(false), _last_sync_ms(0) { } + : _fallback(&fallback), _has_hw_rtc(false), _last_sync_ms(0), _source_name("internal") { } + + const char* getSourceName() const override { return _source_name; } void begin(TwoWire& wire); uint32_t getCurrentTime() override; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index d495aada5f..9f1249f7b4 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -886,6 +886,13 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep #else strcpy(reply, "ERROR: Power management not supported"); #endif + } else if (memcmp(config, "rtcclock", 8) == 0) { + uint32_t now = getRTCClock()->getCurrentTime(); + DateTime dt = DateTime(now); + sprintf(reply, "> %s | %04d-%02d-%02d %02d:%02d:%02d UTC", + getRTCClock()->getSourceName(), + dt.year(), dt.month(), dt.day(), + dt.hour(), dt.minute(), dt.second()); } else { sprintf(reply, "??: %s", config); } From 5ba1e62b4f7e96af5dbbd30dbba25957f0a6eb21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Ma=C5=82ecki?= Date: Fri, 5 Jun 2026 14:23:36 +0200 Subject: [PATCH 3/3] debug on --- variants/heltec_v3/platformio.ini | 12 ++++++------ variants/heltec_v4/platformio.ini | 26 +++++++++++++------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 803ee683e0..7cc4963fff 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -47,8 +47,8 @@ build_flags = -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 + -D MESH_PACKET_LOGGING=1 + -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} + +<../examples/simple_repeater> @@ -167,8 +167,8 @@ build_flags = -D AUTO_SHUTDOWN_MILLIVOLTS=3400 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 + -D MESH_PACKET_LOGGING=1 + -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} + + @@ -191,8 +191,8 @@ build_flags = -D WIFI_SSID='"myssid"' -D WIFI_PWD='"mypwd"' -D OFFLINE_QUEUE_SIZE=256 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 + -D MESH_PACKET_LOGGING=1 + -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} + + diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index 6f6bf2b538..062bee2f31 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -92,8 +92,8 @@ build_flags = -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 + -D MESH_PACKET_LOGGING=1 + -D MESH_DEBUG=1 build_src_filter = ${heltec_v4_oled.build_src_filter} + +<../examples/simple_repeater> @@ -113,9 +113,9 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 -D WITH_ESPNOW_BRIDGE=1 -; -D BRIDGE_DEBUG=1 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 + -D BRIDGE_DEBUG=1 + -D MESH_PACKET_LOGGING=1 + -D MESH_DEBUG=1 build_src_filter = ${heltec_v4_oled.build_src_filter} + + @@ -134,8 +134,8 @@ build_flags = -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D ROOM_PASSWORD='"hello"' -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 + -D MESH_PACKET_LOGGING=1 + -D MESH_DEBUG=1 build_src_filter = ${heltec_v4_oled.build_src_filter} + +<../examples/simple_room_server> @@ -149,8 +149,8 @@ build_flags = ${heltec_v4_oled.build_flags} -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=1 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 + -D MESH_PACKET_LOGGING=1 + -D MESH_DEBUG=1 build_src_filter = ${heltec_v4_oled.build_src_filter} +<../examples/simple_secure_chat/main.cpp> lib_deps = @@ -212,8 +212,8 @@ build_flags = -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' -D WIFI_PWD='"mypwd"' -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 + -D MESH_PACKET_LOGGING=1 + -D MESH_DEBUG=1 build_src_filter = ${heltec_v4_oled.build_src_filter} + + @@ -298,8 +298,8 @@ build_flags = -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D ROOM_PASSWORD='"hello"' -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 + -D MESH_PACKET_LOGGING=1 + -D MESH_DEBUG=1 build_src_filter = ${heltec_v4_tft.build_src_filter} + +<../examples/simple_room_server>