diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 9bb747e79..6eff3a5d6 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1065,13 +1065,8 @@ void MyMesh::handleCmdFrame(size_t len) { } else if (cmd_frame[0] == CMD_SET_DEVICE_TIME && len >= 5) { uint32_t secs; memcpy(&secs, &cmd_frame[1], 4); - uint32_t curr = getRTCClock()->getCurrentTime(); - if (secs >= curr) { - getRTCClock()->setCurrentTime(secs); - writeOKFrame(); - } else { - writeErrFrame(ERR_CODE_ILLEGAL_ARG); - } + getRTCClock()->setCurrentTime(secs); + writeOKFrame(); } else if (cmd_frame[0] == CMD_SEND_SELF_ADVERT) { mesh::Packet* pkt; if (_prefs.advert_loc_policy == ADVERT_LOC_NONE) { diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index ae2d93753..fca7118c4 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -225,7 +225,7 @@ class HomeScreen : public UIScreen { for (int i = 0; i < UI_RECENT_LIST_SIZE; i++, y += 11) { auto a = &recent[i]; if (a->name[0] == 0) continue; // empty slot - int secs = _rtc->getCurrentTime() - a->recv_timestamp; + uint32_t secs = safeElapsedSecs(_rtc->getCurrentTime(), a->recv_timestamp); if (secs < 60) { sprintf(tmp, "%ds", secs); } else if (secs < 60*60) { @@ -488,7 +488,7 @@ class MsgPreviewScreen : public UIScreen { auto p = &unread[head]; - int secs = _rtc->getCurrentTime() - p->timestamp; + uint32_t secs = safeElapsedSecs(_rtc->getCurrentTime(), p->timestamp); if (secs < 60) { sprintf(tmp, "%ds", secs); } else if (secs < 60*60) { diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 6d957cc09..d84719853 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -343,7 +343,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t // add next neighbour to results auto neighbour = sorted_neighbours[index + offset]; - uint32_t heard_seconds_ago = getRTCClock()->getCurrentTime() - neighbour->heard_timestamp; + uint32_t heard_seconds_ago = safeElapsedSecs(getRTCClock()->getCurrentTime(), neighbour->heard_timestamp); memcpy(&results_buffer[results_offset], neighbour->id.pub_key, pubkey_prefix_length); results_offset += pubkey_prefix_length; memcpy(&results_buffer[results_offset], &heard_seconds_ago, 4); results_offset += 4; memcpy(&results_buffer[results_offset], &neighbour->snr, 1); results_offset += 1; @@ -934,7 +934,7 @@ void MyMesh::formatNeighborsReply(char *reply) { mesh::Utils::toHex(hex, neighbour->id.pub_key, 4); // add next neighbour - uint32_t secs_ago = getRTCClock()->getCurrentTime() - neighbour->heard_timestamp; + uint32_t secs_ago = safeElapsedSecs(getRTCClock()->getCurrentTime(), neighbour->heard_timestamp); sprintf(dp, "%s:%d:%d", hex, secs_ago, neighbour->snr); while (*dp) dp++; // find end of string diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index 018ec2a20..c20f5d519 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -158,13 +158,8 @@ class MyMesh : public BaseChatMesh, ContactVisitor { } void setClock(uint32_t timestamp) { - uint32_t curr = getRTCClock()->getCurrentTime(); - if (timestamp > curr) { - getRTCClock()->setCurrentTime(timestamp); - Serial.println(" (OK - clock set!)"); - } else { - Serial.println(" (ERR: clock cannot go backwards)"); - } + getRTCClock()->setCurrentTime(timestamp); + Serial.println(" (OK - clock set!)"); } void importCard(const char* command) { diff --git a/examples/simple_sensor/TimeSeriesData.cpp b/examples/simple_sensor/TimeSeriesData.cpp index f6157f9af..c780c7de2 100644 --- a/examples/simple_sensor/TimeSeriesData.cpp +++ b/examples/simple_sensor/TimeSeriesData.cpp @@ -1,4 +1,5 @@ #include "TimeSeriesData.h" +#include void TimeSeriesData::recordData(mesh::RTCClock* clock, float value) { uint32_t now = clock->getCurrentTime(); @@ -12,7 +13,7 @@ void TimeSeriesData::recordData(mesh::RTCClock* clock, float value) { 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 { int i = next, n = num_slots; - uint32_t ago = clock->getCurrentTime() - last_timestamp; + uint32_t ago = safeElapsedSecs(clock->getCurrentTime(), last_timestamp); int num_values = 0; float total = 0.0f; diff --git a/src/helpers/ArduinoHelpers.h b/src/helpers/ArduinoHelpers.h index 97596daa3..5f12e5b01 100644 --- a/src/helpers/ArduinoHelpers.h +++ b/src/helpers/ArduinoHelpers.h @@ -3,6 +3,15 @@ #include #include +// Safe elapsed time calculation that handles clock corrections (when RTC is set backwards). +// Returns 0 if recorded_timestamp is in the "future" relative to current_time. +inline uint32_t safeElapsedSecs(uint32_t current_time, uint32_t recorded_timestamp) { + if (recorded_timestamp > current_time) { + return 0; // Clock was corrected backwards; treat as "just now" + } + return current_time - recorded_timestamp; +} + class VolatileRTCClock : public mesh::RTCClock { uint32_t base_time; uint64_t accumulator; diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 6de7469d0..2e96d8899 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -609,7 +609,7 @@ bool BaseChatMesh::startConnection(const ContactInfo& contact, uint16_t keep_ali uint32_t interval = connections[use_idx].keep_alive_millis = ((uint32_t)keep_alive_secs)*1000; connections[use_idx].next_ping = futureMillis(interval); connections[use_idx].expected_ack = 0; - connections[use_idx].last_activity = getRTCClock()->getCurrentTime(); + connections[use_idx].last_activity_ms = _ms->getMillis(); return true; // success } @@ -619,7 +619,7 @@ void BaseChatMesh::stopConnection(const uint8_t* pub_key) { connections[i].keep_alive_millis = 0; // mark slot as now free connections[i].next_ping = 0; connections[i].expected_ack = 0; - connections[i].last_activity = 0; + connections[i].last_activity_ms = 0; break; } } @@ -635,7 +635,7 @@ bool BaseChatMesh::hasConnectionTo(const uint8_t* pub_key) { void BaseChatMesh::markConnectionActive(const ContactInfo& contact) { for (int i = 0; i < MAX_CONNECTIONS; i++) { if (connections[i].keep_alive_millis > 0 && connections[i].server_id.matches(contact.id)) { - connections[i].last_activity = getRTCClock()->getCurrentTime(); + connections[i].last_activity_ms = _ms->getMillis(); // re-schedule next KEEP_ALIVE, now that we have heard from server connections[i].next_ping = futureMillis(connections[i].keep_alive_millis); @@ -649,7 +649,7 @@ ContactInfo* BaseChatMesh::checkConnectionsAck(const uint8_t* data) { if (connections[i].keep_alive_millis > 0 && memcmp(&connections[i].expected_ack, data, 4) == 0) { // yes, got an ack for our keep_alive request! connections[i].expected_ack = 0; - connections[i].last_activity = getRTCClock()->getCurrentTime(); + connections[i].last_activity_ms = _ms->getMillis(); // re-schedule next KEEP_ALIVE, now that we have heard from server connections[i].next_ping = futureMillis(connections[i].keep_alive_millis); @@ -666,14 +666,17 @@ void BaseChatMesh::checkConnections() { for (int i = 0; i < MAX_CONNECTIONS; i++) { if (connections[i].keep_alive_millis == 0) continue; // unused slot - uint32_t now = getRTCClock()->getCurrentTime(); - uint32_t expire_secs = (connections[i].keep_alive_millis / 1000) * 5 / 2; // 2.5 x keep_alive interval - if (now >= connections[i].last_activity + expire_secs) { + // Monotonic time is immune to RTC clock changes (GPS, NTP, manual sync). + // Assumes light sleep (millis() keeps incrementing). Deep sleep resets millis(), + // but BaseChatMesh is only used by companion_radio which uses light sleep. + unsigned long now = _ms->getMillis(); + unsigned long expire_millis = (connections[i].keep_alive_millis * 5UL) / 2; // 2.5 x keep_alive interval + if ((now - connections[i].last_activity_ms) >= expire_millis) { // connection now lost connections[i].keep_alive_millis = 0; connections[i].next_ping = 0; connections[i].expected_ack = 0; - connections[i].last_activity = 0; + connections[i].last_activity_ms = 0; continue; } diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index fd391b980..019a34298 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -44,7 +44,7 @@ class ContactsIterator { struct ConnectionInfo { mesh::Identity server_id; unsigned long next_ping; - uint32_t last_activity; + unsigned long last_activity_ms; // monotonic millis() for connection expiry uint32_t keep_alive_millis; uint32_t expected_ack; }; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 10ab86691..3ee47065b 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -205,15 +205,10 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch _callbacks->sendSelfAdvertisement(1500, true); // longer delay, give CLI response time to be sent first strcpy(reply, "OK - Advert sent"); } else if (memcmp(command, "clock sync", 10) == 0) { - uint32_t curr = getRTCClock()->getCurrentTime(); - if (sender_timestamp > curr) { - getRTCClock()->setCurrentTime(sender_timestamp + 1); - uint32_t now = getRTCClock()->getCurrentTime(); - DateTime dt = DateTime(now); - sprintf(reply, "OK - clock set: %02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year()); - } else { - strcpy(reply, "ERR: clock cannot go backwards"); - } + getRTCClock()->setCurrentTime(sender_timestamp + 1); + uint32_t now = getRTCClock()->getCurrentTime(); + DateTime dt = DateTime(now); + sprintf(reply, "OK - clock set: %02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year()); } else if (memcmp(command, "start ota", 9) == 0) { if (!_board->startOTAUpdate(_prefs->node_name, reply)) { strcpy(reply, "Error"); @@ -224,15 +219,10 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sprintf(reply, "%02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year()); } else if (memcmp(command, "time ", 5) == 0) { // set time (to epoch seconds) uint32_t secs = _atoi(&command[5]); - uint32_t curr = getRTCClock()->getCurrentTime(); - if (secs > curr) { - getRTCClock()->setCurrentTime(secs); - uint32_t now = getRTCClock()->getCurrentTime(); - DateTime dt = DateTime(now); - sprintf(reply, "OK - clock set: %02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year()); - } else { - strcpy(reply, "(ERR: clock cannot go backwards)"); - } + getRTCClock()->setCurrentTime(secs); + uint32_t now = getRTCClock()->getCurrentTime(); + DateTime dt = DateTime(now); + sprintf(reply, "OK - clock set: %02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year()); } else if (memcmp(command, "neighbors", 9) == 0) { _callbacks->formatNeighborsReply(reply); } else if (memcmp(command, "neighbor.remove ", 16) == 0) {