From 4a0612f9c8f90ea8da4f803f80b75e1378613448 Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Mon, 3 Nov 2025 16:05:40 -0300 Subject: [PATCH 1/3] fix(nimble): Get descriptors on demand --- libraries/BLE/src/BLERemoteCharacteristic.cpp | 25 ++++++++++++++++--- libraries/BLE/src/BLERemoteCharacteristic.h | 1 - 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/libraries/BLE/src/BLERemoteCharacteristic.cpp b/libraries/BLE/src/BLERemoteCharacteristic.cpp index e964e679897..41812b5040d 100644 --- a/libraries/BLE/src/BLERemoteCharacteristic.cpp +++ b/libraries/BLE/src/BLERemoteCharacteristic.cpp @@ -112,6 +112,11 @@ bool BLERemoteCharacteristic::canWriteNoResponse() { * @brief Retrieve the map of descriptors keyed by UUID. */ std::map *BLERemoteCharacteristic::getDescriptors() { + // Retrieve descriptors if not already done (lazy loading) + if (m_descriptorMap.empty()) { + log_d("Descriptors not yet retrieved, retrieving now..."); + retrieveDescriptors(); + } return &m_descriptorMap; } // getDescriptors @@ -132,6 +137,11 @@ uint16_t BLERemoteCharacteristic::getHandle() { */ BLERemoteDescriptor *BLERemoteCharacteristic::getDescriptor(BLEUUID uuid) { log_v(">> getDescriptor: uuid: %s", uuid.toString().c_str()); + // Retrieve descriptors if not already done (lazy loading) + if (m_descriptorMap.empty()) { + log_d("Descriptors not yet retrieved, retrieving now..."); + retrieveDescriptors(); + } std::string v = uuid.toString().c_str(); for (auto &myPair : m_descriptorMap) { if (myPair.first == v) { @@ -663,14 +673,14 @@ BLERemoteCharacteristic::BLERemoteCharacteristic(BLERemoteService *pRemoteServic m_handle = chr->val_handle; m_defHandle = chr->def_handle; - m_endHandle = 0; m_charProp = chr->properties; m_pRemoteService = pRemoteService; m_notifyCallback = nullptr; m_rawData = nullptr; m_auth = 0; - retrieveDescriptors(); // Get the descriptors for this characteristic + // Don't retrieve descriptors in constructor for NimBLE to avoid deadlock + // Descriptors will be retrieved on-demand when needed (e.g., for notifications) log_v("<< BLERemoteCharacteristic(): %s", m_uuid.toString().c_str()); } // BLERemoteCharacteristic @@ -789,7 +799,7 @@ bool BLERemoteCharacteristic::retrieveDescriptors(const BLEUUID *uuid_filter) { desc_filter_t filter = {uuid_filter, &taskData}; int rc = 0; - rc = ble_gattc_disc_all_dscs(getRemoteService()->getClient()->getConnId(), m_handle, m_endHandle, BLERemoteCharacteristic::descriptorDiscCB, &filter); + rc = ble_gattc_disc_all_dscs(getRemoteService()->getClient()->getConnId(), m_handle, getRemoteService()->getEndHandle(), BLERemoteCharacteristic::descriptorDiscCB, &filter); if (rc != 0) { log_e("ble_gattc_disc_all_dscs: rc=%d %s", rc, BLEUtils::returnCodeToString(rc)); @@ -966,6 +976,15 @@ bool BLERemoteCharacteristic::setNotify(uint16_t val, notify_callback notifyCall m_notifyCallback = notifyCallback; + // Retrieve descriptors if not already done (lazy loading) + if (m_descriptorMap.empty()) { + log_d("Descriptors not yet retrieved, retrieving now..."); + if (!retrieveDescriptors()) { + log_e("<< setNotify(): Failed to retrieve descriptors"); + return false; + } + } + BLERemoteDescriptor *desc = getDescriptor(BLEUUID((uint16_t)0x2902)); if (desc == nullptr) { log_w("<< setNotify(): Callback set, CCCD not found"); diff --git a/libraries/BLE/src/BLERemoteCharacteristic.h b/libraries/BLE/src/BLERemoteCharacteristic.h index 5191087d11c..fb267f55bf1 100644 --- a/libraries/BLE/src/BLERemoteCharacteristic.h +++ b/libraries/BLE/src/BLERemoteCharacteristic.h @@ -159,7 +159,6 @@ class BLERemoteCharacteristic { #if defined(CONFIG_NIMBLE_ENABLED) uint16_t m_defHandle; - uint16_t m_endHandle; #endif /*************************************************************************** From d0d84b795a9c822251c774b15aa11b34d58d453e Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Mon, 3 Nov 2025 16:05:57 -0300 Subject: [PATCH 2/3] feat(ble): Add client multiconnect example --- .../Client_multiconnect.ino | 277 ++++++++++++++++++ .../BLE/examples/Client_multiconnect/ci.yml | 6 + libraries/BLE/src/BLERemoteCharacteristic.cpp | 12 +- libraries/BLE/src/BLERemoteCharacteristic.h | 1 + 4 files changed, 293 insertions(+), 3 deletions(-) create mode 100644 libraries/BLE/examples/Client_multiconnect/Client_multiconnect.ino create mode 100644 libraries/BLE/examples/Client_multiconnect/ci.yml diff --git a/libraries/BLE/examples/Client_multiconnect/Client_multiconnect.ino b/libraries/BLE/examples/Client_multiconnect/Client_multiconnect.ino new file mode 100644 index 00000000000..ae66ac98b32 --- /dev/null +++ b/libraries/BLE/examples/Client_multiconnect/Client_multiconnect.ino @@ -0,0 +1,277 @@ +/** + * A BLE client example that connects to multiple BLE servers simultaneously. + * + * This example demonstrates how to: + * - Scan for multiple BLE servers + * - Connect to multiple servers at the same time + * - Interact with characteristics on different servers + * - Handle disconnections and reconnections + * + * The example looks for servers advertising the service UUID: 4fafc201-1fb5-459e-8fcc-c5c9c331914b + * and connects to up to MAX_SERVERS servers. + * + * Created by lucasssvaz + * Based on the original Client example by Neil Kolban and chegewara + */ + +#include "BLEDevice.h" + +// The remote service we wish to connect to. +static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("beb5483e-36e1-4688-b7f5-ea07361b26a8"); + +// Maximum number of servers to connect to +#define MAX_SERVERS 3 + +// Structure to hold information about each connected server +struct ServerConnection { + BLEClient *pClient; + BLEAdvertisedDevice *pDevice; + BLERemoteCharacteristic *pRemoteCharacteristic; + bool connected; + bool doConnect; + String name; +}; + +// Array to manage multiple server connections +ServerConnection servers[MAX_SERVERS]; +int connectedServers = 0; +static bool doScan = true; + +// Callback function to handle notifications from any server +static void notifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) { + // Find which server this notification came from + for (int i = 0; i < MAX_SERVERS; i++) { + if (servers[i].connected && servers[i].pRemoteCharacteristic == pBLERemoteCharacteristic) { + Serial.print("Notify from server "); + Serial.print(servers[i].name); + Serial.print(" - Characteristic: "); + Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str()); + Serial.print(" | Length: "); + Serial.print(length); + Serial.print(" | Data: "); + Serial.write(pData, length); + Serial.println(); + break; + } + } +} + +// Client callback class to handle connect/disconnect events +class MyClientCallback : public BLEClientCallbacks { + int serverIndex; + +public: + MyClientCallback(int index) : serverIndex(index) {} + + void onConnect(BLEClient *pclient) { + Serial.print("Connected to server "); + Serial.println(servers[serverIndex].name); + } + + void onDisconnect(BLEClient *pclient) { + servers[serverIndex].connected = false; + connectedServers--; + Serial.print("Disconnected from server "); + Serial.print(servers[serverIndex].name); + Serial.print(" | Total connected: "); + Serial.println(connectedServers); + doScan = true; // Resume scanning to find replacement servers + } +}; + +// Function to connect to a specific server +bool connectToServer(int serverIndex) { + Serial.print("Connecting to server "); + Serial.print(serverIndex); + Serial.print(" at address: "); + Serial.println(servers[serverIndex].pDevice->getAddress().toString().c_str()); + + servers[serverIndex].pClient = BLEDevice::createClient(); + Serial.println(" - Created client"); + + // Set the callback for this specific server connection + servers[serverIndex].pClient->setClientCallbacks(new MyClientCallback(serverIndex)); + + // Connect to the remote BLE Server + servers[serverIndex].pClient->connect(servers[serverIndex].pDevice); + Serial.println(" - Connected to server"); + servers[serverIndex].pClient->setMTU(517); // Request maximum MTU from server + + // Obtain a reference to the service we are after in the remote BLE server + BLERemoteService *pRemoteService = servers[serverIndex].pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + Serial.print("Failed to find service UUID: "); + Serial.println(serviceUUID.toString().c_str()); + servers[serverIndex].pClient->disconnect(); + return false; + } + Serial.println(" - Found service"); + + // Obtain a reference to the characteristic in the service + servers[serverIndex].pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (servers[serverIndex].pRemoteCharacteristic == nullptr) { + Serial.print("Failed to find characteristic UUID: "); + Serial.println(charUUID.toString().c_str()); + servers[serverIndex].pClient->disconnect(); + return false; + } + Serial.println(" - Found characteristic"); + + // Read the value of the characteristic + if (servers[serverIndex].pRemoteCharacteristic->canRead()) { + String value = servers[serverIndex].pRemoteCharacteristic->readValue(); + Serial.print("Initial characteristic value: "); + Serial.println(value.c_str()); + } + + // Register for notifications if available + if (servers[serverIndex].pRemoteCharacteristic->canNotify()) { + servers[serverIndex].pRemoteCharacteristic->registerForNotify(notifyCallback); + Serial.println(" - Registered for notifications"); + } + + servers[serverIndex].connected = true; + connectedServers++; + Serial.print("Successfully connected! Total servers connected: "); + Serial.println(connectedServers); + return true; +} + +// Scan callback class to find BLE servers +class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks { + void onResult(BLEAdvertisedDevice advertisedDevice) { + Serial.print("BLE Device found: "); + Serial.println(advertisedDevice.toString().c_str()); + + // Check if this device has the service we're looking for + if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) { + Serial.println(" -> This device has our service!"); + + // Check if we already know about this device + String deviceAddress = advertisedDevice.getAddress().toString().c_str(); + bool alreadyKnown = false; + + for (int i = 0; i < MAX_SERVERS; i++) { + if (servers[i].pDevice != nullptr) { + if (servers[i].pDevice->getAddress().toString() == deviceAddress) { + alreadyKnown = true; + break; + } + } + } + + if (alreadyKnown) { + Serial.println(" -> Already connected or connecting to this device"); + return; + } + + // Find an empty slot for this server + for (int i = 0; i < MAX_SERVERS; i++) { + if (servers[i].pDevice == nullptr || (!servers[i].connected && !servers[i].doConnect)) { + servers[i].pDevice = new BLEAdvertisedDevice(advertisedDevice); + servers[i].doConnect = true; + servers[i].name = "Server_" + String(i); + Serial.print(" -> Assigned to slot "); + Serial.println(i); + + // If we've found enough servers, stop scanning + int pendingConnections = 0; + for (int j = 0; j < MAX_SERVERS; j++) { + if (servers[j].connected || servers[j].doConnect) { + pendingConnections++; + } + } + if (pendingConnections >= MAX_SERVERS) { + Serial.println("Found enough servers, stopping scan"); + BLEDevice::getScan()->stop(); + doScan = false; + } + break; + } + } + } + } +}; + +void setup() { + Serial.begin(115200); + Serial.println("================================="); + Serial.println("BLE Multi-Client Example"); + Serial.println("================================="); + Serial.print("Max servers to connect: "); + Serial.println(MAX_SERVERS); + Serial.println(); + + // Initialize all server connections + for (int i = 0; i < MAX_SERVERS; i++) { + servers[i].pClient = nullptr; + servers[i].pDevice = nullptr; + servers[i].pRemoteCharacteristic = nullptr; + servers[i].connected = false; + servers[i].doConnect = false; + servers[i].name = ""; + } + + // Initialize BLE + BLEDevice::init("ESP32_MultiClient"); + + // Set up BLE scanner + BLEScan *pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setInterval(1349); + pBLEScan->setWindow(449); + pBLEScan->setActiveScan(true); + pBLEScan->start(5, false); + + Serial.println("Scanning for BLE servers..."); +} + +void loop() { + // Process any pending connections + for (int i = 0; i < MAX_SERVERS; i++) { + if (servers[i].doConnect) { + if (connectToServer(i)) { + Serial.println("Connection successful"); + } else { + Serial.println("Connection failed"); + // Clear this slot so we can try another server + delete servers[i].pDevice; + servers[i].pDevice = nullptr; + } + servers[i].doConnect = false; + } + } + + // If we're connected to servers, send data to each one + if (connectedServers > 0) { + for (int i = 0; i < MAX_SERVERS; i++) { + if (servers[i].connected && servers[i].pRemoteCharacteristic != nullptr) { + // Create a unique message for each server + String newValue = servers[i].name + " | Time: " + String(millis() / 1000); + + Serial.print("Sending to "); + Serial.print(servers[i].name); + Serial.print(": "); + Serial.println(newValue); + + // Write the value to the characteristic + servers[i].pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length()); + } + } + } else { + Serial.println("No servers connected"); + } + + // Resume scanning if we have room for more connections + if (doScan && connectedServers < MAX_SERVERS) { + Serial.println("Resuming scan for more servers..."); + BLEDevice::getScan()->start(5, false); + doScan = false; + delay(5000); // Wait for scan to complete + } + + delay(2000); // Delay between loop iterations +} + diff --git a/libraries/BLE/examples/Client_multiconnect/ci.yml b/libraries/BLE/examples/Client_multiconnect/ci.yml new file mode 100644 index 00000000000..c59d5b9dc36 --- /dev/null +++ b/libraries/BLE/examples/Client_multiconnect/ci.yml @@ -0,0 +1,6 @@ +fqbn_append: PartitionScheme=huge_app + +requires_any: + - CONFIG_SOC_BLE_SUPPORTED=y + - CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y + diff --git a/libraries/BLE/src/BLERemoteCharacteristic.cpp b/libraries/BLE/src/BLERemoteCharacteristic.cpp index 41812b5040d..412b06ba19c 100644 --- a/libraries/BLE/src/BLERemoteCharacteristic.cpp +++ b/libraries/BLE/src/BLERemoteCharacteristic.cpp @@ -113,7 +113,7 @@ bool BLERemoteCharacteristic::canWriteNoResponse() { */ std::map *BLERemoteCharacteristic::getDescriptors() { // Retrieve descriptors if not already done (lazy loading) - if (m_descriptorMap.empty()) { + if (!m_descriptorsRetrieved) { log_d("Descriptors not yet retrieved, retrieving now..."); retrieveDescriptors(); } @@ -138,7 +138,7 @@ uint16_t BLERemoteCharacteristic::getHandle() { BLERemoteDescriptor *BLERemoteCharacteristic::getDescriptor(BLEUUID uuid) { log_v(">> getDescriptor: uuid: %s", uuid.toString().c_str()); // Retrieve descriptors if not already done (lazy loading) - if (m_descriptorMap.empty()) { + if (!m_descriptorsRetrieved) { log_d("Descriptors not yet retrieved, retrieving now..."); retrieveDescriptors(); } @@ -297,6 +297,7 @@ void BLERemoteCharacteristic::removeDescriptors() { delete myPair.second; } m_descriptorMap.clear(); + m_descriptorsRetrieved = false; // Allow descriptors to be retrieved again } // removeCharacteristics /** @@ -376,6 +377,7 @@ BLERemoteCharacteristic::BLERemoteCharacteristic(uint16_t handle, BLEUUID uuid, m_notifyCallback = nullptr; m_rawData = nullptr; m_auth = ESP_GATT_AUTH_REQ_NONE; + m_descriptorsRetrieved = false; retrieveDescriptors(); // Get the descriptors for this characteristic log_v("<< BLERemoteCharacteristic"); @@ -559,6 +561,7 @@ void BLERemoteCharacteristic::retrieveDescriptors() { offset++; } // while true //m_haveCharacteristics = true; // Remember that we have received the characteristics. + m_descriptorsRetrieved = true; log_v("<< retrieveDescriptors(): Found %d descriptors.", offset); } // getDescriptors @@ -678,6 +681,7 @@ BLERemoteCharacteristic::BLERemoteCharacteristic(BLERemoteService *pRemoteServic m_notifyCallback = nullptr; m_rawData = nullptr; m_auth = 0; + m_descriptorsRetrieved = false; // Don't retrieve descriptors in constructor for NimBLE to avoid deadlock // Descriptors will be retrieved on-demand when needed (e.g., for notifications) @@ -791,6 +795,7 @@ bool BLERemoteCharacteristic::retrieveDescriptors(const BLEUUID *uuid_filter) { // If this is the last handle then there are no descriptors if (m_handle == getRemoteService()->getEndHandle()) { + m_descriptorsRetrieved = true; log_d("<< retrieveDescriptors(): No descriptors found"); return true; } @@ -816,6 +821,7 @@ bool BLERemoteCharacteristic::retrieveDescriptors(const BLEUUID *uuid_filter) { return false; } + m_descriptorsRetrieved = true; log_d("<< retrieveDescriptors(): Found %d descriptors.", m_descriptorMap.size() - prevDscCount); return true; } // retrieveDescriptors @@ -977,7 +983,7 @@ bool BLERemoteCharacteristic::setNotify(uint16_t val, notify_callback notifyCall m_notifyCallback = notifyCallback; // Retrieve descriptors if not already done (lazy loading) - if (m_descriptorMap.empty()) { + if (!m_descriptorsRetrieved) { log_d("Descriptors not yet retrieved, retrieving now..."); if (!retrieveDescriptors()) { log_e("<< setNotify(): Failed to retrieve descriptors"); diff --git a/libraries/BLE/src/BLERemoteCharacteristic.h b/libraries/BLE/src/BLERemoteCharacteristic.h index fb267f55bf1..7b04a15c7cb 100644 --- a/libraries/BLE/src/BLERemoteCharacteristic.h +++ b/libraries/BLE/src/BLERemoteCharacteristic.h @@ -152,6 +152,7 @@ class BLERemoteCharacteristic { // We maintain a map of descriptors owned by this characteristic keyed by a string representation of the UUID. std::map m_descriptorMap; + bool m_descriptorsRetrieved; // Flag to track if descriptor retrieval has been attempted /*************************************************************************** * NimBLE private properties * From b062cc8d4e24ec24c440ec329af2c53ec9d0bc21 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Wed, 5 Nov 2025 08:45:13 +0000 Subject: [PATCH 3/3] ci(pre-commit): Apply automatic fixes --- .../BLE/examples/Client_multiconnect/Client_multiconnect.ino | 1 - libraries/BLE/examples/Client_multiconnect/ci.yml | 1 - libraries/BLE/src/BLERemoteCharacteristic.cpp | 4 +++- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/BLE/examples/Client_multiconnect/Client_multiconnect.ino b/libraries/BLE/examples/Client_multiconnect/Client_multiconnect.ino index ae66ac98b32..712e5bbffe2 100644 --- a/libraries/BLE/examples/Client_multiconnect/Client_multiconnect.ino +++ b/libraries/BLE/examples/Client_multiconnect/Client_multiconnect.ino @@ -274,4 +274,3 @@ void loop() { delay(2000); // Delay between loop iterations } - diff --git a/libraries/BLE/examples/Client_multiconnect/ci.yml b/libraries/BLE/examples/Client_multiconnect/ci.yml index c59d5b9dc36..cfee8c8935f 100644 --- a/libraries/BLE/examples/Client_multiconnect/ci.yml +++ b/libraries/BLE/examples/Client_multiconnect/ci.yml @@ -3,4 +3,3 @@ fqbn_append: PartitionScheme=huge_app requires_any: - CONFIG_SOC_BLE_SUPPORTED=y - CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y - diff --git a/libraries/BLE/src/BLERemoteCharacteristic.cpp b/libraries/BLE/src/BLERemoteCharacteristic.cpp index 412b06ba19c..70a91288ab2 100644 --- a/libraries/BLE/src/BLERemoteCharacteristic.cpp +++ b/libraries/BLE/src/BLERemoteCharacteristic.cpp @@ -804,7 +804,9 @@ bool BLERemoteCharacteristic::retrieveDescriptors(const BLEUUID *uuid_filter) { desc_filter_t filter = {uuid_filter, &taskData}; int rc = 0; - rc = ble_gattc_disc_all_dscs(getRemoteService()->getClient()->getConnId(), m_handle, getRemoteService()->getEndHandle(), BLERemoteCharacteristic::descriptorDiscCB, &filter); + rc = ble_gattc_disc_all_dscs( + getRemoteService()->getClient()->getConnId(), m_handle, getRemoteService()->getEndHandle(), BLERemoteCharacteristic::descriptorDiscCB, &filter + ); if (rc != 0) { log_e("ble_gattc_disc_all_dscs: rc=%d %s", rc, BLEUtils::returnCodeToString(rc));