Skip to content

Commit 971c6ca

Browse files
fix(ble): Fix descriptor loading and add client multiconnect example (#11978)
* fix(nimble): Get descriptors on demand * feat(ble): Add client multiconnect example * ci(pre-commit): Apply automatic fixes --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
1 parent a8c74c4 commit 971c6ca

File tree

4 files changed

+312
-4
lines changed

4 files changed

+312
-4
lines changed
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
/**
2+
* A BLE client example that connects to multiple BLE servers simultaneously.
3+
*
4+
* This example demonstrates how to:
5+
* - Scan for multiple BLE servers
6+
* - Connect to multiple servers at the same time
7+
* - Interact with characteristics on different servers
8+
* - Handle disconnections and reconnections
9+
*
10+
* The example looks for servers advertising the service UUID: 4fafc201-1fb5-459e-8fcc-c5c9c331914b
11+
* and connects to up to MAX_SERVERS servers.
12+
*
13+
* Created by lucasssvaz
14+
* Based on the original Client example by Neil Kolban and chegewara
15+
*/
16+
17+
#include "BLEDevice.h"
18+
19+
// The remote service we wish to connect to.
20+
static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b");
21+
// The characteristic of the remote service we are interested in.
22+
static BLEUUID charUUID("beb5483e-36e1-4688-b7f5-ea07361b26a8");
23+
24+
// Maximum number of servers to connect to
25+
#define MAX_SERVERS 3
26+
27+
// Structure to hold information about each connected server
28+
struct ServerConnection {
29+
BLEClient *pClient;
30+
BLEAdvertisedDevice *pDevice;
31+
BLERemoteCharacteristic *pRemoteCharacteristic;
32+
bool connected;
33+
bool doConnect;
34+
String name;
35+
};
36+
37+
// Array to manage multiple server connections
38+
ServerConnection servers[MAX_SERVERS];
39+
int connectedServers = 0;
40+
static bool doScan = true;
41+
42+
// Callback function to handle notifications from any server
43+
static void notifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) {
44+
// Find which server this notification came from
45+
for (int i = 0; i < MAX_SERVERS; i++) {
46+
if (servers[i].connected && servers[i].pRemoteCharacteristic == pBLERemoteCharacteristic) {
47+
Serial.print("Notify from server ");
48+
Serial.print(servers[i].name);
49+
Serial.print(" - Characteristic: ");
50+
Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
51+
Serial.print(" | Length: ");
52+
Serial.print(length);
53+
Serial.print(" | Data: ");
54+
Serial.write(pData, length);
55+
Serial.println();
56+
break;
57+
}
58+
}
59+
}
60+
61+
// Client callback class to handle connect/disconnect events
62+
class MyClientCallback : public BLEClientCallbacks {
63+
int serverIndex;
64+
65+
public:
66+
MyClientCallback(int index) : serverIndex(index) {}
67+
68+
void onConnect(BLEClient *pclient) {
69+
Serial.print("Connected to server ");
70+
Serial.println(servers[serverIndex].name);
71+
}
72+
73+
void onDisconnect(BLEClient *pclient) {
74+
servers[serverIndex].connected = false;
75+
connectedServers--;
76+
Serial.print("Disconnected from server ");
77+
Serial.print(servers[serverIndex].name);
78+
Serial.print(" | Total connected: ");
79+
Serial.println(connectedServers);
80+
doScan = true; // Resume scanning to find replacement servers
81+
}
82+
};
83+
84+
// Function to connect to a specific server
85+
bool connectToServer(int serverIndex) {
86+
Serial.print("Connecting to server ");
87+
Serial.print(serverIndex);
88+
Serial.print(" at address: ");
89+
Serial.println(servers[serverIndex].pDevice->getAddress().toString().c_str());
90+
91+
servers[serverIndex].pClient = BLEDevice::createClient();
92+
Serial.println(" - Created client");
93+
94+
// Set the callback for this specific server connection
95+
servers[serverIndex].pClient->setClientCallbacks(new MyClientCallback(serverIndex));
96+
97+
// Connect to the remote BLE Server
98+
servers[serverIndex].pClient->connect(servers[serverIndex].pDevice);
99+
Serial.println(" - Connected to server");
100+
servers[serverIndex].pClient->setMTU(517); // Request maximum MTU from server
101+
102+
// Obtain a reference to the service we are after in the remote BLE server
103+
BLERemoteService *pRemoteService = servers[serverIndex].pClient->getService(serviceUUID);
104+
if (pRemoteService == nullptr) {
105+
Serial.print("Failed to find service UUID: ");
106+
Serial.println(serviceUUID.toString().c_str());
107+
servers[serverIndex].pClient->disconnect();
108+
return false;
109+
}
110+
Serial.println(" - Found service");
111+
112+
// Obtain a reference to the characteristic in the service
113+
servers[serverIndex].pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
114+
if (servers[serverIndex].pRemoteCharacteristic == nullptr) {
115+
Serial.print("Failed to find characteristic UUID: ");
116+
Serial.println(charUUID.toString().c_str());
117+
servers[serverIndex].pClient->disconnect();
118+
return false;
119+
}
120+
Serial.println(" - Found characteristic");
121+
122+
// Read the value of the characteristic
123+
if (servers[serverIndex].pRemoteCharacteristic->canRead()) {
124+
String value = servers[serverIndex].pRemoteCharacteristic->readValue();
125+
Serial.print("Initial characteristic value: ");
126+
Serial.println(value.c_str());
127+
}
128+
129+
// Register for notifications if available
130+
if (servers[serverIndex].pRemoteCharacteristic->canNotify()) {
131+
servers[serverIndex].pRemoteCharacteristic->registerForNotify(notifyCallback);
132+
Serial.println(" - Registered for notifications");
133+
}
134+
135+
servers[serverIndex].connected = true;
136+
connectedServers++;
137+
Serial.print("Successfully connected! Total servers connected: ");
138+
Serial.println(connectedServers);
139+
return true;
140+
}
141+
142+
// Scan callback class to find BLE servers
143+
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
144+
void onResult(BLEAdvertisedDevice advertisedDevice) {
145+
Serial.print("BLE Device found: ");
146+
Serial.println(advertisedDevice.toString().c_str());
147+
148+
// Check if this device has the service we're looking for
149+
if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {
150+
Serial.println(" -> This device has our service!");
151+
152+
// Check if we already know about this device
153+
String deviceAddress = advertisedDevice.getAddress().toString().c_str();
154+
bool alreadyKnown = false;
155+
156+
for (int i = 0; i < MAX_SERVERS; i++) {
157+
if (servers[i].pDevice != nullptr) {
158+
if (servers[i].pDevice->getAddress().toString() == deviceAddress) {
159+
alreadyKnown = true;
160+
break;
161+
}
162+
}
163+
}
164+
165+
if (alreadyKnown) {
166+
Serial.println(" -> Already connected or connecting to this device");
167+
return;
168+
}
169+
170+
// Find an empty slot for this server
171+
for (int i = 0; i < MAX_SERVERS; i++) {
172+
if (servers[i].pDevice == nullptr || (!servers[i].connected && !servers[i].doConnect)) {
173+
servers[i].pDevice = new BLEAdvertisedDevice(advertisedDevice);
174+
servers[i].doConnect = true;
175+
servers[i].name = "Server_" + String(i);
176+
Serial.print(" -> Assigned to slot ");
177+
Serial.println(i);
178+
179+
// If we've found enough servers, stop scanning
180+
int pendingConnections = 0;
181+
for (int j = 0; j < MAX_SERVERS; j++) {
182+
if (servers[j].connected || servers[j].doConnect) {
183+
pendingConnections++;
184+
}
185+
}
186+
if (pendingConnections >= MAX_SERVERS) {
187+
Serial.println("Found enough servers, stopping scan");
188+
BLEDevice::getScan()->stop();
189+
doScan = false;
190+
}
191+
break;
192+
}
193+
}
194+
}
195+
}
196+
};
197+
198+
void setup() {
199+
Serial.begin(115200);
200+
Serial.println("=================================");
201+
Serial.println("BLE Multi-Client Example");
202+
Serial.println("=================================");
203+
Serial.print("Max servers to connect: ");
204+
Serial.println(MAX_SERVERS);
205+
Serial.println();
206+
207+
// Initialize all server connections
208+
for (int i = 0; i < MAX_SERVERS; i++) {
209+
servers[i].pClient = nullptr;
210+
servers[i].pDevice = nullptr;
211+
servers[i].pRemoteCharacteristic = nullptr;
212+
servers[i].connected = false;
213+
servers[i].doConnect = false;
214+
servers[i].name = "";
215+
}
216+
217+
// Initialize BLE
218+
BLEDevice::init("ESP32_MultiClient");
219+
220+
// Set up BLE scanner
221+
BLEScan *pBLEScan = BLEDevice::getScan();
222+
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
223+
pBLEScan->setInterval(1349);
224+
pBLEScan->setWindow(449);
225+
pBLEScan->setActiveScan(true);
226+
pBLEScan->start(5, false);
227+
228+
Serial.println("Scanning for BLE servers...");
229+
}
230+
231+
void loop() {
232+
// Process any pending connections
233+
for (int i = 0; i < MAX_SERVERS; i++) {
234+
if (servers[i].doConnect) {
235+
if (connectToServer(i)) {
236+
Serial.println("Connection successful");
237+
} else {
238+
Serial.println("Connection failed");
239+
// Clear this slot so we can try another server
240+
delete servers[i].pDevice;
241+
servers[i].pDevice = nullptr;
242+
}
243+
servers[i].doConnect = false;
244+
}
245+
}
246+
247+
// If we're connected to servers, send data to each one
248+
if (connectedServers > 0) {
249+
for (int i = 0; i < MAX_SERVERS; i++) {
250+
if (servers[i].connected && servers[i].pRemoteCharacteristic != nullptr) {
251+
// Create a unique message for each server
252+
String newValue = servers[i].name + " | Time: " + String(millis() / 1000);
253+
254+
Serial.print("Sending to ");
255+
Serial.print(servers[i].name);
256+
Serial.print(": ");
257+
Serial.println(newValue);
258+
259+
// Write the value to the characteristic
260+
servers[i].pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length());
261+
}
262+
}
263+
} else {
264+
Serial.println("No servers connected");
265+
}
266+
267+
// Resume scanning if we have room for more connections
268+
if (doScan && connectedServers < MAX_SERVERS) {
269+
Serial.println("Resuming scan for more servers...");
270+
BLEDevice::getScan()->start(5, false);
271+
doScan = false;
272+
delay(5000); // Wait for scan to complete
273+
}
274+
275+
delay(2000); // Delay between loop iterations
276+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
fqbn_append: PartitionScheme=huge_app
2+
3+
requires_any:
4+
- CONFIG_SOC_BLE_SUPPORTED=y
5+
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y

libraries/BLE/src/BLERemoteCharacteristic.cpp

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ bool BLERemoteCharacteristic::canWriteNoResponse() {
112112
* @brief Retrieve the map of descriptors keyed by UUID.
113113
*/
114114
std::map<std::string, BLERemoteDescriptor *> *BLERemoteCharacteristic::getDescriptors() {
115+
// Retrieve descriptors if not already done (lazy loading)
116+
if (!m_descriptorsRetrieved) {
117+
log_d("Descriptors not yet retrieved, retrieving now...");
118+
retrieveDescriptors();
119+
}
115120
return &m_descriptorMap;
116121
} // getDescriptors
117122

@@ -132,6 +137,11 @@ uint16_t BLERemoteCharacteristic::getHandle() {
132137
*/
133138
BLERemoteDescriptor *BLERemoteCharacteristic::getDescriptor(BLEUUID uuid) {
134139
log_v(">> getDescriptor: uuid: %s", uuid.toString().c_str());
140+
// Retrieve descriptors if not already done (lazy loading)
141+
if (!m_descriptorsRetrieved) {
142+
log_d("Descriptors not yet retrieved, retrieving now...");
143+
retrieveDescriptors();
144+
}
135145
std::string v = uuid.toString().c_str();
136146
for (auto &myPair : m_descriptorMap) {
137147
if (myPair.first == v) {
@@ -287,6 +297,7 @@ void BLERemoteCharacteristic::removeDescriptors() {
287297
delete myPair.second;
288298
}
289299
m_descriptorMap.clear();
300+
m_descriptorsRetrieved = false; // Allow descriptors to be retrieved again
290301
} // removeCharacteristics
291302

292303
/**
@@ -366,6 +377,7 @@ BLERemoteCharacteristic::BLERemoteCharacteristic(uint16_t handle, BLEUUID uuid,
366377
m_notifyCallback = nullptr;
367378
m_rawData = nullptr;
368379
m_auth = ESP_GATT_AUTH_REQ_NONE;
380+
m_descriptorsRetrieved = false;
369381

370382
retrieveDescriptors(); // Get the descriptors for this characteristic
371383
log_v("<< BLERemoteCharacteristic");
@@ -549,6 +561,7 @@ void BLERemoteCharacteristic::retrieveDescriptors() {
549561
offset++;
550562
} // while true
551563
//m_haveCharacteristics = true; // Remember that we have received the characteristics.
564+
m_descriptorsRetrieved = true;
552565
log_v("<< retrieveDescriptors(): Found %d descriptors.", offset);
553566
} // getDescriptors
554567

@@ -663,14 +676,15 @@ BLERemoteCharacteristic::BLERemoteCharacteristic(BLERemoteService *pRemoteServic
663676

664677
m_handle = chr->val_handle;
665678
m_defHandle = chr->def_handle;
666-
m_endHandle = 0;
667679
m_charProp = chr->properties;
668680
m_pRemoteService = pRemoteService;
669681
m_notifyCallback = nullptr;
670682
m_rawData = nullptr;
671683
m_auth = 0;
684+
m_descriptorsRetrieved = false;
672685

673-
retrieveDescriptors(); // Get the descriptors for this characteristic
686+
// Don't retrieve descriptors in constructor for NimBLE to avoid deadlock
687+
// Descriptors will be retrieved on-demand when needed (e.g., for notifications)
674688

675689
log_v("<< BLERemoteCharacteristic(): %s", m_uuid.toString().c_str());
676690
} // BLERemoteCharacteristic
@@ -781,6 +795,7 @@ bool BLERemoteCharacteristic::retrieveDescriptors(const BLEUUID *uuid_filter) {
781795

782796
// If this is the last handle then there are no descriptors
783797
if (m_handle == getRemoteService()->getEndHandle()) {
798+
m_descriptorsRetrieved = true;
784799
log_d("<< retrieveDescriptors(): No descriptors found");
785800
return true;
786801
}
@@ -789,7 +804,9 @@ bool BLERemoteCharacteristic::retrieveDescriptors(const BLEUUID *uuid_filter) {
789804
desc_filter_t filter = {uuid_filter, &taskData};
790805
int rc = 0;
791806

792-
rc = ble_gattc_disc_all_dscs(getRemoteService()->getClient()->getConnId(), m_handle, m_endHandle, BLERemoteCharacteristic::descriptorDiscCB, &filter);
807+
rc = ble_gattc_disc_all_dscs(
808+
getRemoteService()->getClient()->getConnId(), m_handle, getRemoteService()->getEndHandle(), BLERemoteCharacteristic::descriptorDiscCB, &filter
809+
);
793810

794811
if (rc != 0) {
795812
log_e("ble_gattc_disc_all_dscs: rc=%d %s", rc, BLEUtils::returnCodeToString(rc));
@@ -806,6 +823,7 @@ bool BLERemoteCharacteristic::retrieveDescriptors(const BLEUUID *uuid_filter) {
806823
return false;
807824
}
808825

826+
m_descriptorsRetrieved = true;
809827
log_d("<< retrieveDescriptors(): Found %d descriptors.", m_descriptorMap.size() - prevDscCount);
810828
return true;
811829
} // retrieveDescriptors
@@ -966,6 +984,15 @@ bool BLERemoteCharacteristic::setNotify(uint16_t val, notify_callback notifyCall
966984

967985
m_notifyCallback = notifyCallback;
968986

987+
// Retrieve descriptors if not already done (lazy loading)
988+
if (!m_descriptorsRetrieved) {
989+
log_d("Descriptors not yet retrieved, retrieving now...");
990+
if (!retrieveDescriptors()) {
991+
log_e("<< setNotify(): Failed to retrieve descriptors");
992+
return false;
993+
}
994+
}
995+
969996
BLERemoteDescriptor *desc = getDescriptor(BLEUUID((uint16_t)0x2902));
970997
if (desc == nullptr) {
971998
log_w("<< setNotify(): Callback set, CCCD not found");

0 commit comments

Comments
 (0)