Skip to content

Commit 71daefa

Browse files
authored
Merge pull request #107 from openppg/ble-improvements
Refactor BLE pairing, whitelist, and advertising
2 parents 9998313 + afd5b26 commit 71daefa

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)