diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 3ed79fd5..6ce4d7ec 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -489,24 +489,45 @@ void DataStore::saveRTCTime() { } } +// The day this firmware was built (__DATE__, "Mmm dd yyyy"), 00:00 UTC: the +// clock can't be any earlier than that. +static uint32_t buildDayEpoch() { + static const char M[] = "JanFebMarAprMayJunJulAugSepOctNovDec"; + const char* d = __DATE__; + int m = 1; + while (m <= 12 && strncmp(M + 3 * (m - 1), d, 3) != 0) m++; + int day = atoi(d + 4), y = atoi(d + 7); + if (m > 12 || day < 1 || y < 2024) return 0; + y -= m <= 2; // days from 1970-01-01 (H. Hinnant's days_from_civil) + int era = y / 400, yoe = y - era * 400; + int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + day - 1; + int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; + return (uint32_t)(era * 146097 + doe - 719468) * 86400UL; +} + void DataStore::restoreRTCTime() { File file = openRead(_fs, "/rtc_save"); + uint32_t t = 0; if (file) { - uint32_t t = 0; file.read((uint8_t *)&t, sizeof(t)); file.close(); -#ifndef SIM_PLATFORM - // Real hardware has no other way to know the time before a GPS fix or - // a phone/CLI sync, so restoring the last-known saved time is the - // right call there. The sim's RTCClock (SimRTCClock.h) is already - // backed by the real host wall clock (time(NULL)) from the moment it's - // constructed -- overwriting that with a stale save from a previous - // visit (persisted via IDBFS, see the site's "returning visitor" note) - // would make a returning instance's on-screen clock drift away from - // the visitor's own real time instead of just showing it. - if (t > 1000000000UL) _clock->setCurrentTime(t); -#endif } + // Saved at power-off and every half hour (MyMesh::loop()); a flash or a + // reset in between can leave it far behind -- never earlier than the build. + uint32_t built = buildDayEpoch(); + if (t < built) t = built; +#ifndef SIM_PLATFORM + // Real hardware has no other way to know the time before a GPS fix or + // a phone/CLI sync, so restoring the last-known saved time is the + // right call there. The sim's RTCClock (SimRTCClock.h) is already + // backed by the real host wall clock (time(NULL)) from the moment it's + // constructed -- overwriting that with a stale save from a previous + // visit (persisted via IDBFS, see the site's "returning visitor" note) + // would make a returning instance's on-screen clock drift away from + // the visitor's own real time instead of just showing it. + // Only ever forward: a board with its own RTC chip may already know better. + if (t > 1000000000UL && t > _clock->getCurrentTime()) _clock->setCurrentTime(t); +#endif } void DataStore::loadContacts(DataStoreHost* host) { diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 7f5f7734..c6ec4863 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -121,6 +121,9 @@ #define DIRECT_SEND_PERHOP_FACTOR 6.0f #define DIRECT_SEND_PERHOP_EXTRA_MILLIS 250 #define LAZY_CONTACTS_WRITE_DELAY 5000 +#define LAZY_PATHS_WRITE_DELAY 60000 // a path learned (a DM's ACK brings one back): no hurry +#define USER_QUIET_MILLIS 15000 // the contacts write stalls the loop: not while the keys are in use +#define RTC_SAVE_INTERVAL_MILLIS (30 * 60 * 1000UL) // the time restored at boot after a reset or a flash #define PUBLIC_GROUP_PSK "izOH6cXN6mrJ5e26oRXNcg==" @@ -546,7 +549,9 @@ void MyMesh::onContactPathUpdated(const ContactInfo &contact) { memcpy(&out_frame[1], contact.id.pub_key, PUB_KEY_SIZE); _serial->writeFrame(out_frame, 1 + PUB_KEY_SIZE); // NOTE: app may not be connected - dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); + // Only the path changed: lost on a reset it costs one flood, so its write + // waits; the same path heard again isn't written at all. + if (!_path_same && !dirty_paths_expiry) dirty_paths_expiry = futureMillis(LAZY_PATHS_WRITE_DELAY); } ContactInfo* MyMesh::processAck(const uint8_t *data) { @@ -1356,7 +1361,11 @@ bool MyMesh::onContactPathRecv(ContactInfo& contact, uint8_t* in_path, uint8_t i } } // let base class handle received path and data + int path_bytes = (out_path_len & 63) * ((out_path_len >> 6) + 1); + _path_same = contact.out_path_len == out_path_len && path_bytes <= MAX_PATH_SIZE + && memcmp(contact.out_path, out_path, path_bytes) == 0; bool r = BaseChatMesh::onContactPathRecv(contact, in_path, in_path_len, out_path, out_path_len, extra_type, extra, extra_len); + _path_same = false; // A flood DM's ACK comes back folded into the return path, which the base // class matches without going through onAckRecv() -- so the UI's delivery // marker never heard of it and kept resending. Tell it here too; it only @@ -3455,9 +3464,22 @@ void MyMesh::loop() { } // is there are pending dirty contacts write needed? - if (dirty_contacts_expiry && millisHasNowPassed(dirty_contacts_expiry)) { - saveContacts(); - dirty_contacts_expiry = 0; + // Rewriting the whole file blocks for up to a second or two on the nRF52: + // put off while the user is pressing keys. + if ((dirty_contacts_expiry && millisHasNowPassed(dirty_contacts_expiry)) + || (dirty_paths_expiry && millisHasNowPassed(dirty_paths_expiry))) { + if (millis() - _last_input_ms < USER_QUIET_MILLIS) { + if (dirty_contacts_expiry) dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); + if (dirty_paths_expiry) dirty_paths_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); + } else { + saveContacts(); + dirty_contacts_expiry = dirty_paths_expiry = 0; + } + } + + if (millis() - _last_rtc_save_ms > RTC_SAVE_INTERVAL_MILLIS && millis() - _last_input_ms >= USER_QUIET_MILLIS) { + _store->saveRTCTime(); + _last_rtc_save_ms = millis(); } if (_prefs.advert_auto_interval_sec > 0 && millisHasNowPassed(_next_auto_advert_ms)) { diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 53032ebb..e8bf4e30 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -428,7 +428,11 @@ public: // this before rebooting; UITask::shutdown() (low-battery auto-shutdown, // long-press power-off) needs the same flush or a whole session's worth // of learned contacts can be lost. - void flushDirtyContacts() { if (dirty_contacts_expiry) { saveContacts(); dirty_contacts_expiry = 0; } } + void flushDirtyContacts() { + if (dirty_contacts_expiry || dirty_paths_expiry) { saveContacts(); dirty_contacts_expiry = dirty_paths_expiry = 0; } + } + // A key press / touch: the lazy contacts write waits until the user pauses. + void noteUserInput() { _last_input_ms = millis(); } DataStore* getDataStore() const { return _store; } void applyApc(); // (re)initialise Adaptive Power Control from prefs // Adaptive Power Control is suppressed while repeating: a repeater wants full, @@ -649,6 +653,10 @@ private: uint32_t sign_data_len; uint32_t sign_data_cap; // what the malloc got: at most MAX_SIGN_DATA_LEN unsigned long dirty_contacts_expiry; + unsigned long dirty_paths_expiry = 0; // only paths changed: written later (not pending work) + unsigned long _last_input_ms = 0; + unsigned long _last_rtc_save_ms = 0; + bool _path_same = false; // onContactPathRecv(): the path heard is the one held unsigned long _bot_last_ch_reply_ms; unsigned long _bot_last_room_reply_ms; unsigned long _next_auto_advert_ms; diff --git a/examples/companion_radio/ui-lvgl/UITask.cpp b/examples/companion_radio/ui-lvgl/UITask.cpp index ab810e18..89212a34 100644 --- a/examples/companion_radio/ui-lvgl/UITask.cpp +++ b/examples/companion_radio/ui-lvgl/UITask.cpp @@ -1561,6 +1561,7 @@ void UITask::loop() { if (lvport::touched()) { lvport::swallowTouch(); wake(); } } } else { + if (lv_display_get_inactive_time(NULL) < 1000) the_mesh.noteUserInput(); // Locked, nobody's using it: the lock screen goes dark after LOCK_OFF_MS // even with auto-off at Never (a woken-up pocket, a boot PIN, a message). uint32_t aoff = autoOffMillis(); diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 004d0ac2..5775de59 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -3663,6 +3663,7 @@ bool UITask::addWaypoint(int32_t lat, int32_t lon, const char* label) { } char UITask::checkDisplayOn(char c) { + the_mesh.noteUserInput(); if (_display != NULL) { if (!_display->isOn()) { _display->turnOn();