mirror of
https://github.com/h2zero/esp-nimble-cpp.git
synced 2026-04-13 13:16:02 +02:00
Compare commits
18 Commits
release/2.
...
release/2.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd1b884c41 | ||
|
|
d3f5063ace | ||
|
|
2db8fedb77 | ||
|
|
56580cbf51 | ||
|
|
9d6e48caa4 | ||
|
|
83fb1dfef8 | ||
|
|
4cf0e7c705 | ||
|
|
ee325395d5 | ||
|
|
1b6f152ae7 | ||
|
|
ee8ea37ebb | ||
|
|
8c51a9027c | ||
|
|
c87b76e997 | ||
|
|
84c5b05b27 | ||
|
|
da13bcc74c | ||
|
|
363c142d25 | ||
|
|
5e33d2659d | ||
|
|
27a9df6d77 | ||
|
|
a78ea43be9 |
5
.github/workflows/build.yml
vendored
5
.github/workflows/build.yml
vendored
@@ -12,12 +12,13 @@ jobs:
|
||||
name: Build with ESP-IDF ${{ matrix.idf_ver }} for ${{ matrix.idf_target }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# The version names here correspond to the versions of espressif/idf Docker image.
|
||||
# See https://hub.docker.com/r/espressif/idf/tags and
|
||||
# https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/tools/idf-docker-image.html
|
||||
# for details.
|
||||
idf_ver: ["release-v4.4", "release-v5.4", "release-v5.5"]
|
||||
idf_ver: ["release-v5.4", "release-v5.5"]
|
||||
idf_target: ["esp32", "esp32s3", "esp32c2", "esp32c3", "esp32c5", "esp32c6", "esp32c61", "esp32h2", "esp32p4"]
|
||||
example:
|
||||
- NimBLE_Client
|
||||
@@ -64,7 +65,7 @@ jobs:
|
||||
build_docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Doxygen Action
|
||||
uses: mattnotmitt/doxygen-action@v1.9.8
|
||||
with:
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,2 +1,4 @@
|
||||
docs/doxydocs
|
||||
dist
|
||||
dist
|
||||
.development
|
||||
_codeql_detected_source_root
|
||||
|
||||
27
CHANGELOG.md
27
CHANGELOG.md
@@ -1,6 +1,33 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [2.4.0] 2026-03-20
|
||||
|
||||
## Fixed
|
||||
- GATT attribute handles are now assigned from the registration callback so duplicate UUID attributes are identified correctly.
|
||||
- Dynamic service changes now properly remove characteristics/descriptors and reset the GATT database when advertising starts.
|
||||
- Missing notification/indication payload data when the value spans multiple mbufs, such as values larger than 255 bytes with small ACL buffers.
|
||||
- `NimBLEDevice::createServer` will longer crash when called before the stack is initialized.
|
||||
- Re-pairing after deleting all bonds now works by unpairing each stored bond instead of only deleting NVS data.
|
||||
- Whitelist bounds checks.
|
||||
- `NimBLEDevice::getBondedAddress` index bounds validation.
|
||||
- Compiler warnings when bonds are disabled.
|
||||
- kconfig warnings, redefined macros.
|
||||
|
||||
## Added
|
||||
- `NimBLEStream`, `NimBLEStreamClient`, and `NimBLEStreamServer` classes and examples.
|
||||
- `NimBLECppVersion.h` with compile-time version macros.
|
||||
- `NimBLEDevice::getVersion` runtime version string helper.
|
||||
- Matching passkey callbacks for both roles: `NimBLEServerCallbacks::onPassKeyEntry` and `NimBLEClientCallbacks::onPassKeyDisplay`.
|
||||
- Bond migration helpers to convert bond storage between v1 and current formats while preserving existing bonds.
|
||||
- `NimBLEUUID` constructor overload for `ble_uuid_t*`.
|
||||
- Optional `index` parameter for `NimBLECharacteristic::getDescriptorByUUID` to access multiple descriptors with the same UUID.
|
||||
- `NimBLEConnInfo::toString` method to get a string representation of the connection info.
|
||||
|
||||
## Changed
|
||||
- `NimBLEService::start` is deprecated; services are now added when the server starts.
|
||||
- `NimBLEHIDDevice::startServices()` is deprecated; services are now added when the server starts.
|
||||
|
||||
## [2.3.4] 2025-12-27
|
||||
|
||||
## Fixed
|
||||
|
||||
@@ -66,6 +66,7 @@ idf_component_register(
|
||||
"src/NimBLEScan.cpp"
|
||||
"src/NimBLEServer.cpp"
|
||||
"src/NimBLEService.cpp"
|
||||
"src/NimBLEStream.cpp"
|
||||
"src/NimBLEUtils.cpp"
|
||||
"src/NimBLEUUID.cpp"
|
||||
REQUIRES
|
||||
|
||||
47
Kconfig
47
Kconfig
@@ -208,51 +208,4 @@ config NIMBLE_CPP_IDF
|
||||
bool
|
||||
default BT_NIMBLE_ENABLED
|
||||
|
||||
#
|
||||
# BT config
|
||||
#
|
||||
config BT_ENABLED
|
||||
bool "Bluetooth"
|
||||
default "y"
|
||||
help
|
||||
Select this option to enable Bluetooth and show the submenu with Bluetooth configuration choices.
|
||||
|
||||
|
||||
config BT_NIMBLE_ENABLED
|
||||
bool "NimBLE - BLE only"
|
||||
default "y"
|
||||
help
|
||||
This option is recommended for BLE only usecases to save on memory
|
||||
|
||||
if IDF_TARGET_ESP32P4
|
||||
|
||||
config BT_NIMBLE_TRANSPORT_UART
|
||||
bool "Enable Uart Transport"
|
||||
default "n"
|
||||
|
||||
#
|
||||
# Enable ESP Hosted BT
|
||||
# Used as VHCI transport between BT Host and Controller
|
||||
#
|
||||
config ESP_ENABLE_BT
|
||||
bool "Enable Hosted Bluetooth support"
|
||||
default "y"
|
||||
help
|
||||
Enable Bluetooth Support via Hosted
|
||||
|
||||
choice ESP_WIFI_REMOTE_LIBRARY
|
||||
prompt "Choose WiFi-remote implementation"
|
||||
default ESP_WIFI_REMOTE_LIBRARY_HOSTED
|
||||
help
|
||||
Select type of WiFi Remote implementation
|
||||
|
||||
ESP-HOSTED is the default and most versatile option.
|
||||
It's also possible to use EPPP, which uses PPPoS link between micros and NAPT, so it's slower
|
||||
and less universal.
|
||||
|
||||
config ESP_WIFI_REMOTE_LIBRARY_HOSTED
|
||||
bool "ESP-HOSTED"
|
||||
endchoice
|
||||
endif
|
||||
|
||||
endmenu
|
||||
@@ -48,7 +48,7 @@ PROJECT_NAME = esp-nimble-cpp
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = 2.3.3
|
||||
PROJECT_NUMBER = 2.4.0
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
# quick idea about the purpose of the project. Keep the description short.
|
||||
|
||||
@@ -36,7 +36,7 @@ static uint8_t secondaryPhy = BLE_HCI_LE_PHY_1M;
|
||||
/** Handler class for server events */
|
||||
class ServerCallbacks : public NimBLEServerCallbacks {
|
||||
void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo) override {
|
||||
printf("Client connected:: %s\n", connInfo.getAddress().toString().c_str());
|
||||
printf("Client connected:\n%s", connInfo.toString().c_str());
|
||||
}
|
||||
|
||||
void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) override {
|
||||
@@ -80,9 +80,6 @@ extern "C" void app_main(void) {
|
||||
|
||||
pCharacteristic->setValue("Hello World");
|
||||
|
||||
/** Start the service */
|
||||
pService->start();
|
||||
|
||||
/**
|
||||
* Create an extended advertisement with the instance ID 0 and set the PHY's.
|
||||
* Multiple instances can be added as long as the instance ID is incremented.
|
||||
|
||||
@@ -36,7 +36,7 @@ static uint8_t secondaryPhy = BLE_HCI_LE_PHY_1M;
|
||||
/** Handler class for server events */
|
||||
class ServerCallbacks : public NimBLEServerCallbacks {
|
||||
void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo) override {
|
||||
printf("Client connected: %s\n", connInfo.getAddress().toString().c_str());
|
||||
printf("Client connected:\n%s", connInfo.toString().c_str());
|
||||
}
|
||||
|
||||
void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) override {
|
||||
@@ -98,9 +98,6 @@ extern "C" void app_main(void) {
|
||||
|
||||
pCharacteristic->setValue("Hello World");
|
||||
|
||||
/** Start the service */
|
||||
pService->start();
|
||||
|
||||
/** Create our multi advertising instances */
|
||||
|
||||
/** extended scannable instance advertising on coded and 1m PHY's. */
|
||||
|
||||
@@ -16,7 +16,7 @@ static NimBLEServer* pServer;
|
||||
** Remove as you see fit for your needs */
|
||||
class ServerCallbacks : public NimBLEServerCallbacks {
|
||||
void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo) override {
|
||||
printf("Client address: %s\n", connInfo.getAddress().toString().c_str());
|
||||
printf("Client connected:\n%s", connInfo.toString().c_str());
|
||||
|
||||
/**
|
||||
* We can use the connection handle here to ask for different connection parameters.
|
||||
@@ -184,10 +184,6 @@ extern "C" void app_main(void) {
|
||||
pC01Ddsc->setValue("Send it back!");
|
||||
pC01Ddsc->setCallbacks(&dscCallbacks);
|
||||
|
||||
/** Start the services when finished creating all Characteristics and Descriptors */
|
||||
pDeadService->start();
|
||||
pBaadService->start();
|
||||
|
||||
/** Create an advertising instance and add the services to the advertised data */
|
||||
NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising();
|
||||
pAdvertising->setName("NimBLE-Server");
|
||||
|
||||
6
examples/NimBLE_Stream_Client/CMakeLists.txt
Normal file
6
examples/NimBLE_Stream_Client/CMakeLists.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(NimBLE_Stream_Client)
|
||||
53
examples/NimBLE_Stream_Client/README.md
Normal file
53
examples/NimBLE_Stream_Client/README.md
Normal file
@@ -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 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. Build and flash the NimBLE_Stream_Server example to one ESP32 using ESP-IDF (`idf.py build flash monitor`)
|
||||
2. Build and flash this client example to another ESP32 using ESP-IDF
|
||||
3. The client will automatically:
|
||||
- Scan for the server
|
||||
- Connect when found
|
||||
- Set up the stream interface
|
||||
- Begin bidirectional communication
|
||||
4. Open `idf.py monitor` on each board to observe stream traffic
|
||||
|
||||
## Service UUIDs
|
||||
|
||||
Must match the server:
|
||||
- Service: `6E400001-B5A3-F393-E0A9-E50E24DCCA9E`
|
||||
- Characteristic: `6E400002-B5A3-F393-E0A9-E50E24DCCA9E`
|
||||
|
||||
## 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 `idf.py monitor` session
|
||||
4
examples/NimBLE_Stream_Client/main/CMakeLists.txt
Normal file
4
examples/NimBLE_Stream_Client/main/CMakeLists.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
set(COMPONENT_SRCS "main.cpp")
|
||||
set(COMPONENT_ADD_INCLUDEDIRS ".")
|
||||
|
||||
register_component()
|
||||
217
examples/NimBLE_Stream_Client/main/main.cpp
Normal file
217
examples/NimBLE_Stream_Client/main/main.cpp
Normal file
@@ -0,0 +1,217 @@
|
||||
/**
|
||||
* NimBLE_Stream_Client Example:
|
||||
*
|
||||
* Demonstrates using NimBLEStreamClient to connect to a BLE GATT server
|
||||
* and communicate using the Stream-like interface.
|
||||
*
|
||||
* This example connects to the NimBLE_Stream_Server example.
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "esp_timer.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include <NimBLEDevice.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;
|
||||
|
||||
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<RxOverflowStats*>(userArg);
|
||||
if (stats) {
|
||||
stats->droppedOld++;
|
||||
}
|
||||
|
||||
// For status/telemetry streams, prioritize newest packets.
|
||||
(void)data;
|
||||
(void)len;
|
||||
return NimBLEStream::DROP_OLDER_DATA;
|
||||
}
|
||||
|
||||
static uint64_t millis() {
|
||||
return esp_timer_get_time() / 1000ULL;
|
||||
}
|
||||
|
||||
// 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 {
|
||||
printf("Advertised Device: %s\n", advertisedDevice->toString().c_str());
|
||||
|
||||
// Check if this device advertises our service.
|
||||
if (advertisedDevice->isAdvertisingService(NimBLEUUID(SERVICE_UUID))) {
|
||||
printf("Found our stream server!\n");
|
||||
pServerDevice = advertisedDevice;
|
||||
NimBLEDevice::getScan()->stop();
|
||||
doConnect = true;
|
||||
}
|
||||
}
|
||||
|
||||
void onScanEnd(const NimBLEScanResults& results, int reason) override {
|
||||
(void)results;
|
||||
(void)reason;
|
||||
printf("Scan ended\n");
|
||||
if (!doConnect && !connected) {
|
||||
printf("Server not found, restarting scan...\n");
|
||||
NimBLEDevice::getScan()->start(scanTime, false, true);
|
||||
}
|
||||
}
|
||||
} scanCallbacks;
|
||||
|
||||
/** Client callbacks for connection/disconnection events */
|
||||
class ClientCallbacks : public NimBLEClientCallbacks {
|
||||
void onConnect(NimBLEClient* pClient) override {
|
||||
printf("Connected to server\n");
|
||||
// Update connection parameters for better throughput.
|
||||
pClient->updateConnParams(12, 24, 0, 200);
|
||||
}
|
||||
|
||||
void onDisconnect(NimBLEClient* pClient, int reason) override {
|
||||
(void)pClient;
|
||||
printf("Disconnected from server, reason: %d\n", reason);
|
||||
connected = false;
|
||||
bleStream.end();
|
||||
|
||||
// Restart scanning.
|
||||
printf("Restarting scan...\n");
|
||||
NimBLEDevice::getScan()->start(scanTime, false, true);
|
||||
}
|
||||
} clientCallbacks;
|
||||
|
||||
/** Connect to the BLE Server and set up the stream */
|
||||
bool connectToServer() {
|
||||
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) {
|
||||
printf("Failed to create client\n");
|
||||
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)) {
|
||||
printf("Failed to connect to server\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
printf("Connected! Discovering services...\n");
|
||||
|
||||
// Get the service and characteristic.
|
||||
NimBLERemoteService* pRemoteService = pClient->getService(SERVICE_UUID);
|
||||
if (!pRemoteService) {
|
||||
printf("Failed to find our service UUID\n");
|
||||
pClient->disconnect();
|
||||
return false;
|
||||
}
|
||||
printf("Found the stream service\n");
|
||||
|
||||
NimBLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(CHARACTERISTIC_UUID);
|
||||
if (!pRemoteCharacteristic) {
|
||||
printf("Failed to find our characteristic UUID\n");
|
||||
pClient->disconnect();
|
||||
return false;
|
||||
}
|
||||
printf("Found the stream characteristic\n");
|
||||
|
||||
// subscribeNotify=true means notifications are stored in the RX buffer.
|
||||
if (!bleStream.begin(pRemoteCharacteristic, true)) {
|
||||
printf("Failed to initialize BLE stream!\n");
|
||||
pClient->disconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
bleStream.setRxOverflowCallback(onRxOverflow, &g_rxOverflowStats);
|
||||
|
||||
printf("BLE Stream initialized successfully!\n");
|
||||
connected = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" void app_main(void) {
|
||||
printf("Starting NimBLE Stream Client\n");
|
||||
|
||||
/** Initialize NimBLE */
|
||||
NimBLEDevice::init("NimBLE-StreamClient");
|
||||
|
||||
// Create the BLE scan instance and set callbacks.
|
||||
NimBLEScan* pScan = NimBLEDevice::getScan();
|
||||
pScan->setScanCallbacks(&scanCallbacks, false);
|
||||
pScan->setActiveScan(true);
|
||||
|
||||
// Start scanning for the server.
|
||||
printf("Scanning for BLE Stream Server...\n");
|
||||
pScan->start(scanTime, false, true);
|
||||
|
||||
uint32_t lastDroppedOld = 0;
|
||||
uint32_t lastDroppedNew = 0;
|
||||
uint64_t lastSend = 0;
|
||||
|
||||
for (;;) {
|
||||
if (g_rxOverflowStats.droppedOld != lastDroppedOld || g_rxOverflowStats.droppedNew != lastDroppedNew) {
|
||||
lastDroppedOld = g_rxOverflowStats.droppedOld;
|
||||
lastDroppedNew = g_rxOverflowStats.droppedNew;
|
||||
printf("RX overflow handled (drop-old=%" PRIu32 ", drop-new=%" PRIu32 ")\n", lastDroppedOld, lastDroppedNew);
|
||||
}
|
||||
|
||||
// If we found a server, try to connect.
|
||||
if (doConnect) {
|
||||
doConnect = false;
|
||||
if (connectToServer()) {
|
||||
printf("Stream ready for communication!\n");
|
||||
} else {
|
||||
printf("Failed to connect to server, restarting scan...\n");
|
||||
pServerDevice = nullptr;
|
||||
NimBLEDevice::getScan()->start(scanTime, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
// If connected, demonstrate stream communication.
|
||||
if (connected && bleStream) {
|
||||
if (bleStream.available()) {
|
||||
printf("Received from server: ");
|
||||
while (bleStream.available()) {
|
||||
char c = bleStream.read();
|
||||
putchar(c);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
uint64_t now = millis();
|
||||
if (now - lastSend > 5000) {
|
||||
lastSend = now;
|
||||
bleStream.printf("Hello from client! Uptime: %" PRIu64 " seconds\n", now / 1000);
|
||||
printf("Sent data to server via BLE stream\n");
|
||||
}
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
}
|
||||
12
examples/NimBLE_Stream_Client/sdkconfig.defaults
Normal file
12
examples/NimBLE_Stream_Client/sdkconfig.defaults
Normal file
@@ -0,0 +1,12 @@
|
||||
# Override some defaults so BT stack is enabled
|
||||
# in this example
|
||||
|
||||
#
|
||||
# BT config
|
||||
#
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
|
||||
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
|
||||
CONFIG_BTDM_CTRL_MODE_BTDM=n
|
||||
CONFIG_BT_BLUEDROID_ENABLED=n
|
||||
CONFIG_BT_NIMBLE_ENABLED=y
|
||||
6
examples/NimBLE_Stream_Echo/CMakeLists.txt
Normal file
6
examples/NimBLE_Stream_Echo/CMakeLists.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(NimBLE_Stream_Echo)
|
||||
39
examples/NimBLE_Stream_Echo/README.md
Normal file
39
examples/NimBLE_Stream_Echo/README.md
Normal file
@@ -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 in the ESP-IDF monitor output
|
||||
|
||||
## Default UUIDs
|
||||
|
||||
- Service: `0xc0de`
|
||||
- Characteristic: `0xfeed`
|
||||
|
||||
## Usage
|
||||
|
||||
1. Build and flash this example to your ESP32 using ESP-IDF (`idf.py build flash monitor`)
|
||||
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 `idf.py monitor`
|
||||
|
||||
## Good For
|
||||
|
||||
- Learning the basic NimBLE Stream API
|
||||
- Testing BLE connectivity
|
||||
- Starting point for custom applications
|
||||
- Understanding Stream read/write operations
|
||||
4
examples/NimBLE_Stream_Echo/main/CMakeLists.txt
Normal file
4
examples/NimBLE_Stream_Echo/main/CMakeLists.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
set(COMPONENT_SRCS "main.cpp")
|
||||
set(COMPONENT_ADD_INCLUDEDIRS ".")
|
||||
|
||||
register_component()
|
||||
83
examples/NimBLE_Stream_Echo/main/main.cpp
Normal file
83
examples/NimBLE_Stream_Echo/main/main.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* NimBLE_Stream_Echo Example:
|
||||
*
|
||||
* A minimal example demonstrating NimBLEStreamServer.
|
||||
* Echoes back any data received from BLE clients.
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include <NimBLEDevice.h>
|
||||
|
||||
NimBLEStreamServer bleStream;
|
||||
|
||||
struct RxOverflowStats {
|
||||
uint32_t droppedOld{0};
|
||||
uint32_t droppedNew{0};
|
||||
};
|
||||
|
||||
RxOverflowStats g_rxOverflowStats;
|
||||
|
||||
NimBLEStream::RxOverflowAction onRxOverflow(const uint8_t* data, size_t len, void* userArg) {
|
||||
auto* stats = static_cast<RxOverflowStats*>(userArg);
|
||||
if (stats) {
|
||||
stats->droppedOld++;
|
||||
}
|
||||
|
||||
// Echo mode prefers the latest incoming bytes.
|
||||
(void)data;
|
||||
(void)len;
|
||||
return NimBLEStream::DROP_OLDER_DATA;
|
||||
}
|
||||
|
||||
extern "C" void app_main(void) {
|
||||
printf("NimBLE Stream Echo Server\n");
|
||||
|
||||
// Initialize BLE.
|
||||
NimBLEDevice::init("BLE-Echo");
|
||||
auto pServer = NimBLEDevice::createServer();
|
||||
pServer->advertiseOnDisconnect(true); // Keep advertising after disconnects.
|
||||
|
||||
if (!bleStream.begin(NimBLEUUID(uint16_t(0xc0de)),
|
||||
NimBLEUUID(uint16_t(0xfeed)),
|
||||
1024,
|
||||
1024,
|
||||
false)) {
|
||||
printf("Failed to initialize BLE stream\n");
|
||||
return;
|
||||
}
|
||||
|
||||
bleStream.setRxOverflowCallback(onRxOverflow, &g_rxOverflowStats);
|
||||
|
||||
// Start advertising.
|
||||
NimBLEDevice::getAdvertising()->start();
|
||||
printf("Ready! Connect with a BLE client and send data.\n");
|
||||
|
||||
uint32_t lastDroppedOld = 0;
|
||||
uint32_t lastDroppedNew = 0;
|
||||
|
||||
for (;;) {
|
||||
if (g_rxOverflowStats.droppedOld != lastDroppedOld || g_rxOverflowStats.droppedNew != lastDroppedNew) {
|
||||
lastDroppedOld = g_rxOverflowStats.droppedOld;
|
||||
lastDroppedNew = g_rxOverflowStats.droppedNew;
|
||||
printf("RX overflow handled (drop-old=%" PRIu32 ", drop-new=%" PRIu32 ")\n", lastDroppedOld, lastDroppedNew);
|
||||
}
|
||||
|
||||
// Echo any received data back to the client.
|
||||
if (bleStream.ready() && bleStream.available()) {
|
||||
printf("Echo: ");
|
||||
while (bleStream.available()) {
|
||||
char c = bleStream.read();
|
||||
putchar(c);
|
||||
bleStream.write(c);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
}
|
||||
12
examples/NimBLE_Stream_Echo/sdkconfig.defaults
Normal file
12
examples/NimBLE_Stream_Echo/sdkconfig.defaults
Normal file
@@ -0,0 +1,12 @@
|
||||
# Override some defaults so BT stack is enabled
|
||||
# in this example
|
||||
|
||||
#
|
||||
# BT config
|
||||
#
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
|
||||
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
|
||||
CONFIG_BTDM_CTRL_MODE_BTDM=n
|
||||
CONFIG_BT_BLUEDROID_ENABLED=n
|
||||
CONFIG_BT_NIMBLE_ENABLED=y
|
||||
6
examples/NimBLE_Stream_Server/CMakeLists.txt
Normal file
6
examples/NimBLE_Stream_Server/CMakeLists.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(NimBLE_Stream_Server)
|
||||
42
examples/NimBLE_Stream_Server/README.md
Normal file
42
examples/NimBLE_Stream_Server/README.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# NimBLE Stream Server Example
|
||||
|
||||
This example demonstrates how to use the `NimBLEStreamServer` class to create a BLE GATT server that behaves like a serial port using the familiar Arduino Stream interface.
|
||||
|
||||
## Features
|
||||
|
||||
- Uses Arduino Stream interface (print, println, read, available, etc.)
|
||||
- Automatic connection management
|
||||
- Bidirectional communication
|
||||
- Buffered TX/RX using ring buffers
|
||||
- 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
|
||||
|
||||
## Usage
|
||||
|
||||
1. Build and flash this example to your ESP32 using ESP-IDF (`idf.py build flash monitor`)
|
||||
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)
|
||||
4. Once connected, the server will:
|
||||
- Send periodic messages to the client
|
||||
- Echo back any data received from the client
|
||||
- Display all communication in `idf.py monitor`
|
||||
|
||||
## Service UUIDs
|
||||
|
||||
- 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.
|
||||
|
||||
## Compatible With
|
||||
|
||||
- NimBLE_Stream_Client example
|
||||
- nRF Connect mobile app
|
||||
- Serial Bluetooth Terminal apps
|
||||
- Any BLE client that supports characteristic notifications and writes
|
||||
4
examples/NimBLE_Stream_Server/main/CMakeLists.txt
Normal file
4
examples/NimBLE_Stream_Server/main/CMakeLists.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
set(COMPONENT_SRCS "main.cpp")
|
||||
set(COMPONENT_ADD_INCLUDEDIRS ".")
|
||||
|
||||
register_component()
|
||||
146
examples/NimBLE_Stream_Server/main/main.cpp
Normal file
146
examples/NimBLE_Stream_Server/main/main.cpp
Normal file
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* NimBLE_Stream_Server Example:
|
||||
*
|
||||
* Demonstrates using NimBLEStreamServer to create a BLE GATT server
|
||||
* that behaves like a serial port using the Stream-like interface.
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_timer.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include <NimBLEDevice.h>
|
||||
|
||||
// Create the stream server instance
|
||||
NimBLEStreamServer bleStream;
|
||||
|
||||
struct RxOverflowStats {
|
||||
uint32_t droppedOld{0};
|
||||
uint32_t droppedNew{0};
|
||||
};
|
||||
|
||||
RxOverflowStats g_rxOverflowStats;
|
||||
|
||||
NimBLEStream::RxOverflowAction onRxOverflow(const uint8_t* data, size_t len, void* userArg) {
|
||||
auto* stats = static_cast<RxOverflowStats*>(userArg);
|
||||
if (stats) {
|
||||
stats->droppedOld++;
|
||||
}
|
||||
|
||||
// Keep the newest bytes for command/stream style traffic.
|
||||
(void)data;
|
||||
(void)len;
|
||||
return NimBLEStream::DROP_OLDER_DATA;
|
||||
}
|
||||
|
||||
static uint64_t millis() {
|
||||
return esp_timer_get_time() / 1000ULL;
|
||||
}
|
||||
|
||||
// Service and Characteristic UUIDs for the stream.
|
||||
#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 {
|
||||
printf("Client connected: %s\n", connInfo.getAddress().toString().c_str());
|
||||
// Optionally update connection parameters for better throughput.
|
||||
pServer->updateConnParams(connInfo.getConnHandle(), 12, 24, 0, 200);
|
||||
}
|
||||
|
||||
void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) override {
|
||||
(void)pServer;
|
||||
(void)connInfo;
|
||||
printf("Client disconnected - reason: %d, restarting advertising\n", reason);
|
||||
NimBLEDevice::startAdvertising();
|
||||
}
|
||||
|
||||
void onMTUChange(uint16_t MTU, NimBLEConnInfo& connInfo) override {
|
||||
printf("MTU updated: %u for connection ID: %u\n", MTU, connInfo.getConnHandle());
|
||||
}
|
||||
} serverCallbacks;
|
||||
|
||||
extern "C" void app_main(void) {
|
||||
printf("Starting NimBLE Stream Server\n");
|
||||
|
||||
/** 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 true for secure connections)
|
||||
*/
|
||||
if (!bleStream.begin(NimBLEUUID(SERVICE_UUID),
|
||||
NimBLEUUID(CHARACTERISTIC_UUID),
|
||||
1024,
|
||||
1024,
|
||||
false)) {
|
||||
printf("Failed to initialize BLE stream!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
bleStream.setRxOverflowCallback(onRxOverflow, &g_rxOverflowStats);
|
||||
|
||||
// Make the stream service discoverable.
|
||||
NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising();
|
||||
pAdvertising->addServiceUUID(SERVICE_UUID);
|
||||
pAdvertising->setName("NimBLE-Stream");
|
||||
pAdvertising->enableScanResponse(true);
|
||||
pAdvertising->start();
|
||||
|
||||
printf("BLE Stream Server ready!\n");
|
||||
printf("Waiting for client connection...\n");
|
||||
|
||||
uint32_t lastDroppedOld = 0;
|
||||
uint32_t lastDroppedNew = 0;
|
||||
uint64_t lastSend = 0;
|
||||
|
||||
for (;;) {
|
||||
if (g_rxOverflowStats.droppedOld != lastDroppedOld || g_rxOverflowStats.droppedNew != lastDroppedNew) {
|
||||
lastDroppedOld = g_rxOverflowStats.droppedOld;
|
||||
lastDroppedNew = g_rxOverflowStats.droppedNew;
|
||||
printf("RX overflow handled (drop-old=%" PRIu32 ", drop-new=%" PRIu32 ")\n", lastDroppedOld, lastDroppedNew);
|
||||
}
|
||||
|
||||
if (bleStream.ready()) {
|
||||
uint64_t now = millis();
|
||||
if (now - lastSend > 2000) {
|
||||
lastSend = now;
|
||||
bleStream.printf("Hello from server! Uptime: %" PRIu64 " seconds\n", now / 1000);
|
||||
bleStream.printf("Free heap: %" PRIu32 " bytes\n", esp_get_free_heap_size());
|
||||
printf("Sent data to client via BLE stream\n");
|
||||
}
|
||||
|
||||
if (bleStream.available()) {
|
||||
printf("Received from client: ");
|
||||
while (bleStream.available()) {
|
||||
char c = bleStream.read();
|
||||
putchar(c);
|
||||
bleStream.write(c); // Echo back to BLE client.
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
} else {
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
}
|
||||
12
examples/NimBLE_Stream_Server/sdkconfig.defaults
Normal file
12
examples/NimBLE_Stream_Server/sdkconfig.defaults
Normal file
@@ -0,0 +1,12 @@
|
||||
# Override some defaults so BT stack is enabled
|
||||
# in this example
|
||||
|
||||
#
|
||||
# BT config
|
||||
#
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
|
||||
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
|
||||
CONFIG_BTDM_CTRL_MODE_BTDM=n
|
||||
CONFIG_BT_BLUEDROID_ENABLED=n
|
||||
CONFIG_BT_NIMBLE_ENABLED=y
|
||||
@@ -1,5 +1,5 @@
|
||||
## IDF Component Manager Manifest File
|
||||
version: "2.3.4"
|
||||
version: "2.4.0"
|
||||
license: "Apache-2.0"
|
||||
description: "C++ wrapper for the NimBLE BLE stack"
|
||||
url: "https://github.com/h2zero/esp-nimble-cpp"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "esp-nimble-cpp",
|
||||
"version": "2.3.4",
|
||||
"version": "2.4.0",
|
||||
"description": "C++ wrapper for the NimBLE BLE stack",
|
||||
"keywords": [
|
||||
"BLE",
|
||||
@@ -19,5 +19,10 @@
|
||||
"email": "ryan@nable-embedded.io",
|
||||
"url": "https://github.com/h2zero/esp-nimble-cpp",
|
||||
"maintainer": true
|
||||
},
|
||||
"build": {
|
||||
"flags": [
|
||||
"-DCONFIG_NIMBLE_CPP_IDF=1"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,8 +197,9 @@ bool NimBLEAdvertising::start(uint32_t duration, const NimBLEAddress* dirAddr) {
|
||||
|
||||
# if MYNEWT_VAL(BLE_ROLE_PERIPHERAL)
|
||||
NimBLEServer* pServer = NimBLEDevice::getServer();
|
||||
if (pServer != nullptr) {
|
||||
pServer->start(); // make sure the GATT server is ready before advertising
|
||||
if (pServer != nullptr && !pServer->start()) { // make sure the GATT server is ready before advertising
|
||||
NIMBLE_LOGE(LOG_TAG, "Failed to start GATT server");
|
||||
return false;
|
||||
}
|
||||
# endif
|
||||
|
||||
|
||||
@@ -165,21 +165,27 @@ void NimBLECharacteristic::removeDescriptor(NimBLEDescriptor* pDescriptor, bool
|
||||
/**
|
||||
* @brief Return the BLE Descriptor for the given UUID.
|
||||
* @param [in] uuid The UUID of the descriptor.
|
||||
* @param [in] index The index of the descriptor to return (used when multiple descriptors have the same UUID).
|
||||
* @return A pointer to the descriptor object or nullptr if not found.
|
||||
*/
|
||||
NimBLEDescriptor* NimBLECharacteristic::getDescriptorByUUID(const char* uuid) const {
|
||||
return getDescriptorByUUID(NimBLEUUID(uuid));
|
||||
NimBLEDescriptor* NimBLECharacteristic::getDescriptorByUUID(const char* uuid, uint16_t index) const {
|
||||
return getDescriptorByUUID(NimBLEUUID(uuid), index);
|
||||
} // getDescriptorByUUID
|
||||
|
||||
/**
|
||||
* @brief Return the BLE Descriptor for the given UUID.
|
||||
* @param [in] uuid The UUID of the descriptor.
|
||||
* @param [in] index The index of the descriptor to return (used when multiple descriptors have the same UUID).
|
||||
* @return A pointer to the descriptor object or nullptr if not found.
|
||||
*/
|
||||
NimBLEDescriptor* NimBLECharacteristic::getDescriptorByUUID(const NimBLEUUID& uuid) const {
|
||||
NimBLEDescriptor* NimBLECharacteristic::getDescriptorByUUID(const NimBLEUUID& uuid, uint16_t index) const {
|
||||
uint16_t position = 0;
|
||||
for (const auto& dsc : m_vDescriptors) {
|
||||
if (dsc->getUUID() == uuid) {
|
||||
return dsc;
|
||||
if (position == index) {
|
||||
return dsc;
|
||||
}
|
||||
position++;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
|
||||
@@ -69,8 +69,8 @@ class NimBLECharacteristic : public NimBLELocalValueAttribute {
|
||||
uint32_t properties = NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE,
|
||||
uint16_t maxLen = BLE_ATT_ATTR_MAX_LEN);
|
||||
NimBLE2904* create2904();
|
||||
NimBLEDescriptor* getDescriptorByUUID(const char* uuid) const;
|
||||
NimBLEDescriptor* getDescriptorByUUID(const NimBLEUUID& uuid) const;
|
||||
NimBLEDescriptor* getDescriptorByUUID(const char* uuid, uint16_t index = 0) const;
|
||||
NimBLEDescriptor* getDescriptorByUUID(const NimBLEUUID& uuid, uint16_t index = 0) const;
|
||||
NimBLEDescriptor* getDescriptorByHandle(uint16_t handle) const;
|
||||
NimBLEService* getService() const;
|
||||
|
||||
@@ -113,7 +113,7 @@ class NimBLECharacteristic : public NimBLELocalValueAttribute {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Template to send a notification with a value from a class that has a data() and size() method with value_type.
|
||||
* @brief Template to send a notification with a value from a class that has a data() and size() method with value_type.
|
||||
* @param [in] v The value to send.
|
||||
* @param [in] connHandle Optional, a connection handle to send the notification to.
|
||||
* @details Correctly calculates byte size for containers with multi-byte element types.
|
||||
@@ -125,11 +125,7 @@ class NimBLECharacteristic : public NimBLELocalValueAttribute {
|
||||
typename std::enable_if<Has_data_size<T>::value && Has_value_type<T>::value, bool>::type
|
||||
# endif
|
||||
notify(const T& v, uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const {
|
||||
return notify(
|
||||
reinterpret_cast<const uint8_t*>(v.data()),
|
||||
v.size() * sizeof(typename T::value_type),
|
||||
connHandle
|
||||
);
|
||||
return notify(reinterpret_cast<const uint8_t*>(v.data()), v.size() * sizeof(typename T::value_type), connHandle);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,11 +189,7 @@ class NimBLECharacteristic : public NimBLELocalValueAttribute {
|
||||
typename std::enable_if<Has_data_size<T>::value && Has_value_type<T>::value, bool>::type
|
||||
# endif
|
||||
indicate(const T& v, uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const {
|
||||
return indicate(
|
||||
reinterpret_cast<const uint8_t*>(v.data()),
|
||||
v.size() * sizeof(typename T::value_type),
|
||||
connHandle
|
||||
);
|
||||
return indicate(reinterpret_cast<const uint8_t*>(v.data()), v.size() * sizeof(typename T::value_type), connHandle);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -232,7 +224,9 @@ class NimBLECharacteristic : public NimBLELocalValueAttribute {
|
||||
const T& value, uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const {
|
||||
if constexpr (Has_data_size<T>::value) {
|
||||
if constexpr (Has_value_type<T>::value) {
|
||||
return notify(reinterpret_cast<const uint8_t*>(value.data()), value.size() * sizeof(typename T::value_type), connHandle);
|
||||
return notify(reinterpret_cast<const uint8_t*>(value.data()),
|
||||
value.size() * sizeof(typename T::value_type),
|
||||
connHandle);
|
||||
} else {
|
||||
return notify(reinterpret_cast<const uint8_t*>(value.data()), value.size(), connHandle);
|
||||
}
|
||||
@@ -258,7 +252,9 @@ class NimBLECharacteristic : public NimBLELocalValueAttribute {
|
||||
const T& value, uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const {
|
||||
if constexpr (Has_data_size<T>::value) {
|
||||
if constexpr (Has_value_type<T>::value) {
|
||||
return indicate(reinterpret_cast<const uint8_t*>(value.data()), value.size() * sizeof(typename T::value_type), connHandle);
|
||||
return indicate(reinterpret_cast<const uint8_t*>(value.data()),
|
||||
value.size() * sizeof(typename T::value_type),
|
||||
connHandle);
|
||||
} else {
|
||||
return indicate(reinterpret_cast<const uint8_t*>(value.data()), value.size(), connHandle);
|
||||
}
|
||||
|
||||
@@ -933,7 +933,6 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
|
||||
|
||||
switch (event->type) {
|
||||
case BLE_GAP_EVENT_DISCONNECT: {
|
||||
|
||||
// workaround for bug in NimBLE stack where disconnect event argument is not passed correctly
|
||||
pClient = NimBLEDevice::getClientByPeerAddress(event->disconnect.conn.peer_ota_addr);
|
||||
if (pClient == nullptr) {
|
||||
@@ -946,8 +945,7 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
|
||||
}
|
||||
|
||||
if (pClient == nullptr) {
|
||||
NIMBLE_LOGE(LOG_TAG, "Disconnected client not found, conn_handle=%d",
|
||||
event->disconnect.conn.conn_handle);
|
||||
NIMBLE_LOGE(LOG_TAG, "Disconnected client not found, conn_handle=%d", event->disconnect.conn.conn_handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1050,33 +1048,46 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
|
||||
} // BLE_GAP_EVENT_TERM_FAILURE
|
||||
|
||||
case BLE_GAP_EVENT_NOTIFY_RX: {
|
||||
if (pClient->m_connHandle != event->notify_rx.conn_handle) return 0;
|
||||
if (pClient->m_connHandle != event->notify_rx.conn_handle) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
NIMBLE_LOGD(LOG_TAG, "Notify Received for handle: %d", event->notify_rx.attr_handle);
|
||||
|
||||
for (const auto& svc : pClient->m_svcVec) {
|
||||
// Dont waste cycles searching services without this handle in its range
|
||||
if (svc->getEndHandle() < event->notify_rx.attr_handle) {
|
||||
continue;
|
||||
}
|
||||
NimBLERemoteCharacteristic* pChr = pClient->getCharacteristic(event->notify_rx.attr_handle);
|
||||
if (pChr == nullptr) {
|
||||
NIMBLE_LOGW(LOG_TAG, "unknown handle: %d", event->notify_rx.attr_handle);
|
||||
return BLE_ATT_ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
NIMBLE_LOGD(LOG_TAG,
|
||||
"checking service %s for handle: %d",
|
||||
svc->getUUID().toString().c_str(),
|
||||
event->notify_rx.attr_handle);
|
||||
|
||||
for (const auto& chr : svc->m_vChars) {
|
||||
if (chr->getHandle() == event->notify_rx.attr_handle) {
|
||||
NIMBLE_LOGD(LOG_TAG, "Got Notification for characteristic %s", chr->toString().c_str());
|
||||
|
||||
uint32_t data_len = OS_MBUF_PKTLEN(event->notify_rx.om);
|
||||
chr->m_value.setValue(event->notify_rx.om->om_data, data_len);
|
||||
|
||||
if (chr->m_notifyCallback != nullptr) {
|
||||
chr->m_notifyCallback(chr, event->notify_rx.om->om_data, data_len, !event->notify_rx.indication);
|
||||
}
|
||||
auto len = event->notify_rx.om->om_len;
|
||||
if (pChr->m_value.setValue(event->notify_rx.om->om_data, len)) {
|
||||
os_mbuf* next;
|
||||
next = SLIST_NEXT(event->notify_rx.om, om_next);
|
||||
while (next != NULL) {
|
||||
pChr->m_value.append(next->om_data, next->om_len);
|
||||
if (pChr->m_value.length() != len + next->om_len) {
|
||||
rc = BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
break;
|
||||
}
|
||||
len += next->om_len;
|
||||
next = SLIST_NEXT(next, om_next);
|
||||
}
|
||||
} else {
|
||||
rc = BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
|
||||
if (rc != 0) { // This should never happen
|
||||
NIMBLE_LOGE(LOG_TAG, "notification value error; exceeds limit");
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (pChr->m_notifyCallback != nullptr) {
|
||||
// TODO: change this callback to use the NimBLEAttValue class instead of raw data and length
|
||||
pChr->m_notifyCallback(pChr,
|
||||
const_cast<uint8_t*>(pChr->m_value.getValue().data()),
|
||||
pChr->m_value.length(),
|
||||
!event->notify_rx.indication);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -1199,7 +1210,16 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) {
|
||||
if (event->passkey.params.action == BLE_SM_IOACT_DISP) {
|
||||
struct ble_sm_io pkey = {0, 0};
|
||||
pkey.action = event->passkey.params.action;
|
||||
pkey.passkey = NimBLEDevice::getSecurityPasskey();
|
||||
if (pkey.passkey == 123456) {
|
||||
pkey.passkey = pClient->m_pClientCallbacks->onPassKeyDisplay(peerInfo);
|
||||
}
|
||||
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
|
||||
NIMBLE_LOGD(LOG_TAG, "BLE_SM_IOACT_DISP; ble_sm_inject_io result: %d", rc);
|
||||
} else if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) {
|
||||
NIMBLE_LOGD(LOG_TAG, "Passkey on device's display: %" PRIu32, event->passkey.params.numcmp);
|
||||
pClient->m_pClientCallbacks->onConfirmPasskey(peerInfo, event->passkey.params.numcmp);
|
||||
} else if (event->passkey.params.action == BLE_SM_IOACT_OOB) {
|
||||
@@ -1303,6 +1323,11 @@ void NimBLEClientCallbacks::onPassKeyEntry(NimBLEConnInfo& connInfo) {
|
||||
NimBLEDevice::injectPassKey(connInfo, 123456);
|
||||
} // onPassKeyEntry
|
||||
|
||||
uint32_t NimBLEClientCallbacks::onPassKeyDisplay(NimBLEConnInfo& connInfo) {
|
||||
NIMBLE_LOGD(CB_TAG, "onPassKeyDisplay: default");
|
||||
return NimBLEDevice::getSecurityPasskey();
|
||||
} // onPassKeyDisplay
|
||||
|
||||
void NimBLEClientCallbacks::onAuthenticationComplete(NimBLEConnInfo& connInfo) {
|
||||
NIMBLE_LOGD(CB_TAG, "onAuthenticationComplete: default");
|
||||
} // onAuthenticationComplete
|
||||
|
||||
@@ -187,6 +187,13 @@ class NimBLEClientCallbacks {
|
||||
*/
|
||||
virtual void onPassKeyEntry(NimBLEConnInfo& connInfo);
|
||||
|
||||
/**
|
||||
* @brief Called when using passkey entry pairing and the passkey should be displayed.
|
||||
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.
|
||||
* @return The passkey to display to the user. The peer device must enter this passkey to complete the pairing.
|
||||
*/
|
||||
virtual uint32_t onPassKeyDisplay(NimBLEConnInfo& connInfo);
|
||||
|
||||
/**
|
||||
* @brief Called when the pairing procedure is complete.
|
||||
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.\n
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#endif
|
||||
|
||||
#include "NimBLEAddress.h"
|
||||
#include <cstdio>
|
||||
|
||||
/**
|
||||
* @brief Connection information.
|
||||
@@ -70,6 +71,41 @@ class NimBLEConnInfo {
|
||||
/** @brief Gets the key size used to encrypt the connection */
|
||||
uint8_t getSecKeySize() const { return m_desc.sec_state.key_size; }
|
||||
|
||||
/** @brief Get a string representation of the connection info, useful for debugging */
|
||||
std::string toString() const {
|
||||
std::string str;
|
||||
// 294 chars max expected from all labels + worst-case values, round up to 300.
|
||||
str.resize(300);
|
||||
|
||||
snprintf(&str[0],
|
||||
str.size(),
|
||||
" Address: %s\n"
|
||||
" ID Address: %s\n"
|
||||
" Connection Handle: %u\n"
|
||||
" Connection Interval: %.1f ms\n"
|
||||
" Connection Timeout: %u ms\n"
|
||||
" Connection Latency: %u\n"
|
||||
" MTU: %u bytes\n"
|
||||
" Role: %s\n"
|
||||
" Bonded: %s\n"
|
||||
" Encrypted: %s\n"
|
||||
" Authenticated: %s\n"
|
||||
" Security Key Size: %u\n",
|
||||
getAddress().toString().c_str(),
|
||||
getIdAddress().toString().c_str(),
|
||||
getConnHandle(),
|
||||
getConnInterval() * 1.25f,
|
||||
getConnTimeout() * 10,
|
||||
getConnLatency(),
|
||||
getMTU(),
|
||||
isMaster() ? "Master" : "Slave",
|
||||
isBonded() ? "Yes" : "No",
|
||||
isEncrypted() ? "Yes" : "No",
|
||||
isAuthenticated() ? "Yes" : "No",
|
||||
getSecKeySize());
|
||||
return str;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class NimBLEServer;
|
||||
friend class NimBLEClient;
|
||||
|
||||
80
src/NimBLECppVersion.h
Normal file
80
src/NimBLECppVersion.h
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2020-2025 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* esp-nimble-cpp, NimBLE-Arduino contributors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef NIMBLE_CPP_VERSION_H_
|
||||
#define NIMBLE_CPP_VERSION_H_
|
||||
|
||||
/** @brief NimBLE-Arduino library major version number. */
|
||||
#define NIMBLE_CPP_VERSION_MAJOR 2
|
||||
|
||||
/** @brief NimBLE-Arduino library minor version number. */
|
||||
#define NIMBLE_CPP_VERSION_MINOR 4
|
||||
|
||||
/** @brief NimBLE-Arduino library patch version number. */
|
||||
#define NIMBLE_CPP_VERSION_PATCH 0
|
||||
|
||||
/**
|
||||
* @brief Macro to create a version number for comparison.
|
||||
* @param major Major version number.
|
||||
* @param minor Minor version number.
|
||||
* @param patch Patch version number.
|
||||
* @details Example usage:
|
||||
* @code{.cpp}
|
||||
* #if NIMBLE_CPP_VERSION >= NIMBLE_CPP_VERSION_VAL(2, 0, 0)
|
||||
* // Using NimBLE-Arduino v2 or later
|
||||
* #endif
|
||||
* @endcode
|
||||
*/
|
||||
#define NIMBLE_CPP_VERSION_VAL(major, minor, patch) (((major) << 16) | ((minor) << 8) | (patch))
|
||||
|
||||
/**
|
||||
* @brief The library version as a single integer for compile-time comparison.
|
||||
* @details Format: (major << 16) | (minor << 8) | patch
|
||||
*/
|
||||
#define NIMBLE_CPP_VERSION \
|
||||
NIMBLE_CPP_VERSION_VAL(NIMBLE_CPP_VERSION_MAJOR, NIMBLE_CPP_VERSION_MINOR, NIMBLE_CPP_VERSION_PATCH)
|
||||
|
||||
/** @cond NIMBLE_CPP_INTERNAL */
|
||||
#define NIMBLE_CPP_VERSION_STRINGIFY_IMPL(x) #x
|
||||
#define NIMBLE_CPP_VERSION_STRINGIFY(x) NIMBLE_CPP_VERSION_STRINGIFY_IMPL(x)
|
||||
/** @endcond */
|
||||
|
||||
/**
|
||||
* @brief Optional Semantic Versioning prerelease suffix.
|
||||
* @details Include the leading '-' when defined, for example: "-beta.1"
|
||||
*/
|
||||
#ifndef NIMBLE_CPP_VERSION_PRERELEASE
|
||||
# define NIMBLE_CPP_VERSION_PRERELEASE ""
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Optional Semantic Versioning build metadata suffix.
|
||||
* @details Include the leading '+' when defined, for example: "+sha.abcd1234"
|
||||
*/
|
||||
#ifndef NIMBLE_CPP_VERSION_BUILD_METADATA
|
||||
# define NIMBLE_CPP_VERSION_BUILD_METADATA ""
|
||||
#endif
|
||||
|
||||
/** @brief library version as a prefixed Semantic Versioning string. */
|
||||
#define NIMBLE_CPP_VERSION_STR \
|
||||
"NimBLE-CPP " \
|
||||
NIMBLE_CPP_VERSION_STRINGIFY(NIMBLE_CPP_VERSION_MAJOR) "." \
|
||||
NIMBLE_CPP_VERSION_STRINGIFY(NIMBLE_CPP_VERSION_MINOR) "." \
|
||||
NIMBLE_CPP_VERSION_STRINGIFY(NIMBLE_CPP_VERSION_PATCH) \
|
||||
NIMBLE_CPP_VERSION_PRERELEASE NIMBLE_CPP_VERSION_BUILD_METADATA
|
||||
|
||||
#endif // NIMBLE_CPP_VERSION_H_
|
||||
@@ -98,15 +98,11 @@ ble_gap_event_listener NimBLEDevice::m_listener{};
|
||||
std::vector<NimBLEAddress> NimBLEDevice::m_whiteList{};
|
||||
uint8_t NimBLEDevice::m_ownAddrType{BLE_OWN_ADDR_PUBLIC};
|
||||
|
||||
# ifdef ESP_PLATFORM
|
||||
# if CONFIG_BTDM_BLE_SCAN_DUPL
|
||||
uint16_t NimBLEDevice::m_scanDuplicateSize{CONFIG_BTDM_SCAN_DUPL_CACHE_SIZE};
|
||||
uint8_t NimBLEDevice::m_scanFilterMode{CONFIG_BTDM_SCAN_DUPL_TYPE};
|
||||
# if NIMBLE_CPP_SCAN_DUPL_ENABLED
|
||||
uint16_t NimBLEDevice::m_scanDuplicateSize{100};
|
||||
uint8_t NimBLEDevice::m_scanFilterMode{0};
|
||||
uint16_t NimBLEDevice::m_scanDuplicateResetTime{0};
|
||||
# elif CONFIG_BT_LE_SCAN_DUPL
|
||||
uint16_t NimBLEDevice::m_scanDuplicateSize{CONFIG_BT_LE_LL_DUP_SCAN_LIST_COUNT};
|
||||
uint8_t NimBLEDevice::m_scanFilterMode{CONFIG_BT_LE_SCAN_DUPL_TYPE};
|
||||
uint16_t NimBLEDevice::m_scanDuplicateResetTime{0};
|
||||
# if SOC_ESP_NIMBLE_CONTROLLER
|
||||
extern "C" int ble_vhci_disc_duplicate_set_max_cache_size(int max_cache_size);
|
||||
extern "C" int ble_vhci_disc_duplicate_set_period_refresh_time(int refresh_period_time);
|
||||
extern "C" int ble_vhci_disc_duplicate_mode_disable(int mode);
|
||||
@@ -126,9 +122,6 @@ extern "C" int ble_vhci_disc_duplicate_mode_enable(int mode);
|
||||
NimBLEServer* NimBLEDevice::createServer() {
|
||||
if (NimBLEDevice::m_pServer == nullptr) {
|
||||
NimBLEDevice::m_pServer = new NimBLEServer();
|
||||
ble_gatts_reset();
|
||||
ble_svc_gap_init();
|
||||
ble_svc_gatt_init();
|
||||
}
|
||||
|
||||
return m_pServer;
|
||||
@@ -618,6 +611,7 @@ uint16_t NimBLEDevice::getMTU() {
|
||||
* @brief Gets the number of bonded peers stored
|
||||
*/
|
||||
int NimBLEDevice::getNumBonds() {
|
||||
# if MYNEWT_VAL(BLE_STORE_MAX_BONDS)
|
||||
ble_addr_t peer_id_addrs[MYNEWT_VAL(BLE_STORE_MAX_BONDS)];
|
||||
int num_peers, rc;
|
||||
rc = ble_store_util_bonded_peers(&peer_id_addrs[0], &num_peers, MYNEWT_VAL(BLE_STORE_MAX_BONDS));
|
||||
@@ -626,6 +620,9 @@ int NimBLEDevice::getNumBonds() {
|
||||
}
|
||||
|
||||
return num_peers;
|
||||
# else
|
||||
return 0;
|
||||
# endif
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -633,10 +630,13 @@ int NimBLEDevice::getNumBonds() {
|
||||
* @returns True on success.
|
||||
*/
|
||||
bool NimBLEDevice::deleteAllBonds() {
|
||||
int rc = ble_store_clear();
|
||||
if (rc != 0) {
|
||||
NIMBLE_LOGE(LOG_TAG, "Failed to delete all bonds; rc=%d", rc);
|
||||
return false;
|
||||
int numBonds = NimBLEDevice::getNumBonds();
|
||||
for (int i = numBonds - 1; i >= 0; i--) {
|
||||
auto addr = NimBLEDevice::getBondedAddress(i);
|
||||
if (!NimBLEDevice::deleteBond(addr)) {
|
||||
NIMBLE_LOGE(LOG_TAG, "Failed to delete bond for address: %s", addr.toString().c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -656,6 +656,7 @@ bool NimBLEDevice::deleteBond(const NimBLEAddress& address) {
|
||||
* @returns True if bonded.
|
||||
*/
|
||||
bool NimBLEDevice::isBonded(const NimBLEAddress& address) {
|
||||
# if MYNEWT_VAL(BLE_STORE_MAX_BONDS)
|
||||
ble_addr_t peer_id_addrs[MYNEWT_VAL(BLE_STORE_MAX_BONDS)];
|
||||
int num_peers, rc;
|
||||
|
||||
@@ -670,7 +671,8 @@ bool NimBLEDevice::isBonded(const NimBLEAddress& address) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
# endif
|
||||
(void)address; // unused
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -680,14 +682,19 @@ bool NimBLEDevice::isBonded(const NimBLEAddress& address) {
|
||||
* @returns NimBLEAddress of the found bonded peer or null address if not found.
|
||||
*/
|
||||
NimBLEAddress NimBLEDevice::getBondedAddress(int index) {
|
||||
# if MYNEWT_VAL(BLE_STORE_MAX_BONDS)
|
||||
ble_addr_t peer_id_addrs[MYNEWT_VAL(BLE_STORE_MAX_BONDS)];
|
||||
int num_peers, rc;
|
||||
rc = ble_store_util_bonded_peers(&peer_id_addrs[0], &num_peers, MYNEWT_VAL(BLE_STORE_MAX_BONDS));
|
||||
if (rc != 0 || index > num_peers || index < 0) {
|
||||
if (rc != 0 || index >= num_peers || index < 0) {
|
||||
return NimBLEAddress{};
|
||||
}
|
||||
|
||||
return NimBLEAddress(peer_id_addrs[index]);
|
||||
# else
|
||||
(void)index; // unused
|
||||
return NimBLEAddress{};
|
||||
# endif
|
||||
}
|
||||
# endif
|
||||
|
||||
@@ -766,7 +773,7 @@ size_t NimBLEDevice::getWhiteListCount() {
|
||||
* @returns The NimBLEAddress at the whitelist index or null address if not found.
|
||||
*/
|
||||
NimBLEAddress NimBLEDevice::getWhiteListAddress(size_t index) {
|
||||
if (index > m_whiteList.size()) {
|
||||
if (index >= m_whiteList.size()) {
|
||||
NIMBLE_LOGE(LOG_TAG, "Invalid index; %u", index);
|
||||
return NimBLEAddress{};
|
||||
}
|
||||
@@ -868,7 +875,7 @@ void NimBLEDevice::onSync(void) {
|
||||
* @brief The main host task.
|
||||
*/
|
||||
void NimBLEDevice::host_task(void* param) {
|
||||
NIMBLE_LOGI(LOG_TAG, "BLE Host Task Started");
|
||||
NIMBLE_LOGI(LOG_TAG, "NimBLE Started!");
|
||||
nimble_port_run(); // This function will return only when nimble_port_stop() is executed
|
||||
nimble_port_freertos_deinit();
|
||||
} // host_task
|
||||
@@ -879,6 +886,7 @@ void NimBLEDevice::host_task(void* param) {
|
||||
*/
|
||||
bool NimBLEDevice::init(const std::string& deviceName) {
|
||||
if (!m_initialized) {
|
||||
NIMBLE_LOGD(LOG_TAG, "Starting %s", getVersion());
|
||||
# ifdef ESP_PLATFORM
|
||||
|
||||
# if defined(CONFIG_ENABLE_ARDUINO_DEPENDS) && SOC_BT_SUPPORTED
|
||||
@@ -909,28 +917,31 @@ bool NimBLEDevice::init(const std::string& deviceName) {
|
||||
bt_cfg.mode = ESP_BT_MODE_BLE;
|
||||
bt_cfg.ble_max_conn = MYNEWT_VAL(BLE_MAX_CONNECTIONS);
|
||||
# elif defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
bt_cfg.ble_max_act = MYNEWT_VAL(BLE_MAX_CONNECTIONS) + MYNEWT_VAL(BLE_ROLE_BROADCASTER) + MYNEWT_VAL(BLE_ROLE_OBSERVER);
|
||||
bt_cfg.ble_max_act =
|
||||
MYNEWT_VAL(BLE_MAX_CONNECTIONS) + MYNEWT_VAL(BLE_ROLE_BROADCASTER) + MYNEWT_VAL(BLE_ROLE_OBSERVER);
|
||||
# else
|
||||
bt_cfg.nimble_max_connections = MYNEWT_VAL(BLE_MAX_CONNECTIONS);
|
||||
# endif
|
||||
|
||||
# if CONFIG_BTDM_BLE_SCAN_DUPL
|
||||
# if NIMBLE_CPP_SCAN_DUPL_ENABLED
|
||||
# if !SOC_ESP_NIMBLE_CONTROLLER
|
||||
bt_cfg.normal_adv_size = m_scanDuplicateSize;
|
||||
bt_cfg.scan_duplicate_type = m_scanFilterMode;
|
||||
# if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
bt_cfg.scan_duplicate_mode = 0; // Ensure normal filter mode, could be set to mesh in default config
|
||||
# if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
bt_cfg.dup_list_refresh_period = m_scanDuplicateResetTime;
|
||||
# endif
|
||||
# elif CONFIG_BT_LE_SCAN_DUPL
|
||||
# endif
|
||||
# else // SOC_ESP_NIMBLE_CONTROLLER
|
||||
bt_cfg.ble_ll_rsp_dup_list_count = m_scanDuplicateSize;
|
||||
bt_cfg.ble_ll_adv_dup_list_count = m_scanDuplicateSize;
|
||||
# endif
|
||||
# endif // SOC_ESP_NIMBLE_CONTROLLER
|
||||
err = esp_bt_controller_init(&bt_cfg);
|
||||
if (err != ESP_OK) {
|
||||
NIMBLE_LOGE(LOG_TAG, "esp_bt_controller_init() failed; err=%d", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
# if CONFIG_BT_LE_SCAN_DUPL
|
||||
# if SOC_ESP_NIMBLE_CONTROLLER
|
||||
int mode = (1UL << 4); // FILTER_DUPLICATE_EXCEPTION_FOR_MESH
|
||||
switch (m_scanFilterMode) {
|
||||
case 1:
|
||||
@@ -940,14 +951,15 @@ bool NimBLEDevice::init(const std::string& deviceName) {
|
||||
mode |= ((1UL << 2) | (1UL << 3)); // FILTER_DUPLICATE_ADDRESS | FILTER_DUPLICATE_ADVDATA
|
||||
break;
|
||||
default:
|
||||
mode |= (1UL << 0) | (1UL << 2); // FILTER_DUPLICATE_PDUTYPE | FILTER_DUPLICATE_ADDRESS
|
||||
mode |= ((1UL << 0) | (1UL << 2)); // FILTER_DUPLICATE_PDUTYPE | FILTER_DUPLICATE_ADDRESS
|
||||
}
|
||||
|
||||
ble_vhci_disc_duplicate_mode_disable(0xFFFFFFFF);
|
||||
ble_vhci_disc_duplicate_mode_enable(mode);
|
||||
ble_vhci_disc_duplicate_set_max_cache_size(m_scanDuplicateSize);
|
||||
ble_vhci_disc_duplicate_set_period_refresh_time(m_scanDuplicateResetTime);
|
||||
# endif
|
||||
# endif // SOC_ESP_NIMBLE_CONTROLLER
|
||||
# endif // NIMBLE_CPP_SCAN_DUPL_ENABLED
|
||||
|
||||
err = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (err != ESP_OK) {
|
||||
@@ -996,6 +1008,7 @@ bool NimBLEDevice::init(const std::string& deviceName) {
|
||||
}
|
||||
|
||||
m_initialized = true; // Set the initialization flag to ensure we are only initialized once.
|
||||
NIMBLE_LOGD(LOG_TAG, "Initialized");
|
||||
return true;
|
||||
} // init
|
||||
|
||||
@@ -1262,10 +1275,17 @@ bool NimBLEDevice::startSecurity(uint16_t connHandle, int* rcPtr) {
|
||||
* @return true if the passkey was injected successfully.
|
||||
*/
|
||||
bool NimBLEDevice::injectPassKey(const NimBLEConnInfo& peerInfo, uint32_t passkey) {
|
||||
#if MYNEWT_VAL(BLE_SM_LEGACY)
|
||||
ble_sm_io pkey{.action = BLE_SM_IOACT_INPUT, .passkey = passkey};
|
||||
int rc = ble_sm_inject_io(peerInfo.getConnHandle(), &pkey);
|
||||
NIMBLE_LOGD(LOG_TAG, "BLE_SM_IOACT_INPUT; ble_sm_inject_io result: %d", rc);
|
||||
return rc == 0;
|
||||
#else
|
||||
(void)peerInfo;
|
||||
(void)passkey;
|
||||
NIMBLE_LOGE(LOG_TAG, "Passkey entry not supported with current security settings");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1274,10 +1294,17 @@ bool NimBLEDevice::injectPassKey(const NimBLEConnInfo& peerInfo, uint32_t passke
|
||||
* @param [in] accept Whether the user confirmed or declined the comparison.
|
||||
*/
|
||||
bool NimBLEDevice::injectConfirmPasskey(const NimBLEConnInfo& peerInfo, bool accept) {
|
||||
#if MYNEWT_VAL(BLE_SM_SC)
|
||||
ble_sm_io pkey{.action = BLE_SM_IOACT_NUMCMP, .numcmp_accept = accept};
|
||||
int rc = ble_sm_inject_io(peerInfo.getConnHandle(), &pkey);
|
||||
NIMBLE_LOGD(LOG_TAG, "BLE_SM_IOACT_NUMCMP; ble_sm_inject_io result: %d", rc);
|
||||
return rc == 0;
|
||||
#else
|
||||
(void)peerInfo;
|
||||
(void)accept;
|
||||
NIMBLE_LOGE(LOG_TAG, "Numeric comparison not supported with current security settings");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
# endif // MYNEWT_VAL(BLE_ROLE_CENTRAL) || MYNEWT_VAL(BLE_ROLE_PERIPHERAL)
|
||||
|
||||
@@ -1290,13 +1317,13 @@ bool NimBLEDevice::injectConfirmPasskey(const NimBLEConnInfo& peerInfo, bool acc
|
||||
* @param [in] deviceName The name to set.
|
||||
*/
|
||||
bool NimBLEDevice::setDeviceName(const std::string& deviceName) {
|
||||
#if !defined(MYNEWT_VAL_BLE_GATTS) || MYNEWT_VAL(BLE_GATTS) > 0
|
||||
# if !defined(MYNEWT_VAL_BLE_GATTS) || MYNEWT_VAL(BLE_GATTS) > 0
|
||||
int rc = ble_svc_gap_device_name_set(deviceName.c_str());
|
||||
if (rc != 0) {
|
||||
NIMBLE_LOGE(LOG_TAG, "Device name not set - too long");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
# endif
|
||||
return true;
|
||||
} // setDeviceName
|
||||
|
||||
@@ -1326,6 +1353,14 @@ std::string NimBLEDevice::toString() {
|
||||
return getAddress().toString();
|
||||
} // toString
|
||||
|
||||
/**
|
||||
* @brief Return the library version as a string.
|
||||
* @return A const char* containing library version information.
|
||||
*/
|
||||
const char* NimBLEDevice::getVersion() {
|
||||
return NIMBLE_CPP_VERSION_STR;
|
||||
} // getVersion
|
||||
|
||||
# if MYNEWT_VAL(NIMBLE_CPP_DEBUG_ASSERT_ENABLED) || __DOXYGEN__
|
||||
/**
|
||||
* @brief Debug assert - weak function.
|
||||
|
||||
@@ -18,12 +18,15 @@
|
||||
#ifndef NIMBLE_CPP_DEVICE_H_
|
||||
#define NIMBLE_CPP_DEVICE_H_
|
||||
|
||||
#include "NimBLECppVersion.h"
|
||||
#include "syscfg/syscfg.h"
|
||||
#if CONFIG_BT_NIMBLE_ENABLED
|
||||
# ifdef ESP_PLATFORM
|
||||
# ifndef CONFIG_IDF_TARGET_ESP32P4
|
||||
# include <esp_bt.h>
|
||||
# endif
|
||||
# define NIMBLE_CPP_SCAN_DUPL_ENABLED \
|
||||
(CONFIG_BTDM_BLE_SCAN_DUPL || CONFIG_BT_LE_SCAN_DUPL || CONFIG_BT_CTRL_BLE_SCAN_DUPL)
|
||||
# endif
|
||||
|
||||
# if defined(CONFIG_NIMBLE_CPP_IDF)
|
||||
@@ -121,6 +124,7 @@ class NimBLEDevice {
|
||||
static bool isInitialized();
|
||||
static NimBLEAddress getAddress();
|
||||
static std::string toString();
|
||||
static const char* getVersion();
|
||||
static bool whiteListAdd(const NimBLEAddress& address);
|
||||
static bool whiteListRemove(const NimBLEAddress& address);
|
||||
static bool onWhiteList(const NimBLEAddress& address);
|
||||
@@ -243,7 +247,7 @@ class NimBLEDevice {
|
||||
# endif
|
||||
|
||||
# ifdef ESP_PLATFORM
|
||||
# if CONFIG_BTDM_BLE_SCAN_DUPL || CONFIG_BT_LE_SCAN_DUPL
|
||||
# if NIMBLE_CPP_SCAN_DUPL_ENABLED
|
||||
static uint16_t m_scanDuplicateSize;
|
||||
static uint8_t m_scanFilterMode;
|
||||
static uint16_t m_scanDuplicateResetTime;
|
||||
@@ -304,6 +308,7 @@ class NimBLEDevice {
|
||||
|
||||
# if MYNEWT_VAL(BLE_ROLE_CENTRAL) || MYNEWT_VAL(BLE_ROLE_PERIPHERAL)
|
||||
# include "NimBLEConnInfo.h"
|
||||
# include "NimBLEStream.h"
|
||||
# endif
|
||||
|
||||
# include "NimBLEAddress.h"
|
||||
|
||||
@@ -84,9 +84,7 @@ void NimBLEHIDDevice::setReportMap(uint8_t* map, uint16_t size) {
|
||||
* This function called when all the services have been created.
|
||||
*/
|
||||
void NimBLEHIDDevice::startServices() {
|
||||
m_deviceInfoSvc->start();
|
||||
m_hidSvc->start();
|
||||
m_batterySvc->start();
|
||||
// no-op now, services started by server start.
|
||||
} // startServices
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,7 +49,8 @@ class NimBLEHIDDevice {
|
||||
NimBLEHIDDevice(NimBLEServer* server);
|
||||
|
||||
void setReportMap(uint8_t* map, uint16_t);
|
||||
void startServices();
|
||||
void startServices() __attribute__((deprecated("Services are now started by the server when start() is called, "
|
||||
"this function is no longer needed and will be removed in a future release.")));
|
||||
bool setManufacturer(const std::string& name);
|
||||
void setPnp(uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version);
|
||||
void setHidInfo(uint8_t country, uint8_t flags);
|
||||
|
||||
@@ -32,10 +32,22 @@ static NimBLEScanCallbacks defaultScanCallbacks;
|
||||
*/
|
||||
NimBLEScan::NimBLEScan()
|
||||
: m_pScanCallbacks{&defaultScanCallbacks},
|
||||
// default interval + window, no whitelist scan filter,not limited scan, no scan response, filter_duplicates
|
||||
m_scanParams{0, 0, BLE_HCI_SCAN_FILT_NO_WL, 0, 1, 1},
|
||||
m_scanParams{
|
||||
.itvl = 0, // default interval
|
||||
.window = 0, // default window
|
||||
.filter_policy = BLE_HCI_SCAN_FILT_NO_WL, // no whitelist scan filter
|
||||
.limited = 0, // no limited scan
|
||||
.passive = 1, // no scan response
|
||||
.filter_duplicates = 1, // filter duplicates
|
||||
# if defined(ESP_PLATFORM) && !defined(CONFIG_USING_NIMBLE_COMPONENT)
|
||||
# if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 2)
|
||||
.disable_observer_mode = 0, // observer role enabled
|
||||
# endif
|
||||
# endif
|
||||
},
|
||||
m_pTaskData{nullptr},
|
||||
m_maxResults{0xFF} {}
|
||||
m_maxResults{0xFF} {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Scan destructor, release any allocated resources.
|
||||
|
||||
@@ -39,6 +39,11 @@
|
||||
static const char* LOG_TAG = "NimBLEServer";
|
||||
static NimBLEServerCallbacks defaultCallbacks;
|
||||
|
||||
struct gattRegisterCallbackArgs {
|
||||
NimBLEService* pSvc{nullptr};
|
||||
NimBLECharacteristic* pChar{nullptr};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Construct a BLE Server
|
||||
*
|
||||
@@ -188,47 +193,122 @@ void NimBLEServer::serviceChanged() {
|
||||
}
|
||||
} // serviceChanged
|
||||
|
||||
/**
|
||||
* @brief Callback for GATT registration events,
|
||||
* used to obtain the assigned handles for services, characteristics, and descriptors.
|
||||
* @param [in] ctxt The context of the registration event.
|
||||
* @param [in] arg A pointer to the gattRegisterCallbackArgs struct used to track the
|
||||
* service and characteristic being registered.
|
||||
*/
|
||||
void NimBLEServer::gattRegisterCallback(ble_gatt_register_ctxt* ctxt, void* arg) {
|
||||
gattRegisterCallbackArgs* args = static_cast<gattRegisterCallbackArgs*>(arg);
|
||||
|
||||
if (ctxt->op == BLE_GATT_REGISTER_OP_SVC) {
|
||||
NimBLEUUID uuid(ctxt->svc.svc_def->uuid);
|
||||
args->pSvc = nullptr;
|
||||
for (auto pSvc : NimBLEDevice::getServer()->m_svcVec) {
|
||||
if (!pSvc->getRemoved() && pSvc->m_handle == 0 && pSvc->getUUID() == uuid) {
|
||||
pSvc->m_handle = ctxt->svc.handle;
|
||||
NIMBLE_LOGD(LOG_TAG, "Service registered: %s, handle=%d", uuid.toString().c_str(), ctxt->svc.handle);
|
||||
// Set the arg to the service so we know that the following
|
||||
// characteristics and descriptors belong to this service
|
||||
args->pSvc = pSvc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (args->pSvc == nullptr) {
|
||||
// If the service is not found then this is likely a characteristic or descriptor that was registered as
|
||||
// part of the GATT server setup and not found in the service vector
|
||||
NIMBLE_LOGD(LOG_TAG, "Skipping characteristic or descriptor registered with unknown service");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctxt->op == BLE_GATT_REGISTER_OP_CHR) {
|
||||
NimBLEUUID uuid(ctxt->chr.chr_def->uuid);
|
||||
args->pChar = nullptr;
|
||||
for (auto pChr : args->pSvc->m_vChars) {
|
||||
if (!pChr->getRemoved() && pChr->m_handle == 0 && pChr->getUUID() == uuid) {
|
||||
pChr->m_handle = ctxt->chr.val_handle;
|
||||
// Set the arg to the characteristic so we know that the following descriptors belong to this characteristic
|
||||
args->pChar = pChr;
|
||||
NIMBLE_LOGD(LOG_TAG,
|
||||
"Characteristic registered: %s, def_handle=%d, val_handle=%d",
|
||||
uuid.toString().c_str(),
|
||||
ctxt->chr.def_handle,
|
||||
ctxt->chr.val_handle);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctxt->op == BLE_GATT_REGISTER_OP_DSC) {
|
||||
if (args->pChar == nullptr) {
|
||||
NIMBLE_LOGE(LOG_TAG, "Descriptor registered with unknown characteristic, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
NimBLEUUID uuid(ctxt->dsc.dsc_def->uuid);
|
||||
for (auto pDsc : args->pChar->m_vDescriptors) {
|
||||
if (!pDsc->getRemoved() && pDsc->m_handle == 0 && pDsc->getUUID() == uuid) {
|
||||
pDsc->m_handle = ctxt->dsc.handle;
|
||||
NIMBLE_LOGD(LOG_TAG, "Descriptor registered: %s, handle=%d", uuid.toString().c_str(), ctxt->dsc.handle);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Start the GATT server.
|
||||
* @details Required to be called after setup of all services and characteristics / descriptors
|
||||
* for the NimBLE host to register them.
|
||||
*/
|
||||
void NimBLEServer::start() {
|
||||
if (m_gattsStarted) {
|
||||
return; // already started
|
||||
bool NimBLEServer::start() {
|
||||
if (m_svcChanged && !getConnectedCount()) {
|
||||
NIMBLE_LOGD(LOG_TAG, "Services have changed since last start, resetting GATT server");
|
||||
m_gattsStarted = false;
|
||||
}
|
||||
|
||||
if (m_gattsStarted) {
|
||||
return true; // already started
|
||||
}
|
||||
|
||||
if (!resetGATT()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ble_hs_cfg.gatts_register_cb = NimBLEServer::gattRegisterCallback;
|
||||
gattRegisterCallbackArgs args{};
|
||||
ble_hs_cfg.gatts_register_arg = &args;
|
||||
|
||||
int rc = ble_gatts_start();
|
||||
if (rc != 0) {
|
||||
NIMBLE_LOGE(LOG_TAG, "ble_gatts_start; rc=%d, %s", rc, NimBLEUtils::returnCodeToString(rc));
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
# if MYNEWT_VAL(NIMBLE_CPP_LOG_LEVEL) >= 4
|
||||
ble_gatts_show_local();
|
||||
# endif
|
||||
|
||||
// Get the assigned service handles and build a vector of characteristics
|
||||
// with Notify / Indicate capabilities for event handling
|
||||
// Check that all services were registered and log if any are missing.
|
||||
for (const auto& svc : m_svcVec) {
|
||||
if (svc->getRemoved() == 0) {
|
||||
rc = ble_gatts_find_svc(svc->getUUID().getBase(), &svc->m_handle);
|
||||
rc = ble_gatts_find_svc(svc->getUUID().getBase(), NULL);
|
||||
if (rc != 0) {
|
||||
NIMBLE_LOGW(LOG_TAG,
|
||||
NIMBLE_LOGD(LOG_TAG,
|
||||
"GATT Server started without service: %s, Service %s",
|
||||
svc->getUUID().toString().c_str(),
|
||||
svc->isStarted() ? "missing" : "not started");
|
||||
continue; // Skip this service as it was not started
|
||||
}
|
||||
}
|
||||
|
||||
// Set the descriptor handles now as the stack does not set these when the service is started
|
||||
for (const auto& chr : svc->m_vChars) {
|
||||
for (auto& desc : chr->m_vDescriptors) {
|
||||
ble_gatts_find_dsc(svc->getUUID().getBase(), chr->getUUID().getBase(), desc->getUUID().getBase(), &desc->m_handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
# endif
|
||||
|
||||
// If the services have changed indicate it now
|
||||
if (m_svcChanged) {
|
||||
@@ -237,6 +317,7 @@ void NimBLEServer::start() {
|
||||
}
|
||||
|
||||
m_gattsStarted = true;
|
||||
return true;
|
||||
} // start
|
||||
|
||||
/**
|
||||
@@ -259,7 +340,7 @@ bool NimBLEServer::disconnect(uint16_t connHandle, uint8_t reason) const {
|
||||
* @brief Disconnect the specified client with optional reason.
|
||||
* @param [in] connInfo Connection of the client to disconnect.
|
||||
* @param [in] reason code for disconnecting.
|
||||
* @return NimBLE host return code.
|
||||
* @return True if successful.
|
||||
*/
|
||||
bool NimBLEServer::disconnect(const NimBLEConnInfo& connInfo, uint8_t reason) const {
|
||||
return disconnect(connInfo.getConnHandle(), reason);
|
||||
@@ -427,13 +508,9 @@ int NimBLEServer::handleGapEvent(ble_gap_event* event, void* arg) {
|
||||
}
|
||||
# endif
|
||||
|
||||
if (pServer->m_svcChanged) {
|
||||
pServer->resetGATT();
|
||||
}
|
||||
|
||||
peerInfo.m_desc = event->disconnect.conn;
|
||||
pServer->m_pServerCallbacks->onDisconnect(pServer, peerInfo, event->disconnect.reason);
|
||||
# if !MYNEWT_VAL(BLE_EXT_ADV)
|
||||
# if !MYNEWT_VAL(BLE_EXT_ADV) && MYNEWT_VAL(BLE_ROLE_BROADCASTER)
|
||||
if (pServer->m_advertiseOnDisconnect) {
|
||||
pServer->startAdvertising();
|
||||
}
|
||||
@@ -612,6 +689,15 @@ int NimBLEServer::handleGapEvent(ble_gap_event* event, void* arg) {
|
||||
// }
|
||||
// rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
|
||||
// NIMBLE_LOGD(LOG_TAG, "BLE_SM_IOACT_OOB; ble_sm_inject_io result: %d", rc);
|
||||
} else if (event->passkey.params.action == BLE_SM_IOACT_INPUT) {
|
||||
NIMBLE_LOGD(LOG_TAG, "Enter the passkey");
|
||||
|
||||
rc = ble_gap_conn_find(event->passkey.conn_handle, &peerInfo.m_desc);
|
||||
if (rc != 0) {
|
||||
return BLE_ATT_ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
pServer->m_pServerCallbacks->onPassKeyEntry(peerInfo);
|
||||
} else if (event->passkey.params.action == BLE_SM_IOACT_NONE) {
|
||||
NIMBLE_LOGD(LOG_TAG, "No passkey action required");
|
||||
}
|
||||
@@ -782,35 +868,65 @@ void NimBLEServer::addService(NimBLEService* service) {
|
||||
|
||||
/**
|
||||
* @brief Resets the GATT server, used when services are added/removed after initialization.
|
||||
* @return True if successful.
|
||||
* @details This will reset the GATT server and re-register all services, characteristics, and
|
||||
* descriptors that have not been removed. Services, characteristics, and descriptors that have been
|
||||
* removed but not deleted will be skipped and have their handles cleared, and those that have been
|
||||
* deleted will be removed from the server's service vector.
|
||||
*/
|
||||
void NimBLEServer::resetGATT() {
|
||||
if (getConnectedCount() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool NimBLEServer::resetGATT() {
|
||||
# if MYNEWT_VAL(BLE_ROLE_BROADCASTER)
|
||||
NimBLEDevice::stopAdvertising();
|
||||
# endif
|
||||
|
||||
ble_gatts_reset();
|
||||
ble_svc_gap_init();
|
||||
ble_svc_gatt_init();
|
||||
|
||||
for (auto it = m_svcVec.begin(); it != m_svcVec.end();) {
|
||||
if ((*it)->getRemoved() > 0) {
|
||||
if ((*it)->getRemoved() == NIMBLE_ATT_REMOVE_DELETE) {
|
||||
delete *it;
|
||||
it = m_svcVec.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
for (auto svcIt = m_svcVec.begin(); svcIt != m_svcVec.end();) {
|
||||
auto* pSvc = *svcIt;
|
||||
if (pSvc->getRemoved() == NIMBLE_ATT_REMOVE_DELETE) {
|
||||
delete pSvc;
|
||||
svcIt = m_svcVec.erase(svcIt);
|
||||
continue;
|
||||
}
|
||||
|
||||
(*it)->start();
|
||||
++it;
|
||||
for (auto chrIt = pSvc->m_vChars.begin(); chrIt != pSvc->m_vChars.end();) {
|
||||
auto* pChr = *chrIt;
|
||||
if (pChr->getRemoved() == NIMBLE_ATT_REMOVE_DELETE) {
|
||||
delete pChr;
|
||||
chrIt = pSvc->m_vChars.erase(chrIt);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto dscIt = pChr->m_vDescriptors.begin(); dscIt != pChr->m_vDescriptors.end();) {
|
||||
auto* pDsc = *dscIt;
|
||||
if (pDsc->getRemoved() == NIMBLE_ATT_REMOVE_DELETE) {
|
||||
delete pDsc;
|
||||
dscIt = pChr->m_vDescriptors.erase(dscIt);
|
||||
continue;
|
||||
}
|
||||
|
||||
pDsc->m_handle = 0;
|
||||
++dscIt;
|
||||
}
|
||||
|
||||
pChr->m_handle = 0;
|
||||
++chrIt;
|
||||
}
|
||||
|
||||
if (pSvc->getRemoved() == 0) {
|
||||
if (!pSvc->start_internal()) {
|
||||
NIMBLE_LOGE(LOG_TAG, "Failed to start service: %s", pSvc->getUUID().toString().c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
pSvc->m_handle = 0;
|
||||
++svcIt;
|
||||
}
|
||||
|
||||
m_gattsStarted = false;
|
||||
return true;
|
||||
} // resetGATT
|
||||
|
||||
/**
|
||||
@@ -1025,6 +1141,11 @@ uint32_t NimBLEServerCallbacks::onPassKeyDisplay() {
|
||||
return 123456;
|
||||
} // onPassKeyDisplay
|
||||
|
||||
void NimBLEServerCallbacks::onPassKeyEntry(NimBLEConnInfo& connInfo) {
|
||||
NIMBLE_LOGD("NimBLEServerCallbacks", "onPassKeyEntry: default: 123456");
|
||||
NimBLEDevice::injectPassKey(connInfo, 123456);
|
||||
} // onPassKeyEntry
|
||||
|
||||
void NimBLEServerCallbacks::onConfirmPassKey(NimBLEConnInfo& connInfo, uint32_t pin) {
|
||||
NIMBLE_LOGD("NimBLEServerCallbacks", "onConfirmPasskey: default: true");
|
||||
NimBLEDevice::injectConfirmPasskey(connInfo, true);
|
||||
|
||||
@@ -61,7 +61,7 @@ class NimBLEClient;
|
||||
*/
|
||||
class NimBLEServer {
|
||||
public:
|
||||
void start();
|
||||
bool start();
|
||||
uint8_t getConnectedCount() const;
|
||||
bool disconnect(uint16_t connHandle, uint8_t reason = BLE_ERR_REM_USER_CONN_TERM) const;
|
||||
bool disconnect(const NimBLEConnInfo& connInfo, uint8_t reason = BLE_ERR_REM_USER_CONN_TERM) const;
|
||||
@@ -119,15 +119,16 @@ class NimBLEServer {
|
||||
|
||||
NimBLEServer();
|
||||
~NimBLEServer();
|
||||
static int handleGapEvent(struct ble_gap_event* event, void* arg);
|
||||
static int handleGattEvent(uint16_t connHandle, uint16_t attrHandle, ble_gatt_access_ctxt* ctxt, void* arg);
|
||||
void serviceChanged();
|
||||
void resetGATT();
|
||||
static int handleGapEvent(struct ble_gap_event* event, void* arg);
|
||||
static int handleGattEvent(uint16_t connHandle, uint16_t attrHandle, ble_gatt_access_ctxt* ctxt, void* arg);
|
||||
static void gattRegisterCallback(struct ble_gatt_register_ctxt* ctxt, void* arg);
|
||||
void serviceChanged();
|
||||
bool resetGATT();
|
||||
|
||||
bool m_gattsStarted : 1;
|
||||
bool m_svcChanged : 1;
|
||||
bool m_deleteCallbacks : 1;
|
||||
# if !MYNEWT_VAL(BLE_EXT_ADV)
|
||||
# if !MYNEWT_VAL(BLE_EXT_ADV) && MYNEWT_VAL(BLE_ROLE_BROADCASTER)
|
||||
bool m_advertiseOnDisconnect : 1;
|
||||
# endif
|
||||
NimBLEServerCallbacks* m_pServerCallbacks;
|
||||
@@ -179,6 +180,15 @@ class NimBLEServerCallbacks {
|
||||
*/
|
||||
virtual uint32_t onPassKeyDisplay();
|
||||
|
||||
/**
|
||||
* @brief Called when using passkey entry pairing and the peer requires the passkey to be entered.
|
||||
* @param [in] connInfo A reference to a NimBLEConnInfo instance with information
|
||||
* about the peer connection parameters.
|
||||
* @details The application should call NimBLEDevice::injectPassKey with the passkey
|
||||
* displayed on the peer device to complete the pairing process.
|
||||
*/
|
||||
virtual void onPassKeyEntry(NimBLEConnInfo& connInfo);
|
||||
|
||||
/**
|
||||
* @brief Called when using numeric comparision for pairing.
|
||||
* @param [in] connInfo A reference to a NimBLEConnInfo instance with information
|
||||
|
||||
@@ -48,12 +48,7 @@ NimBLEService::NimBLEService(const NimBLEUUID& uuid)
|
||||
* @brief Destructor, make sure we release the resources allocated for the service.
|
||||
*/
|
||||
NimBLEService::~NimBLEService() {
|
||||
if (m_pSvcDef->characteristics) {
|
||||
if (m_pSvcDef->characteristics->descriptors) {
|
||||
delete[] m_pSvcDef->characteristics->descriptors;
|
||||
}
|
||||
delete[] m_pSvcDef->characteristics;
|
||||
}
|
||||
clearServiceDefinitions();
|
||||
|
||||
for (const auto& it : m_vChars) {
|
||||
delete it;
|
||||
@@ -88,22 +83,10 @@ void NimBLEService::dump() const {
|
||||
* and registers it with the NimBLE stack.
|
||||
* @return bool success/failure .
|
||||
*/
|
||||
bool NimBLEService::start() {
|
||||
NIMBLE_LOGD(LOG_TAG, ">> start(): Starting service: %s", toString().c_str());
|
||||
|
||||
// If started previously and no characteristics have been added or removed,
|
||||
// then we can skip the service registration process.
|
||||
if (m_pSvcDef->characteristics && !getServer()->m_svcChanged) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If started previously, clear everything and start over
|
||||
if (m_pSvcDef->characteristics) {
|
||||
if (m_pSvcDef->characteristics->descriptors) {
|
||||
delete[] m_pSvcDef->characteristics->descriptors;
|
||||
}
|
||||
delete[] m_pSvcDef->characteristics;
|
||||
}
|
||||
bool NimBLEService::start_internal() {
|
||||
NIMBLE_LOGD(LOG_TAG, ">> start(): Starting service: UUID: %s", getUUID().toString().c_str());
|
||||
// Make sure the definitions are cleared first
|
||||
clearServiceDefinitions();
|
||||
|
||||
size_t numChrs = 0;
|
||||
for (const auto& chr : m_vChars) {
|
||||
@@ -113,7 +96,7 @@ bool NimBLEService::start() {
|
||||
++numChrs;
|
||||
}
|
||||
|
||||
NIMBLE_LOGD(LOG_TAG, "Adding %d characteristics for service %s", numChrs, toString().c_str());
|
||||
NIMBLE_LOGD(LOG_TAG, "Adding %zu characteristics for service %s", numChrs, getUUID().toString().c_str());
|
||||
if (numChrs) {
|
||||
int i = 0;
|
||||
|
||||
@@ -160,7 +143,7 @@ bool NimBLEService::start() {
|
||||
pChrs[i].arg = chr;
|
||||
pChrs[i].flags = chr->getProperties();
|
||||
pChrs[i].min_key_size = 0;
|
||||
pChrs[i].val_handle = &chr->m_handle;
|
||||
pChrs[i].val_handle = nullptr;
|
||||
++i;
|
||||
}
|
||||
|
||||
@@ -168,15 +151,18 @@ bool NimBLEService::start() {
|
||||
}
|
||||
|
||||
m_pSvcDef->type = BLE_GATT_SVC_TYPE_PRIMARY;
|
||||
int rc = ble_gatts_count_cfg(m_pSvcDef);
|
||||
|
||||
int rc = ble_gatts_count_cfg(m_pSvcDef);
|
||||
if (rc != 0) {
|
||||
NIMBLE_LOGE(LOG_TAG, "ble_gatts_count_cfg failed, rc= %d, %s", rc, NimBLEUtils::returnCodeToString(rc));
|
||||
clearServiceDefinitions(); // Clear the definitions to free memory and reset the service for re-registration.
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = ble_gatts_add_svcs(m_pSvcDef);
|
||||
if (rc != 0) {
|
||||
NIMBLE_LOGE(LOG_TAG, "ble_gatts_add_svcs, rc= %d, %s", rc, NimBLEUtils::returnCodeToString(rc));
|
||||
clearServiceDefinitions(); // Clear the definitions to free memory and reset the service for re-registration.
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -184,6 +170,25 @@ bool NimBLEService::start() {
|
||||
return true;
|
||||
} // start
|
||||
|
||||
/**
|
||||
* @brief Clear the service definitions to free memory and reset the service for re-registration.
|
||||
*/
|
||||
void NimBLEService::clearServiceDefinitions() {
|
||||
if (m_pSvcDef->characteristics) {
|
||||
const ble_gatt_chr_def* chrDef = m_pSvcDef->characteristics;
|
||||
while (chrDef->uuid != nullptr) {
|
||||
if (chrDef->descriptors) {
|
||||
delete[] chrDef->descriptors;
|
||||
}
|
||||
++chrDef;
|
||||
}
|
||||
delete[] m_pSvcDef->characteristics;
|
||||
m_pSvcDef->characteristics = nullptr;
|
||||
}
|
||||
|
||||
m_pSvcDef->type = 0; // Clear the type to indicate the service is not started/registered.
|
||||
} // clearServiceDefinitions
|
||||
|
||||
/**
|
||||
* @brief Create a new BLE Characteristic associated with this service.
|
||||
* @param [in] uuid - The UUID of the characteristic.
|
||||
|
||||
@@ -37,11 +37,19 @@ class NimBLEService : public NimBLELocalAttribute {
|
||||
NimBLEService(const NimBLEUUID& uuid);
|
||||
~NimBLEService();
|
||||
|
||||
NimBLEServer* getServer() const;
|
||||
std::string toString() const;
|
||||
void dump() const;
|
||||
bool isStarted() const;
|
||||
bool start();
|
||||
NimBLEServer* getServer() const;
|
||||
std::string toString() const;
|
||||
void dump() const;
|
||||
bool isStarted() const;
|
||||
|
||||
/**
|
||||
* @brief Dummy function to start the service. Services are started when the server is started.
|
||||
* This will be removed in a future release. Use `NimBLEServer::start()` to start the server and all associated services.
|
||||
*/
|
||||
__attribute__((deprecated("NimBLEService::start() has no effect. "
|
||||
"Services are started when the server is started.")))
|
||||
bool start() { return true; }
|
||||
|
||||
NimBLECharacteristic* createCharacteristic(const char* uuid,
|
||||
uint32_t properties = NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE,
|
||||
uint16_t max_len = BLE_ATT_ATTR_MAX_LEN);
|
||||
@@ -61,6 +69,8 @@ class NimBLEService : public NimBLELocalAttribute {
|
||||
|
||||
private:
|
||||
friend class NimBLEServer;
|
||||
bool start_internal();
|
||||
void clearServiceDefinitions();
|
||||
|
||||
std::vector<NimBLECharacteristic*> m_vChars{};
|
||||
// Nimble requires an array of services to be sent to the api
|
||||
|
||||
1067
src/NimBLEStream.cpp
Normal file
1067
src/NimBLEStream.cpp
Normal file
File diff suppressed because it is too large
Load Diff
227
src/NimBLEStream.h
Normal file
227
src/NimBLEStream.h
Normal file
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Copyright 2020-2025 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* esp-nimble-cpp, NimBLE-Arduino contributors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef NIMBLE_CPP_STREAM_H
|
||||
#define NIMBLE_CPP_STREAM_H
|
||||
|
||||
#include "syscfg/syscfg.h"
|
||||
#if CONFIG_BT_NIMBLE_ENABLED && (MYNEWT_VAL(BLE_ROLE_PERIPHERAL) || MYNEWT_VAL(BLE_ROLE_CENTRAL))
|
||||
|
||||
# if defined(CONFIG_NIMBLE_CPP_IDF)
|
||||
# include "nimble/nimble_npl.h"
|
||||
# else
|
||||
# include "nimble/nimble/include/nimble/nimble_npl.h"
|
||||
# endif
|
||||
|
||||
# include <functional>
|
||||
# include <type_traits>
|
||||
# include <cstdarg>
|
||||
|
||||
# if NIMBLE_CPP_ARDUINO_STRING_AVAILABLE
|
||||
# include <Stream.h>
|
||||
# else
|
||||
|
||||
// Minimal Stream/Print stubs when Arduino not available
|
||||
class Print {
|
||||
public:
|
||||
virtual ~Print() {}
|
||||
virtual size_t write(uint8_t) = 0;
|
||||
virtual size_t write(const uint8_t* buffer, size_t size) = 0;
|
||||
size_t print(const char* s);
|
||||
size_t println(const char* s);
|
||||
size_t printf(const char* format, ...) __attribute__((format(printf, 2, 3)));
|
||||
};
|
||||
|
||||
class Stream : public Print {
|
||||
public:
|
||||
virtual int available() = 0;
|
||||
virtual int read() = 0;
|
||||
virtual int peek() = 0;
|
||||
virtual void flush() {}
|
||||
void setTimeout(unsigned long timeout) { m_timeout = timeout; }
|
||||
unsigned long getTimeout() const { return m_timeout; }
|
||||
|
||||
protected:
|
||||
unsigned long m_timeout{0};
|
||||
};
|
||||
# endif
|
||||
|
||||
class NimBLEStream : public Stream {
|
||||
public:
|
||||
enum RxOverflowAction {
|
||||
DROP_OLDER_DATA, // Drop older buffered data to make room for new data
|
||||
DROP_NEW_DATA // Drop new incoming data when buffer is full
|
||||
};
|
||||
|
||||
using RxOverflowCallback = std::function<RxOverflowAction(const uint8_t* data, size_t len, void* userArg)>;
|
||||
|
||||
NimBLEStream() = default;
|
||||
virtual ~NimBLEStream() { end(); }
|
||||
|
||||
// Print/Stream TX methods
|
||||
virtual size_t write(const uint8_t* data, size_t len) override;
|
||||
virtual size_t write(uint8_t data) override { return write(&data, 1); }
|
||||
|
||||
// Template for other integral types (char, int, long, etc.)
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_integral<T>::value && !std::is_same<T, uint8_t>::value, size_t>::type write(T data) {
|
||||
return write(static_cast<uint8_t>(data));
|
||||
}
|
||||
|
||||
size_t availableForWrite() const;
|
||||
|
||||
// Read up to len bytes into buffer (non-blocking)
|
||||
size_t read(uint8_t* buffer, size_t len);
|
||||
|
||||
// Stream RX methods
|
||||
virtual int available() override;
|
||||
virtual int read() override;
|
||||
virtual int peek() override;
|
||||
virtual bool ready() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Set a callback to be invoked when incoming data exceeds RX buffer capacity.
|
||||
* @param cb The callback function, which should return DROP_OLDER_DATA to drop older buffered data and
|
||||
* make room for the new data, or DROP_NEW_DATA to drop the new data instead.
|
||||
*/
|
||||
void setRxOverflowCallback(RxOverflowCallback cb, void* userArg = nullptr) {
|
||||
m_rxOverflowCallback = cb;
|
||||
m_rxOverflowUserArg = userArg;
|
||||
}
|
||||
|
||||
operator bool() const { return ready(); }
|
||||
|
||||
using Print::write;
|
||||
|
||||
struct ByteRingBuffer;
|
||||
|
||||
protected:
|
||||
bool begin();
|
||||
void drainTx();
|
||||
size_t pushRx(const uint8_t* data, size_t len);
|
||||
virtual void end();
|
||||
virtual bool send() = 0;
|
||||
static void txDrainEventCb(struct ble_npl_event* ev);
|
||||
static void txDrainCalloutCb(struct ble_npl_event* ev);
|
||||
|
||||
ByteRingBuffer* m_txBuf{nullptr};
|
||||
ByteRingBuffer* m_rxBuf{nullptr};
|
||||
uint8_t m_txChunkBuf[MYNEWT_VAL(BLE_ATT_PREFERRED_MTU)];
|
||||
uint32_t m_txBufSize{1024};
|
||||
uint32_t m_rxBufSize{1024};
|
||||
ble_npl_event m_txDrainEvent{};
|
||||
ble_npl_callout m_txDrainCallout{};
|
||||
RxOverflowCallback m_rxOverflowCallback{nullptr};
|
||||
void* m_rxOverflowUserArg{nullptr};
|
||||
bool m_coInitialized{false};
|
||||
bool m_eventInitialized{false};
|
||||
};
|
||||
|
||||
# if MYNEWT_VAL(BLE_ROLE_PERIPHERAL)
|
||||
# include "NimBLECharacteristic.h"
|
||||
|
||||
class NimBLEStreamServer : public NimBLEStream {
|
||||
public:
|
||||
NimBLEStreamServer() : m_charCallbacks(this) {}
|
||||
~NimBLEStreamServer() override { end(); }
|
||||
|
||||
// non-copyable
|
||||
NimBLEStreamServer(const NimBLEStreamServer&) = delete;
|
||||
NimBLEStreamServer& operator=(const NimBLEStreamServer&) = delete;
|
||||
|
||||
bool begin(NimBLECharacteristic* chr, uint32_t txBufSize = 1024, uint32_t rxBufSize = 1024);
|
||||
|
||||
// Convenience overload to create service/characteristic internally; service will be deleted on end()
|
||||
bool begin(const NimBLEUUID& svcUuid,
|
||||
const NimBLEUUID& chrUuid,
|
||||
uint32_t txBufSize = 1024,
|
||||
uint32_t rxBufSize = 1024,
|
||||
bool secure = false);
|
||||
|
||||
void end() override;
|
||||
size_t write(const uint8_t* data, size_t len) override;
|
||||
uint16_t getPeerHandle() const { return m_charCallbacks.m_peerHandle; }
|
||||
void setCallbacks(NimBLECharacteristicCallbacks* pCallbacks) { m_charCallbacks.m_userCallbacks = pCallbacks; }
|
||||
bool ready() const override;
|
||||
virtual void flush() override;
|
||||
|
||||
using NimBLEStream::write; // Inherit template write overloads
|
||||
|
||||
protected:
|
||||
bool send() override;
|
||||
|
||||
struct ChrCallbacks : public NimBLECharacteristicCallbacks {
|
||||
ChrCallbacks(NimBLEStreamServer* parent)
|
||||
: m_parent(parent), m_userCallbacks(nullptr), m_peerHandle(BLE_HS_CONN_HANDLE_NONE) {}
|
||||
void onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) override;
|
||||
void onSubscribe(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo, uint16_t subValue) override;
|
||||
void onStatus(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo, int code) override;
|
||||
// override this to avoid recursion when debug logs are enabled
|
||||
void onStatus(NimBLECharacteristic* pCharacteristic, int code) override {
|
||||
if (m_userCallbacks != nullptr) {
|
||||
m_userCallbacks->onStatus(pCharacteristic, code);
|
||||
}
|
||||
}
|
||||
|
||||
NimBLEStreamServer* m_parent;
|
||||
NimBLECharacteristicCallbacks* m_userCallbacks;
|
||||
uint16_t m_peerHandle;
|
||||
} m_charCallbacks;
|
||||
|
||||
NimBLECharacteristic* m_pChr{nullptr};
|
||||
int m_rc{0};
|
||||
// Whether to delete the BLE service when end() is called; set to false if service is managed externally
|
||||
bool m_deleteSvcOnEnd{false};
|
||||
};
|
||||
# endif // BLE_ROLE_PERIPHERAL
|
||||
|
||||
# if MYNEWT_VAL(BLE_ROLE_CENTRAL)
|
||||
# include "NimBLERemoteCharacteristic.h"
|
||||
|
||||
class NimBLEStreamClient : public NimBLEStream {
|
||||
public:
|
||||
NimBLEStreamClient() = default;
|
||||
~NimBLEStreamClient() override { end(); }
|
||||
|
||||
// non-copyable
|
||||
NimBLEStreamClient(const NimBLEStreamClient&) = delete;
|
||||
NimBLEStreamClient& operator=(const NimBLEStreamClient&) = delete;
|
||||
|
||||
// Attach a discovered remote characteristic; app owns discovery/connection.
|
||||
// Set subscribeNotify=true to receive notifications into RX buffer.
|
||||
bool begin(NimBLERemoteCharacteristic* pChr,
|
||||
bool subscribeNotify = false,
|
||||
uint32_t txBufSize = 1024,
|
||||
uint32_t rxBufSize = 1024);
|
||||
void end() override;
|
||||
void setNotifyCallback(NimBLERemoteCharacteristic::notify_callback cb) { m_userNotifyCallback = cb; }
|
||||
bool ready() const override;
|
||||
virtual void flush() override;
|
||||
|
||||
using NimBLEStream::write; // Inherit template write overloads
|
||||
|
||||
protected:
|
||||
bool send() override;
|
||||
void notifyCallback(NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t len, bool isNotify);
|
||||
|
||||
NimBLERemoteCharacteristic* m_pChr{nullptr};
|
||||
NimBLERemoteCharacteristic::notify_callback m_userNotifyCallback{nullptr};
|
||||
};
|
||||
# endif // BLE_ROLE_CENTRAL
|
||||
|
||||
#endif // CONFIG_BT_NIMBLE_ENABLED && (MYNEWT_VAL(BLE_ROLE_PERIPHERAL) || MYNEWT_VAL(BLE_ROLE_CENTRAL))
|
||||
#endif // NIMBLE_CPP_STREAM_H
|
||||
@@ -38,6 +38,20 @@ static const uint8_t ble_base_uuid[] = {
|
||||
*/
|
||||
NimBLEUUID::NimBLEUUID(const ble_uuid_any_t& uuid) : m_uuid{uuid} {}
|
||||
|
||||
/**
|
||||
* @brief Create a UUID from the native UUID pointer.
|
||||
* @param [in] uuid The native UUID pointer.
|
||||
*/
|
||||
NimBLEUUID::NimBLEUUID(const ble_uuid_t* uuid) {
|
||||
if (uuid == nullptr) {
|
||||
NIMBLE_LOGE(LOG_TAG, "Invalid UUID pointer");
|
||||
m_uuid.u.type = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
ble_uuid_copy(&m_uuid, uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a UUID from a string.
|
||||
*
|
||||
|
||||
@@ -45,6 +45,7 @@ class NimBLEUUID {
|
||||
*/
|
||||
NimBLEUUID() = default;
|
||||
NimBLEUUID(const ble_uuid_any_t& uuid);
|
||||
NimBLEUUID(const ble_uuid_t* uuid);
|
||||
NimBLEUUID(const std::string& uuid);
|
||||
NimBLEUUID(uint16_t uuid);
|
||||
NimBLEUUID(uint32_t uuid);
|
||||
|
||||
Reference in New Issue
Block a user