diff --git a/include/opendisplay_structs.h b/include/opendisplay_structs.h index f8b0cb2..4df1582 100644 --- a/include/opendisplay_structs.h +++ b/include/opendisplay_structs.h @@ -1246,7 +1246,7 @@ OD_STATIC_ASSERT(sizeof(struct AuthProof) == 32, "AuthProof wire size"); /* MsdAdvertisement.status @bits MsdStatusBits. bit0 is the 9th (MSB) bit of the * 10-bit battery voltage; bits 4-7 are a free-running main-loop nibble counter. */ #define OD_MSD_STATUS_BATTERY_VOLTAGE_BIT8 (1u << 0) /* @doc "high bit of the 10-bit battery voltage (units of 10 mV); combine with battery_voltage_low" */ -#define OD_MSD_STATUS_REBOOT_FLAG (1u << 1) /* @doc "device rebooted since last read" */ +#define OD_MSD_STATUS_REBOOT_FLAG (1u << 1) /* @doc "device state changed since last read (reboot or live config write)" */ #define OD_MSD_STATUS_CONNECTION_REQUESTED (1u << 2) /* @doc "device is requesting a connection" */ #define OD_MSD_STATUS_ENCRYPTION_ENABLED (1u << 3) /* @doc "application-layer encryption active (encryption_enabled and non-zero master key)" */ #define OD_MSD_STATUS_RESERVED_3 (1u << 3) /* @deprecated @doc "legacy name for OD_MSD_STATUS_ENCRYPTION_ENABLED (same bit)" */ diff --git a/src/communication.cpp b/src/communication.cpp index c6e4a8b..522e3a2 100644 --- a/src/communication.cpp +++ b/src/communication.cpp @@ -595,6 +595,12 @@ void handleWriteConfig(uint8_t* data, uint16_t len) { reloadConfigAfterSave(); } sendResponse(ok ? responseOk : responseErr, 4); + if (ok) { + // Runs after the ACK: unlike the reload above, this only affects what + // OTHER scanners see, and updatemsdata() can block tens of ms on a + // sensor-cache miss -- no reason to make the writer's own ACK wait on it. + notifyConfigChangedOverBle(); + } } void handleClearConfig(void) { @@ -644,6 +650,10 @@ void handleWriteConfigChunk(uint8_t* data, uint16_t len) { reloadConfigAfterSave(); } sendResponse(saved ? ok : err, 4); + if (saved) { + // See handleWriteConfig(): deferred past the ACK on purpose. + notifyConfigChangedOverBle(); + } resetChunkedWriteState(); } else { uint8_t ackResponse[] = {RESP_ACK, RESP_CONFIG_CHUNK, 0x00, 0x00}; diff --git a/src/communication.h b/src/communication.h index 9cc17b6..dba946b 100644 --- a/src/communication.h +++ b/src/communication.h @@ -9,6 +9,7 @@ uint16_t calculateCRC16CCITT(uint8_t* data, uint32_t len); uint8_t getFirmwareMajor(); uint8_t getFirmwareMinor(); uint8_t getFirmwarePatch(); +uint8_t getRebootFlag(); // 1 after reboot or a live config write, cleared to 0 on BLE connection const char* getFirmwareShaString(); void handleFirmwareVersion(); void handleReadMSD(); @@ -80,4 +81,10 @@ void requestTransferSessionCleanup(void); /// re-arm on both targets (see BleTransport::restartsAdvertisingOnDisconnect). void requestAdvertisingRestart(void); +/// Set rebootFlag and refresh the advertised MSD payload immediately after a +/// config write. Unlike the two requests above this is not deferred: it is +/// safe to run synchronously from the command handler, the same way +/// updatemsdata() already runs synchronously from button/sensor call sites. +void notifyConfigChangedOverBle(void); + #endif diff --git a/src/device_control.cpp b/src/device_control.cpp index dd7bb19..f5867a8 100644 --- a/src/device_control.cpp +++ b/src/device_control.cpp @@ -29,7 +29,6 @@ extern "C" void bootloader_util_app_start(uint32_t start_addr); #include "ble_transport.h" -extern uint8_t rebootFlag; extern struct GlobalConfig globalConfig; extern uint8_t activeLedInstance; extern bool ledFlashActive; diff --git a/src/display_service.cpp b/src/display_service.cpp index 592e5ec..29d885c 100644 --- a/src/display_service.cpp +++ b/src/display_service.cpp @@ -65,7 +65,6 @@ extern BBEPDISP bbep; extern struct GlobalConfig globalConfig; extern uint8_t msd_payload[16]; extern uint8_t dynamicreturndata[11]; -extern uint8_t rebootFlag; extern uint8_t activeLedInstance; extern bool connectionRequested; extern uint8_t mloopcounter; @@ -1868,7 +1867,7 @@ void updatemsdata(){ uint8_t temperatureByte = (uint8_t)tempEncoded; uint8_t batteryVoltageLowByte = (uint8_t)(batteryVoltage10mv & 0xFF); uint8_t statusByte = (((batteryVoltage10mv >> 8) & 0x01) ? OD_MSD_STATUS_BATTERY_VOLTAGE_BIT8 : 0) | - (rebootFlag ? OD_MSD_STATUS_REBOOT_FLAG : 0) | + (getRebootFlag() ? OD_MSD_STATUS_REBOOT_FLAG : 0) | (connectionRequested ? OD_MSD_STATUS_CONNECTION_REQUESTED : 0) | (isEncryptionEnabled() ? OD_MSD_STATUS_ENCRYPTION_ENABLED : 0) | (((uint8_t)(mloopcounter << OD_MSD_STATUS_MAIN_LOOP_COUNTER_SHIFT)) & OD_MSD_STATUS_MAIN_LOOP_COUNTER_MASK); diff --git a/src/main.cpp b/src/main.cpp index 1f1b322..d1b6194 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -321,6 +321,10 @@ uint32_t getDeepSleepCount() { #endif } +uint8_t getRebootFlag() { + return rebootFlag; +} + // Deferred work, serviced by loop(). File-static on purpose: these encode // application policy, so nothing outside this file reads them, and the two that // other translation units need to RAISE do so through the request functions @@ -360,6 +364,17 @@ void requestAdvertisingRestart(void) { s_advertisingRestartPending = true; } +void notifyConfigChangedOverBle(void) { + // Mirrors the boot-time meaning of rebootFlag: a scanning hub sees it set in + // the advertised MSD payload and knows to reconnect and re-read config + // instead of trusting a cached copy (e.g. stale color_scheme). A config + // write changes the same on-device state a reboot would, so it must raise + // the same flag -- otherwise a live BLE write is invisible to anyone who + // isn't the client that just wrote it. + rebootFlag = 1; + updatemsdata(); +} + #ifdef TARGET_ESP32 // Minimum awake window (first boot / button wake). A floor layered UNDER the // quiet-window logic, not a replacement: sleep requires both the existing diff --git a/src/main.h b/src/main.h index 430b3c1..78da264 100644 --- a/src/main.h +++ b/src/main.h @@ -109,9 +109,9 @@ uint8_t mloopcounter = 0; #ifdef TARGET_ESP32 // Persists across deep sleep so a wake is not mistaken for a reboot. Re-armed // on the boot-screen path in setup(), which is the only path a real reset takes. -RTC_DATA_ATTR uint8_t rebootFlag = 1; // Set to 1 after reboot, cleared to 0 after BLE connection +RTC_DATA_ATTR uint8_t rebootFlag = 1; // Set to 1 on reboot or config write, cleared to 0 on BLE connection #else -uint8_t rebootFlag = 1; // Set to 1 after reboot, cleared to 0 after BLE connection +uint8_t rebootFlag = 1; // Set to 1 on reboot or config write, cleared to 0 on BLE connection #endif uint8_t connectionRequested = 0; // Reserved for future features (connection requested flag) uint8_t dynamicreturndata[11] = {0}; // Dynamic return data blocks (bytes 2-12 in advertising payload) @@ -248,6 +248,7 @@ uint8_t getFirmwareMajor(); uint8_t getFirmwareMinor(); uint8_t getFirmwarePatch(); uint32_t getDeepSleepCount(); // RTC-persisted wake cycle count on ESP32; always 0 on nRF52840 +uint8_t getRebootFlag(); // 1 after reboot or a live config write, cleared to 0 on BLE connection float readBatteryVoltage(); // Returns battery voltage in volts, or -1.0 if not configured float readChipTemperature(); // Returns chip temperature in degrees Celsius int getplane(); diff --git a/tools/od-device-cli.py b/tools/od-device-cli.py index 27e5107..11fbfa5 100755 --- a/tools/od-device-cli.py +++ b/tools/od-device-cli.py @@ -1051,7 +1051,7 @@ def cmd_read_msd(args: argparse.Namespace) -> int: voltage = info["battery_voltage_v"] print(f"Battery voltage: {f'{voltage:.2f} V' if voltage is not None else 'unknown (raw=0 - unconfigured or not yet sampled)'}") print(f"Chip temperature: {info['temperature_c']:.1f} C") - print(f"Reboot flag: {info['reboot_flag']}") + print(f"State changed (reboot/config): {info['reboot_flag']}") print(f"Connection requested: {info['connection_requested']}") print(f"Loop counter: {info['loop_counter']}") print(f"Dynamic data (bytes 2-12): {info['dynamic_data_hex']}")