fix(companion): recover radio profile settings after flash

This commit is contained in:
mikecarper committed 2026-09-21 18:21:36 -07:00
1 parent d1c18edd88
commit e7160dfd26
7 files changed
+139 -68

No files matched your search

+55 -5
View File
@@ -254,9 +254,16 @@ int main(int argc, char** argv) {
restored.loop();
assert(radio.p.reply_tx==mesh::RADIO_TX_BOTH && !radio.p.reply_force);
assert(!radio.p.enabled());
assert(restored.handle("set tx.reply radio",f.reply));
assert(strstr(f.reply,"Error"));
assert(f.fs.files["/radio_profiles"]==damaged);
assert(restored.handle("set radio2.cross on",f.reply));
assert(!strcmp(f.reply,"OK"));
assert(radio.p.cross==mesh::RadioCrossMode::On);
assert(restored.handle("set tempradio2 911.3,500,8,7,rxtx,2",f.reply));
assert(!strncmp(f.reply,"OK",2));
g_mock_millis+=2000; restored.loop();
assert(radio.p.secondary_temporary && radio.p.canCross());
Radio reboot; mesh::RadioProfileCLI repaired;
repaired.begin(&f.fs,&reboot,&f.clock,true);
assert(reboot.p.cross==mesh::RadioCrossMode::On);
}
{
Fixture companion;
@@ -469,8 +476,51 @@ int main(int argc, char** argv) {
Radio corrupt; mesh::RadioProfileCLI invalid;
invalid.begin(&f.fs, &corrupt, &f.clock);
assert(!corrupt.p.enabled());
assert(invalid.handle("set radio2 off", f.reply));
assert(strstr(f.reply,"Error")); // newer/corrupt image is not overwritten
const auto damaged=f.fs.files["/radio_profiles"];
f.fs.fail_remove=true;
assert(invalid.handle("set radio2.cross on", f.reply));
assert(strstr(f.reply,"Error")); // failed cleanup never overwrites corruption
assert(f.fs.files["/radio_profiles"]==damaged);
f.fs.fail_remove=false;
assert(invalid.handle("set radio2.cross on", f.reply));
assert(!strcmp(f.reply,"OK"));
assert(corrupt.p.cross==mesh::RadioCrossMode::On);
assert(invalid.handle("set tempradio2 911.3,500,8,7,rxtx,2", f.reply));
assert(!strncmp(f.reply,"OK",2));
g_mock_millis+=2000; invalid.loop();
assert(corrupt.p.secondary_temporary && corrupt.p.canCross());
}
{
Fixture f;
f.cmd("set radio2 910.5,500,8,5,rxtx,80");
const auto saved=f.fs.files["/radio_profiles"];
f.fs.fail_read_open=true;
Radio radio; mesh::RadioProfileCLI unavailable;
unavailable.begin(&f.fs, &radio, &f.clock);
f.fs.fail_read_open=false;
assert(unavailable.handle("set radio2.cross on", f.reply));
assert(strstr(f.reply,"Error")); // an I/O fault is never treated as corruption
assert(f.fs.files["/radio_profiles"]==saved);
}
{
Fixture f;
f.cmd("set radio2 910.5,500,8,5,rxtx,80");
const auto backup=f.fs.files["/radio_profiles"];
f.fs.files["/radio_profiles.bak"]=backup;
// This versioned image has a valid CRC but an unsupported saved mode.
// An intact backup must prevent automatic discard/recreation.
f.fs.files["/radio_profiles"][3]=3;
uint32_t crc=0xffffffffU;
for (size_t i=0;i<20;++i) {
crc ^= f.fs.files["/radio_profiles"][i];
for (unsigned bit=0;bit<8;++bit) crc=(crc>>1)^((crc&1)?0xedb88320U:0);
}
memcpy(f.fs.files["/radio_profiles"].data()+20,&crc,4);
Radio radio; mesh::RadioProfileCLI protected_backup;
protected_backup.begin(&f.fs, &radio, &f.clock);
assert(protected_backup.handle("set radio2.cross on", f.reply));
assert(strstr(f.reply,"Error"));
assert(f.fs.files["/radio_profiles.bak"]==backup);
}
{
Fixture f;
+10 -15
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Run the actual task wrapper and BLE startup with injected allocation failures."""
"""Run the actual task wrapper and BLE startup failure handling."""
from pathlib import Path
import subprocess
@@ -29,7 +29,6 @@ HARNESS = r'''
#include <cstring>
#include <helpers/BluetoothMac.h>
#include <helpers/BleMotaProtocol.h>
#include <helpers/nrf52/BleTaskStartup.h>
#ifndef COMPANION_FEATURE_BLE_MOTA_SOURCE
#define COMPANION_FEATURE_BLE_MOTA_SOURCE 1
#endif
@@ -53,7 +52,6 @@ struct Logging { void println(const char*) {} };
Logging& usbLoggingPort() { static Logging port; return port; }
}
static const char* fail_task = nullptr;
static int fail_service = 0;
static bool fail_softdevice = false;
static unsigned last_depth;
@@ -62,9 +60,8 @@ extern "C" BaseType_t __wrap_xTaskCreate(TaskFunction_t, const char*,
extern "C" BaseType_t __real_xTaskCreate(TaskFunction_t, const char* name,
configSTACK_DEPTH_TYPE depth, void*, UBaseType_t, TaskHandle_t* out) {
last_depth = depth;
bool fail = fail_task && name && strcmp(name, fail_task) == 0;
if (out) *out = fail ? nullptr : reinterpret_cast<void*>(1);
return fail ? 0 : pdPASS;
if (out) *out = reinterpret_cast<void*>(1);
return pdPASS;
}
struct Settings {
template<class... T> void clearBonds(T...) {}
@@ -93,7 +90,8 @@ struct BluefruitStub {
++starts;
if (fail_softdevice) return false;
TaskHandle_t task;
// Match the pinned core: it ignores both worker creation return values.
// The pinned LTO core resolves these internal calls directly, rather than
// through the application's linker wrapper.
__wrap_xTaskCreate(nullptr, "BLE", 1280, nullptr, 3, &task);
__wrap_xTaskCreate(nullptr, "SOC", 200, nullptr, 3, &task);
return true;
@@ -142,21 +140,18 @@ struct SerialBLEInterface {
static SerialBLEInterface* instance = nullptr;
@BEGIN@
int main() {
mesh::nrf52::resetBleTaskStartup();
assert(!mesh::nrf52::bleTasksStarted());
__wrap_xTaskCreate(nullptr, "loop", 1024, nullptr, 1, nullptr);
assert(last_depth == 2048 && !mesh::nrf52::bleTasksStarted());
assert(last_depth == 2048);
__wrap_xTaskCreate(nullptr, "callback", 768, nullptr, 1, nullptr);
assert(last_depth == 768);
__wrap_xTaskCreate(nullptr, nullptr, 100, nullptr, 1, nullptr);
assert(last_depth == 100);
for (int fault = 0; fault <= 8; ++fault) {
for (int fault = 0; fault <= 6; ++fault) {
#if !COMPANION_FEATURE_BLE_MOTA_SOURCE
if (fault >= 5 && fault <= 7) continue;
if (fault >= 3 && fault <= 5) continue;
#endif
fail_softdevice = fault == 1;
fail_task = fault == 2 ? "BLE" : fault == 3 ? "SOC" : nullptr;
fail_service = fault >= 4 ? fault - 3 : 0;
fail_service = fault >= 2 ? fault - 1 : 0;
SerialBLEInterface port;
const int starts = Bluefruit.starts;
const bool expected = fault == 0;
@@ -174,7 +169,7 @@ int main() {
class Nrf52BleStartupTest(unittest.TestCase):
def test_all_startup_failures_are_reported_without_reinitializing(self):
def test_reported_startup_failures_are_not_reinitialized(self):
begin = method((ROOT / "src/helpers/nrf52/SerialBLEInterface.cpp").read_text(),
"bool SerialBLEInterface::begin(")
header = (ROOT / "src/helpers/nrf52/SerialBLEInterface.h").read_text()