Skip to content

Commit d772983

Browse files
h2zerodoudar
andcommitted
Add BLE stream classes.
Co-authored-by: doudar <17362216+doudar@users.noreply.github.com>
1 parent cc47671 commit d772983

12 files changed

Lines changed: 1921 additions & 8 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
docs/doxydocs
22
.development
3+
_codeql_detected_source_root
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
/**
2+
* NimBLE_Stream_Client Example:
3+
*
4+
* Demonstrates using NimBLEStreamClient to connect to a BLE GATT server
5+
* and communicate using the Arduino Stream interface.
6+
*
7+
* This allows you to use familiar methods like print(), println(),
8+
* read(), and available() over BLE, similar to how you would use Serial.
9+
*
10+
* This example connects to the NimBLE_Stream_Server example.
11+
*
12+
* Created: November 2025
13+
* Author: NimBLE-Arduino Contributors
14+
*/
15+
16+
#include <Arduino.h>
17+
#include <NimBLEDevice.h>
18+
19+
// Service and Characteristic UUIDs (must match the server)
20+
#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
21+
#define CHARACTERISTIC_UUID "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
22+
23+
// Create the stream client instance
24+
NimBLEStreamClient bleStream;
25+
26+
struct RxOverflowStats {
27+
uint32_t droppedOld{0};
28+
uint32_t droppedNew{0};
29+
};
30+
31+
RxOverflowStats g_rxOverflowStats;
32+
uint32_t scanTime = 5000; // Scan duration in milliseconds
33+
34+
NimBLEStream::RxOverflowAction onRxOverflow(const uint8_t* data, size_t len, void* userArg) {
35+
auto* stats = static_cast<RxOverflowStats*>(userArg);
36+
if (stats) {
37+
stats->droppedOld++;
38+
}
39+
40+
// For status/telemetry streams, prioritize newest packets.
41+
(void)data;
42+
(void)len;
43+
return NimBLEStream::DROP_OLDER_DATA;
44+
}
45+
46+
// Connection state variables
47+
static bool doConnect = false;
48+
static bool connected = false;
49+
static const NimBLEAdvertisedDevice* pServerDevice = nullptr;
50+
static NimBLEClient* pClient = nullptr;
51+
52+
/** Scan callbacks to find the server */
53+
class ScanCallbacks : public NimBLEScanCallbacks {
54+
void onResult(const NimBLEAdvertisedDevice* advertisedDevice) override {
55+
Serial.printf("Advertised Device: %s\n", advertisedDevice->toString().c_str());
56+
57+
// Check if this device advertises our service
58+
if (advertisedDevice->isAdvertisingService(NimBLEUUID(SERVICE_UUID))) {
59+
Serial.println("Found our stream server!");
60+
pServerDevice = advertisedDevice;
61+
NimBLEDevice::getScan()->stop();
62+
doConnect = true;
63+
}
64+
}
65+
66+
void onScanEnd(const NimBLEScanResults& results, int reason) override {
67+
Serial.println("Scan ended");
68+
if (!doConnect && !connected) {
69+
Serial.println("Server not found, restarting scan...");
70+
NimBLEDevice::getScan()->start(scanTime, false, true);
71+
}
72+
}
73+
} scanCallbacks;
74+
75+
/** Client callbacks for connection/disconnection events */
76+
class ClientCallbacks : public NimBLEClientCallbacks {
77+
void onConnect(NimBLEClient* pClient) override {
78+
Serial.println("Connected to server");
79+
// Update connection parameters for better throughput
80+
pClient->updateConnParams(12, 24, 0, 200);
81+
}
82+
83+
void onDisconnect(NimBLEClient* pClient, int reason) override {
84+
Serial.printf("Disconnected from server, reason: %d\n", reason);
85+
connected = false;
86+
bleStream.end();
87+
88+
// Restart scanning
89+
Serial.println("Restarting scan...");
90+
NimBLEDevice::getScan()->start(scanTime, false, true);
91+
}
92+
} clientCallbacks;
93+
94+
/** Connect to the BLE Server and set up the stream */
95+
bool connectToServer() {
96+
Serial.printf("Connecting to: %s\n", pServerDevice->getAddress().toString().c_str());
97+
98+
// Create or reuse a client
99+
pClient = NimBLEDevice::getClientByPeerAddress(pServerDevice->getAddress());
100+
if (!pClient) {
101+
pClient = NimBLEDevice::createClient();
102+
if (!pClient) {
103+
Serial.println("Failed to create client");
104+
return false;
105+
}
106+
pClient->setClientCallbacks(&clientCallbacks, false);
107+
pClient->setConnectionParams(12, 24, 0, 200);
108+
pClient->setConnectTimeout(5000);
109+
}
110+
111+
// Connect to the remote BLE Server
112+
if (!pClient->connect(pServerDevice)) {
113+
Serial.println("Failed to connect to server");
114+
return false;
115+
}
116+
117+
Serial.println("Connected! Discovering services...");
118+
119+
// Get the service and characteristic
120+
NimBLERemoteService* pRemoteService = pClient->getService(SERVICE_UUID);
121+
if (!pRemoteService) {
122+
Serial.println("Failed to find our service UUID");
123+
pClient->disconnect();
124+
return false;
125+
}
126+
Serial.println("Found the stream service");
127+
128+
NimBLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(CHARACTERISTIC_UUID);
129+
if (!pRemoteCharacteristic) {
130+
Serial.println("Failed to find our characteristic UUID");
131+
pClient->disconnect();
132+
return false;
133+
}
134+
Serial.println("Found the stream characteristic");
135+
136+
/**
137+
* Initialize the stream client with the remote characteristic
138+
* subscribeNotify=true means we'll receive notifications in the RX buffer
139+
*/
140+
if (!bleStream.begin(pRemoteCharacteristic, true)) {
141+
Serial.println("Failed to initialize BLE stream!");
142+
pClient->disconnect();
143+
return false;
144+
}
145+
146+
bleStream.setRxOverflowCallback(onRxOverflow, &g_rxOverflowStats);
147+
148+
Serial.println("BLE Stream initialized successfully!");
149+
connected = true;
150+
return true;
151+
}
152+
153+
void setup() {
154+
Serial.begin(115200);
155+
Serial.println("Starting NimBLE Stream Client");
156+
157+
/** Initialize NimBLE */
158+
NimBLEDevice::init("NimBLE-StreamClient");
159+
160+
/**
161+
* Create the BLE scan instance and set callbacks
162+
* Configure scan parameters
163+
*/
164+
NimBLEScan* pScan = NimBLEDevice::getScan();
165+
pScan->setScanCallbacks(&scanCallbacks, false);
166+
pScan->setActiveScan(true);
167+
168+
/** Start scanning for the server */
169+
Serial.println("Scanning for BLE Stream Server...");
170+
pScan->start(scanTime, false, true);
171+
}
172+
173+
void loop() {
174+
static uint32_t lastDroppedOld = 0;
175+
static uint32_t lastDroppedNew = 0;
176+
if (g_rxOverflowStats.droppedOld != lastDroppedOld || g_rxOverflowStats.droppedNew != lastDroppedNew) {
177+
lastDroppedOld = g_rxOverflowStats.droppedOld;
178+
lastDroppedNew = g_rxOverflowStats.droppedNew;
179+
Serial.printf("RX overflow handled (drop-old=%lu, drop-new=%lu)\n", lastDroppedOld, lastDroppedNew);
180+
}
181+
182+
// If we found a server, try to connect
183+
if (doConnect) {
184+
doConnect = false;
185+
if (connectToServer()) {
186+
Serial.println("Stream ready for communication!");
187+
} else {
188+
Serial.println("Failed to connect to server, restarting scan...");
189+
pServerDevice = nullptr;
190+
NimBLEDevice::getScan()->start(scanTime, false, true);
191+
}
192+
}
193+
194+
// If we're connected, demonstrate the stream interface
195+
if (connected && bleStream) {
196+
// Check if we received any data from the server
197+
if (bleStream.available()) {
198+
Serial.print("Received from server: ");
199+
200+
// Read all available data (just like Serial.read())
201+
while (bleStream.available()) {
202+
char c = bleStream.read();
203+
Serial.write(c);
204+
}
205+
Serial.println();
206+
}
207+
208+
// Send a message every 5 seconds using Stream methods
209+
static unsigned long lastSend = 0;
210+
if (millis() - lastSend > 5000) {
211+
lastSend = millis();
212+
213+
// Using familiar Serial-like methods!
214+
bleStream.print("Hello from client! Uptime: ");
215+
bleStream.print(millis() / 1000);
216+
bleStream.println(" seconds");
217+
218+
Serial.println("Sent data to server via BLE stream");
219+
}
220+
221+
// You can also read from Serial and send over BLE
222+
if (Serial.available()) {
223+
Serial.println("Reading from Serial and sending via BLE:");
224+
while (Serial.available()) {
225+
char c = Serial.read();
226+
Serial.write(c); // Echo locally
227+
bleStream.write(c); // Send via BLE
228+
}
229+
Serial.println();
230+
}
231+
}
232+
233+
delay(10);
234+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# NimBLE Stream Client Example
2+
3+
This example demonstrates how to use the `NimBLEStreamClient` class to connect to a BLE GATT server and communicate using the familiar Arduino Stream interface.
4+
5+
## Features
6+
7+
- Uses Arduino Stream interface (print, println, read, available, etc.)
8+
- Automatic server discovery and connection
9+
- Bidirectional communication
10+
- Buffered TX/RX using ring buffers
11+
- Automatic reconnection on disconnect
12+
- Similar usage to Serial communication
13+
14+
## How it Works
15+
16+
1. Scans for BLE devices advertising the target service UUID
17+
2. Connects to the server and discovers the stream characteristic
18+
3. Initializes `NimBLEStreamClient` with the remote characteristic
19+
4. Subscribes to notifications to receive data in the RX buffer
20+
5. Uses familiar Stream methods like `print()`, `println()`, `read()`, and `available()`
21+
22+
## Usage
23+
24+
1. First, upload the NimBLE_Stream_Server example to one ESP32
25+
2. Upload this client sketch to another ESP32
26+
3. The client will automatically:
27+
- Scan for the server
28+
- Connect when found
29+
- Set up the stream interface
30+
- Begin bidirectional communication
31+
4. You can also type in the Serial monitor to send data to the server
32+
33+
## Service UUIDs
34+
35+
Must match the server:
36+
- Service: `6E400001-B5A3-F393-E0A9-E50E24DCCA9E`
37+
- Characteristic: `6E400002-B5A3-F393-E0A9-E50E24DCCA9E`
38+
39+
## Serial Monitor Output
40+
41+
The example displays:
42+
- Server discovery progress
43+
- Connection status
44+
- All data received from the server
45+
- Confirmation of data sent to the server
46+
47+
## Testing
48+
49+
Run with NimBLE_Stream_Server to see bidirectional communication:
50+
- Server sends periodic status messages
51+
- Client sends periodic uptime messages
52+
- Both echo data received from each other
53+
- You can send data from either Serial monitor
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/**
2+
* NimBLE_Stream_Echo Example:
3+
*
4+
* A minimal example demonstrating NimBLEStreamServer.
5+
* Echoes back any data received from BLE clients.
6+
*
7+
* This is the simplest way to use the NimBLE Stream interface,
8+
* showing just the essential setup and read/write operations.
9+
*
10+
* Created: November 2025
11+
* Author: NimBLE-Arduino Contributors
12+
*/
13+
14+
#include <Arduino.h>
15+
#include <NimBLEDevice.h>
16+
17+
NimBLEStreamServer bleStream;
18+
19+
struct RxOverflowStats {
20+
uint32_t droppedOld{0};
21+
uint32_t droppedNew{0};
22+
};
23+
24+
RxOverflowStats g_rxOverflowStats;
25+
26+
NimBLEStream::RxOverflowAction onRxOverflow(const uint8_t* data, size_t len, void* userArg) {
27+
auto* stats = static_cast<RxOverflowStats*>(userArg);
28+
if (stats) {
29+
stats->droppedOld++;
30+
}
31+
32+
// Echo mode prefers the latest incoming bytes.
33+
(void)data;
34+
(void)len;
35+
return NimBLEStream::DROP_OLDER_DATA;
36+
}
37+
38+
void setup() {
39+
Serial.begin(115200);
40+
Serial.println("NimBLE Stream Echo Server");
41+
42+
// Initialize BLE
43+
NimBLEDevice::init("BLE-Echo");
44+
auto pServer = NimBLEDevice::createServer();
45+
pServer->advertiseOnDisconnect(true); // Keep advertising after clients disconnect
46+
47+
/**
48+
* Initialize the stream server with:
49+
* - Service UUID
50+
* - Characteristic UUID
51+
* - txBufSize: 1024 bytes for outgoing data (notifications)
52+
* - rxBufSize: 1024 bytes for incoming data (writes)
53+
* - secure: false (no encryption required - set to true for secure connections)
54+
*/
55+
if (!bleStream.begin(NimBLEUUID(uint16_t(0xc0de)), // Service UUID
56+
NimBLEUUID(uint16_t(0xfeed)), // Characteristic UUID
57+
1024, // txBufSize
58+
1024, // rxBufSize
59+
false)) { // secure
60+
Serial.println("Failed to initialize BLE stream");
61+
return;
62+
}
63+
64+
bleStream.setRxOverflowCallback(onRxOverflow, &g_rxOverflowStats);
65+
66+
// Start advertising
67+
NimBLEDevice::getAdvertising()->start();
68+
Serial.println("Ready! Connect with a BLE client and send data.");
69+
}
70+
71+
void loop() {
72+
static uint32_t lastDroppedOld = 0;
73+
static uint32_t lastDroppedNew = 0;
74+
if (g_rxOverflowStats.droppedOld != lastDroppedOld || g_rxOverflowStats.droppedNew != lastDroppedNew) {
75+
lastDroppedOld = g_rxOverflowStats.droppedOld;
76+
lastDroppedNew = g_rxOverflowStats.droppedNew;
77+
Serial.printf("RX overflow handled (drop-old=%lu, drop-new=%lu)\n", lastDroppedOld, lastDroppedNew);
78+
}
79+
80+
// Echo any received data back to the client
81+
if (bleStream.ready() && bleStream.available()) {
82+
Serial.print("Echo: ");
83+
while (bleStream.available()) {
84+
char c = bleStream.read();
85+
Serial.write(c);
86+
bleStream.write(c); // Echo back
87+
}
88+
Serial.println();
89+
}
90+
delay(10);
91+
}

0 commit comments

Comments
 (0)