Contacts: cap chunked save to twice a day; flush on low battery

Throttle the ESP32 lazy contact save from a 5s debounce to a 12h
interval (CONTACTS_SAVE_INTERVAL). A new _nextContactSaveDue anchors
the next flush 12h out on the first dirty pass and only writes once
reached, clearing after each save so the next dirty event re-anchors.
Cuts the recurring ~95KB LittleFS rewrites on busy meshes. nRF52/STM32
blocking-save path unchanged.

Flush contacts (blocking) in the AUTO_SHUTDOWN_MILLIVOLTS path before
shutdown() so low-battery power-off doesn't drop up to 12h of contact
learning. Manual power-off left untouched.

Gate the SHUTDOWN home page out on the watch (MECK_TWATCH): removed
from the HomePage enum with render/input/isOnShutdownPage guarded.
The AXP2101 PWR-button long-press is the shutdown path there.

Files: MyMesh.h, MyMesh.cpp, UITask.cpp
This commit is contained in:
pelgraine
2026-07-12 17:37:04 +10:00
parent 1c0778b00e
commit 29f60c5bcd
3 changed files with 28 additions and 3 deletions
+11 -2
View File
@@ -119,6 +119,7 @@
#define DIRECT_SEND_PERHOP_EXTRA_MILLIS 250
#define LAZY_CONTACTS_WRITE_DELAY 5000
#define USER_IDLE_SAVE_THRESHOLD 15000 // Defer saves until 15s after last keypress
#define CONTACTS_SAVE_INTERVAL 43200000 // 12h: cap chunked contact flush to twice a day
#define PUBLIC_GROUP_PSK "izOH6cXN6mrJ5e26oRXNcg=="
@@ -1495,6 +1496,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
next_ack_idx = 0;
sign_data = NULL;
dirty_contacts_expiry = 0;
_nextContactSaveDue = 0;
advert_paths = nullptr; // PSRAM-allocated in begin()
_rxlog = nullptr; // PSRAM-allocated in begin()
_rxlog_head = 0;
@@ -3714,8 +3716,15 @@ void MyMesh::loop() {
// Voice session or active keyboard use -- push save forward
dirty_contacts_expiry = futureMillis(2000);
} else if (!_store->isSaveInProgress()) {
_store->beginSaveContacts(this);
dirty_contacts_expiry = 0;
// Cap the chunked flush to CONTACTS_SAVE_INTERVAL (twice a day): anchor
// the next-due time on the first dirty pass, then only write once it is
// reached. Cleared after a save so the next dirty event re-anchors.
if (_nextContactSaveDue == 0) _nextContactSaveDue = futureMillis(CONTACTS_SAVE_INTERVAL);
if (millisHasNowPassed(_nextContactSaveDue)) {
_store->beginSaveContacts(this);
dirty_contacts_expiry = 0;
_nextContactSaveDue = 0;
}
}
#endif
}
+1
View File
@@ -346,6 +346,7 @@ private:
uint8_t *sign_data;
uint32_t sign_data_len;
unsigned long dirty_contacts_expiry;
unsigned long _nextContactSaveDue; // ESP32 chunked save: next scheduled flush (0 = unscheduled)
TransportKey send_scope;
+16 -1
View File
@@ -183,7 +183,9 @@ class HomeScreen : public UIScreen {
#if HAS_BQ27220
BATTERY,
#endif
#if !defined(MECK_TWATCH)
SHUTDOWN,
#endif
Count // keep as last
};
@@ -366,7 +368,11 @@ public:
bool isEditingUTC() const { return _editing_utc; }
bool isFirstPage() const { return _page == HomePage::FIRST; }
bool isOnRecentPage() const { return _page == HomePage::RECENT; }
#if defined(MECK_TWATCH)
bool isOnShutdownPage() const { return false; }
#else
bool isOnShutdownPage() const { return _page == HomePage::SHUTDOWN; }
#endif
void cancelEditing() {
if (_editing_utc) {
_node_prefs->utc_offset_hours = _saved_utc_offset;
@@ -1260,7 +1266,9 @@ public:
sprintf(buf, "%d.%d C", battTemp / 10, abs(battTemp % 10));
display.drawTextRightAlign(display.width()-1-EINK_X_OFFSET, y, buf);
#endif
} else if (_page == HomePage::SHUTDOWN) {
}
#if !defined(MECK_TWATCH)
else if (_page == HomePage::SHUTDOWN) {
display.setColor(DisplayDriver::GREEN);
display.setTextSize(1);
if (_shutdown_init) {
@@ -1304,6 +1312,7 @@ public:
display.drawTextCentered(display.width() / 2, y2, line2);
}
}
#endif
return _editing_utc ? 700 : 5000;
}
@@ -1342,6 +1351,7 @@ public:
return true; // Consume all other keys while editing
}
#if !defined(MECK_TWATCH)
// SHUTDOWN page -- intercept up/down and Enter before page cycling
if (_page == HomePage::SHUTDOWN) {
if (_poweroff_confirm) {
@@ -1376,6 +1386,7 @@ public:
}
// Left/right fall through to page cycling below
}
#endif
if (c == KEY_LEFT || c == KEY_PREV || c == 'a') {
_page = (_page + HomePage::Count - 1) % HomePage::Count;
@@ -2894,6 +2905,10 @@ if (curr) curr->poll();
}
#endif
// Flush contacts before the battery dies -- the chunked lazy save may not
// have run for hours. Blocking save (tmp-then-rename, brownout-safe).
the_mesh.saveContacts();
shutdown();
}
} else {