Skip to content

Commit afd5b26

Browse files
committed
Refactor BLE pairing, whitelist, and advertising
Add pairingModeTransitionActive to suppress advertising restarts during bond-clear transitions and prevent advertising while the whitelist is being modified. Replace clearWhiteList with syncWhiteListFromBonds that prunes stale whitelist entries and only adds missing bonded addresses, and ensure advertising is stopped before changing whitelist to avoid BLE_HS_EBUSY. Make startAdvertising respect the transition flag and avoid immediate restarts from onDisconnect during transitions. Implement enterBLEPairingMode to safely disconnect, stop advertising, and clear bonds (with retries), and add BLE_PAIRING_HOLD_MS plus related button-handler changes to enter pairing mode on a long hold (removing the previous multi-tier unbond/delete flow). These changes improve robustness of whitelist and pairing operations.
1 parent 9998313 commit afd5b26

2 files changed

Lines changed: 71 additions & 58 deletions

File tree

src/sp140/ble/ble_core.cpp

Lines changed: 65 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ TimerHandle_t gConnTuneTimer = nullptr;
2626
TimerHandle_t gPairingTimer = nullptr;
2727
TimerHandle_t gAdvertisingWatchdogTimer = nullptr;
2828
bool pairingModeActive = false;
29+
bool pairingModeTransitionActive = false;
2930

3031
// Store the active connection handle for conn param updates
3132
uint16_t activeConnHandle = 0;
@@ -36,18 +37,24 @@ void stopPairingModeTimer() {
3637
}
3738
}
3839

39-
void clearWhiteList() {
40-
while (NimBLEDevice::getWhiteListCount() > 0) {
41-
NimBLEDevice::whiteListRemove(NimBLEDevice::getWhiteListAddress(0));
42-
}
43-
}
44-
4540
size_t syncWhiteListFromBonds() {
46-
clearWhiteList();
41+
// Reconcile the whitelist to the current bond store. Advertising must be
42+
// stopped before calling this — the BLE controller rejects whitelist changes
43+
// while advertising with a filter (rc=524 / BLE_HS_EBUSY). Prune stale
44+
// entries first, then add any missing bonded addresses.
45+
for (size_t i = NimBLEDevice::getWhiteListCount(); i > 0; --i) {
46+
const NimBLEAddress addr = NimBLEDevice::getWhiteListAddress(i - 1);
47+
if (!NimBLEDevice::isBonded(addr)) {
48+
NimBLEDevice::whiteListRemove(addr);
49+
}
50+
}
4751

4852
const int bondCount = NimBLEDevice::getNumBonds();
4953
for (int i = 0; i < bondCount; ++i) {
50-
NimBLEDevice::whiteListAdd(NimBLEDevice::getBondedAddress(i));
54+
const NimBLEAddress addr = NimBLEDevice::getBondedAddress(i);
55+
if (!NimBLEDevice::onWhiteList(addr)) {
56+
NimBLEDevice::whiteListAdd(addr);
57+
}
5158
}
5259

5360
return NimBLEDevice::getWhiteListCount();
@@ -77,21 +84,26 @@ void applyPreferredLinkParams(TimerHandle_t timer) {
7784
}
7885

7986
bool shouldAdvertiseWhilePowered() {
80-
return pairingModeActive || NimBLEDevice::getNumBonds() > 0;
87+
return !pairingModeTransitionActive &&
88+
(pairingModeActive || NimBLEDevice::getNumBonds() > 0);
8189
}
8290

8391
bool startAdvertising(NimBLEServer *server) {
84-
if (server == nullptr) {
92+
if (server == nullptr || pairingModeTransitionActive) {
8593
return false;
8694
}
8795

8896
const size_t bondCount = static_cast<size_t>(NimBLEDevice::getNumBonds());
8997
const bool allowOpenAdvertising = pairingModeActive;
98+
99+
// Stop advertising BEFORE modifying the whitelist — the BLE controller
100+
// rejects whitelist changes while advertising with a filter (BLE_HS_EBUSY).
101+
auto *advertising = server->getAdvertising();
102+
advertising->stop();
103+
90104
const size_t whiteListCount = syncWhiteListFromBonds();
91105

92106
if (!allowOpenAdvertising && bondCount == 0) {
93-
auto *advertising = server->getAdvertising();
94-
advertising->stop();
95107
USBSerial.println(
96108
"[BLE] No bonds present and pairing mode inactive; advertising stopped");
97109
return false;
@@ -132,8 +144,6 @@ bool startAdvertising(NimBLEServer *server) {
132144
static_cast<uint8_t>(allowOpenAdvertising ? 0x01 : 0x00)};
133145
scanRsp.setManufacturerData(mfrData, sizeof(mfrData));
134146

135-
auto *advertising = server->getAdvertising();
136-
advertising->stop();
137147
advertising->removeAll();
138148
const bool configured = advertising->setInstanceData(kExtAdvInstance, adv);
139149
const bool scanRspConfigured =
@@ -148,11 +158,6 @@ bool startAdvertising(NimBLEServer *server) {
148158
static_cast<unsigned>(bondCount), static_cast<unsigned>(whiteListCount));
149159
return started;
150160
#else
151-
auto *advertising = server->getAdvertising();
152-
153-
// Stop before reconfiguring (safe even if not running)
154-
advertising->stop();
155-
156161
// Configure payload once — NimBLE accumulates addServiceUUID calls
157162
static bool payloadConfigured = false;
158163
if (!payloadConfigured) {
@@ -252,9 +257,11 @@ class BleServerConnectionCallbacks : public NimBLEServerCallbacks {
252257

253258
USBSerial.printf("Device disconnected reason=%d\n", reason);
254259

255-
// Restart immediately, then let the watchdog keep retrying forever if this
256-
// first post-disconnect start does not stick.
257-
startAdvertising(server);
260+
// Suppress the immediate advertising restart during a pairing transition —
261+
// enterBLEPairingMode() issues its own startAdvertising after clearing bonds.
262+
if (!pairingModeTransitionActive) {
263+
startAdvertising(server);
264+
}
258265
}
259266

260267
void onAuthenticationComplete(NimBLEConnInfo &connInfo) override {
@@ -380,7 +387,43 @@ void restartBLEAdvertising() {
380387
}
381388

382389
void enterBLEPairingMode() {
390+
// Block advertising restarts (e.g. from onDisconnect) during this transition.
391+
pairingModeTransitionActive = true;
392+
393+
// Single-bond model: disconnect the current peer so we can safely clear bonds.
394+
if (deviceConnected && pServer != nullptr &&
395+
connectedHandle != BLE_HS_CONN_HANDLE_NONE) {
396+
pServer->disconnect(connectedHandle);
397+
vTaskDelay(pdMS_TO_TICKS(100));
398+
}
399+
400+
// Stop advertising before modifying bonds — NimBLE rejects bond deletion
401+
// while the controller is advertising (BLE_HS_EBUSY).
402+
if (pServer != nullptr) {
403+
auto *adv = pServer->getAdvertising();
404+
if (adv != nullptr) {
405+
adv->stop();
406+
}
407+
}
408+
vTaskDelay(pdMS_TO_TICKS(50));
409+
410+
const int bondCount = NimBLEDevice::getNumBonds();
411+
bool cleared = false;
412+
if (bondCount > 0) {
413+
cleared = NimBLEDevice::deleteAllBonds();
414+
if (!cleared) {
415+
for (int i = NimBLEDevice::getNumBonds() - 1; i >= 0; --i) {
416+
NimBLEDevice::deleteBond(NimBLEDevice::getBondedAddress(i));
417+
}
418+
cleared = NimBLEDevice::getNumBonds() == 0;
419+
}
420+
}
421+
USBSerial.printf("[BLE] Cleared bonds: %s (was %d, now %d)\n",
422+
cleared ? "OK" : (bondCount == 0 ? "NONE" : "FAILED"),
423+
bondCount, NimBLEDevice::getNumBonds());
424+
383425
pairingModeActive = true;
426+
pairingModeTransitionActive = false;
384427

385428
if (gPairingTimer == nullptr) {
386429
gPairingTimer = xTimerCreate("blePair", kPairingTimeoutTicks, pdFALSE,

src/sp140/main.cpp

Lines changed: 6 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ int8_t bmsCS = MCP_CS;
7474
#define SECOND_HOLD_TIME_MS 2000 // How long to hold on second press to arm
7575
#define CRUISE_HOLD_TIME_MS 2000
7676
#define BUTTON_SEQUENCE_TIMEOUT_MS 1500 // Time window for arm/disarm sequence
77-
#define PERFORMANCE_MODE_HOLD_MS 3000 // Longer hold time for performance mode
77+
#define PERFORMANCE_MODE_HOLD_MS 3000 // Longer hold time for performance mode
78+
#define BLE_PAIRING_HOLD_MS 10000 // Hold duration to enter BLE pairing mode
7879

7980
// Throttle control constants moved to inc/sp140/throttle.h
8081
#define CRUISE_MAX_PERCENTAGE \
@@ -851,7 +852,6 @@ void buttonHandlerTask(void *parameter) {
851852
uint32_t lastDebounceTime = 0;
852853
bool lastButtonState = HIGH;
853854
bool buttonState;
854-
bool unbondHoldHandled = false;
855855
bool pairingHoldHandled = false;
856856

857857
while (true) {
@@ -865,7 +865,6 @@ void buttonHandlerTask(void *parameter) {
865865

866866
if (buttonState == LOW) { // Button pressed
867867
buttonPressed = true;
868-
unbondHoldHandled = false;
869868
pairingHoldHandled = false;
870869
buttonPressStartTime = currentTime;
871870
USBSerial.println("Button pressed");
@@ -883,7 +882,7 @@ void buttonHandlerTask(void *parameter) {
883882
// Disarmed long hold toggles performance mode on release.
884883
if (currentState == DISARMED &&
885884
holdDuration >= PERFORMANCE_MODE_HOLD_MS &&
886-
holdDuration < 10000 && !unbondHoldHandled &&
885+
holdDuration < BLE_PAIRING_HOLD_MS &&
887886
!pairingHoldHandled) {
888887
perfModeSwitch();
889888
lastButtonState = buttonState;
@@ -928,38 +927,9 @@ void buttonHandlerTask(void *parameter) {
928927
} else if (buttonPressed) { // Only handle other button actions if we're not in an arm sequence
929928
uint32_t currentHoldTime = currentTime - buttonPressStartTime;
930929

931-
// Tiered long hold while disarmed:
932-
// 10s = enter BLE pairing mode (single vibration)
933-
// 20s = delete all bonds (double vibration + reboot)
934-
if (currentState == DISARMED && currentHoldTime >= 20000 &&
935-
!unbondHoldHandled) {
936-
// Tier 2: Delete all bonds
937-
const bool deleted = NimBLEDevice::deleteAllBonds();
938-
USBSerial.printf("[BLE] Delete all bonds: %s\n",
939-
deleted ? "OK" : "FAILED");
940-
if (deviceConnected && pServer != nullptr &&
941-
connectedHandle != BLE_HS_CONN_HANDLE_NONE) {
942-
pServer->disconnect(connectedHandle);
943-
vTaskDelay(pdMS_TO_TICKS(40));
944-
}
945-
pulseVibeMotor();
946-
vTaskDelay(pdMS_TO_TICKS(300));
947-
pulseVibeMotor();
948-
USBSerial.println("[BLE] Bonds cleared. Rebooting with BLE locked "
949-
"until pairing mode is reopened...");
950-
vTaskDelay(pdMS_TO_TICKS(500));
951-
diagnosticsMarkPlannedRestart(
952-
PlannedRestartReason::BLE_UNBOND_REBOOT);
953-
ESP.restart();
954-
unbondHoldHandled = true;
955-
buttonPressed = false;
956-
buttonPressStartTime = currentTime;
957-
continue;
958-
}
959-
960-
if (currentState == DISARMED && currentHoldTime >= 10000 &&
961-
!pairingHoldHandled) {
962-
// Tier 1: Enter pairing mode (open advertising for 60s)
930+
// Long hold while disarmed: enter BLE pairing mode (clears bonds).
931+
if (currentState == DISARMED &&
932+
currentHoldTime >= BLE_PAIRING_HOLD_MS && !pairingHoldHandled) {
963933
enterBLEPairingMode();
964934
pulseVibeMotor();
965935
USBSerial.println("[BLE] Pairing mode activated via button hold");

0 commit comments

Comments
 (0)