Skip to content

Commit b5919c4

Browse files
committed
feat: isolate event radio profiles
1 parent 0199a1f commit b5919c4

3 files changed

Lines changed: 108 additions & 2 deletions

File tree

src/mesh/NodeDB.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2166,6 +2166,19 @@ void NodeDB::loadFromDisk()
21662166
migrationSavePending = false;
21672167
configDecodeFailed = false;
21682168

2169+
#if USERPREFS_EVENT_MODE
2170+
// The event config is intentionally isolated from the ordinary config.
2171+
// We only seed it when no event file exists; a corrupt event file must not
2172+
// silently fall back to and overwrite the user's normal radio profile.
2173+
bool eventConfigMissing = false;
2174+
#ifdef FSCom
2175+
spiLock->lock();
2176+
eventConfigMissing = !FSCom.exists(configFileName);
2177+
spiLock->unlock();
2178+
#endif
2179+
bool initializedEventConfig = false;
2180+
#endif
2181+
21692182
meshtastic_Config_SecurityConfig backupSecurity = meshtastic_Config_SecurityConfig_init_zero;
21702183

21712184
#ifdef ARCH_ESP32
@@ -2354,6 +2367,29 @@ void NodeDB::loadFromDisk()
23542367

23552368
state = loadProto(configFileName, meshtastic_LocalConfig_size, sizeof(meshtastic_LocalConfig), &meshtastic_LocalConfig_msg,
23562369
&config);
2370+
#if USERPREFS_EVENT_MODE
2371+
if (eventConfigMissing && state != LoadFileResult::LOAD_SUCCESS) {
2372+
const LoadFileResult eventConfigState = state;
2373+
if (loadProto(standardConfigFileName, meshtastic_LocalConfig_size, sizeof(meshtastic_LocalConfig),
2374+
&meshtastic_LocalConfig_msg, &config) == LoadFileResult::LOAD_SUCCESS) {
2375+
// Preserve the user's identity and non-radio preferences, then
2376+
// replace only LoRa with this event build's compiled defaults.
2377+
const meshtastic_LocalConfig standardConfig = config;
2378+
installDefaultConfig(true);
2379+
const meshtastic_Config_LoRaConfig eventLora = config.lora;
2380+
config = standardConfig;
2381+
config.has_lora = true;
2382+
config.lora = eventLora;
2383+
state = LoadFileResult::LOAD_SUCCESS;
2384+
initializedEventConfig = true;
2385+
LOG_INFO("Initialized event config without modifying %s", standardConfigFileName);
2386+
} else {
2387+
// loadProto() clears config before decoding. Restore the event
2388+
// outcome so the normal default/degraded path handles that safely.
2389+
state = eventConfigState;
2390+
}
2391+
}
2392+
#endif
23572393
if (state == LoadFileResult::DECODE_FAILED) {
23582394
// Config file present but undecodable this boot (corruption / torn write / transient decrypt fail).
23592395
// loadProto() already zeroed `config`, so the keypair is gone from RAM; minting a new one would change
@@ -2415,6 +2451,15 @@ void NodeDB::loadFromDisk()
24152451
config.lora.override_frequency = USERPREFS_LORACONFIG_OVERRIDE_FREQUENCY;
24162452
#endif
24172453

2454+
#if USERPREFS_EVENT_MODE
2455+
if (initializedEventConfig) {
2456+
// This is the first durable event-profile write. A failed write is
2457+
// safe: normal files remain untouched and the next event boot retries.
2458+
if (!saveToDisk(SEGMENT_CONFIG))
2459+
LOG_ERROR("Unable to persist initial event config");
2460+
}
2461+
#endif
2462+
24182463
if (backupSecurity.private_key.size > 0) {
24192464
LOG_DEBUG("Restoring backup of security config");
24202465
config.security = backupSecurity;

src/mesh/NodeDB.h

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,34 @@ extern meshtastic_Position localPosition;
112112
static constexpr const char *deviceStateFileName = "/prefs/device.proto";
113113
static constexpr const char *legacyPrefFileName = "/prefs/db.proto";
114114
static constexpr const char *nodeDatabaseFileName = "/prefs/nodes.proto";
115-
static constexpr const char *configFileName = "/prefs/config.proto";
115+
// Event builds keep their radio profile in separate files. A normal firmware
116+
// image therefore resumes the user's ordinary config and channel table without
117+
// a client-mediated restore after an event OTA.
118+
static constexpr const char *standardConfigFileName = "/prefs/config.proto";
119+
static constexpr const char *standardChannelFileName = "/prefs/channels.proto";
120+
static constexpr const char *eventConfigFileName = "/prefs/event-config.proto";
121+
static constexpr const char *eventChannelFileName = "/prefs/event-channels.proto";
122+
123+
struct RadioProfileStoragePaths {
124+
const char *config;
125+
const char *channels;
126+
};
127+
128+
constexpr RadioProfileStoragePaths radioProfileStoragePaths(bool eventMode)
129+
{
130+
return eventMode ? RadioProfileStoragePaths{eventConfigFileName, eventChannelFileName}
131+
: RadioProfileStoragePaths{standardConfigFileName, standardChannelFileName};
132+
}
133+
134+
#if USERPREFS_EVENT_MODE
135+
static constexpr auto radioProfileStorage = radioProfileStoragePaths(true);
136+
#else
137+
static constexpr auto radioProfileStorage = radioProfileStoragePaths(false);
138+
#endif
139+
static constexpr const char *configFileName = radioProfileStorage.config;
140+
static constexpr const char *channelFileName = radioProfileStorage.channels;
116141
static constexpr const char *uiconfigFileName = "/prefs/uiconfig.proto";
117142
static constexpr const char *moduleConfigFileName = "/prefs/module.proto";
118-
static constexpr const char *channelFileName = "/prefs/channels.proto";
119143
static constexpr const char *backupFileName = "/backups/backup.proto";
120144

121145
/// Given a node, return how many seconds in the past (vs now) that we last heard from it
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#include "MeshTypes.h" // Include BEFORE TestUtil.h
2+
#include "TestUtil.h"
3+
#include "mesh/NodeDB.h"
4+
#include <unity.h>
5+
6+
void test_standard_profile_uses_standard_files()
7+
{
8+
const auto paths = radioProfileStoragePaths(false);
9+
10+
TEST_ASSERT_EQUAL_STRING("/prefs/config.proto", paths.config);
11+
TEST_ASSERT_EQUAL_STRING("/prefs/channels.proto", paths.channels);
12+
}
13+
14+
void test_event_profile_uses_isolated_files()
15+
{
16+
const auto paths = radioProfileStoragePaths(true);
17+
18+
TEST_ASSERT_EQUAL_STRING("/prefs/event-config.proto", paths.config);
19+
TEST_ASSERT_EQUAL_STRING("/prefs/event-channels.proto", paths.channels);
20+
}
21+
22+
void setUp(void) {}
23+
24+
void tearDown(void) {}
25+
26+
extern "C" {
27+
void setup()
28+
{
29+
initializeTestEnvironment();
30+
UNITY_BEGIN();
31+
RUN_TEST(test_standard_profile_uses_standard_files);
32+
RUN_TEST(test_event_profile_uses_isolated_files);
33+
exit(UNITY_END());
34+
}
35+
36+
void loop() {}
37+
}

0 commit comments

Comments
 (0)