Skip to content

Commit b7d7e73

Browse files
authored
feat(ble): Add simultaneous client and server example (#12141)
1 parent 295ff48 commit b7d7e73

File tree

2 files changed

+285
-0
lines changed

2 files changed

+285
-0
lines changed
Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
/*
2+
* BLE Client and Server Coexistence Example
3+
*
4+
* This example demonstrates how to run both BLE client and server
5+
* functionality on the same ESP32 device simultaneously.
6+
*
7+
* The device will:
8+
* - Act as a BLE server, advertising a service with a characteristic
9+
* - Act as a BLE client, scanning for other BLE servers
10+
* - Connect to found servers and interact with their services
11+
* - Handle both incoming and outgoing connections
12+
*
13+
* You can test this example by uploading it to two ESP32 boards.
14+
*
15+
* Author: lucasssvaz
16+
* Based on Arduino BLE examples
17+
*/
18+
19+
#include <BLEDevice.h>
20+
#include <BLEUtils.h>
21+
#include <BLEServer.h>
22+
#include <BLEClient.h>
23+
#include <BLEScan.h>
24+
25+
// Server-side definitions
26+
#define SERVER_SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
27+
#define SERVER_CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
28+
29+
// Client-side definitions (looking for the same service)
30+
static BLEUUID clientServiceUUID(SERVER_SERVICE_UUID);
31+
static BLEUUID clientCharUUID(SERVER_CHARACTERISTIC_UUID);
32+
33+
// Server objects
34+
BLEServer *pServer = nullptr;
35+
BLECharacteristic *pServerCharacteristic = nullptr;
36+
37+
// Client objects
38+
static boolean doConnect = false;
39+
static boolean clientConnected = false;
40+
static BLERemoteCharacteristic *pRemoteCharacteristic;
41+
static BLEAdvertisedDevice *targetDevice;
42+
static BLEClient *pClient = nullptr;
43+
BLEScan *pBLEScan = nullptr;
44+
45+
// Server callbacks
46+
class ServerCallbacks : public BLEServerCallbacks {
47+
void onConnect(BLEServer *pServer) {
48+
Serial.println("Server: Client connected");
49+
}
50+
51+
void onDisconnect(BLEServer *pServer) {
52+
Serial.println("Server: Client disconnected");
53+
// Restart advertising
54+
BLEDevice::startAdvertising();
55+
}
56+
};
57+
58+
// Characteristic callbacks for server
59+
class CharacteristicCallbacks : public BLECharacteristicCallbacks {
60+
void onWrite(BLECharacteristic *pCharacteristic) {
61+
String value = pCharacteristic->getValue();
62+
Serial.print("Server: Characteristic written, value: ");
63+
Serial.println(value.c_str());
64+
}
65+
66+
void onRead(BLECharacteristic *pCharacteristic) {
67+
Serial.println("Server: Characteristic read");
68+
}
69+
};
70+
71+
// Client callbacks
72+
class ClientCallbacks : public BLEClientCallbacks {
73+
void onConnect(BLEClient *pClient) {
74+
Serial.println("Client: Connected to server");
75+
clientConnected = true;
76+
}
77+
78+
void onDisconnect(BLEClient *pClient) {
79+
Serial.println("Client: Disconnected from server");
80+
clientConnected = false;
81+
}
82+
};
83+
84+
// Client notification callback
85+
static void notifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) {
86+
Serial.print("Client: Notify callback for characteristic ");
87+
Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
88+
Serial.print(" of data length ");
89+
Serial.println(length);
90+
Serial.print("Client: Data: ");
91+
Serial.write(pData, length);
92+
Serial.println();
93+
}
94+
95+
// Scan callbacks
96+
class AdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
97+
void onResult(BLEAdvertisedDevice advertisedDevice) {
98+
Serial.print("Client: Found device: ");
99+
Serial.println(advertisedDevice.toString().c_str());
100+
101+
// Check if this device has our target service
102+
if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(clientServiceUUID) && !clientConnected) {
103+
104+
Serial.println("Client: Found target service, attempting connection...");
105+
BLEDevice::getScan()->stop();
106+
targetDevice = new BLEAdvertisedDevice(advertisedDevice);
107+
doConnect = true;
108+
}
109+
}
110+
};
111+
112+
bool connectToServer() {
113+
Serial.print("Client: Forming connection to ");
114+
Serial.println(targetDevice->getAddress().toString().c_str());
115+
116+
// Create client if it doesn't exist, otherwise reuse existing one
117+
if (pClient == nullptr) {
118+
pClient = BLEDevice::createClient();
119+
pClient->setClientCallbacks(new ClientCallbacks());
120+
Serial.println("Client: Created new client");
121+
} else {
122+
Serial.println("Client: Reusing existing client");
123+
}
124+
125+
if (!pClient->connect(targetDevice)) {
126+
Serial.println("Client: Failed to connect");
127+
return false;
128+
}
129+
130+
Serial.println("Client: Connected to server");
131+
pClient->setMTU(517); // Request maximum MTU
132+
133+
// Get the service
134+
BLERemoteService *pRemoteService = pClient->getService(clientServiceUUID);
135+
if (pRemoteService == nullptr) {
136+
Serial.print("Client: Failed to find service UUID: ");
137+
Serial.println(clientServiceUUID.toString().c_str());
138+
pClient->disconnect();
139+
return false;
140+
}
141+
Serial.println("Client: Found service");
142+
143+
// Get the characteristic
144+
pRemoteCharacteristic = pRemoteService->getCharacteristic(clientCharUUID);
145+
if (pRemoteCharacteristic == nullptr) {
146+
Serial.print("Client: Failed to find characteristic UUID: ");
147+
Serial.println(clientCharUUID.toString().c_str());
148+
pClient->disconnect();
149+
return false;
150+
}
151+
Serial.println("Client: Found characteristic");
152+
153+
// Read the initial value
154+
if (pRemoteCharacteristic->canRead()) {
155+
String value = pRemoteCharacteristic->readValue();
156+
Serial.print("Client: Initial characteristic value: ");
157+
Serial.println(value.c_str());
158+
}
159+
160+
// Register for notifications if available
161+
if (pRemoteCharacteristic->canNotify()) {
162+
pRemoteCharacteristic->registerForNotify(notifyCallback);
163+
Serial.println("Client: Registered for notifications");
164+
}
165+
166+
return true;
167+
}
168+
169+
void setupServer() {
170+
Serial.println("Setting up BLE Server...");
171+
172+
// Create server
173+
pServer = BLEDevice::createServer();
174+
pServer->setCallbacks(new ServerCallbacks());
175+
176+
// Create service
177+
BLEService *pService = pServer->createService(SERVER_SERVICE_UUID);
178+
179+
// Create characteristic
180+
pServerCharacteristic = pService->createCharacteristic(
181+
SERVER_CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY
182+
);
183+
184+
pServerCharacteristic->setCallbacks(new CharacteristicCallbacks());
185+
pServerCharacteristic->setValue("Hello from Coexistence Server");
186+
187+
// Start service
188+
pService->start();
189+
190+
// Start advertising
191+
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
192+
pAdvertising->addServiceUUID(SERVER_SERVICE_UUID);
193+
pAdvertising->setScanResponse(true);
194+
pAdvertising->setMinPreferred(0x06);
195+
pAdvertising->setMinPreferred(0x12);
196+
197+
BLEDevice::startAdvertising();
198+
199+
Serial.println("Server: Advertising started");
200+
}
201+
202+
void setupClient() {
203+
Serial.println("Setting up BLE Client...");
204+
205+
// Create scanner
206+
pBLEScan = BLEDevice::getScan();
207+
pBLEScan->setAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks());
208+
pBLEScan->setActiveScan(true);
209+
pBLEScan->setInterval(100);
210+
pBLEScan->setWindow(99);
211+
212+
Serial.println("Client: Scanner configured");
213+
}
214+
215+
void setup() {
216+
Serial.begin(115200);
217+
Serial.println("Starting BLE Client-Server Coexistence Example...");
218+
219+
// Initialize BLE device with a name
220+
BLEDevice::init("ESP32-Coexistence");
221+
222+
// Setup both server and client
223+
setupServer();
224+
setupClient();
225+
226+
// Start initial scan
227+
pBLEScan->start(10, false); // Scan for 10 seconds, don't repeat
228+
229+
Serial.println("Setup complete. Device is advertising as server and scanning as client.");
230+
}
231+
232+
void loop() {
233+
static unsigned long lastServerUpdate = 0;
234+
static unsigned long lastClientWrite = 0;
235+
static unsigned long lastScanStart = 0;
236+
unsigned long currentTime = millis();
237+
238+
// Handle client connection attempts
239+
if (doConnect && !clientConnected) {
240+
if (connectToServer()) {
241+
Serial.println("Client: Successfully connected to remote server");
242+
} else {
243+
Serial.println("Client: Failed to connect, will retry scanning");
244+
// Restart scanning after failed connection
245+
pBLEScan->start(10, false);
246+
}
247+
doConnect = false;
248+
}
249+
250+
// Update server characteristic periodically
251+
if (currentTime - lastServerUpdate > 5000) { // Every 5 seconds
252+
String value = "Server time: " + String(millis() / 1000);
253+
pServerCharacteristic->setValue(value.c_str());
254+
pServerCharacteristic->notify(); // Notify connected clients
255+
Serial.print("Server: Updated characteristic to: ");
256+
Serial.println(value);
257+
258+
lastServerUpdate = currentTime;
259+
}
260+
261+
// Write to remote characteristic if connected as client
262+
if (clientConnected && pRemoteCharacteristic && currentTime - lastClientWrite > 3000) {
263+
if (pRemoteCharacteristic->canWrite()) {
264+
String clientValue = "Client msg: " + String(millis() / 1000);
265+
pRemoteCharacteristic->writeValue(clientValue.c_str(), clientValue.length());
266+
Serial.print("Client: Wrote to remote characteristic: ");
267+
Serial.println(clientValue);
268+
lastClientWrite = currentTime;
269+
}
270+
}
271+
272+
// Restart scanning periodically if not connected
273+
if (!clientConnected && currentTime - lastScanStart > 15000) { // Every 15 seconds
274+
Serial.println("Client: Restarting scan...");
275+
pBLEScan->start(10, false);
276+
lastScanStart = currentTime;
277+
}
278+
279+
delay(100);
280+
}
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

0 commit comments

Comments
 (0)