Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
docs/doxydocs
.development
_codeql_detected_source_root
215 changes: 215 additions & 0 deletions examples/NimBLE_Stream_Client/NimBLE_Stream_Client.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
/**
* NimBLE_Stream_Client Example:
*
* Demonstrates using NimBLEStreamClient to connect to a BLE GATT server
* 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.
*
* This example connects to the NimBLE_Stream_Server example.
*
* Created: November 2025
* Author: NimBLE-Arduino Contributors
*/

#include <Arduino.h>
#include <NimBLEDevice.h>
#include <NimBLEStream.h>

// Service and Characteristic UUIDs (must match the server)
#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"

// Create the stream client instance
NimBLEStreamClient bleStream;

// Connection state variables
static bool doConnect = false;
static bool connected = false;
static const NimBLEAdvertisedDevice* pServerDevice = nullptr;
static NimBLEClient* pClient = nullptr;

/** Scan callbacks to find the server */
class ScanCallbacks : public NimBLEScanCallbacks {
void onResult(const NimBLEAdvertisedDevice* advertisedDevice) override {
Serial.printf("Advertised Device: %s\n", advertisedDevice->toString().c_str());

// Check if this device advertises our service
if (advertisedDevice->isAdvertisingService(NimBLEUUID(SERVICE_UUID))) {
Serial.println("Found our stream server!");
pServerDevice = advertisedDevice;
NimBLEDevice::getScan()->stop();
doConnect = true;
}
}

void onScanEnd(const NimBLEScanResults& results, int reason) override {
Serial.println("Scan ended");
if (!doConnect && !connected) {
Serial.println("Server not found, restarting scan...");
NimBLEDevice::getScan()->start(5, false, true);
}
}
} scanCallbacks;

/** Client callbacks for connection/disconnection events */
class ClientCallbacks : public NimBLEClientCallbacks {
void onConnect(NimBLEClient* pClient) override {
Serial.println("Connected to server");
// Update connection parameters for better throughput
pClient->updateConnParams(12, 24, 0, 200);
}

void onDisconnect(NimBLEClient* pClient, int reason) override {
Serial.printf("Disconnected from server, reason: %d\n", reason);
connected = false;
bleStream.deinit();

// Restart scanning
Serial.println("Restarting scan...");
NimBLEDevice::getScan()->start(5, false, true);
}
} clientCallbacks;

/** Connect to the BLE Server and set up the stream */
bool connectToServer() {
Serial.printf("Connecting to: %s\n", pServerDevice->getAddress().toString().c_str());

// Create or reuse a client
pClient = NimBLEDevice::getClientByPeerAddress(pServerDevice->getAddress());
if (!pClient) {
pClient = NimBLEDevice::createClient();
if (!pClient) {
Serial.println("Failed to create client");
return false;
}
pClient->setClientCallbacks(&clientCallbacks, false);
pClient->setConnectionParams(12, 24, 0, 200);
pClient->setConnectTimeout(5000);
}

// Connect to the remote BLE Server
if (!pClient->connect(pServerDevice)) {
Serial.println("Failed to connect to server");
return false;
}

Serial.println("Connected! Discovering services...");

// Get the service and characteristic
NimBLERemoteService* pRemoteService = pClient->getService(SERVICE_UUID);
if (!pRemoteService) {
Serial.println("Failed to find our service UUID");
pClient->disconnect();
return false;
}
Serial.println("Found the stream service");

NimBLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(CHARACTERISTIC_UUID);
if (!pRemoteCharacteristic) {
Serial.println("Failed to find our characteristic UUID");
pClient->disconnect();
return false;
}
Serial.println("Found the stream characteristic");

/**
* Initialize the stream client with the remote characteristic
* subscribeNotify=true means we'll receive notifications in the RX buffer
*/
if (!bleStream.init(pRemoteCharacteristic, true)) {
Serial.println("Failed to initialize BLE stream!");
pClient->disconnect();
return false;
}

/** Start the stream (begins internal buffers and tasks) */
if (!bleStream.begin()) {
Serial.println("Failed to start BLE stream!");
bleStream.deinit();
pClient->disconnect();
return false;
}

Serial.println("BLE Stream initialized successfully!");
connected = true;
return true;
}

void setup() {
Serial.begin(115200);
Serial.println("Starting NimBLE Stream Client");

/** Initialize NimBLE */
NimBLEDevice::init("NimBLE-StreamClient");

/**
* Create the BLE scan instance and set callbacks
* Configure scan parameters
*/
NimBLEScan* pScan = NimBLEDevice::getScan();
pScan->setScanCallbacks(&scanCallbacks, false);
pScan->setInterval(100);
pScan->setWindow(99);
pScan->setActiveScan(true);

/** Start scanning for the server */
Serial.println("Scanning for BLE Stream Server...");
pScan->start(5, false, true);
}

void loop() {
// If we found a server, try to connect
if (doConnect) {
doConnect = false;
if (connectToServer()) {
Serial.println("Stream ready for communication!");
} else {
Serial.println("Failed to connect to server");
// Scan will restart automatically
}
}

// If we're connected, demonstrate the stream interface
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) {
lastSend = millis();

// Using familiar Serial-like methods!
bleStream.print("Hello from client! Uptime: ");
bleStream.print(millis() / 1000);
bleStream.println(" seconds");

Serial.println("Sent data to server via BLE stream");
}

// You can also read from Serial and send over BLE
if (Serial.available()) {
Serial.println("Reading from Serial and sending via BLE:");
while (Serial.available()) {
char c = Serial.read();
Serial.write(c); // Echo locally
bleStream.write(c); // Send via BLE
}
Serial.println();
}
}

delay(10);
}
53 changes: 53 additions & 0 deletions examples/NimBLE_Stream_Client/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# NimBLE Stream Client Example

This example demonstrates how to use the `NimBLEStreamClient` class to connect to a BLE GATT server and communicate using the familiar Arduino Stream interface.

## Features

- Uses Arduino Stream interface (print, println, read, available, etc.)
- Automatic server discovery and connection
- Bidirectional communication
- Buffered TX/RX using FreeRTOS ring buffers
- Automatic reconnection on disconnect
- 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()`

## Usage

1. First, upload the NimBLE_Stream_Server example to one ESP32
2. Upload this client sketch to another ESP32
3. The client will automatically:
- Scan for the server
- Connect when found
- Set up the stream interface
- Begin bidirectional communication
4. You can also type in the Serial monitor to send data to the server

## Service UUIDs

Must match the server:
- Service: `6E400001-B5A3-F393-E0A9-E50E24DCCA9E`
- Characteristic: `6E400002-B5A3-F393-E0A9-E50E24DCCA9E`

## Serial Monitor Output

The example displays:
- Server discovery progress
- Connection status
- All data received from the server
- Confirmation of data sent to the server

## Testing

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
52 changes: 52 additions & 0 deletions examples/NimBLE_Stream_Echo/NimBLE_Stream_Echo.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* NimBLE_Stream_Echo Example:
*
* A minimal example demonstrating NimBLEStreamServer.
* Echoes back any data received from BLE clients.
*
* This is the simplest way to use the NimBLE Stream interface,
* showing just the essential setup and read/write operations.
*
* Created: November 2025
* Author: NimBLE-Arduino Contributors
*/

#include <Arduino.h>
#include <NimBLEDevice.h>
#include <NimBLEStream.h>

NimBLEStreamServer bleStream;

void setup() {
Serial.begin(115200);
Serial.println("NimBLE Stream Echo Server");

// Initialize BLE
NimBLEDevice::init("BLE-Echo");
NimBLEDevice::createServer();

// Initialize stream with default UUIDs, allow writes
bleStream.init(NimBLEUUID(uint16_t(0xc0de)), // Service UUID
NimBLEUUID(uint16_t(0xfeed)), // Characteristic UUID
true, // canWrite
false); // secure
bleStream.begin();

// Start advertising
NimBLEDevice::getAdvertising()->start();
Serial.println("Ready! Connect with a BLE client and send data.");
}

void loop() {
// Echo any received data back to the client
if (bleStream.hasSubscriber() && bleStream.available()) {
Serial.print("Echo: ");
while (bleStream.available()) {
char c = bleStream.read();
Serial.write(c);
bleStream.write(c); // Echo back
}
Serial.println();
}
delay(10);
}
39 changes: 39 additions & 0 deletions examples/NimBLE_Stream_Echo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# NimBLE Stream Echo Example

This is the simplest example demonstrating `NimBLEStreamServer`. It echoes back any data received from BLE clients.

## Features

- Minimal code showing essential NimBLE Stream usage
- Echoes all received data back to the client
- Uses default service and characteristic UUIDs
- Perfect starting point for learning the Stream interface

## How it Works

1. Initializes BLE with minimal configuration
2. Creates a stream server with default UUIDs
3. Waits for client connection and data
4. Echoes received data back to the client
5. Displays received data on Serial monitor

## Default UUIDs

- Service: `0xc0de`
- Characteristic: `0xfeed`

## Usage

1. Upload this sketch to your ESP32
2. Connect with a BLE client app (nRF Connect, Serial Bluetooth Terminal, etc.)
3. Find the service `0xc0de` and characteristic `0xfeed`
4. Subscribe to notifications
5. Write data to the characteristic
6. The data will be echoed back and displayed in Serial monitor

## Good For

- Learning the basic NimBLE Stream API
- Testing BLE connectivity
- Starting point for custom applications
- Understanding Stream read/write operations
Loading
Loading