mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-22 13:01:37 +00:00
Persist Companion Bluetooth on-off preference across reboots
This commit is contained in:
1 parent
c917158167
commit
4086222d85
11 files changed
+528
-21
No files matched your search
@@ -205,6 +205,9 @@ jobs:
|
||||
- name: Verify Companion Bluetooth CLI connection protection
|
||||
run: python3 -B test/test_companion_bluetooth_control.py -v
|
||||
|
||||
- name: Verify Companion Bluetooth reboot persistence
|
||||
run: python3 -B test/test_companion_bluetooth_persistence.py -v
|
||||
|
||||
- name: Verify nRF52 SoftDevice LTO and persistent BLE subscriptions
|
||||
env:
|
||||
MESHCORE_REQUIRE_ARM_COMPILER: '1'
|
||||
|
||||
+13
-2
@@ -1619,7 +1619,17 @@ set bluetooth on
|
||||
|
||||
`get ble` and `set ble ...` are aliases. This controls the running Bluetooth
|
||||
service on ESP32 and nRF52 Companions: `off` stops advertising and disconnects
|
||||
Bluetooth clients; `on` enables it again. The change lasts until reboot.
|
||||
Bluetooth clients; `on` enables it again. Both choices are saved and survive
|
||||
reboots. USB remains available when Bluetooth is off. Older saved preferences
|
||||
without this setting retain the previous default of Bluetooth on.
|
||||
|
||||
Successful local commands report `(saved)`. If saving fails, the command reports
|
||||
an error and leaves Bluetooth unchanged. When requested over Bluetooth, `off`
|
||||
first reports `(save pending)` so its reply can drain; the firmware rechecks the
|
||||
alternate connection and saves the preference before disconnecting. If that
|
||||
connection disappears or the save fails, shutdown is cancelled. `get bluetooth`
|
||||
reports the current runtime state. Broad `2.4ghz` controls still apply to the
|
||||
current boot only and do not change this saved Bluetooth preference.
|
||||
|
||||
Plain `off` requires another active management connection. A connected USB
|
||||
terminal or binary client, TCP client, or browser terminal can provide that
|
||||
@@ -1629,7 +1639,8 @@ bridge), issue the command through that USB client so the request itself proves
|
||||
it is active.
|
||||
|
||||
Append `force` to `off` to permit disconnecting the only active connection.
|
||||
Use USB, another management transport, or reboot to regain access afterward.
|
||||
Use USB or another management transport and `set bluetooth on` to regain
|
||||
access afterward. Reboot alone will not re-enable saved-off Bluetooth.
|
||||
`force` is accepted only as the final argument to `off`.
|
||||
|
||||
Commands sent through Bluetooth allow its reply to drain before shutdown
|
||||
|
||||
@@ -999,6 +999,9 @@ bool DataStore::loadPrefsInt(const char *filename,
|
||||
File file = openRead(_fs, filename);
|
||||
if (file) {
|
||||
CompanionNodePrefs loaded_prefs = _prefs;
|
||||
// Files written before this tail existed always started Bluetooth at boot.
|
||||
// Do not inherit a runtime off value when loading one of those images.
|
||||
loaded_prefs.bluetooth_enabled = 1;
|
||||
double loaded_lat = node_lat;
|
||||
double loaded_lon = node_lon;
|
||||
// The original image ended after ble_pin at byte 84. Later releases only
|
||||
@@ -1046,6 +1049,13 @@ bool DataStore::loadPrefsInt(const char *filename,
|
||||
#endif
|
||||
#if defined(RP2040_PLATFORM) && defined(ENABLE_WIFI_INTERFACE)
|
||||
328,
|
||||
#endif
|
||||
#ifdef TBEAM_1W
|
||||
239, // Bluetooth enable preference, appended after one-key DM policy
|
||||
#elif defined(RP2040_PLATFORM) && defined(ENABLE_WIFI_INTERFACE)
|
||||
329,
|
||||
#else
|
||||
232,
|
||||
#endif
|
||||
};
|
||||
const uint32_t prefs_size = file.size();
|
||||
@@ -1188,10 +1198,13 @@ bool DataStore::loadPrefsInt(const char *filename,
|
||||
sizeof(loaded_prefs.flood_retry_advert_enabled));
|
||||
readOptionalField(&loaded_prefs.one_key_dm_enabled,
|
||||
sizeof(loaded_prefs.one_key_dm_enabled));
|
||||
readOptionalField(&loaded_prefs.bluetooth_enabled,
|
||||
sizeof(loaded_prefs.bluetooth_enabled));
|
||||
|
||||
// Any bytes left over form only part of a historically appended field.
|
||||
// Preserve the file and defaults rather than treating that tail as EOF.
|
||||
success = success && file.available() == 0;
|
||||
success = success && loaded_prefs.bluetooth_enabled <= 1;
|
||||
file.close();
|
||||
if (!success) return false;
|
||||
_prefs = loaded_prefs;
|
||||
@@ -1348,6 +1361,8 @@ bool DataStore::savePrefs(const CompanionNodePrefs& _prefs, double node_lat, dou
|
||||
sizeof(_prefs.flood_retry_advert_enabled)) == sizeof(_prefs.flood_retry_advert_enabled);
|
||||
success = success && file.write((uint8_t *)&_prefs.one_key_dm_enabled,
|
||||
sizeof(_prefs.one_key_dm_enabled)) == sizeof(_prefs.one_key_dm_enabled);
|
||||
success = success && file.write((uint8_t *)&_prefs.bluetooth_enabled,
|
||||
sizeof(_prefs.bluetooth_enabled)) == sizeof(_prefs.bluetooth_enabled);
|
||||
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) || defined(ESP32_PLATFORM) || defined(RP2040_PLATFORM)
|
||||
success = file.commit(success);
|
||||
|
||||
@@ -2310,6 +2310,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
||||
_prefs.wifi_enabled = 0;
|
||||
#endif
|
||||
memset(_prefs.bluetooth_name, 0, sizeof(_prefs.bluetooth_name));
|
||||
_prefs.bluetooth_enabled = 1;
|
||||
_prefs.display_rotation_degrees = 0;
|
||||
_prefs.cad_enabled = DEFAULT_CAD_ENABLED ? 1 : 0;
|
||||
_prefs.cad_scan_timeout_ms = 0;
|
||||
@@ -4137,6 +4138,14 @@ uint32_t MyMesh::getBLEPin() {
|
||||
return _active_ble_pin;
|
||||
}
|
||||
|
||||
bool MyMesh::setBluetoothEnabledPreference(bool enabled) {
|
||||
return savePreference(_prefs.bluetooth_enabled, static_cast<uint8_t>(enabled));
|
||||
}
|
||||
|
||||
bool MyMesh::isBluetoothEnabledPreference() const {
|
||||
return _prefs.bluetooth_enabled != 0;
|
||||
}
|
||||
|
||||
#if defined(WITH_MQTT_BRIDGE) && defined(ESP32_PLATFORM) && defined(WIFI_SSID)
|
||||
static void copyMqttString(char* dest, size_t dest_size, const char* src) {
|
||||
strncpy(dest, src ? src : "", dest_size - 1);
|
||||
@@ -9161,7 +9170,7 @@ void MyMesh::handleTerminalCommand(char* command) {
|
||||
terminalOutput().print(" set bluetooth.name <name|default>\r\n");
|
||||
#if defined(BLE_PIN_CODE)
|
||||
terminalOutput().print(" get bluetooth (alias: get ble)\r\n");
|
||||
terminalOutput().print(" set bluetooth <on|off> [force] (this boot; force only with off)\r\n");
|
||||
terminalOutput().print(" set bluetooth <on|off> [force] (saved; force only with off)\r\n");
|
||||
terminalOutput().print(" get bluetooth.mac\r\n");
|
||||
terminalOutput().print(
|
||||
" set bluetooth.mac <address|random|random-every-boot|random-after-connect|default>\r\n");
|
||||
|
||||
@@ -194,6 +194,8 @@ public:
|
||||
const char *getNodeName();
|
||||
CompanionNodePrefs *getNodePrefs();
|
||||
uint32_t getBLEPin();
|
||||
bool setBluetoothEnabledPreference(bool enabled);
|
||||
bool isBluetoothEnabledPreference() const;
|
||||
#if defined(BLE_PIN_CODE)
|
||||
bool prepareBluetoothMacForBoot(bool& address_rotated);
|
||||
bool armBluetoothMacRotationAfterConnection();
|
||||
|
||||
@@ -108,6 +108,8 @@ public:
|
||||
uint8_t flood_retry_advert_enabled = 1;
|
||||
// Appended persisted field: old preferences images default to consent required.
|
||||
uint8_t one_key_dm_enabled = 0;
|
||||
// Appended after all existing fields so older images keep Bluetooth enabled.
|
||||
uint8_t bluetooth_enabled = 1;
|
||||
|
||||
private:
|
||||
class RadioPrefs : public CommonRadioPrefs {
|
||||
|
||||
@@ -2340,7 +2340,9 @@ void halt() {
|
||||
#endif
|
||||
companion_bluetooth_initialized = true;
|
||||
companion_bluetooth_start_failure[0] = 0;
|
||||
if (interface_manager.isEnabled()) bluetooth_interface.enable();
|
||||
if (interface_manager.isEnabled() && the_mesh.isBluetoothEnabledPreference()) {
|
||||
bluetooth_interface.enable();
|
||||
}
|
||||
}
|
||||
|
||||
static void serviceDeferredCompanionBluetooth() {
|
||||
@@ -2539,6 +2541,13 @@ static void serviceCompanionBluetoothControl() {
|
||||
// Give the requesting BLE client its reply, with a bounded wait even when
|
||||
// its notification queue is stuck. The command itself reports a request.
|
||||
if (bluetooth_interface.hasPendingIO() && elapsed < 1750) return;
|
||||
if (!the_mesh.setBluetoothEnabledPreference(false)) {
|
||||
companion_bluetooth_off_at = 0;
|
||||
mesh::usbLoggingPort().println(
|
||||
"Bluetooth off cancelled: preference save failed");
|
||||
return;
|
||||
}
|
||||
interface_manager.setBluetoothAutoEnable(false);
|
||||
disableCompanionBluetoothForCli();
|
||||
}
|
||||
#endif
|
||||
@@ -2554,12 +2563,6 @@ bool handleCompanionBluetoothCommand(const char* command, char* reply,
|
||||
return true;
|
||||
}
|
||||
#if defined(BLE_PIN_CODE)
|
||||
if (!companion_bluetooth_initialized) {
|
||||
snprintf(reply, reply_size,
|
||||
"Error: Bluetooth unavailable in this boot: %s",
|
||||
companion_bluetooth_start_failure);
|
||||
return true;
|
||||
}
|
||||
if (action == CompanionBluetoothCommand::Get) {
|
||||
snprintf(reply, reply_size, "bluetooth %s%s",
|
||||
interface_manager.isBluetoothEnabled() ? "on" : "off",
|
||||
@@ -2567,19 +2570,43 @@ bool handleCompanionBluetoothCommand(const char* command, char* reply,
|
||||
return true;
|
||||
}
|
||||
if (action == CompanionBluetoothCommand::On) {
|
||||
if (!companion_bluetooth_initialized) {
|
||||
snprintf(reply, reply_size,
|
||||
"Error: Bluetooth unavailable in this boot: %s",
|
||||
companion_bluetooth_start_failure);
|
||||
return true;
|
||||
}
|
||||
if (!mesh::wireless::control().allowService(mesh::wireless::Bluetooth)) {
|
||||
snprintf(reply, reply_size, "Error: 2.4ghz is off or changing; use set 2.4ghz on first");
|
||||
return true;
|
||||
}
|
||||
companion_bluetooth_off_at = 0;
|
||||
const bool previous_preference = the_mesh.isBluetoothEnabledPreference();
|
||||
if (!the_mesh.setBluetoothEnabledPreference(true)) {
|
||||
snprintf(reply, reply_size, "Error: Bluetooth preference save failed; unchanged");
|
||||
return true;
|
||||
}
|
||||
interface_manager.setBluetoothAutoEnable(true);
|
||||
interface_manager.enableBluetooth();
|
||||
snprintf(reply, reply_size, "%s", interface_manager.isBluetoothEnabled()
|
||||
? "OK - Bluetooth on (this boot)" : "Error: Bluetooth enable failed");
|
||||
if (interface_manager.isBluetoothEnabled()) {
|
||||
companion_bluetooth_off_at = 0;
|
||||
snprintf(reply, reply_size, "OK - Bluetooth on (saved)");
|
||||
} else if (the_mesh.setBluetoothEnabledPreference(previous_preference)) {
|
||||
interface_manager.setBluetoothAutoEnable(previous_preference);
|
||||
snprintf(reply, reply_size, "Error: Bluetooth enable failed; preference restored");
|
||||
} else {
|
||||
snprintf(reply, reply_size,
|
||||
"Error: Bluetooth enable failed; rollback save failed (saved on)");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!interface_manager.isBluetoothEnabled()) {
|
||||
if (!the_mesh.setBluetoothEnabledPreference(false)) {
|
||||
snprintf(reply, reply_size, "Error: Bluetooth preference save failed; unchanged");
|
||||
return true;
|
||||
}
|
||||
companion_bluetooth_off_at = 0;
|
||||
snprintf(reply, reply_size, "OK - Bluetooth already off (this boot)");
|
||||
interface_manager.setBluetoothAutoEnable(false);
|
||||
snprintf(reply, reply_size, "OK - Bluetooth already off (saved)");
|
||||
return true;
|
||||
}
|
||||
const bool force = action == CompanionBluetoothCommand::ForceOff;
|
||||
@@ -2598,13 +2625,18 @@ bool handleCompanionBluetoothCommand(const char* command, char* reply,
|
||||
return true;
|
||||
}
|
||||
if (non_bluetooth_requester) {
|
||||
if (!the_mesh.setBluetoothEnabledPreference(false)) {
|
||||
snprintf(reply, reply_size, "Error: Bluetooth preference save failed; unchanged");
|
||||
return true;
|
||||
}
|
||||
interface_manager.setBluetoothAutoEnable(false);
|
||||
disableCompanionBluetoothForCli();
|
||||
snprintf(reply, reply_size, "OK - Bluetooth off (this boot)");
|
||||
snprintf(reply, reply_size, "OK - Bluetooth off (saved)");
|
||||
} else {
|
||||
companion_bluetooth_force_off = force;
|
||||
companion_bluetooth_off_at = millis() + 250;
|
||||
if (companion_bluetooth_off_at == 0) companion_bluetooth_off_at = 1;
|
||||
snprintf(reply, reply_size, "OK - Bluetooth off requested (this boot)");
|
||||
snprintf(reply, reply_size, "OK - Bluetooth off requested (save pending)");
|
||||
}
|
||||
#else
|
||||
(void)source;
|
||||
@@ -3021,6 +3053,12 @@ void setup() {
|
||||
// platforms have no separate port, so this is a harmless no-op there.
|
||||
mesh::beginUsbLoggingPort();
|
||||
|
||||
#if defined(BLE_PIN_CODE)
|
||||
// Keep stack registration available for a later USB `set bluetooth on`,
|
||||
// without advertising even briefly when startup enables all interfaces.
|
||||
interface_manager.setBluetoothAutoEnable(the_mesh.isBluetoothEnabledPreference());
|
||||
#endif
|
||||
|
||||
// Lock the saved transport selection before bringing up either wireless stack.
|
||||
#if defined(COMPANION_EXCLUSIVE_WIFI_BLE)
|
||||
loadCompanionTransportModeForBoot();
|
||||
|
||||
@@ -27,6 +27,7 @@ private:
|
||||
};
|
||||
|
||||
bool _enabled = false;
|
||||
bool _bluetooth_auto_enable = true;
|
||||
RegisteredInterface _interfaces[MAX_INTERFACES] = {};
|
||||
BaseSerialInterface* _lastRxInterface = nullptr;
|
||||
BaseSerialInterface* _lockedReplyInterface = nullptr;
|
||||
@@ -55,6 +56,10 @@ private:
|
||||
}
|
||||
|
||||
public:
|
||||
// The Companion's saved preference controls blanket startup/wake enables.
|
||||
// Explicit Bluetooth/wireless controls remain able to enable it temporarily.
|
||||
void setBluetoothAutoEnable(bool enabled) { _bluetooth_auto_enable = enabled; }
|
||||
|
||||
bool addInterface(InterfaceType type, BaseSerialInterface* iface) {
|
||||
// make sure an interface was provided
|
||||
if(iface == nullptr){
|
||||
@@ -143,6 +148,7 @@ public:
|
||||
_lockedReplyInterface = nullptr;
|
||||
for(auto iface : _interfaces){
|
||||
if(iface.instance){
|
||||
if (iface.type == InterfaceType::Bluetooth && !_bluetooth_auto_enable) continue;
|
||||
iface.instance->enable();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@ uint32_t companion_bluetooth_off_at=0;
|
||||
bool companion_bluetooth_force_off=false;
|
||||
struct Transport : BaseSerialInterface {
|
||||
bool enabled=false,connected=false,busy=false,frame=false,enable_fails=false;
|
||||
unsigned disabled=0;
|
||||
void enable() override {enabled=!enable_fails;}
|
||||
unsigned disabled=0,enables=0;
|
||||
void enable() override {++enables;enabled=!enable_fails;}
|
||||
void disable() override {enabled=false;connected=false;++disabled;}
|
||||
bool isEnabled() const override{return enabled;}
|
||||
bool isConnected() const override{return connected;}
|
||||
@@ -43,6 +43,13 @@ MultiSerialInterface interface_manager;
|
||||
bool isUsbTerminalDataConnected(){return usb_open;}
|
||||
unsigned cancelled=0,logs=0;
|
||||
struct {
|
||||
bool bluetooth_preference=true,durable_bluetooth_preference=true,storage_accepts=true;
|
||||
unsigned saves=0,fail_save_at=0;
|
||||
bool isBluetoothEnabledPreference() const {return bluetooth_preference;}
|
||||
bool setBluetoothEnabledPreference(bool enabled){
|
||||
++saves;if(!storage_accepts||saves==fail_save_at)return false;
|
||||
bluetooth_preference=durable_bluetooth_preference=enabled;return true;
|
||||
}
|
||||
bool isAnyNetworkTerminalMode(){return network_terminal;}
|
||||
void cancelSerialOperationsForRoute(BaseSerialInterface* route){
|
||||
assert(route==&bluetooth_interface);++cancelled;
|
||||
@@ -80,6 +87,7 @@ int main(){
|
||||
assert(!command("set ble.stealth on"));
|
||||
assert(command("set bluetooth off")&&strstr(reply,"no other active"));
|
||||
assert(usb_power&&bluetooth_interface.enabled&&!companion_bluetooth_off_at);
|
||||
assert(the_mesh.saves==0&&the_mesh.durable_bluetooth_preference);
|
||||
// A disabled TCP interface and a socket that is merely listening don't count.
|
||||
wifi_interface.connected=true;wifi_interface.enabled=false;
|
||||
assert(command("set ble off")&&strstr(reply,"Error:"));
|
||||
@@ -89,9 +97,11 @@ int main(){
|
||||
wifi_interface.connected=true;
|
||||
assert(command("set ble off")&&strstr(reply,"requested"));
|
||||
assert(command("get ble")&&strstr(reply,"off pending"));
|
||||
assert(the_mesh.durable_bluetooth_preference); // Requested is not yet committed.
|
||||
now_ms+=249;serviceCompanionBluetoothControl();assert(bluetooth_interface.enabled);
|
||||
wifi_interface.connected=false;++now_ms;serviceCompanionBluetoothControl();
|
||||
assert(bluetooth_interface.enabled&&!companion_bluetooth_off_at&&logs==1);
|
||||
assert(the_mesh.saves==0&&the_mesh.durable_bluetooth_preference);
|
||||
// Force may disconnect the sole client, but waits for queued notifications.
|
||||
assert(command(" set ble\t off\tforce \t")&&strstr(reply,"requested"));
|
||||
bluetooth_interface.busy=true;now_ms+=250;serviceCompanionBluetoothControl();
|
||||
@@ -99,11 +109,13 @@ int main(){
|
||||
now_ms+=1749;serviceCompanionBluetoothControl();assert(bluetooth_interface.enabled);
|
||||
++now_ms;serviceCompanionBluetoothControl();
|
||||
assert(!bluetooth_interface.enabled&&cancelled==1);
|
||||
assert(!the_mesh.durable_bluetooth_preference);
|
||||
assert(interface_manager.captureReplyRoute()==nullptr);
|
||||
assert(command("set ble off")&&strstr(reply,"already off"));
|
||||
assert(command("get ble")&&strcmp(reply,"bluetooth off")==0);
|
||||
bluetooth_interface.busy=false;
|
||||
assert(command("set bluetooth on")&&strstr(reply,"OK"));route(bluetooth_interface);
|
||||
assert(the_mesh.durable_bluetooth_preference);
|
||||
// Another native CDC session counts; HWCDC/bridge presence alone doesn't.
|
||||
usb_open=true;usb_serial_interface.connected=true;
|
||||
assert(command("set ble off"));
|
||||
@@ -120,9 +132,18 @@ int main(){
|
||||
usb_mota_mode=false;usb_open=false;
|
||||
// An actual USB command proves that requester is present on every backend.
|
||||
route(usb_serial_interface);
|
||||
assert(command("set ble off")&&strstr(reply,"Bluetooth off (this boot)"));
|
||||
assert(command("set ble off")&&strstr(reply,"Bluetooth off"));
|
||||
assert(!bluetooth_interface.enabled&&interface_manager.captureReplyRoute()==&usb_serial_interface);
|
||||
assert(!the_mesh.durable_bluetooth_preference);
|
||||
assert(usb_serial_interface.enabled); // Persisting BLE off never disables USB.
|
||||
const unsigned saved_off_enables=bluetooth_interface.enables;
|
||||
interface_manager.disable();interface_manager.enable();
|
||||
assert(!bluetooth_interface.enabled&&bluetooth_interface.enables==saved_off_enables);
|
||||
assert(usb_serial_interface.enabled);route(usb_serial_interface);
|
||||
assert(command("set ble on")&&bluetooth_interface.enabled);
|
||||
assert(the_mesh.durable_bluetooth_preference);
|
||||
interface_manager.disable();interface_manager.enable();
|
||||
assert(bluetooth_interface.enabled); // Successful on also restores the aggregate boot policy.
|
||||
// ASCII terminal commands cannot inherit the previous framed BLE route.
|
||||
route(bluetooth_interface);
|
||||
assert(command("set ble off",CompanionBluetoothCommandSource::Terminal));
|
||||
@@ -137,6 +158,34 @@ int main(){
|
||||
assert(command("set ble off")&&strstr(reply,"requested"));
|
||||
now_ms+=250;serviceCompanionBluetoothControl();assert(!bluetooth_interface.enabled);
|
||||
assert(command("set ble on"));route(bluetooth_interface);ethernet_interface.connected=false;
|
||||
// Failed delayed saves leave the requesting BLE session alive and cancellable.
|
||||
assert(command("set ble off force"));
|
||||
const unsigned delayed_disabled=bluetooth_interface.disabled;
|
||||
const uint32_t pending_off=companion_bluetooth_off_at;
|
||||
the_mesh.storage_accepts=false;
|
||||
assert(command("set ble on")&&strstr(reply,"Error:"));
|
||||
assert(companion_bluetooth_off_at==pending_off); // Failed reversal does not cancel a prior request.
|
||||
now_ms+=250;serviceCompanionBluetoothControl();
|
||||
assert(bluetooth_interface.enabled&&the_mesh.durable_bluetooth_preference);
|
||||
assert(!companion_bluetooth_off_at&&bluetooth_interface.disabled==delayed_disabled);
|
||||
the_mesh.storage_accepts=true;
|
||||
// Failed immediate off saves must not disconnect the USB recovery command.
|
||||
route(usb_serial_interface);the_mesh.storage_accepts=false;
|
||||
assert(command("set ble off")&&strstr(reply,"Error:"));
|
||||
assert(bluetooth_interface.enabled&&the_mesh.durable_bluetooth_preference);
|
||||
assert(interface_manager.captureReplyRoute()==&usb_serial_interface);
|
||||
the_mesh.storage_accepts=true;
|
||||
assert(command("set ble off")&&!bluetooth_interface.enabled);
|
||||
assert(!the_mesh.durable_bluetooth_preference);
|
||||
// Failed on saves restore the off runtime state as well as durable preference.
|
||||
the_mesh.storage_accepts=false;
|
||||
const unsigned failed_on_enables=bluetooth_interface.enables;
|
||||
assert(command("set ble on")&&strstr(reply,"Error:"));
|
||||
assert(!bluetooth_interface.enabled&&!the_mesh.durable_bluetooth_preference);
|
||||
assert(bluetooth_interface.enables==failed_on_enables); // No transient advertising.
|
||||
the_mesh.storage_accepts=true;
|
||||
assert(command("set ble on")&&bluetooth_interface.enabled);
|
||||
assert(the_mesh.durable_bluetooth_preference);route(bluetooth_interface);
|
||||
// Pending deadlines work across millis() wrap, including the zero sentinel.
|
||||
for(uint32_t start : {0xffffff00u,0xffffff06u}){
|
||||
now_ms=start;assert(command("set ble off force"));
|
||||
@@ -145,12 +194,28 @@ int main(){
|
||||
assert(command("set ble on"));route(bluetooth_interface);
|
||||
}
|
||||
companion_bluetooth_initialized=false;
|
||||
bluetooth_interface.enabled=false;
|
||||
assert(command("get ble")&&strstr(reply,"bluetooth off"));
|
||||
assert(command("set ble on")&&strstr(reply,"unavailable"));
|
||||
assert(command("set ble off force")&&strstr(reply,"unavailable"));
|
||||
assert(command("set ble off force")&&strstr(reply,"already off"));
|
||||
assert(!the_mesh.durable_bluetooth_preference);
|
||||
companion_bluetooth_initialized=true;
|
||||
assert(command("set ble off",CompanionBluetoothCommandSource::Terminal));
|
||||
bluetooth_interface.enable_fails=true;
|
||||
assert(command("set ble on")&&strstr(reply,"enable failed"));
|
||||
assert(!the_mesh.durable_bluetooth_preference&&!bluetooth_interface.enabled);
|
||||
assert(command("get ble")&&strcmp(reply,"bluetooth off")==0);
|
||||
bluetooth_interface.enable_fails=false;
|
||||
interface_manager.disable();interface_manager.enable();
|
||||
assert(!bluetooth_interface.enabled&&usb_serial_interface.enabled); // Policy rollback matches saved off.
|
||||
// If both enable and its rollback save fail, report the durable on choice honestly.
|
||||
bluetooth_interface.enable_fails=true;the_mesh.fail_save_at=the_mesh.saves+2;
|
||||
assert(command("set ble on")&&strstr(reply,"rollback save failed"));
|
||||
assert(the_mesh.durable_bluetooth_preference&&!bluetooth_interface.enabled);
|
||||
assert(command("get ble")&&strcmp(reply,"bluetooth off")==0); // Status remains the live state.
|
||||
the_mesh.fail_save_at=0;bluetooth_interface.enable_fails=false;
|
||||
interface_manager.disable();interface_manager.enable();
|
||||
assert(bluetooth_interface.enabled); // Aggregate policy remains consistent with the durable on value.
|
||||
#else
|
||||
assert(command("get ble")&&strstr(reply,"not supported"));
|
||||
assert(command("set ble off force")&&strstr(reply,"not supported"));
|
||||
@@ -200,6 +265,89 @@ class BluetoothControlTests(unittest.TestCase):
|
||||
ran = subprocess.run([str(binary)],capture_output=True,text=True)
|
||||
self.assertEqual(ran.returncode,0,ran.stderr)
|
||||
|
||||
def test_saved_off_policy_keeps_usb_recovery_and_explicit_runtime_on(self):
|
||||
# Execute the actual multi-transport manager. A saved-off boot must
|
||||
# not briefly advertise when the aggregate interface is enabled.
|
||||
source_text = HARNESS.split('@METHODS@')[0] + r'''
|
||||
int main(){
|
||||
interface_manager.addInterface(InterfaceType::Bluetooth,&bluetooth_interface);
|
||||
interface_manager.addInterface(InterfaceType::USB,&usb_serial_interface);
|
||||
interface_manager.addInterface(InterfaceType::WiFi,&wifi_interface);
|
||||
interface_manager.addInterface(InterfaceType::Ethernet,ðernet_interface);
|
||||
interface_manager.setBluetoothAutoEnable(false);
|
||||
interface_manager.enable();
|
||||
assert(!bluetooth_interface.enabled&&bluetooth_interface.enables==0);
|
||||
assert(usb_serial_interface.enabled&&wifi_interface.enabled&ðernet_interface.enabled);
|
||||
interface_manager.disable();interface_manager.enable();
|
||||
assert(!bluetooth_interface.enabled&&bluetooth_interface.enables==0);
|
||||
// USB can explicitly enable an already-initialized stack while saved off.
|
||||
interface_manager.enableBluetooth();assert(bluetooth_interface.enabled);
|
||||
assert(bluetooth_interface.enables==1);
|
||||
interface_manager.disable();interface_manager.enable();
|
||||
assert(!bluetooth_interface.enabled&&bluetooth_interface.enables==1);
|
||||
interface_manager.setBluetoothAutoEnable(true);
|
||||
interface_manager.disable();interface_manager.enable();
|
||||
assert(bluetooth_interface.enabled&&bluetooth_interface.enables==2);
|
||||
// The legacy default remains automatic on.
|
||||
MultiSerialInterface legacy;Transport ble;
|
||||
legacy.addInterface(InterfaceType::Bluetooth,&ble);legacy.enable();
|
||||
assert(ble.enabled&&ble.enables==1);
|
||||
}
|
||||
'''
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
folder = Path(directory)
|
||||
(folder/'Arduino.h').write_text('#pragma once\n#include <cstdint>\n#include <cstddef>\n')
|
||||
source = folder/'startup_policy.cpp'
|
||||
source.write_text(source_text)
|
||||
binary = folder/'startup_policy'
|
||||
built = subprocess.run([os.environ.get('CXX','g++'), '-std=c++17',
|
||||
'-I',str(folder),'-I',str(ROOT),'-I',str(ROOT/'src'),
|
||||
str(source),'-o',str(binary)],capture_output=True,text=True)
|
||||
self.assertEqual(built.returncode,0,built.stderr)
|
||||
ran = subprocess.run([str(binary)],capture_output=True,text=True)
|
||||
self.assertEqual(ran.returncode,0,ran.stderr)
|
||||
|
||||
def test_wireless_restore_does_not_change_the_saved_bluetooth_choice(self):
|
||||
adapter = method(MAIN, 'class CompanionWirelessBackend')+';\n'
|
||||
source_text = HARNESS.split('@METHODS@')[0] + '\n' + method(
|
||||
MAIN, 'static void disableCompanionBluetoothForCli(') + '\n' + adapter + r'''
|
||||
int main(){
|
||||
using namespace mesh::wireless;
|
||||
interface_manager.addInterface(InterfaceType::Bluetooth,&bluetooth_interface);
|
||||
interface_manager.addInterface(InterfaceType::USB,&usb_serial_interface);
|
||||
the_mesh.bluetooth_preference=the_mesh.durable_bluetooth_preference=false;
|
||||
interface_manager.setBluetoothAutoEnable(false);interface_manager.enable();
|
||||
CompanionWirelessBackend backend;control().begin(backend);
|
||||
assert(backend.available()==Bluetooth&&!bluetooth_interface.enabled);
|
||||
char reply[160];
|
||||
control().handle("set 2.4ghz off",reply,sizeof(reply),now_ms,Independent);
|
||||
now_ms+=250;control().service(now_ms);
|
||||
control().handle("set 2.4ghz on",reply,sizeof(reply),now_ms,Independent);
|
||||
now_ms+=250;control().service(now_ms);
|
||||
assert(!bluetooth_interface.enabled&&bluetooth_interface.enables==0);
|
||||
assert(!the_mesh.durable_bluetooth_preference&&the_mesh.saves==0);
|
||||
// Explicit all is a runtime-only override; the saved-off boot policy remains.
|
||||
control().handle("set 2.4ghz on all",reply,sizeof(reply),now_ms,Independent);
|
||||
now_ms+=250;control().service(now_ms);
|
||||
assert(bluetooth_interface.enabled&&!the_mesh.durable_bluetooth_preference);
|
||||
assert(the_mesh.saves==0);
|
||||
interface_manager.disable();interface_manager.enable();
|
||||
assert(!bluetooth_interface.enabled&&usb_serial_interface.enabled);
|
||||
}
|
||||
'''
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
folder = Path(directory)
|
||||
(folder/'Arduino.h').write_text('#pragma once\n#include <cstdint>\n#include <cstddef>\n')
|
||||
source = folder/'wireless_policy.cpp'
|
||||
source.write_text(source_text)
|
||||
binary = folder/'wireless_policy'
|
||||
built = subprocess.run([os.environ.get('CXX','g++'), '-std=c++17',
|
||||
'-DBLE_PIN_CODE=123456','-I',str(folder),'-I',str(ROOT),'-I',str(ROOT/'src'),
|
||||
str(source),'-o',str(binary)],capture_output=True,text=True)
|
||||
self.assertEqual(built.returncode,0,built.stderr)
|
||||
ran = subprocess.run([str(binary)],capture_output=True,text=True)
|
||||
self.assertEqual(ran.returncode,0,ran.stderr)
|
||||
|
||||
def test_cli_entry_points_pass_the_current_source(self):
|
||||
mesh = (ROOT/'examples/companion_radio/MyMesh.cpp').read_text()
|
||||
terminal = method(mesh,'void MyMesh::handleTerminalCommand(')
|
||||
|
||||
@@ -0,0 +1,273 @@
|
||||
"""Execute persistent Bluetooth preferences, storage faults, and boot gates."""
|
||||
from pathlib import Path
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from test_companion_preferences_transaction import HARNESS as STORE_HARNESS
|
||||
from test_companion_preferences_transaction import esp_recovery_helpers
|
||||
from test_radio_receive_contract import method
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
PREFERENCE_HARNESS = r'''
|
||||
struct MyMesh {
|
||||
CompanionNodePrefs _prefs;
|
||||
DataStore store;
|
||||
unsigned saves=0;
|
||||
bool savePrefs(){++saves;return store.savePrefs(_prefs,47.1,-122.2);}
|
||||
@SAVE_PREFERENCE@
|
||||
bool setBluetoothEnabledPreference(bool);
|
||||
bool isBluetoothEnabledPreference() const;
|
||||
};
|
||||
@PREFERENCE_METHODS@
|
||||
int main(){
|
||||
MyMesh mesh;
|
||||
assert(mesh.isBluetoothEnabledPreference()); // New devices retain legacy-on behavior.
|
||||
mesh._prefs.wifi_enabled=0;mesh._prefs.usb_logging_enabled=1;
|
||||
strcpy(mesh._prefs.node_name,"durable node");mesh._prefs.freq=910.525f;
|
||||
mesh._prefs.ble_pin=876543;mesh._prefs.bluetooth_stealth_mode=2;
|
||||
mesh._prefs.one_key_dm_enabled=1;
|
||||
assert(mesh.savePrefs());
|
||||
const auto on_disk=mesh.store.fs.files["/new_prefs"];
|
||||
#if defined(TBEAM_1W)
|
||||
assert(on_disk.size()==239);
|
||||
#elif defined(RP2040_PLATFORM) && defined(ENABLE_WIFI_INTERFACE)
|
||||
assert(on_disk.size()==329);
|
||||
#else
|
||||
assert(on_disk.size()==232);
|
||||
#endif
|
||||
assert(on_disk.back()==1);
|
||||
// Both choices survive a fresh instance reading the actual on-device image.
|
||||
for(bool enabled : {false,true,false}){
|
||||
assert(mesh.setBluetoothEnabledPreference(enabled));
|
||||
DataStore reboot;reboot.fs=mesh.store.fs;
|
||||
CompanionNodePrefs prefs;prefs.bluetooth_enabled=!enabled;
|
||||
double lat=0,lon=0;assert(reboot.loadPrefs(prefs,lat,lon));
|
||||
assert(prefs.bluetooth_enabled==enabled&&lat==47.1&&lon==-122.2);
|
||||
assert(prefs.ble_pin==876543&&prefs.freq==910.525f);
|
||||
assert(prefs.bluetooth_stealth_mode==2&&prefs.one_key_dm_enabled==1);
|
||||
assert(prefs.wifi_enabled==0&&prefs.usb_logging_enabled==1);
|
||||
assert(reboot.fs.files["/new_prefs"].back()==enabled);
|
||||
}
|
||||
const auto off_disk=mesh.store.fs.files["/new_prefs"];
|
||||
// The public setter must roll live state back when the atomic save fails.
|
||||
mesh.store.fs.fail_write_after=17;
|
||||
assert(!mesh.setBluetoothEnabledPreference(true));
|
||||
assert(!mesh.isBluetoothEnabledPreference());
|
||||
assert(mesh.store.fs.files["/new_prefs"]==off_disk);
|
||||
mesh.store.fs.fail_write_after=-1;
|
||||
mesh.store.fs.fail_rename=1;
|
||||
assert(!mesh.setBluetoothEnabledPreference(true));
|
||||
assert(!mesh.isBluetoothEnabledPreference());
|
||||
assert(mesh.store.fs.files["/new_prefs"]==off_disk);
|
||||
mesh.store.fs.fail_rename=0;
|
||||
assert(mesh.setBluetoothEnabledPreference(true));
|
||||
mesh.store.fs.fail_write=true;
|
||||
assert(!mesh.setBluetoothEnabledPreference(false));
|
||||
assert(mesh.isBluetoothEnabledPreference());
|
||||
mesh.store.fs.fail_write=false;
|
||||
// Legacy images cannot accidentally inherit an off value from the caller.
|
||||
for(unsigned size : {84u,156u,215u,226u,unsigned(on_disk.size()-1)}){
|
||||
DataStore legacy;legacy.fs.files["/new_prefs"]=on_disk;
|
||||
legacy.fs.files["/new_prefs"].resize(size);
|
||||
CompanionNodePrefs prefs;prefs.bluetooth_enabled=0;
|
||||
double lat=0,lon=0;assert(legacy.loadPrefs(prefs,lat,lon));
|
||||
assert(prefs.bluetooth_enabled==1&&prefs.ble_pin==876543);
|
||||
assert(legacy.fs.files["/new_prefs"].size()==size); // Read-only migration.
|
||||
}
|
||||
// Invalid new encoding rejects the transaction rather than partially loading.
|
||||
for(uint8_t corrupt : {2,255}){
|
||||
DataStore damaged;damaged.fs.files["/new_prefs"]=off_disk;
|
||||
damaged.fs.files["/new_prefs"].back()=corrupt;
|
||||
CompanionNodePrefs prefs;prefs.bluetooth_enabled=1;
|
||||
strcpy(prefs.node_name,"unchanged");double lat=1,lon=2;
|
||||
assert(!damaged.loadPrefsInt("/new_prefs",prefs,lat,lon));
|
||||
assert(prefs.bluetooth_enabled==1&&!strcmp(prefs.node_name,"unchanged"));
|
||||
assert(lat==1&&lon==2);
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
STARTUP_HARNESS = r'''
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <initializer_list>
|
||||
#include <helpers/MultiSerialInterface.h>
|
||||
#include <helpers/BluetoothMac.h>
|
||||
#include "examples/companion_radio/BluetoothName.h"
|
||||
#define BLE_NAME_PREFIX "MC-"
|
||||
uint32_t now_ms=100;
|
||||
uint32_t millis(){return now_ms;}
|
||||
bool companion_bluetooth_initialized=false;
|
||||
char companion_bluetooth_start_failure[96]={};
|
||||
uint32_t companion_bluetooth_start_at=0;
|
||||
constexpr uint32_t COMPANION_BLUETOOTH_RETRY_MS=5000;
|
||||
uint8_t companion_bluetooth_session_stealth_mode=mesh::companion::BLUETOOTH_STEALTH_OFF;
|
||||
struct CompanionNodePrefs {
|
||||
char bluetooth_name[64]={},node_name[32]="durable node";
|
||||
uint8_t bluetooth_stealth_peer_type=0,bluetooth_stealth_peer[6]={};
|
||||
};
|
||||
struct {
|
||||
CompanionNodePrefs prefs;
|
||||
bool preference=true;
|
||||
CompanionNodePrefs* getNodePrefs(){return &prefs;}
|
||||
bool isBluetoothEnabledPreference() const {return preference;}
|
||||
uint32_t getBLEPin(){return 876543;}
|
||||
} the_mesh;
|
||||
namespace mesh {
|
||||
struct Logger {void println(const char*){}};
|
||||
Logger& usbLoggingPort(){static Logger logger;return logger;}
|
||||
}
|
||||
struct Transport : BaseSerialInterface {
|
||||
bool enabled=false,begin_succeeds=true;
|
||||
unsigned enables=0,begins=0;
|
||||
template<class... T> bool begin(T...){++begins;return begin_succeeds;}
|
||||
const char* beginFailure(){return "fixture startup failure";}
|
||||
void enable() override{enabled=true;++enables;}
|
||||
void disable() override{enabled=false;}
|
||||
bool isEnabled() const override{return enabled;}
|
||||
bool isConnected() const override{return false;}
|
||||
bool isReadBusy() const override{return false;}
|
||||
bool isWriteBusy() const override{return false;}
|
||||
size_t writeFrame(const uint8_t*,size_t n) override{return n;}
|
||||
size_t checkRecvFrame(uint8_t*) override{return 0;}
|
||||
} bluetooth_interface,usb_serial_interface;
|
||||
MultiSerialInterface interface_manager;
|
||||
bool identity_accepts=true;
|
||||
unsigned identity_preparations=0;
|
||||
bool prepareCompanionBluetoothIdentity(CompanionNodePrefs*){
|
||||
++identity_preparations;return identity_accepts;
|
||||
}
|
||||
const uint8_t* companionBluetoothAddress(const CompanionNodePrefs*,bool&){return nullptr;}
|
||||
@METHODS@
|
||||
void fresh(bool preference){
|
||||
interface_manager=MultiSerialInterface();bluetooth_interface=Transport();
|
||||
usb_serial_interface=Transport();companion_bluetooth_initialized=false;
|
||||
companion_bluetooth_start_at=0;identity_accepts=true;identity_preparations=0;
|
||||
the_mesh.preference=preference;
|
||||
interface_manager.setBluetoothAutoEnable(preference);
|
||||
assert(interface_manager.addInterface(InterfaceType::USB,&usb_serial_interface));
|
||||
}
|
||||
int main(){
|
||||
// Both immediate and deferred stack registration must honor saved-off
|
||||
// advertising while preserving an initialized stack for USB recovery.
|
||||
for(bool manager_started : {false,true}){
|
||||
fresh(false);
|
||||
if(manager_started)interface_manager.enable();
|
||||
startCompanionBluetooth();
|
||||
assert(companion_bluetooth_initialized&&bluetooth_interface.begins==1);
|
||||
assert(!bluetooth_interface.enabled&&bluetooth_interface.enables==0);
|
||||
if(!manager_started)interface_manager.enable();
|
||||
assert(usb_serial_interface.enabled&&!bluetooth_interface.enabled);
|
||||
startCompanionBluetooth();serviceDeferredCompanionBluetooth();
|
||||
assert(bluetooth_interface.begins==1&&bluetooth_interface.enables==0);
|
||||
interface_manager.enableBluetooth();
|
||||
assert(bluetooth_interface.enabled&&bluetooth_interface.begins==1);
|
||||
}
|
||||
// A scheduled identity/start retry may initialize, never enable a saved-off
|
||||
// transport when the preference was changed before the retry became due.
|
||||
fresh(true);interface_manager.enable();identity_accepts=false;
|
||||
startCompanionBluetooth();assert(companion_bluetooth_start_at!=0);
|
||||
assert(!companion_bluetooth_initialized&&bluetooth_interface.begins==0);
|
||||
the_mesh.preference=false;interface_manager.setBluetoothAutoEnable(false);
|
||||
identity_accepts=true;now_ms+=COMPANION_BLUETOOTH_RETRY_MS;
|
||||
serviceDeferredCompanionBluetooth();
|
||||
assert(companion_bluetooth_initialized&&bluetooth_interface.begins==1);
|
||||
assert(bluetooth_interface.enables==0&&!bluetooth_interface.enabled&&usb_serial_interface.enabled);
|
||||
// Existing on/default preference still starts normally in either order.
|
||||
fresh(true);startCompanionBluetooth();interface_manager.enable();
|
||||
assert(bluetooth_interface.enabled&&bluetooth_interface.enables==1);
|
||||
fresh(true);interface_manager.enable();startCompanionBluetooth();
|
||||
assert(bluetooth_interface.enabled&&bluetooth_interface.enables==1);
|
||||
}
|
||||
'''
|
||||
|
||||
|
||||
class BluetoothPersistenceTests(unittest.TestCase):
|
||||
def test_actual_startup_and_deferred_retry_do_not_advertise_saved_off(self):
|
||||
main = (ROOT/'examples/companion_radio/main.cpp').read_text()
|
||||
methods = '\n'.join(method(main, signature) for signature in (
|
||||
'static void scheduleCompanionBluetoothRetry(',
|
||||
'static void startCompanionBluetooth(',
|
||||
'static void serviceDeferredCompanionBluetooth('))
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
work = Path(directory)
|
||||
(work/'Arduino.h').write_text('#pragma once\n#include <cstdint>\n#include <cstddef>\n')
|
||||
source = work/'startup.cpp'
|
||||
source.write_text(STARTUP_HARNESS.replace('@METHODS@', methods))
|
||||
for platform in ('NRF52_PLATFORM', 'ESP32'):
|
||||
with self.subTest(platform=platform):
|
||||
binary = work/'startup'
|
||||
built = subprocess.run([os.environ.get('CXX','g++'), '-std=c++17',
|
||||
'-DBLE_PIN_CODE=123456','-D'+platform+'=1',
|
||||
'-I',str(work),'-I',str(ROOT/'src'),'-I',str(ROOT),
|
||||
str(source),'-o',str(binary)],capture_output=True,text=True)
|
||||
self.assertEqual(built.returncode,0,built.stdout+built.stderr)
|
||||
ran = subprocess.run([str(binary)],capture_output=True,text=True)
|
||||
self.assertEqual(ran.returncode,0,ran.stdout+ran.stderr)
|
||||
|
||||
def test_boot_policy_is_applied_before_any_interface_can_start(self):
|
||||
main = (ROOT/'examples/companion_radio/main.cpp').read_text()
|
||||
setup = method(main, '\nvoid setup()')
|
||||
policy = setup.index('interface_manager.setBluetoothAutoEnable(')
|
||||
self.assertLess(policy, setup.index('startCompanionBluetooth();'))
|
||||
self.assertLess(policy, setup.index('the_mesh.startInterface('))
|
||||
|
||||
def test_actual_preference_roundtrips_and_atomic_failure_rollback(self):
|
||||
store = (ROOT/'examples/companion_radio/DataStore.cpp').read_text()
|
||||
mesh = (ROOT/'examples/companion_radio/MyMesh.cpp').read_text()
|
||||
header = (ROOT/'examples/companion_radio/MyMesh.h').read_text()
|
||||
methods = esp_recovery_helpers(store) + '\n'.join(method(store, signature) for signature in (
|
||||
'bool DataStore::loadPrefs(', 'bool DataStore::loadPrefsInt(',
|
||||
'bool DataStore::savePrefs('))
|
||||
prefix = STORE_HARNESS.split('int main(')[0].replace('@METHODS@', methods)
|
||||
harness = PREFERENCE_HARNESS.replace('@SAVE_PREFERENCE@', method(
|
||||
header, 'template <typename T> bool savePreference('))
|
||||
harness = harness.replace('@PREFERENCE_METHODS@', '\n'.join(method(mesh, signature)
|
||||
for signature in ('bool MyMesh::setBluetoothEnabledPreference(',
|
||||
'bool MyMesh::isBluetoothEnabledPreference(')))
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
work = Path(directory)
|
||||
(work/'Utils.h').write_text('''#pragma once
|
||||
#include <Arduino.h>
|
||||
#include <cassert>
|
||||
namespace mesh { struct Utils {
|
||||
static void printHex(Stream&,const uint8_t*,size_t){assert(false);}
|
||||
static void fromHex(uint8_t*,size_t,const char*){assert(false);}
|
||||
}; }
|
||||
''')
|
||||
(work/'platform_shim.h').write_text('''#include <cstdlib>
|
||||
#include <cstdio>
|
||||
inline char* utoa(unsigned int value,char* output,int base){
|
||||
if(base!=10)abort();sprintf(output,"%u",value);return output;
|
||||
}
|
||||
''')
|
||||
transaction = (ROOT/'src/helpers/ContactFileTransaction.h').read_text()
|
||||
(work/'ContactFileTransaction.h').write_text(transaction.replace(
|
||||
'#include "IdentityStore.h"', '#include <helpers/IdentityStore.h>'))
|
||||
source = work/'preferences.cpp'
|
||||
source.write_text(prefix+harness)
|
||||
for flags in ('NRF52_PLATFORM', 'ESP32_PLATFORM', 'RP2040_PLATFORM',
|
||||
'ESP32_PLATFORM,TBEAM_1W', 'RP2040_PLATFORM,ENABLE_WIFI_INTERFACE'):
|
||||
with self.subTest(platform=flags):
|
||||
binary = work/'preferences'
|
||||
built = subprocess.run([os.environ.get('CXX','g++'), '-std=c++17',
|
||||
*['-D'+flag+'=1' for flag in flags.split(',')],
|
||||
'-include',str(work/'platform_shim.h'),
|
||||
'-I',str(work),'-I',str(ROOT/'test/fixtures/radio_profiles/mocks'),
|
||||
'-I',str(ROOT/'test/mocks'),'-I',str(ROOT/'src'),
|
||||
'-I',str(ROOT/'src/helpers'),'-I',str(ROOT), str(source),
|
||||
*[str(ROOT/'src/helpers'/name) for name in ('ConfigSerializer.cpp',
|
||||
'DynamicConfigSerializer.cpp','CommonRadioPrefs.cpp','TxtDataHelpers.cpp')],
|
||||
'-o',str(binary)],capture_output=True,text=True)
|
||||
self.assertEqual(built.returncode,0,built.stdout+built.stderr)
|
||||
ran = subprocess.run([str(binary)],capture_output=True,text=True)
|
||||
self.assertEqual(ran.returncode,0,ran.stdout+ran.stderr)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -194,7 +194,7 @@ int main(int argc,char** argv){
|
||||
} else if(scenario==7){
|
||||
// Pre-consent image: all earlier settings survive with default-off consent.
|
||||
DataStore legacy;legacy.fs.files["/new_prefs"]=disk;
|
||||
legacy.fs.files["/new_prefs"].resize(disk.size()-1);
|
||||
legacy.fs.files["/new_prefs"].resize(disk.size()-2);
|
||||
CompanionNodePrefs loaded;double lat=0,lon=0;
|
||||
assert(legacy.loadPrefs(loaded,lat,lon));
|
||||
assert(loaded.flood_retry_advert_enabled==0);
|
||||
|
||||
Reference in new issue
Block a user