1+ /* *
2+ * NimBLE_Stream_Client Example:
3+ *
4+ * Demonstrates using NimBLEStreamClient to connect to a BLE GATT server
5+ * and communicate using the Stream-like interface.
6+ *
7+ * This example connects to the NimBLE_Stream_Server example.
8+ */
9+
10+ #include < inttypes.h>
11+ #include < stdint.h>
12+ #include < stdio.h>
13+
14+ #include " esp_timer.h"
15+ #include " freertos/FreeRTOS.h"
16+ #include " freertos/task.h"
17+
18+ #include < NimBLEDevice.h>
19+
20+ // Service and Characteristic UUIDs (must match the server)
21+ #define SERVICE_UUID " 6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
22+ #define CHARACTERISTIC_UUID " 6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
23+
24+ // Create the stream client instance
25+ NimBLEStreamClient bleStream;
26+
27+ struct RxOverflowStats {
28+ uint32_t droppedOld{0 };
29+ uint32_t droppedNew{0 };
30+ };
31+
32+ RxOverflowStats g_rxOverflowStats;
33+ uint32_t scanTime = 5000 ; // Scan duration in milliseconds
34+
35+ NimBLEStream::RxOverflowAction onRxOverflow (const uint8_t * data, size_t len, void * userArg) {
36+ auto * stats = static_cast <RxOverflowStats*>(userArg);
37+ if (stats) {
38+ stats->droppedOld ++;
39+ }
40+
41+ // For status/telemetry streams, prioritize newest packets.
42+ (void )data;
43+ (void )len;
44+ return NimBLEStream::DROP_OLDER_DATA;
45+ }
46+
47+ static uint64_t millis () {
48+ return esp_timer_get_time () / 1000ULL ;
49+ }
50+
51+ // Connection state variables
52+ static bool doConnect = false ;
53+ static bool connected = false ;
54+ static const NimBLEAdvertisedDevice* pServerDevice = nullptr ;
55+ static NimBLEClient* pClient = nullptr ;
56+
57+ /* * Scan callbacks to find the server */
58+ class ScanCallbacks : public NimBLEScanCallbacks {
59+ void onResult (const NimBLEAdvertisedDevice* advertisedDevice) override {
60+ printf (" Advertised Device: %s\n " , advertisedDevice->toString ().c_str ());
61+
62+ // Check if this device advertises our service.
63+ if (advertisedDevice->isAdvertisingService (NimBLEUUID (SERVICE_UUID))) {
64+ printf (" Found our stream server!\n " );
65+ pServerDevice = advertisedDevice;
66+ NimBLEDevice::getScan ()->stop ();
67+ doConnect = true ;
68+ }
69+ }
70+
71+ void onScanEnd (const NimBLEScanResults& results, int reason) override {
72+ (void )results;
73+ (void )reason;
74+ printf (" Scan ended\n " );
75+ if (!doConnect && !connected) {
76+ printf (" Server not found, restarting scan...\n " );
77+ NimBLEDevice::getScan ()->start (scanTime, false , true );
78+ }
79+ }
80+ } scanCallbacks;
81+
82+ /* * Client callbacks for connection/disconnection events */
83+ class ClientCallbacks : public NimBLEClientCallbacks {
84+ void onConnect (NimBLEClient* pClient) override {
85+ printf (" Connected to server\n " );
86+ // Update connection parameters for better throughput.
87+ pClient->updateConnParams (12 , 24 , 0 , 200 );
88+ }
89+
90+ void onDisconnect (NimBLEClient* pClient, int reason) override {
91+ (void )pClient;
92+ printf (" Disconnected from server, reason: %d\n " , reason);
93+ connected = false ;
94+ bleStream.end ();
95+
96+ // Restart scanning.
97+ printf (" Restarting scan...\n " );
98+ NimBLEDevice::getScan ()->start (scanTime, false , true );
99+ }
100+ } clientCallbacks;
101+
102+ /* * Connect to the BLE Server and set up the stream */
103+ bool connectToServer () {
104+ printf (" Connecting to: %s\n " , pServerDevice->getAddress ().toString ().c_str ());
105+
106+ // Create or reuse a client.
107+ pClient = NimBLEDevice::getClientByPeerAddress (pServerDevice->getAddress ());
108+ if (!pClient) {
109+ pClient = NimBLEDevice::createClient ();
110+ if (!pClient) {
111+ printf (" Failed to create client\n " );
112+ return false ;
113+ }
114+ pClient->setClientCallbacks (&clientCallbacks, false );
115+ pClient->setConnectionParams (12 , 24 , 0 , 200 );
116+ pClient->setConnectTimeout (5000 );
117+ }
118+
119+ // Connect to the remote BLE Server.
120+ if (!pClient->connect (pServerDevice)) {
121+ printf (" Failed to connect to server\n " );
122+ return false ;
123+ }
124+
125+ printf (" Connected! Discovering services...\n " );
126+
127+ // Get the service and characteristic.
128+ NimBLERemoteService* pRemoteService = pClient->getService (SERVICE_UUID);
129+ if (!pRemoteService) {
130+ printf (" Failed to find our service UUID\n " );
131+ pClient->disconnect ();
132+ return false ;
133+ }
134+ printf (" Found the stream service\n " );
135+
136+ NimBLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic (CHARACTERISTIC_UUID);
137+ if (!pRemoteCharacteristic) {
138+ printf (" Failed to find our characteristic UUID\n " );
139+ pClient->disconnect ();
140+ return false ;
141+ }
142+ printf (" Found the stream characteristic\n " );
143+
144+ // subscribeNotify=true means notifications are stored in the RX buffer.
145+ if (!bleStream.begin (pRemoteCharacteristic, true )) {
146+ printf (" Failed to initialize BLE stream!\n " );
147+ pClient->disconnect ();
148+ return false ;
149+ }
150+
151+ bleStream.setRxOverflowCallback (onRxOverflow, &g_rxOverflowStats);
152+
153+ printf (" BLE Stream initialized successfully!\n " );
154+ connected = true ;
155+ return true ;
156+ }
157+
158+ extern " C" void app_main (void ) {
159+ printf (" Starting NimBLE Stream Client\n " );
160+
161+ /* * Initialize NimBLE */
162+ NimBLEDevice::init (" NimBLE-StreamClient" );
163+
164+ // Create the BLE scan instance and set callbacks.
165+ NimBLEScan* pScan = NimBLEDevice::getScan ();
166+ pScan->setScanCallbacks (&scanCallbacks, false );
167+ pScan->setActiveScan (true );
168+
169+ // Start scanning for the server.
170+ printf (" Scanning for BLE Stream Server...\n " );
171+ pScan->start (scanTime, false , true );
172+
173+ uint32_t lastDroppedOld = 0 ;
174+ uint32_t lastDroppedNew = 0 ;
175+ uint64_t lastSend = 0 ;
176+
177+ for (;;) {
178+ if (g_rxOverflowStats.droppedOld != lastDroppedOld || g_rxOverflowStats.droppedNew != lastDroppedNew) {
179+ lastDroppedOld = g_rxOverflowStats.droppedOld ;
180+ lastDroppedNew = g_rxOverflowStats.droppedNew ;
181+ printf (" RX overflow handled (drop-old=%" PRIu32 " , drop-new=%" PRIu32 " )\n " , lastDroppedOld, lastDroppedNew);
182+ }
183+
184+ // If we found a server, try to connect.
185+ if (doConnect) {
186+ doConnect = false ;
187+ if (connectToServer ()) {
188+ printf (" Stream ready for communication!\n " );
189+ } else {
190+ printf (" Failed to connect to server, restarting scan...\n " );
191+ pServerDevice = nullptr ;
192+ NimBLEDevice::getScan ()->start (scanTime, false , true );
193+ }
194+ }
195+
196+ // If connected, demonstrate stream communication.
197+ if (connected && bleStream) {
198+ if (bleStream.available ()) {
199+ printf (" Received from server: " );
200+ while (bleStream.available ()) {
201+ char c = bleStream.read ();
202+ putchar (c);
203+ }
204+ printf (" \n " );
205+ }
206+
207+ uint64_t now = millis ();
208+ if (now - lastSend > 5000 ) {
209+ lastSend = now;
210+ bleStream.printf (" Hello from client! Uptime: %" PRIu64 " seconds\n " , now / 1000 );
211+ printf (" Sent data to server via BLE stream\n " );
212+ }
213+ }
214+
215+ vTaskDelay (pdMS_TO_TICKS (10 ));
216+ }
217+ }
0 commit comments