From fe13fc96a901c6d10ab15375f5ecac518ff2c569 Mon Sep 17 00:00:00 2001 From: pelgraine <140762863+pelgraine@users.noreply.github.com> Date: Mon, 7 Sep 2026 06:43:57 +1000 Subject: [PATCH] BLE: recover deviceConnected when auth completes before GATT connect --- examples/companion_radio/MyMesh.h | 2 +- src/helpers/esp32/SerialBLEInterface.cpp | 34 +++++++++++++++++++++++- src/helpers/esp32/SerialBLEInterface.h | 2 ++ variants/lilygo_tdeck_max/platformio.ini | 4 ++- 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index e1694002..39c17e2d 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -8,7 +8,7 @@ #define FIRMWARE_VER_CODE 11 #ifndef FIRMWARE_BUILD_DATE -#define FIRMWARE_BUILD_DATE "12 August 2026" +#define FIRMWARE_BUILD_DATE "7 Sept 2026" #endif #ifndef FIRMWARE_VERSION diff --git a/src/helpers/esp32/SerialBLEInterface.cpp b/src/helpers/esp32/SerialBLEInterface.cpp index b21d9ec6..696a0ed0 100644 --- a/src/helpers/esp32/SerialBLEInterface.cpp +++ b/src/helpers/esp32/SerialBLEInterface.cpp @@ -46,7 +46,14 @@ void SerialBLEInterface::_realBegin() { BLESecurity sec; sec.setStaticPIN(_pin_code); +#ifdef MECK_BLE_JUST_WORKS + // Garmin Connect IQ has no way to enter a PIN, so a watch can only reach an + // encrypted-but-unverified ("just works") link. Drop the MITM requirement so + // that link is accepted. The static PIN above is then never asked for. + sec.setAuthenticationMode(ESP_LE_AUTH_REQ_SC_BOND); +#else sec.setAuthenticationMode(ESP_LE_AUTH_REQ_SC_MITM_BOND); +#endif //BLEDevice::setPower(ESP_PWR_LVL_N8); @@ -59,11 +66,19 @@ void SerialBLEInterface::_realBegin() { // Create a BLE Characteristic pTxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); +#ifdef MECK_BLE_JUST_WORKS + pTxCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED); +#else pTxCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENC_MITM); +#endif pTxCharacteristic->addDescriptor(new BLE2902()); BLECharacteristic * pRxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE); +#ifdef MECK_BLE_JUST_WORKS + pRxCharacteristic->setAccessPermissions(ESP_GATT_PERM_WRITE_ENCRYPTED); +#else pRxCharacteristic->setAccessPermissions(ESP_GATT_PERM_WRITE_ENC_MITM); +#endif pRxCharacteristic->setCallbacks(this); pServer->getAdvertising()->addServiceUUID(SERVICE_UUID); @@ -94,6 +109,13 @@ void SerialBLEInterface::onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl) { if (cmpl.success) { BLE_DEBUG_PRINTLN(" - SecurityCallback - Authentication Success"); deviceConnected = true; + _auth_ok = true; + + // Take the peer address from the event itself. A peer that pairs at + // connect time (e.g. a bonded Garmin watch) reaches this callback before + // onConnect() has stored _remote_bda, leaving it zeroed and making the + // three requests below fail with "Invalid connection remote_bda". + memcpy(_remote_bda, cmpl.bd_addr, 6); // Request fast connection interval (15ms) for faster contact sync. // Phone may negotiate higher, but most modern phones accept 15ms. @@ -118,6 +140,7 @@ void SerialBLEInterface::onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl) { BLE_DEBUG_PRINTLN(" - Requested 2M PHY and DLE (251 bytes)"); } else { BLE_DEBUG_PRINTLN(" - SecurityCallback - Authentication Failure*"); + _auth_ok = false; //pServer->removePeerDevice(pServer->getConnId(), true); pServer->disconnect(pServer->getConnId()); @@ -142,6 +165,7 @@ void SerialBLEInterface::onMtuChanged(BLEServer* pServer, esp_ble_gatts_cb_param void SerialBLEInterface::onDisconnect(BLEServer* pServer) { BLE_DEBUG_PRINTLN("onDisconnect()"); + _auth_ok = false; if (_isEnabled) { adv_restart_time = millis() + ADVERT_RESTART_DELAY; @@ -267,7 +291,15 @@ size_t SerialBLEInterface::checkRecvFrame(uint8_t dest[]) { return len; } - if (pServer->getConnectedCount() == 0) deviceConnected = false; + if (pServer->getConnectedCount() == 0) { + deviceConnected = false; + } else if (_auth_ok) { + // Authentication can complete before the GATT connect event (a bonded + // peer, e.g. a Garmin watch, encrypts immediately). The connected count + // is still 0 then, so the line above resets deviceConnected; restore it + // once the connect event has landed. + deviceConnected = true; + } if (deviceConnected != oldDeviceConnected) { if (!deviceConnected) { // disconnecting diff --git a/src/helpers/esp32/SerialBLEInterface.h b/src/helpers/esp32/SerialBLEInterface.h index 48b08b07..7eaec1ae 100644 --- a/src/helpers/esp32/SerialBLEInterface.h +++ b/src/helpers/esp32/SerialBLEInterface.h @@ -14,6 +14,7 @@ class SerialBLEInterface : public BaseSerialInterface, BLESecurityCallbacks, BLE bool oldDeviceConnected; bool _isEnabled; bool _begun; // has _realBegin() run? (deferred BLE bring-up) + bool _auth_ok; // authentication completed on the current link (see checkRecvFrame) uint16_t last_conn_id; uint8_t _remote_bda[6]; // peer BDA, stored in onConnect for conn param updates uint32_t _pin_code; @@ -62,6 +63,7 @@ public: adv_restart_time = 0; _isEnabled = false; _begun = false; + _auth_ok = false; _last_write = 0; last_conn_id = 0; memset(_remote_bda, 0, 6); diff --git a/variants/lilygo_tdeck_max/platformio.ini b/variants/lilygo_tdeck_max/platformio.ini index 8c5cf17f..6f11d467 100644 --- a/variants/lilygo_tdeck_max/platformio.ini +++ b/variants/lilygo_tdeck_max/platformio.ini @@ -177,7 +177,9 @@ build_flags = -D MECK_WEB_READER=1 -D HAS_4G_MODEM=1 -D MECK_OTA_UPDATE=1 - -D FIRMWARE_VERSION='"Meck v1.12.6.MAX"' + -D BLE_DEBUG_LOGGING=1 + -D MECK_BLE_JUST_WORKS=1 + -D FIRMWARE_VERSION='"Meck v1.12.7.MAX"' build_src_filter = ${LilyGo_TDeck_Pro_Max.build_src_filter} + +