diff --git a/.vscode/extensions.json b/.vscode/extensions.json index dc42e991..f45a9d62 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -3,8 +3,7 @@ // for the documentation about the extensions.json format "recommendations": [ "ms-vscode.cpptools-extension-pack", - "pioarduino.pioarduino-ide", - "platformio.platformio-ide" + "pioarduino.pioarduino-ide" ], "unwantedRecommendations": [ "ms-vscode.cpptools-extension-pack" diff --git a/inc/sp140/ble/ble_core.h b/inc/sp140/ble/ble_core.h index bd1d2cdb..124f363f 100644 --- a/inc/sp140/ble/ble_core.h +++ b/inc/sp140/ble/ble_core.h @@ -17,5 +17,6 @@ void requestNormalConnParams(); // Temporarily disable whitelist filtering so a new device can bond. // Advertising reopens for ~60 seconds then whitelisting is restored. void enterBLEPairingMode(); +bool isBLEPairingModeActive(); #endif // INC_SP140_BLE_BLE_CORE_H_ diff --git a/inc/sp140/lvgl/lvgl_updates.h b/inc/sp140/lvgl/lvgl_updates.h index 30ee0f7c..89ac66ee 100644 --- a/inc/sp140/lvgl/lvgl_updates.h +++ b/inc/sp140/lvgl/lvgl_updates.h @@ -79,5 +79,7 @@ void stopCriticalBorderFlashDirect(); // BLE pairing icon flash functions void startBLEPairingIconFlash(); void stopBLEPairingIconFlash(); +void showBLEStatusIcon(); +void hideBLEStatusIcon(); #endif // INC_SP140_LVGL_LVGL_UPDATES_H_ diff --git a/platformio.ini b/platformio.ini index 32a447d7..00f46887 100644 --- a/platformio.ini +++ b/platformio.ini @@ -42,7 +42,7 @@ build_flags = -D CORE_DEBUG_LEVEL=2 -D CONFIG_ARDUINO_LOOP_STACK_SIZE=8192 -Wno-error=format - -D BLE_PAIR_ON_BOOT + ;-D BLE_PAIR_ON_BOOT build_type = debug debug_speed = 12000 debug_tool = esp-builtin diff --git a/src/sp140/ble/ble_core.cpp b/src/sp140/ble/ble_core.cpp index aa962e37..2a9345c6 100644 --- a/src/sp140/ble/ble_core.cpp +++ b/src/sp140/ble/ble_core.cpp @@ -8,7 +8,6 @@ #include #include "sp140/ble.h" -#include "sp140/lvgl/lvgl_updates.h" #include "sp140/ble/ble_ids.h" #include "sp140/ble/config_service.h" #include "sp140/ble/fastlink_service.h" @@ -63,6 +62,7 @@ void stopPairingModeTimer() { } } + size_t syncWhiteListFromBonds() { // Reconcile the whitelist to the current bond store. Advertising must be // stopped before calling this — the BLE controller rejects whitelist changes @@ -89,7 +89,6 @@ size_t syncWhiteListFromBonds() { void onPairingTimeout(TimerHandle_t timer) { (void)timer; pairingModeActive = false; - stopBLEPairingIconFlash(); USBSerial.println("[BLE] Pairing mode expired, re-enabling whitelist"); restartBLEAdvertising(); } @@ -112,7 +111,8 @@ void applyPreferredLinkParams(TimerHandle_t timer) { bool shouldAdvertiseWhilePowered() { return !pairingModeTransitionActive && - (pairingModeActive || NimBLEDevice::getNumBonds() > 0); + (pairingModeActive || + NimBLEDevice::getNumBonds() > 0); } bool startAdvertising(NimBLEServer *server) { @@ -136,6 +136,10 @@ bool startAdvertising(NimBLEServer *server) { return false; } + // Bonded devices can always reconnect via whitelist advertising — + // no reconnect window gating. Power draw is negligible for + // whitelist-only advertising. + #if CONFIG_BT_NIMBLE_EXT_ADV // Legacy connectable undirected advertising via the extended API. // Legacy PDUs avoid NimBLE's extended-adv EBUSY state machine bug. @@ -161,26 +165,13 @@ bool startAdvertising(NimBLEServer *server) { // Flutter app's `startScan()` filters for CONFIG_SERVICE_UUID. adv.addServiceUUID(NimBLEUUID(CONFIG_SERVICE_UUID)); - // Scan response: manufacturer data with pairing-mode flag so the Flutter app - // can hide non-pairable controllers from the connect list. - // Format: Espressif company ID (0x02E5 LE) + 1 flag byte. - NimBLEExtAdvertisement scanRsp(BLE_HCI_LE_PHY_1M, BLE_HCI_LE_PHY_1M); - scanRsp.setLegacyAdvertising(true); - scanRsp.setScannable(true); - const uint8_t mfrData[] = {0xE5, 0x02, - static_cast(allowOpenAdvertising ? 0x01 : 0x00)}; - scanRsp.setManufacturerData(mfrData, sizeof(mfrData)); - advertising->removeAll(); const bool configured = advertising->setInstanceData(kExtAdvInstance, adv); - const bool scanRspConfigured = - configured ? advertising->setScanResponseData(kExtAdvInstance, scanRsp) - : false; const bool started = - configured && scanRspConfigured && advertising->start(kExtAdvInstance); + configured && advertising->start(kExtAdvInstance); USBSerial.printf( - "[BLE] Ext adv cfg=%d scanRsp=%d start=%d mode=%s bonds=%u wl=%u\n", - configured, scanRspConfigured, started, + "[BLE] Ext adv cfg=%d start=%d mode=%s bonds=%u wl=%u\n", + configured, started, allowOpenAdvertising ? "OPEN" : "BONDED", static_cast(bondCount), static_cast(whiteListCount)); return started; @@ -206,12 +197,6 @@ bool startAdvertising(NimBLEServer *server) { advertising->setScanFilter(false, true); } - // Manufacturer data with pairing-mode flag (updated every restart). - // Espressif company ID (0x02E5 LE) + 1 flag byte. - const std::string mfrPayload = {'\xE5', '\x02', - static_cast(allowOpenAdvertising ? 0x01 : 0x00)}; - advertising->setManufacturerData(mfrPayload); - const bool started = advertising->start(); USBSerial.printf("[BLE] Legacy adv start=%s mode=%s bonds=%u whitelist=%u\n", started ? "OK" : "FAIL", @@ -298,7 +283,6 @@ class BleServerConnectionCallbacks : public NimBLEServerCallbacks { if (pairingModeActive) { pairingModeActive = false; stopPairingModeTimer(); - stopBLEPairingIconFlash(); } } @@ -388,8 +372,10 @@ void setupBLE() { #ifdef BLE_PAIR_ON_BOOT USBSerial.println("[BLE] BLE_PAIR_ON_BOOT: entering pairing mode automatically"); enterBLEPairingMode(); - startBLEPairingIconFlash(); #endif + if (shouldAdvertiseWhilePowered()) { + restartBLEAdvertising(); + } } void requestFastConnParams() { @@ -468,3 +454,5 @@ void enterBLEPairingMode() { USBSerial.println("[BLE] Pairing mode active for 60s"); restartBLEAdvertising(); } + +bool isBLEPairingModeActive() { return pairingModeActive; } diff --git a/src/sp140/lvgl/lvgl_updates.cpp b/src/sp140/lvgl/lvgl_updates.cpp index af78e915..a5ebb199 100644 --- a/src/sp140/lvgl/lvgl_updates.cpp +++ b/src/sp140/lvgl/lvgl_updates.cpp @@ -4,6 +4,8 @@ #include "../../../inc/sp140/globals.h" #include "../../../inc/sp140/vibration_pwm.h" #include "../../../inc/sp140/shared-config.h" +#include "../../../inc/sp140/ble.h" +#include "../../../inc/sp140/ble/ble_core.h" #include // Flash timer globals - definitions @@ -308,7 +310,21 @@ void startBLEPairingIconFlash() { } } -void stopBLEPairingIconFlash() { +void showBLEStatusIcon() { + if (xSemaphoreTake(lvglMutex, pdMS_TO_TICKS(50)) == pdTRUE) { + if (ble_pairing_flash_timer != NULL) { + lv_timer_del(ble_pairing_flash_timer); + ble_pairing_flash_timer = NULL; + } + if (ble_pairing_icon != NULL) { + lv_obj_remove_flag(ble_pairing_icon, LV_OBJ_FLAG_HIDDEN); + } + isFlashingBLEPairingIcon = false; + xSemaphoreGive(lvglMutex); + } +} + +void hideBLEStatusIcon() { if (xSemaphoreTake(lvglMutex, pdMS_TO_TICKS(50)) == pdTRUE) { if (ble_pairing_flash_timer != NULL) { lv_timer_del(ble_pairing_flash_timer); @@ -322,6 +338,10 @@ void stopBLEPairingIconFlash() { } } +void stopBLEPairingIconFlash() { + hideBLEStatusIcon(); +} + // Update the climb rate indicator void updateClimbRateIndicator(float climbRate) { // Clamp climb rate to displayable range (-0.6 to +0.6 m/s) @@ -833,6 +853,36 @@ void updateLvglMainScreen( lv_obj_set_style_image_recolor(arm_fail_warning_icon_img, original_arm_fail_icon_color, LV_PART_MAIN); } + // Keep the BLE icon synced from the UI task so missed callback updates recover. + if (ble_pairing_icon != NULL) { + if (deviceConnected) { + if (ble_pairing_flash_timer != NULL) { + lv_timer_del(ble_pairing_flash_timer); + ble_pairing_flash_timer = NULL; + } + lv_obj_remove_flag(ble_pairing_icon, LV_OBJ_FLAG_HIDDEN); + isFlashingBLEPairingIcon = false; + } else if (isBLEPairingModeActive()) { + if (!isFlashingBLEPairingIcon) { + isFlashingBLEPairingIcon = true; + lv_obj_remove_flag(ble_pairing_icon, LV_OBJ_FLAG_HIDDEN); + ble_pairing_flash_timer = lv_timer_create(ble_pairing_flash_timer_cb, 500, NULL); + if (ble_pairing_flash_timer == NULL) { + isFlashingBLEPairingIcon = false; + lv_obj_add_flag(ble_pairing_icon, LV_OBJ_FLAG_HIDDEN); + USBSerial.println("Error: Failed to create BLE pairing flash timer!"); + } + } + } else { + if (ble_pairing_flash_timer != NULL) { + lv_timer_del(ble_pairing_flash_timer); + ble_pairing_flash_timer = NULL; + } + lv_obj_add_flag(ble_pairing_icon, LV_OBJ_FLAG_HIDDEN); + isFlashingBLEPairingIcon = false; + } + } + // Update climb rate indicator static float lastAltitude = 0.0f; static uint32_t lastAltitudeTime = 0; diff --git a/src/sp140/main.cpp b/src/sp140/main.cpp index f94f33fb..174e77ff 100644 --- a/src/sp140/main.cpp +++ b/src/sp140/main.cpp @@ -769,8 +769,10 @@ void setup() { setLEDColor(LED_GREEN); // Show splash screen (blocking) + lv_obj_t* splash_screen = NULL; if (xSemaphoreTake(lvglMutex, portMAX_DELAY) == pdTRUE) { displayLvglSplash(deviceData, 2000); + splash_screen = lv_screen_active(); // Keep mutex held through main screen setup } else { USBSerial.println("Failed to acquire LVGL mutex for splash"); @@ -782,9 +784,14 @@ void setup() { setupMainScreen(deviceData.theme == 1); } - // Load main screen + // Force the first main-screen repaint before other tasks can contend for SPI. if (main_screen != NULL) { - lv_screen_load(main_screen); + lv_obj_invalidate(main_screen); + lv_refr_now(main_display); + if (splash_screen != NULL && splash_screen != main_screen) { + lv_obj_delete(splash_screen); + splash_screen = NULL; + } USBSerial.println("Main screen loaded"); } else { USBSerial.println("Error: Main screen object is NULL after setup attempt"); @@ -932,7 +939,6 @@ void buttonHandlerTask(void *parameter) { currentHoldTime >= BLE_PAIRING_HOLD_MS && !pairingHoldHandled) { enterBLEPairingMode(); pulseVibeMotor(); - startBLEPairingIconFlash(); USBSerial.println("[BLE] Pairing mode activated via button hold"); pairingHoldHandled = true; }