Skip to content

Commit 496e2f2

Browse files
committed
Add BLE reconnect window and status icon control
Introduce a 5-minute reconnect window and UI status control for BLE advertising/connection handling. Adds reconnect window timer/flag and init logic so whitelist advertising after boot or disconnect only runs while the reconnect window is active; advertising logic now checks reconnectWindowActive instead of relying solely on bond count. Add updateBLEStatusIcon(), showBLEStatusIcon(), and hideBLEStatusIcon() and wire status updates across BLE lifecycle events; LVGL changes centralize pairing/status icon show/hide and make stopBLEPairingIconFlash delegate to hideBLEStatusIcon. Remove some legacy scan-response manufacturer payload code paths and stop triggering the pairing-icon flash from the main button handler. Also comment out the BLE_PAIR_ON_BOOT build flag in platformio.ini.
1 parent 2d408f3 commit 496e2f2

5 files changed

Lines changed: 111 additions & 29 deletions

File tree

inc/sp140/lvgl/lvgl_updates.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,5 +79,7 @@ void stopCriticalBorderFlashDirect();
7979
// BLE pairing icon flash functions
8080
void startBLEPairingIconFlash();
8181
void stopBLEPairingIconFlash();
82+
void showBLEStatusIcon();
83+
void hideBLEStatusIcon();
8284

8385
#endif // INC_SP140_LVGL_LVGL_UPDATES_H_

platformio.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ build_flags =
4242
-D CORE_DEBUG_LEVEL=2
4343
-D CONFIG_ARDUINO_LOOP_STACK_SIZE=8192
4444
-Wno-error=format
45-
-D BLE_PAIR_ON_BOOT
45+
;-D BLE_PAIR_ON_BOOT
4646
build_type = debug
4747
debug_speed = 12000
4848
debug_tool = esp-builtin

src/sp140/ble/ble_core.cpp

Lines changed: 89 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,23 @@ namespace {
2222
constexpr size_t kAdvertisingNameCapacity = 32;
2323
constexpr TickType_t kConnTuneDelayTicks = pdMS_TO_TICKS(1200);
2424
constexpr TickType_t kPairingTimeoutTicks = pdMS_TO_TICKS(60000);
25+
constexpr TickType_t kReconnectWindowTicks = pdMS_TO_TICKS(300000); // 5 min
2526
constexpr TickType_t kAdvertisingWatchdogTicks = pdMS_TO_TICKS(1000);
2627
TimerHandle_t gConnTuneTimer = nullptr;
2728
TimerHandle_t gPairingTimer = nullptr;
29+
TimerHandle_t gReconnectWindowTimer = nullptr;
2830
TimerHandle_t gAdvertisingWatchdogTimer = nullptr;
2931
char gAdvertisingName[kAdvertisingNameCapacity];
3032
bool pairingModeActive = false;
3133
bool pairingModeTransitionActive = false;
34+
bool reconnectWindowActive = false;
3235

3336
// Store the active connection handle for conn param updates
3437
uint16_t activeConnHandle = 0;
3538

3639
bool shouldAdvertiseWhilePowered();
3740
bool startAdvertising(NimBLEServer *server);
41+
void updateBLEStatusIcon();
3842

3943
// Builds gAdvertisingName from the BT MAC and returns the full MAC address
4044
// as an uppercase string for use as a unique device ID. Must be called before
@@ -63,6 +67,41 @@ void stopPairingModeTimer() {
6367
}
6468
}
6569

70+
void stopReconnectWindowTimer() {
71+
if (gReconnectWindowTimer != nullptr) {
72+
xTimerStop(gReconnectWindowTimer, 0);
73+
}
74+
}
75+
76+
void onReconnectWindowTimeout(TimerHandle_t timer) {
77+
(void)timer;
78+
reconnectWindowActive = false;
79+
USBSerial.println("[BLE] Reconnect window expired; stopping advertising");
80+
restartBLEAdvertising();
81+
}
82+
83+
void startReconnectWindowTimer() {
84+
reconnectWindowActive = true;
85+
if (gReconnectWindowTimer == nullptr) {
86+
gReconnectWindowTimer = xTimerCreate("bleReconn", kReconnectWindowTicks,
87+
pdFALSE, nullptr,
88+
onReconnectWindowTimeout);
89+
}
90+
if (gReconnectWindowTimer != nullptr) {
91+
xTimerStop(gReconnectWindowTimer, 0);
92+
xTimerStart(gReconnectWindowTimer, 0);
93+
}
94+
}
95+
96+
void initReconnectWindowFromBoot() {
97+
if (NimBLEDevice::getNumBonds() > 0) {
98+
startReconnectWindowTimer();
99+
USBSerial.println("[BLE] Reconnect window active (5 min)");
100+
} else {
101+
reconnectWindowActive = false;
102+
}
103+
}
104+
66105
size_t syncWhiteListFromBonds() {
67106
// Reconcile the whitelist to the current bond store. Advertising must be
68107
// stopped before calling this — the BLE controller rejects whitelist changes
@@ -89,7 +128,6 @@ size_t syncWhiteListFromBonds() {
89128
void onPairingTimeout(TimerHandle_t timer) {
90129
(void)timer;
91130
pairingModeActive = false;
92-
stopBLEPairingIconFlash();
93131
USBSerial.println("[BLE] Pairing mode expired, re-enabling whitelist");
94132
restartBLEAdvertising();
95133
}
@@ -112,11 +150,25 @@ void applyPreferredLinkParams(TimerHandle_t timer) {
112150

113151
bool shouldAdvertiseWhilePowered() {
114152
return !pairingModeTransitionActive &&
115-
(pairingModeActive || NimBLEDevice::getNumBonds() > 0);
153+
(pairingModeActive || reconnectWindowActive);
154+
}
155+
156+
void updateBLEStatusIcon() {
157+
if (deviceConnected) {
158+
showBLEStatusIcon();
159+
return;
160+
}
161+
162+
if (pairingModeActive) {
163+
startBLEPairingIconFlash();
164+
} else {
165+
hideBLEStatusIcon();
166+
}
116167
}
117168

118169
bool startAdvertising(NimBLEServer *server) {
119170
if (server == nullptr || pairingModeTransitionActive) {
171+
updateBLEStatusIcon();
120172
return false;
121173
}
122174

@@ -133,6 +185,14 @@ bool startAdvertising(NimBLEServer *server) {
133185
if (!allowOpenAdvertising && bondCount == 0) {
134186
USBSerial.println(
135187
"[BLE] No bonds present and pairing mode inactive; advertising stopped");
188+
updateBLEStatusIcon();
189+
return false;
190+
}
191+
192+
if (!allowOpenAdvertising && bondCount > 0 && !reconnectWindowActive) {
193+
USBSerial.println(
194+
"[BLE] Reconnect window inactive; whitelist advertising not started");
195+
updateBLEStatusIcon();
136196
return false;
137197
}
138198

@@ -161,28 +221,16 @@ bool startAdvertising(NimBLEServer *server) {
161221
// Flutter app's `startScan()` filters for CONFIG_SERVICE_UUID.
162222
adv.addServiceUUID(NimBLEUUID(CONFIG_SERVICE_UUID));
163223

164-
// Scan response: manufacturer data with pairing-mode flag so the Flutter app
165-
// can hide non-pairable controllers from the connect list.
166-
// Format: Espressif company ID (0x02E5 LE) + 1 flag byte.
167-
NimBLEExtAdvertisement scanRsp(BLE_HCI_LE_PHY_1M, BLE_HCI_LE_PHY_1M);
168-
scanRsp.setLegacyAdvertising(true);
169-
scanRsp.setScannable(true);
170-
const uint8_t mfrData[] = {0xE5, 0x02,
171-
static_cast<uint8_t>(allowOpenAdvertising ? 0x01 : 0x00)};
172-
scanRsp.setManufacturerData(mfrData, sizeof(mfrData));
173-
174224
advertising->removeAll();
175225
const bool configured = advertising->setInstanceData(kExtAdvInstance, adv);
176-
const bool scanRspConfigured =
177-
configured ? advertising->setScanResponseData(kExtAdvInstance, scanRsp)
178-
: false;
179226
const bool started =
180-
configured && scanRspConfigured && advertising->start(kExtAdvInstance);
227+
configured && advertising->start(kExtAdvInstance);
181228
USBSerial.printf(
182-
"[BLE] Ext adv cfg=%d scanRsp=%d start=%d mode=%s bonds=%u wl=%u\n",
183-
configured, scanRspConfigured, started,
229+
"[BLE] Ext adv cfg=%d start=%d mode=%s bonds=%u wl=%u\n",
230+
configured, started,
184231
allowOpenAdvertising ? "OPEN" : "BONDED",
185232
static_cast<unsigned>(bondCount), static_cast<unsigned>(whiteListCount));
233+
updateBLEStatusIcon();
186234
return started;
187235
#else
188236
// Configure payload once — NimBLE accumulates addServiceUUID calls
@@ -206,18 +254,13 @@ bool startAdvertising(NimBLEServer *server) {
206254
advertising->setScanFilter(false, true);
207255
}
208256

209-
// Manufacturer data with pairing-mode flag (updated every restart).
210-
// Espressif company ID (0x02E5 LE) + 1 flag byte.
211-
const std::string mfrPayload = {'\xE5', '\x02',
212-
static_cast<char>(allowOpenAdvertising ? 0x01 : 0x00)};
213-
advertising->setManufacturerData(mfrPayload);
214-
215257
const bool started = advertising->start();
216258
USBSerial.printf("[BLE] Legacy adv start=%s mode=%s bonds=%u whitelist=%u\n",
217259
started ? "OK" : "FAIL",
218260
allowOpenAdvertising ? "OPEN" : "BONDED",
219261
static_cast<unsigned>(bondCount),
220262
static_cast<unsigned>(whiteListCount));
263+
updateBLEStatusIcon();
221264
return started;
222265
#endif
223266
}
@@ -249,6 +292,9 @@ class BleServerConnectionCallbacks : public NimBLEServerCallbacks {
249292
deviceConnected = true;
250293
connectedHandle = connInfo.getConnHandle();
251294

295+
stopReconnectWindowTimer();
296+
reconnectWindowActive = false;
297+
252298
if (gConnTuneTimer != nullptr) {
253299
xTimerStop(gConnTuneTimer, 0);
254300
xTimerStart(gConnTuneTimer, 0);
@@ -258,6 +304,7 @@ class BleServerConnectionCallbacks : public NimBLEServerCallbacks {
258304
connectedHandle, connInfo.getAddress().toString().c_str(),
259305
connInfo.isBonded() ? 1 : 0, connInfo.isEncrypted() ? 1 : 0,
260306
pairingModeActive ? 1 : 0);
307+
updateBLEStatusIcon();
261308

262309
// During pairing mode, proactively request fresh security negotiation.
263310
// This helps recover from stale iOS bonds where iOS tries to restore
@@ -287,8 +334,15 @@ class BleServerConnectionCallbacks : public NimBLEServerCallbacks {
287334
// Suppress the immediate advertising restart during a pairing transition —
288335
// enterBLEPairingMode() issues its own startAdvertising after clearing bonds.
289336
if (!pairingModeTransitionActive) {
337+
if (NimBLEDevice::getNumBonds() > 0) {
338+
startReconnectWindowTimer();
339+
} else {
340+
reconnectWindowActive = false;
341+
stopReconnectWindowTimer();
342+
}
290343
startAdvertising(server);
291344
}
345+
updateBLEStatusIcon();
292346
}
293347

294348
void onAuthenticationComplete(NimBLEConnInfo &connInfo) override {
@@ -298,7 +352,6 @@ class BleServerConnectionCallbacks : public NimBLEServerCallbacks {
298352
if (pairingModeActive) {
299353
pairingModeActive = false;
300354
stopPairingModeTimer();
301-
stopBLEPairingIconFlash();
302355
}
303356
}
304357

@@ -327,6 +380,7 @@ class BleServerConnectionCallbacks : public NimBLEServerCallbacks {
327380
}
328381
}
329382
}
383+
updateBLEStatusIcon();
330384
}
331385

332386
void onIdentity(NimBLEConnInfo &connInfo) override {
@@ -388,8 +442,14 @@ void setupBLE() {
388442
#ifdef BLE_PAIR_ON_BOOT
389443
USBSerial.println("[BLE] BLE_PAIR_ON_BOOT: entering pairing mode automatically");
390444
enterBLEPairingMode();
391-
startBLEPairingIconFlash();
392445
#endif
446+
// After optional boot pairing: start reconnect window only if bonds remain.
447+
initReconnectWindowFromBoot();
448+
if (shouldAdvertiseWhilePowered()) {
449+
restartBLEAdvertising();
450+
} else {
451+
updateBLEStatusIcon();
452+
}
393453
}
394454

395455
void requestFastConnParams() {
@@ -410,6 +470,7 @@ void requestNormalConnParams() {
410470

411471
void restartBLEAdvertising() {
412472
if (pServer == nullptr) {
473+
updateBLEStatusIcon();
413474
return;
414475
}
415476

@@ -419,6 +480,8 @@ void restartBLEAdvertising() {
419480
void enterBLEPairingMode() {
420481
// Block advertising restarts (e.g. from onDisconnect) during this transition.
421482
pairingModeTransitionActive = true;
483+
stopReconnectWindowTimer();
484+
reconnectWindowActive = false;
422485

423486
// Single-bond model: disconnect the current peer so we can safely clear bonds.
424487
if (deviceConnected && pServer != nullptr &&

src/sp140/lvgl/lvgl_updates.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,21 @@ void startBLEPairingIconFlash() {
308308
}
309309
}
310310

311-
void stopBLEPairingIconFlash() {
311+
void showBLEStatusIcon() {
312+
if (xSemaphoreTake(lvglMutex, pdMS_TO_TICKS(50)) == pdTRUE) {
313+
if (ble_pairing_flash_timer != NULL) {
314+
lv_timer_del(ble_pairing_flash_timer);
315+
ble_pairing_flash_timer = NULL;
316+
}
317+
if (ble_pairing_icon != NULL) {
318+
lv_obj_remove_flag(ble_pairing_icon, LV_OBJ_FLAG_HIDDEN);
319+
}
320+
isFlashingBLEPairingIcon = false;
321+
xSemaphoreGive(lvglMutex);
322+
}
323+
}
324+
325+
void hideBLEStatusIcon() {
312326
if (xSemaphoreTake(lvglMutex, pdMS_TO_TICKS(50)) == pdTRUE) {
313327
if (ble_pairing_flash_timer != NULL) {
314328
lv_timer_del(ble_pairing_flash_timer);
@@ -322,6 +336,10 @@ void stopBLEPairingIconFlash() {
322336
}
323337
}
324338

339+
void stopBLEPairingIconFlash() {
340+
hideBLEStatusIcon();
341+
}
342+
325343
// Update the climb rate indicator
326344
void updateClimbRateIndicator(float climbRate) {
327345
// Clamp climb rate to displayable range (-0.6 to +0.6 m/s)

src/sp140/main.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -932,7 +932,6 @@ void buttonHandlerTask(void *parameter) {
932932
currentHoldTime >= BLE_PAIRING_HOLD_MS && !pairingHoldHandled) {
933933
enterBLEPairingMode();
934934
pulseVibeMotor();
935-
startBLEPairingIconFlash();
936935
USBSerial.println("[BLE] Pairing mode activated via button hold");
937936
pairingHoldHandled = true;
938937
}

0 commit comments

Comments
 (0)