diff --git a/examples/NimBLE_Stream_Client/NimBLE_Stream_Client.ino b/examples/NimBLE_Stream_Client/NimBLE_Stream_Client.ino index 1ae4c30a7..a8556c893 100644 --- a/examples/NimBLE_Stream_Client/NimBLE_Stream_Client.ino +++ b/examples/NimBLE_Stream_Client/NimBLE_Stream_Client.ino @@ -5,9 +5,10 @@ * and communicate using the Arduino Stream interface. * * This allows you to use familiar methods like print(), println(), - * read(), and available() over BLE, similar to how you would use Serial. + * and write() over BLE, similar to how you would use Serial. * - * This example connects to the NimBLE_Stream_Server example. + * This example connects to the NimBLE_Stream_Server example using the Nordic UART + * Service (NUS) with separate TX and RX characteristics. * * Created: November 2025 * Author: NimBLE-Arduino Contributors @@ -16,39 +17,33 @@ #include #include -// Service and Characteristic UUIDs (must match the server) -#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" -#define CHARACTERISTIC_UUID "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" +// Nordic UART Service (NUS) UUIDs (must match the server) +#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" +#define TX_CHAR_UUID "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" // Server TX: client subscribes here +#define RX_CHAR_UUID "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" // Server RX: client writes here -// Create the stream client instance +/** + * Stream for sending data to the server. + * Attached to the server's RX characteristic (6E400002, WRITE_NR). + * Data received from the server arrives via the onServerNotify() callback below. + */ NimBLEStreamClient bleStream; -struct RxOverflowStats { - uint32_t droppedOld{0}; - uint32_t droppedNew{0}; -}; - -RxOverflowStats g_rxOverflowStats; uint32_t scanTime = 5000; // Scan duration in milliseconds -NimBLEStream::RxOverflowAction onRxOverflow(const uint8_t* data, size_t len, void* userArg) { - auto* stats = static_cast(userArg); - if (stats) { - stats->droppedOld++; - } - - // For status/telemetry streams, prioritize newest packets. - (void)data; - (void)len; - return NimBLEStream::DROP_OLDER_DATA; -} - // Connection state variables static bool doConnect = false; static bool connected = false; static const NimBLEAdvertisedDevice* pServerDevice = nullptr; static NimBLEClient* pClient = nullptr; +/** Callback invoked when the server sends a notification on its TX characteristic (6E400003) */ +void onServerNotify(NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t len, bool isNotify) { + Serial.print("Received from server: "); + Serial.write(pData, len); + Serial.println(); +} + /** Scan callbacks to find the server */ class ScanCallbacks : public NimBLEScanCallbacks { void onResult(const NimBLEAdvertisedDevice* advertisedDevice) override { @@ -116,7 +111,7 @@ bool connectToServer() { Serial.println("Connected! Discovering services..."); - // Get the service and characteristic + // Get the service NimBLERemoteService* pRemoteService = pClient->getService(SERVICE_UUID); if (!pRemoteService) { Serial.println("Failed to find our service UUID"); @@ -125,25 +120,45 @@ bool connectToServer() { } Serial.println("Found the stream service"); - NimBLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(CHARACTERISTIC_UUID); - if (!pRemoteCharacteristic) { - Serial.println("Failed to find our characteristic UUID"); + // Get the server's RX characteristic -- client writes here (our TX path) + NimBLERemoteCharacteristic* pRxChar = pRemoteService->getCharacteristic(RX_CHAR_UUID); + if (!pRxChar) { + Serial.println("Failed to find RX characteristic"); + pClient->disconnect(); + return false; + } + Serial.println("Found the RX characteristic"); + + // Get the server's TX characteristic -- client subscribes here (our RX path) + NimBLERemoteCharacteristic* pTxChar = pRemoteService->getCharacteristic(TX_CHAR_UUID); + if (!pTxChar) { + Serial.println("Failed to find TX characteristic"); pClient->disconnect(); return false; } - Serial.println("Found the stream characteristic"); + Serial.println("Found the TX characteristic"); /** - * Initialize the stream client with the remote characteristic - * subscribeNotify=true means we'll receive notifications in the RX buffer + * Initialize the stream with the server's RX characteristic for writing (our TX). + * The RX characteristic (6E400002) supports WRITE but not NOTIFY, so subscribeNotify + * must be false. Notifications are handled separately on the TX characteristic below. */ - if (!bleStream.begin(pRemoteCharacteristic, true)) { + if (!bleStream.begin(pRxChar, false)) { Serial.println("Failed to initialize BLE stream!"); pClient->disconnect(); return false; } - bleStream.setRxOverflowCallback(onRxOverflow, &g_rxOverflowStats); + /** + * Subscribe to the server's TX characteristic (6E400003) to receive notifications. + * Received data is handled directly in the onServerNotify callback. + */ + if (!pTxChar->subscribe(true, onServerNotify)) { + Serial.println("Failed to subscribe to server TX characteristic"); + bleStream.end(); + pClient->disconnect(); + return false; + } Serial.println("BLE Stream initialized successfully!"); connected = true; @@ -171,14 +186,6 @@ void setup() { } void loop() { - static uint32_t lastDroppedOld = 0; - static uint32_t lastDroppedNew = 0; - if (g_rxOverflowStats.droppedOld != lastDroppedOld || g_rxOverflowStats.droppedNew != lastDroppedNew) { - lastDroppedOld = g_rxOverflowStats.droppedOld; - lastDroppedNew = g_rxOverflowStats.droppedNew; - Serial.printf("RX overflow handled (drop-old=%lu, drop-new=%lu)\n", lastDroppedOld, lastDroppedNew); - } - // If we found a server, try to connect if (doConnect) { doConnect = false; @@ -191,20 +198,8 @@ void loop() { } } - // If we're connected, demonstrate the stream interface + // If we're connected, use the stream to send data if (connected && bleStream) { - // Check if we received any data from the server - if (bleStream.available()) { - Serial.print("Received from server: "); - - // Read all available data (just like Serial.read()) - while (bleStream.available()) { - char c = bleStream.read(); - Serial.write(c); - } - Serial.println(); - } - // Send a message every 5 seconds using Stream methods static unsigned long lastSend = 0; if (millis() - lastSend > 5000) { @@ -218,7 +213,7 @@ void loop() { Serial.println("Sent data to server via BLE stream"); } - // You can also read from Serial and send over BLE + // Read from Serial and send over BLE if (Serial.available()) { Serial.println("Reading from Serial and sending via BLE:"); while (Serial.available()) { diff --git a/examples/NimBLE_Stream_Client/README.md b/examples/NimBLE_Stream_Client/README.md index ffd348450..da3d49ae2 100644 --- a/examples/NimBLE_Stream_Client/README.md +++ b/examples/NimBLE_Stream_Client/README.md @@ -4,20 +4,22 @@ This example demonstrates how to use the `NimBLEStreamClient` class to connect t ## Features -- Uses Arduino Stream interface (print, println, read, available, etc.) +- Uses Arduino Stream interface (print, println, write, etc.) - Automatic server discovery and connection -- Bidirectional communication -- Buffered TX/RX using ring buffers +- Bidirectional communication using the Nordic UART Service (NUS) +- TX: `NimBLEStreamClient` writes to the server's RX characteristic (6E400002) +- RX: direct notification callback subscribed to the server's TX characteristic (6E400003) - Automatic reconnection on disconnect +- Compatible with the NimBLE_Stream_Server example and NUS terminal apps - Similar usage to Serial communication ## How it Works -1. Scans for BLE devices advertising the target service UUID -2. Connects to the server and discovers the stream characteristic -3. Initializes `NimBLEStreamClient` with the remote characteristic -4. Subscribes to notifications to receive data in the RX buffer -5. Uses familiar Stream methods like `print()`, `println()`, `read()`, and `available()` +1. Scans for BLE devices advertising the NUS service UUID +2. Connects to the server and discovers the TX and RX characteristics +3. Initializes `NimBLEStreamClient` with the server's RX characteristic for writing (our TX path) +4. Subscribes directly to the server's TX characteristic to receive notifications (our RX path) +5. Uses familiar Stream methods like `print()`, `println()`, and `write()` to send data ## Usage @@ -30,18 +32,19 @@ This example demonstrates how to use the `NimBLEStreamClient` class to connect t - Begin bidirectional communication 4. You can also type in the Serial monitor to send data to the server -## Service UUIDs +## Service UUIDs (Nordic UART Service) Must match the server: - Service: `6E400001-B5A3-F393-E0A9-E50E24DCCA9E` -- Characteristic: `6E400002-B5A3-F393-E0A9-E50E24DCCA9E` +- TX Characteristic (server → client, client subscribes): `6E400003-B5A3-F393-E0A9-E50E24DCCA9E` +- RX Characteristic (client → server, client writes): `6E400002-B5A3-F393-E0A9-E50E24DCCA9E` ## Serial Monitor Output The example displays: - Server discovery progress - Connection status -- All data received from the server +- All data received from the server (via notification callback) - Confirmation of data sent to the server ## Testing @@ -49,5 +52,5 @@ The example displays: Run with NimBLE_Stream_Server to see bidirectional communication: - Server sends periodic status messages - Client sends periodic uptime messages -- Both echo data received from each other -- You can send data from either Serial monitor +- Server echoes data back to the client +- You can send data from the Serial monitor diff --git a/examples/NimBLE_Stream_Server/NimBLE_Stream_Server.ino b/examples/NimBLE_Stream_Server/NimBLE_Stream_Server.ino index 960402053..2146f315a 100644 --- a/examples/NimBLE_Stream_Server/NimBLE_Stream_Server.ino +++ b/examples/NimBLE_Stream_Server/NimBLE_Stream_Server.ino @@ -7,6 +7,9 @@ * This allows you to use familiar methods like print(), println(), * read(), and available() over BLE, similar to how you would use Serial. * + * Uses the Nordic UART Service (NUS) UUIDs with separate TX and RX characteristics + * for compatibility with NUS terminal apps (e.g. nRF UART, Serial Bluetooth Terminal). + * * Created: November 2025 * Author: NimBLE-Arduino Contributors */ @@ -14,12 +17,26 @@ #include #include -// Create the stream server instance -NimBLEStreamServer bleStream; +// Nordic UART Service (NUS) UUIDs +#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" +#define TX_CHAR_UUID "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" // Server TX: notify (server -> client) +#define RX_CHAR_UUID "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" // Server RX: write (client -> server) + +/** + * Two stream server instances: + * - bleStreamTx sends notifications to the client (server -> client). + * Backed by the TX characteristic (NOTIFY-only); TX buffer is enabled, RX buffer disabled. + * - bleStreamRx receives data written by the client (client -> server). + * Backed by the RX characteristic (WRITE-only); RX buffer is enabled, TX buffer disabled. + * + * NimBLEStreamServer::begin(pChr) automatically enables only the directions supported + * by the characteristic's properties, so no special configuration is needed. + */ +NimBLEStreamServer bleStreamTx; +NimBLEStreamServer bleStreamRx; struct RxOverflowStats { uint32_t droppedOld{0}; - uint32_t droppedNew{0}; }; RxOverflowStats g_rxOverflowStats; @@ -36,11 +53,6 @@ NimBLEStream::RxOverflowAction onRxOverflow(const uint8_t* data, size_t len, voi return NimBLEStream::DROP_OLDER_DATA; } -// Service and Characteristic UUIDs for the stream -// Using custom UUIDs - you can change these as needed -#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" -#define CHARACTERISTIC_UUID "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" - /** Server callbacks to handle connection/disconnection events */ class ServerCallbacks : public NimBLEServerCallbacks { void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo) override { @@ -66,32 +78,31 @@ void setup() { /** Initialize NimBLE and set the device name */ NimBLEDevice::init("NimBLE-Stream"); - /** - * Create the BLE server and set callbacks - * Note: The stream will create its own service and characteristic - */ NimBLEServer* pServer = NimBLEDevice::createServer(); pServer->setCallbacks(&serverCallbacks); /** - * Initialize the stream server with: - * - Service UUID - * - Characteristic UUID - * - txBufSize: 1024 bytes for outgoing data (notifications) - * - rxBufSize: 1024 bytes for incoming data (writes) - * - secure: false (no encryption required - set to true for secure connections) + * Create the NUS service with two characteristics: + * - TX (6E400003): NOTIFY -- server sends data to the client + * - RX (6E400002): WRITE -- client sends data to the server + */ + NimBLEService* pSvc = pServer->createService(SERVICE_UUID); + NimBLECharacteristic* pTxChar = pSvc->createCharacteristic(TX_CHAR_UUID, NIMBLE_PROPERTY::NOTIFY); + NimBLECharacteristic* pRxChar = pSvc->createCharacteristic(RX_CHAR_UUID, + NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR); + + /** + * Pass each characteristic to its own NimBLEStreamServer instance. + * begin() checks the characteristic properties and enables only the supported + * direction: pTxChar (NOTIFY-only) enables the TX buffer; pRxChar (WRITE-only) + * enables the RX buffer. */ - if (!bleStream.begin(NimBLEUUID(SERVICE_UUID), - NimBLEUUID(CHARACTERISTIC_UUID), - 1024, // txBufSize - 1024, // rxBufSize - false)) // secure - { + if (!bleStreamTx.begin(pTxChar) || !bleStreamRx.begin(pRxChar)) { Serial.println("Failed to initialize BLE stream!"); return; } - bleStream.setRxOverflowCallback(onRxOverflow, &g_rxOverflowStats); + bleStreamRx.setRxOverflowCallback(onRxOverflow, &g_rxOverflowStats); /** * Create advertising instance and add service UUID @@ -110,39 +121,37 @@ void setup() { void loop() { static uint32_t lastDroppedOld = 0; - static uint32_t lastDroppedNew = 0; - if (g_rxOverflowStats.droppedOld != lastDroppedOld || g_rxOverflowStats.droppedNew != lastDroppedNew) { + if (g_rxOverflowStats.droppedOld != lastDroppedOld) { lastDroppedOld = g_rxOverflowStats.droppedOld; - lastDroppedNew = g_rxOverflowStats.droppedNew; - Serial.printf("RX overflow handled (drop-old=%lu, drop-new=%lu)\n", lastDroppedOld, lastDroppedNew); + Serial.printf("RX overflow: %lu packets dropped\n", lastDroppedOld); } - // Check if a client is subscribed (connected and listening) - if (bleStream.ready()) { + // bleStreamTx.ready() is true when a client has subscribed to the TX characteristic + if (bleStreamTx.ready()) { // Send a message every 2 seconds using Stream methods static unsigned long lastSend = 0; if (millis() - lastSend > 2000) { lastSend = millis(); // Using familiar Serial-like methods! - bleStream.print("Hello from BLE Server! Time: "); - bleStream.println(millis()); + bleStreamTx.print("Hello from BLE Server! Time: "); + bleStreamTx.println(millis()); // You can also use printf - bleStream.printf("Free heap: %d bytes\n", ESP.getFreeHeap()); + bleStreamTx.printf("Free heap: %d bytes\n", ESP.getFreeHeap()); Serial.println("Sent data to client via BLE stream"); } - // Check if we received any data from the client - if (bleStream.available()) { + // Check if we received any data written by the client on the RX characteristic + if (bleStreamRx.available()) { Serial.print("Received from client: "); // Read all available data (just like Serial.read()) - while (bleStream.available()) { - char c = bleStream.read(); - Serial.write(c); // Echo to Serial - bleStream.write(c); // Echo back to BLE client + while (bleStreamRx.available()) { + char c = bleStreamRx.read(); + Serial.write(c); // Echo to Serial + bleStreamTx.write(c); // Echo back to BLE client via TX notification } Serial.println(); } diff --git a/examples/NimBLE_Stream_Server/README.md b/examples/NimBLE_Stream_Server/README.md index 90ce5e1e2..f0449e96b 100644 --- a/examples/NimBLE_Stream_Server/README.md +++ b/examples/NimBLE_Stream_Server/README.md @@ -6,37 +6,39 @@ This example demonstrates how to use the `NimBLEStreamServer` class to create a - Uses Arduino Stream interface (print, println, read, available, etc.) - Automatic connection management -- Bidirectional communication +- Bidirectional communication using the Nordic UART Service (NUS) - Buffered TX/RX using ring buffers +- Compatible with NUS terminal apps (nRF UART, Serial Bluetooth Terminal, etc.) - Similar usage to Serial communication ## How it Works -1. Creates a BLE GATT server with a custom service and characteristic -2. Initializes `NimBLEStreamServer` with the characteristic configured for notifications and writes -3. Uses familiar Stream methods like `print()`, `println()`, `read()`, and `available()` -4. Automatically handles connection state and MTU negotiation +1. Creates the NUS service with two characteristics (TX and RX) +2. Initializes two `NimBLEStreamServer` instances — one for TX (notifications) and one for RX (writes) +3. `NimBLEStreamServer::begin()` automatically enables only the direction supported by the characteristic's properties: the TX characteristic (NOTIFY-only) enables the TX buffer; the RX characteristic (WRITE-only) enables the RX buffer +4. Uses familiar Stream methods like `print()`, `println()`, `read()`, and `available()` +5. Automatically handles connection state and MTU negotiation ## Usage 1. Upload this sketch to your ESP32 2. The device will advertise as "NimBLE-Stream" -3. Connect with a BLE client (such as the NimBLE_Stream_Client example or a mobile app) +3. Connect with a BLE client (such as the NimBLE_Stream_Client example or a NUS terminal app) 4. Once connected, the server will: - - Send periodic messages to the client - - Echo back any data received from the client + - Send periodic messages to the client via the TX characteristic + - Echo back any data received from the client on the RX characteristic - Display all communication on the Serial monitor -## Service UUIDs +## Service UUIDs (Nordic UART Service) - Service: `6E400001-B5A3-F393-E0A9-E50E24DCCA9E` -- Characteristic: `6E400002-B5A3-F393-E0A9-E50E24DCCA9E` - -These are based on the Nordic UART Service (NUS) UUIDs for compatibility with many BLE terminal apps. +- TX Characteristic (server notifies → client subscribes): `6E400003-B5A3-F393-E0A9-E50E24DCCA9E` +- RX Characteristic (client writes → server receives): `6E400002-B5A3-F393-E0A9-E50E24DCCA9E` ## Compatible With - NimBLE_Stream_Client example - nRF Connect mobile app -- Serial Bluetooth Terminal apps -- Any BLE client that supports characteristic notifications and writes +- nRF UART app +- Serial Bluetooth Terminal app +- Any BLE client that supports the Nordic UART Service (NUS)