fix: settings and lists lost to a restart mid-save on ESP32, LR1121 boards, repeater/room sims

- A restart between commitTempFile()'s remove and rename left only the .tmp;
  loads now recover it (prefs, contacts, channels, scopes).
- CustomLR1121Wrapper lacked readLiveRSSI/readLiveSNR: LR1121 boards didn't
  compile.
- The repeater and room server sims gain DynamicConfigSerializer.cpp and
  CommonRadioPrefs.cpp (broken since the v1.18 merge).
- Comments still describing the removed DualSerialInterface.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-10-15 14:42:04 +02:00
co-authored by Claude Opus 5.5
parent 8c20914689
commit cdf979dcca
7 changed files with 28 additions and 11 deletions
+4 -4
View File
@@ -40,7 +40,7 @@ protected:
}
// hasConnection() means "a BLE companion app is connected". Not isConnected()
// (a dual interface hardcodes that true as a USB send-fallback). Drives the BT
// (true whenever the build has a USB serial port). Drives the BT
// status indicator, pairing PIN, and the GPX-export collision warning — all
// BLE-specific. The Auto buzzer mute / message-wake use isClientConnected()
// (BLE *or* an open USB port) directly instead. Each UI calls this at the top
@@ -86,9 +86,9 @@ public:
}
bool hasConnection() const { return _connected; }
virtual void onBLEDisconnected() {}
// True only when a BLE central is actually bonded/connected. On a dual
// (BLE+USB) interface hasConnection() is always true (USB counts), so use
// this for BLE-specific UI like the pairing-PIN prompt.
// True only when a BLE central is actually bonded/connected, right now
// (hasConnection() is the same, as of the last pollConnection()). For
// BLE-specific UI like the pairing-PIN prompt.
bool isBLEConnected() const { return _serial->isBLEConnected(); }
// True when a companion app is connected over any transport (BLE bonded or an
// open USB-CDC port). For app-connected behaviour like Auto buzzer mute.
+13
View File
@@ -60,6 +60,15 @@ static bool commitTempFile(FILESYSTEM* fs, const char* tmp, const char* final_pa
#endif
}
// Before a load: a restart between commitTempFile()'s remove and rename leaves
// only the temp file, complete. (A save cut short leaves the old file beside
// it, and the next save overwrites the temp. Only the very first save, cut
// short, leaves a temp alone that is incomplete: the loaders stop at a short
// record, and the JSON reader keeps what it read.)
static void recoverTempFile(FILESYSTEM* fs, const char* tmp, const char* final_path) {
if (!fs->exists(final_path) && fs->exists(tmp)) fs->rename(tmp, final_path);
}
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
static uint32_t _ContactsChannelsTotalBlocks = 0;
#endif
@@ -441,6 +450,7 @@ static void removeStrayDir(FILESYSTEM* fs, const char* path) {
void DataStore::loadPrefs(NodePrefs& prefs, double& node_lat, double& node_lon) {
removeStrayDir(_fs, "/prefs");
if (_fs->exists("/prefs")) _fs->remove("/prefs"); // 2.0-rc's binary settings: 2.0 starts from the defaults
recoverTempFile(_fs, "/prefs.tmp", "/prefs.json");
File file = openRead(_fs, "/prefs.json");
if (file) {
BufferedFile in(file);
@@ -536,6 +546,7 @@ void DataStore::restoreRTCTime() {
}
void DataStore::loadContacts(DataStoreHost* host) {
recoverTempFile(_getContactsChannelsFS(), "/contacts3.tmp", "/contacts3");
File file = openRead(_getContactsChannelsFS(), "/contacts3");
if (file) {
bool full = false;
@@ -614,6 +625,7 @@ void DataStore::saveContacts(DataStoreHost* host, bool (*filter)(const ContactIn
bool DataStore::loadChannels(DataStoreHost* host) {
FILESYSTEM* fs = _getContactsChannelsFS();
recoverTempFile(fs, "/channels3.tmp", "/channels3");
File file = openRead(fs, "/channels3");
if (file) {
// /channels3: the leading 4-byte field's first byte is the channel's
@@ -735,6 +747,7 @@ void DataStore::saveChannels(DataStoreHost* host) {
}
bool DataStore::loadScopeList(ScopeList& list, const NodePrefs& prefs) {
recoverTempFile(_fs, "/scopes1.tmp", "/scopes1");
File file = openRead("/scopes1");
if (file) {
uint8_t hdr[2] = { 0, 0 }; // default_idx is read back below even if the header read fails
+1 -2
View File
@@ -335,8 +335,7 @@ class HomeScreen : public UIScreen {
if (!on) strcpy(v, "-");
else if (_task->isBLEConnected()) strcpy(v, "Connected");
// The pairing PIN is BLE-specific: show it while BLE is on but not yet
// bonded. (Gating on a plain isConnected() broke this on dual builds,
// where it's hardcoded true.)
// bonded. (Not a plain isConnected(): a USB port makes that true.)
else if (the_mesh.getBLEPin() != 0) snprintf(v, sizeof(rows[0].value), "PIN %d", (int)the_mesh.getBLEPin());
else strcpy(v, "Waiting");
} else if (page == ADVERT) {
+4 -5
View File
@@ -16,14 +16,13 @@ public:
virtual bool isConnected() const = 0;
virtual void loop() {};
// Returns true when a BLE companion app is connected. Default delegates to
// isConnected(); override in dual-interface wrappers that always report
// isConnected()=true but still need to distinguish BLE from USB state.
// isConnected(); MultiSerialInterface asks its Bluetooth interfaces only.
virtual bool isBLEConnected() const { return isConnected(); }
// True when a companion app is actually connected over *any* transport
// (BLE bonded or a host holding the USB-CDC port open). Distinct from
// isConnected(), which a dual interface hardcodes true as a send fallback.
// Used for app-connected UI (Auto buzzer mute, message-wake). Default =
// isConnected() so single-transport interfaces are unchanged.
// isConnected(), which a USB serial port reports true even with nothing on
// the other end (it can't tell). Used for app-connected UI (Auto buzzer
// mute, message-wake). Default = isConnected().
virtual bool isClientConnected() const { return isConnected(); }
// For a loop that sleeps between passes: frames waiting to be read or sent.
@@ -41,6 +41,8 @@ public:
float getLastRSSI() const override { return ((CustomLR1121 *)_radio)->getRSSI(); }
float getLastSNR() const override { return ((CustomLR1121 *)_radio)->getSNR(); }
float readLiveRSSI() const override { return ((CustomLR1121 *)_radio)->getRSSI(); }
float readLiveSNR() const override { return ((CustomLR1121 *)_radio)->getSNR(); }
uint8_t getSpreadingFactor() const override { return ((CustomLR1121 *)_radio)->getSpreadingFactor(); }
+2
View File
@@ -75,6 +75,8 @@ SRCS=(
src/helpers/ClientACL.cpp
src/helpers/CommonCLI.cpp
src/helpers/ConfigSerializer.cpp
src/helpers/DynamicConfigSerializer.cpp
src/helpers/CommonRadioPrefs.cpp
src/helpers/DeviceDiag.cpp
src/helpers/IdentityStore.cpp
src/helpers/RegionMap.cpp
+2
View File
@@ -61,6 +61,8 @@ SRCS=(
src/helpers/ClientACL.cpp
src/helpers/CommonCLI.cpp
src/helpers/ConfigSerializer.cpp
src/helpers/DynamicConfigSerializer.cpp
src/helpers/CommonRadioPrefs.cpp
src/helpers/DeviceDiag.cpp
src/helpers/IdentityStore.cpp
src/helpers/RegionMap.cpp