Skip to content

Commit da54005

Browse files
committed
Allow setting RTC clock backwards and fix elapsed-time underflow
Remove the "clock cannot go backwards" restriction from all set-time paths (CLI `time`, `clock sync`, companion radio CMD_SET_DEVICE_TIME, and simple_secure_chat). The ESP32-S3 RTC drifts 5-10% during deep sleep, making backwards correction necessary after even a few days. Add safeElapsedSecs() helper in ArduinoHelpers.h that clamps elapsed time to 0 when a stored timestamp appears to be in the future after a clock correction. Applied to: - Neighbor "heard X ago" displays in simple_repeater - UI time displays in companion_radio - TimeSeriesData calculations in simple_sensor Switch BaseChatMesh connection expiry from RTC timestamps to monotonic millis(), making it immune to RTC adjustments from GPS, NTP, or manual sync. Rename last_activity to last_activity_ms to reflect the change.
1 parent 8c0d5c5 commit da54005

9 files changed

Lines changed: 39 additions & 46 deletions

File tree

examples/companion_radio/MyMesh.cpp

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,13 +1226,8 @@ void MyMesh::handleCmdFrame(size_t len) {
12261226
} else if (cmd_frame[0] == CMD_SET_DEVICE_TIME && len >= 5) {
12271227
uint32_t secs;
12281228
memcpy(&secs, &cmd_frame[1], 4);
1229-
uint32_t curr = getRTCClock()->getCurrentTime();
1230-
if (secs >= curr) {
1231-
getRTCClock()->setCurrentTime(secs);
1232-
writeOKFrame();
1233-
} else {
1234-
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
1235-
}
1229+
getRTCClock()->setCurrentTime(secs);
1230+
writeOKFrame();
12361231
} else if (cmd_frame[0] == CMD_SEND_SELF_ADVERT) {
12371232
mesh::Packet* pkt;
12381233
if (_prefs.advert_loc_policy == ADVERT_LOC_NONE) {

examples/companion_radio/ui-new/UITask.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ class HomeScreen : public UIScreen {
241241
for (int i = 0; i < UI_RECENT_LIST_SIZE; i++, y += 11) {
242242
auto a = &recent[i];
243243
if (a->name[0] == 0) continue; // empty slot
244-
int secs = _rtc->getCurrentTime() - a->recv_timestamp;
244+
uint32_t secs = safeElapsedSecs(_rtc->getCurrentTime(), a->recv_timestamp);
245245
if (secs < 60) {
246246
sprintf(tmp, "%ds", secs);
247247
} else if (secs < 60*60) {
@@ -504,7 +504,7 @@ class MsgPreviewScreen : public UIScreen {
504504

505505
auto p = &unread[head];
506506

507-
int secs = _rtc->getCurrentTime() - p->timestamp;
507+
uint32_t secs = safeElapsedSecs(_rtc->getCurrentTime(), p->timestamp);
508508
if (secs < 60) {
509509
sprintf(tmp, "%ds", secs);
510510
} else if (secs < 60*60) {

examples/simple_repeater/MyMesh.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t
355355
#if MAX_NEIGHBOURS
356356
// add next neighbour to results
357357
auto neighbour = sorted_neighbours[index + offset];
358-
uint32_t heard_seconds_ago = getRTCClock()->getCurrentTime() - neighbour->heard_timestamp;
358+
uint32_t heard_seconds_ago = safeElapsedSecs(getRTCClock()->getCurrentTime(), neighbour->heard_timestamp);
359359
memcpy(&results_buffer[results_offset], neighbour->id.pub_key, pubkey_prefix_length); results_offset += pubkey_prefix_length;
360360
memcpy(&results_buffer[results_offset], &heard_seconds_ago, 4); results_offset += 4;
361361
memcpy(&results_buffer[results_offset], &neighbour->snr, 1); results_offset += 1;
@@ -1096,7 +1096,7 @@ void MyMesh::formatNeighborsReply(char *reply) {
10961096
mesh::Utils::toHex(hex, neighbour->id.pub_key, 4);
10971097

10981098
// add next neighbour
1099-
uint32_t secs_ago = getRTCClock()->getCurrentTime() - neighbour->heard_timestamp;
1099+
uint32_t secs_ago = safeElapsedSecs(getRTCClock()->getCurrentTime(), neighbour->heard_timestamp);
11001100
sprintf(dp, "%s:%d:%d", hex, secs_ago, neighbour->snr);
11011101
while (*dp)
11021102
dp++; // find end of string

examples/simple_secure_chat/main.cpp

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -158,13 +158,8 @@ class MyMesh : public BaseChatMesh, ContactVisitor {
158158
}
159159

160160
void setClock(uint32_t timestamp) {
161-
uint32_t curr = getRTCClock()->getCurrentTime();
162-
if (timestamp > curr) {
163-
getRTCClock()->setCurrentTime(timestamp);
164-
Serial.println(" (OK - clock set!)");
165-
} else {
166-
Serial.println(" (ERR: clock cannot go backwards)");
167-
}
161+
getRTCClock()->setCurrentTime(timestamp);
162+
Serial.println(" (OK - clock set!)");
168163
}
169164

170165
void importCard(const char* command) {

examples/simple_sensor/TimeSeriesData.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "TimeSeriesData.h"
2+
#include <helpers/ArduinoHelpers.h>
23

34
void TimeSeriesData::recordData(mesh::RTCClock* clock, float value) {
45
uint32_t now = clock->getCurrentTime();
@@ -12,7 +13,7 @@ void TimeSeriesData::recordData(mesh::RTCClock* clock, float value) {
1213

1314
void TimeSeriesData::calcMinMaxAvg(mesh::RTCClock* clock, uint32_t start_secs_ago, uint32_t end_secs_ago, MinMaxAvg* dest, uint8_t channel, uint8_t lpp_type) const {
1415
int i = next, n = num_slots;
15-
uint32_t ago = clock->getCurrentTime() - last_timestamp;
16+
uint32_t ago = safeElapsedSecs(clock->getCurrentTime(), last_timestamp);
1617
int num_values = 0;
1718
float total = 0.0f;
1819

src/helpers/ArduinoHelpers.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@
33
#include <Mesh.h>
44
#include <Arduino.h>
55

6+
// Safe elapsed time calculation that handles clock corrections (when RTC is set backwards).
7+
// Returns 0 if recorded_timestamp is in the "future" relative to current_time.
8+
inline uint32_t safeElapsedSecs(uint32_t current_time, uint32_t recorded_timestamp) {
9+
if (recorded_timestamp > current_time) {
10+
return 0; // Clock was corrected backwards; treat as "just now"
11+
}
12+
return current_time - recorded_timestamp;
13+
}
14+
615
class VolatileRTCClock : public mesh::RTCClock {
716
uint32_t base_time;
817
uint64_t accumulator;

src/helpers/BaseChatMesh.cpp

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -688,7 +688,7 @@ bool BaseChatMesh::startConnection(const ContactInfo& contact, uint16_t keep_ali
688688
uint32_t interval = connections[use_idx].keep_alive_millis = ((uint32_t)keep_alive_secs)*1000;
689689
connections[use_idx].next_ping = futureMillis(interval);
690690
connections[use_idx].expected_ack = 0;
691-
connections[use_idx].last_activity = getRTCClock()->getCurrentTime();
691+
connections[use_idx].last_activity_ms = _ms->getMillis();
692692
return true; // success
693693
}
694694

@@ -698,7 +698,7 @@ void BaseChatMesh::stopConnection(const uint8_t* pub_key) {
698698
connections[i].keep_alive_millis = 0; // mark slot as now free
699699
connections[i].next_ping = 0;
700700
connections[i].expected_ack = 0;
701-
connections[i].last_activity = 0;
701+
connections[i].last_activity_ms = 0;
702702
break;
703703
}
704704
}
@@ -714,7 +714,7 @@ bool BaseChatMesh::hasConnectionTo(const uint8_t* pub_key) {
714714
void BaseChatMesh::markConnectionActive(const ContactInfo& contact) {
715715
for (int i = 0; i < MAX_CONNECTIONS; i++) {
716716
if (connections[i].keep_alive_millis > 0 && connections[i].server_id.matches(contact.id)) {
717-
connections[i].last_activity = getRTCClock()->getCurrentTime();
717+
connections[i].last_activity_ms = _ms->getMillis();
718718

719719
// re-schedule next KEEP_ALIVE, now that we have heard from server
720720
connections[i].next_ping = futureMillis(connections[i].keep_alive_millis);
@@ -728,7 +728,7 @@ ContactInfo* BaseChatMesh::checkConnectionsAck(const uint8_t* data) {
728728
if (connections[i].keep_alive_millis > 0 && memcmp(&connections[i].expected_ack, data, 4) == 0) {
729729
// yes, got an ack for our keep_alive request!
730730
connections[i].expected_ack = 0;
731-
connections[i].last_activity = getRTCClock()->getCurrentTime();
731+
connections[i].last_activity_ms = _ms->getMillis();
732732

733733
// re-schedule next KEEP_ALIVE, now that we have heard from server
734734
connections[i].next_ping = futureMillis(connections[i].keep_alive_millis);
@@ -745,14 +745,17 @@ void BaseChatMesh::checkConnections() {
745745
for (int i = 0; i < MAX_CONNECTIONS; i++) {
746746
if (connections[i].keep_alive_millis == 0) continue; // unused slot
747747

748-
uint32_t now = getRTCClock()->getCurrentTime();
749-
uint32_t expire_secs = (connections[i].keep_alive_millis / 1000) * 5 / 2; // 2.5 x keep_alive interval
750-
if (now >= connections[i].last_activity + expire_secs) {
748+
// Monotonic time is immune to RTC clock changes (GPS, NTP, manual sync).
749+
// Assumes light sleep (millis() keeps incrementing). Deep sleep resets millis(),
750+
// but BaseChatMesh is only used by companion_radio which uses light sleep.
751+
unsigned long now = _ms->getMillis();
752+
unsigned long expire_millis = (connections[i].keep_alive_millis * 5UL) / 2; // 2.5 x keep_alive interval
753+
if ((now - connections[i].last_activity_ms) >= expire_millis) {
751754
// connection now lost
752755
connections[i].keep_alive_millis = 0;
753756
connections[i].next_ping = 0;
754757
connections[i].expected_ack = 0;
755-
connections[i].last_activity = 0;
758+
connections[i].last_activity_ms = 0;
756759
continue;
757760
}
758761

src/helpers/BaseChatMesh.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class ContactsIterator {
4646
struct ConnectionInfo {
4747
mesh::Identity server_id;
4848
unsigned long next_ping;
49-
uint32_t last_activity;
49+
unsigned long last_activity_ms; // monotonic millis() for connection expiry
5050
uint32_t keep_alive_millis;
5151
uint32_t expected_ack;
5252
};

src/helpers/CommonCLI.cpp

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -230,15 +230,10 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
230230
_callbacks->sendSelfAdvertisement(1500, true); // longer delay, give CLI response time to be sent first
231231
strcpy(reply, "OK - Advert sent");
232232
} else if (memcmp(command, "clock sync", 10) == 0) {
233-
uint32_t curr = getRTCClock()->getCurrentTime();
234-
if (sender_timestamp > curr) {
235-
getRTCClock()->setCurrentTime(sender_timestamp + 1);
236-
uint32_t now = getRTCClock()->getCurrentTime();
237-
DateTime dt = DateTime(now);
238-
sprintf(reply, "OK - clock set: %02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year());
239-
} else {
240-
strcpy(reply, "ERR: clock cannot go backwards");
241-
}
233+
getRTCClock()->setCurrentTime(sender_timestamp + 1);
234+
uint32_t now = getRTCClock()->getCurrentTime();
235+
DateTime dt = DateTime(now);
236+
sprintf(reply, "OK - clock set: %02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year());
242237
} else if (memcmp(command, "start ota", 9) == 0) {
243238
if (!_board->startOTAUpdate(_prefs->node_name, reply)) {
244239
strcpy(reply, "Error");
@@ -249,15 +244,10 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
249244
sprintf(reply, "%02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year());
250245
} else if (memcmp(command, "time ", 5) == 0) { // set time (to epoch seconds)
251246
uint32_t secs = _atoi(&command[5]);
252-
uint32_t curr = getRTCClock()->getCurrentTime();
253-
if (secs > curr) {
254-
getRTCClock()->setCurrentTime(secs);
255-
uint32_t now = getRTCClock()->getCurrentTime();
256-
DateTime dt = DateTime(now);
257-
sprintf(reply, "OK - clock set: %02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year());
258-
} else {
259-
strcpy(reply, "(ERR: clock cannot go backwards)");
260-
}
247+
getRTCClock()->setCurrentTime(secs);
248+
uint32_t now = getRTCClock()->getCurrentTime();
249+
DateTime dt = DateTime(now);
250+
sprintf(reply, "OK - clock set: %02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year());
261251
} else if (memcmp(command, "neighbors", 9) == 0) {
262252
_callbacks->formatNeighborsReply(reply);
263253
} else if (memcmp(command, "neighbor.remove ", 16) == 0) {

0 commit comments

Comments
 (0)