mirror of
https://github.com/h2zero/esp-nimble-cpp.git
synced 2025-12-23 15:18:16 +01:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
067274973d | ||
|
|
30877344bd | ||
|
|
68dd0b37b5 | ||
|
|
e615deb1af | ||
|
|
e29d1ce3cf | ||
|
|
a5702746c5 | ||
|
|
f8e8830112 | ||
|
|
80def6663b | ||
|
|
7a8d10bb71 | ||
|
|
ce1b8bc2ec | ||
|
|
dadbc0d423 | ||
|
|
da31f90436 | ||
|
|
e55ad9019c | ||
|
|
59e111ab55 | ||
|
|
e00dd88add | ||
|
|
e18d78678f | ||
|
|
54ec3f4c92 | ||
|
|
9ac9551a2b | ||
|
|
0172cb4b39 | ||
|
|
856adebad5 | ||
|
|
033c7c27ad | ||
|
|
d134276d2d | ||
|
|
01976cec54 | ||
|
|
723cdf0a66 | ||
|
|
148087c508 | ||
|
|
88c5d0c7b7 | ||
|
|
a6c03a2aaa | ||
|
|
9df8445241 | ||
|
|
e5f4d52a5a | ||
|
|
7ef247c8db | ||
|
|
c582c8c67e | ||
|
|
74b5c59887 | ||
|
|
60efffdf2b | ||
|
|
b6379848ae | ||
|
|
b29919df96 | ||
|
|
052c0a0455 | ||
|
|
784e6f30fa | ||
|
|
5490cef129 | ||
|
|
459e8c9fcd | ||
|
|
ffa8414747 | ||
|
|
8130f88be8 | ||
|
|
c39e288c3e | ||
|
|
8158a16fbf | ||
|
|
57fe9cb77f |
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@@ -17,8 +17,8 @@ jobs:
|
||||
# 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.1", "v5.3.2"]
|
||||
idf_target: ["esp32", "esp32s3", "esp32c2", "esp32c3", "esp32c6", "esp32h2", "esp32p4"]
|
||||
idf_ver: ["release-v4.4", "release-v5.4"]
|
||||
idf_target: ["esp32", "esp32s3", "esp32c2", "esp32c3", "esp32c5", "esp32c6", "esp32h2", "esp32p4"]
|
||||
example:
|
||||
- NimBLE_Client
|
||||
- NimBLE_Server
|
||||
@@ -31,6 +31,8 @@ jobs:
|
||||
example: Bluetooth_5/NimBLE_extended_server
|
||||
- idf_ver: release-v4.4
|
||||
idf_target: "esp32c2"
|
||||
- idf_ver: release-v4.4
|
||||
idf_target: "esp32c5"
|
||||
- idf_ver: release-v4.4
|
||||
idf_target: "esp32c6"
|
||||
- idf_ver: release-v4.4
|
||||
|
||||
36
CHANGELOG.md
36
CHANGELOG.md
@@ -1,6 +1,42 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [2.3.0] 2025-05-19
|
||||
|
||||
## Fixed
|
||||
- Incorrect `NimBLECharacteristic::onSubscribe` value when indications are set.
|
||||
- `NimBLECharacteristic::onRead` callback not called in some cases.
|
||||
- Clear attribute value when zero length value is written.
|
||||
- Notify/Indicate incorrectly returning success with custom value.
|
||||
- Corrected NimBLEClient array initialization.
|
||||
- Prevent potential exception when scan is restarted.
|
||||
- Attribute getValue failing with some data types
|
||||
- Incorrectly passing a pointer to a function taking const reference.
|
||||
|
||||
## Added
|
||||
- Support for esp32c5
|
||||
- L2CAP infrastructure.
|
||||
- Scan duplicate cache reset time.
|
||||
|
||||
## Changed
|
||||
- Cleaned up examples.
|
||||
- Allow PHY updates without enabling extended advertising.
|
||||
|
||||
## [2.2.1] 2025-02-28
|
||||
|
||||
## Fixed
|
||||
- Added back `NimBLEClient::connect` overload with `NimBLEAdvertisedDevice` parameter to resolve connection error due to NULL address.
|
||||
- Crash caused by returning invalid vector entry when retrieving remote descriptors.
|
||||
|
||||
## [2.2.0] 2025-02-24
|
||||
|
||||
## Fixed
|
||||
- Crash when calling `NimBLEClient::DiscoverAttributes`.
|
||||
|
||||
## Added
|
||||
- Conditional macros for logging.
|
||||
- `NimBLEDeviceCallbacks` class with a callback for handling bond storage.
|
||||
|
||||
## [2.1.1] 2025-01-26
|
||||
|
||||
## Fixed
|
||||
|
||||
@@ -35,6 +35,7 @@ idf_component_register(
|
||||
"esp32s3"
|
||||
"esp32c2"
|
||||
"esp32c3"
|
||||
"esp32c5"
|
||||
"esp32c6"
|
||||
"esp32h2"
|
||||
"esp32p4"
|
||||
@@ -55,6 +56,8 @@ idf_component_register(
|
||||
"src/NimBLEEddystoneTLM.cpp"
|
||||
"src/NimBLEExtAdvertising.cpp"
|
||||
"src/NimBLEHIDDevice.cpp"
|
||||
"src/NimBLEL2CAPChannel.cpp"
|
||||
"src/NimBLEL2CAPServer.cpp"
|
||||
"src/NimBLERemoteCharacteristic.cpp"
|
||||
"src/NimBLERemoteDescriptor.cpp"
|
||||
"src/NimBLERemoteService.cpp"
|
||||
|
||||
2
NOTICE
2
NOTICE
@@ -1,6 +1,6 @@
|
||||
esp-nimble-cpp
|
||||
NimBLE-Arduino
|
||||
Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
Copyright 2020-2025 Ryan Powell <ryan@nable-embedded.io> and
|
||||
esp-nimble-cpp, NimBLE-Arduino contributors.
|
||||
|
||||
The Initial Developer of some parts of this library, which are copied from,
|
||||
|
||||
@@ -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.1.1
|
||||
PROJECT_NUMBER = 2.3.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
|
||||
|
||||
@@ -65,8 +65,7 @@ class AdvertisingCallbacks : public NimBLEExtAdvertisingCallbacks {
|
||||
}
|
||||
} advertisingCallbacks;
|
||||
|
||||
extern "C"
|
||||
void app_main(void) {
|
||||
extern "C" void app_main(void) {
|
||||
/** Initialize NimBLE and set the device name */
|
||||
NimBLEDevice::init("Extended advertiser");
|
||||
|
||||
@@ -105,7 +104,6 @@ void app_main(void) {
|
||||
"This example message is 226 bytes long "
|
||||
"and is using CODED_PHY for long range."));
|
||||
|
||||
extAdv.setCompleteServices16({NimBLEUUID(SERVICE_UUID)});
|
||||
extAdv.setName("Extended advertiser");
|
||||
|
||||
/** When extended advertising is enabled `NimBLEDevice::getAdvertising` returns a pointer to `NimBLEExtAdvertising */
|
||||
|
||||
5
examples/L2CAP/.gitignore
vendored
Normal file
5
examples/L2CAP/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
.vscode
|
||||
build
|
||||
sdkconfig
|
||||
sdkconfig.old
|
||||
dependencies.lock
|
||||
7
examples/L2CAP/L2CAP_Client/CMakeLists.txt
Normal file
7
examples/L2CAP/L2CAP_Client/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
# 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)
|
||||
set(SUPPORTED_TARGETS esp32 esp32s3 esp32c3 esp32c6)
|
||||
project(L2CAP_client)
|
||||
3
examples/L2CAP/L2CAP_Client/Makefile
Normal file
3
examples/L2CAP/L2CAP_Client/Makefile
Normal file
@@ -0,0 +1,3 @@
|
||||
PROJECT_NAME := L2CAP_client
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
4
examples/L2CAP/L2CAP_Client/main/CMakeLists.txt
Normal file
4
examples/L2CAP/L2CAP_Client/main/CMakeLists.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
set(COMPONENT_SRCS "main.cpp")
|
||||
set(COMPONENT_ADD_INCLUDEDIRS ".")
|
||||
|
||||
register_component()
|
||||
4
examples/L2CAP/L2CAP_Client/main/component.mk
Normal file
4
examples/L2CAP/L2CAP_Client/main/component.mk
Normal file
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
3
examples/L2CAP/L2CAP_Client/main/idf_component.yml
Normal file
3
examples/L2CAP/L2CAP_Client/main/idf_component.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
dependencies:
|
||||
local/esp-nimble-cpp:
|
||||
path: ../../../../../esp-nimble-cpp/
|
||||
165
examples/L2CAP/L2CAP_Client/main/main.cpp
Normal file
165
examples/L2CAP/L2CAP_Client/main/main.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
#include <NimBLEDevice.h>
|
||||
|
||||
// See the following for generating UUIDs:
|
||||
// https://www.uuidgenerator.net/
|
||||
|
||||
// The remote service we wish to connect to.
|
||||
static BLEUUID serviceUUID("dcbc7255-1e9e-49a0-a360-b0430b6c6905");
|
||||
// The characteristic of the remote service we are interested in.
|
||||
static BLEUUID charUUID("371a55c8-f251-4ad2-90b3-c7c195b049be");
|
||||
|
||||
#define L2CAP_CHANNEL 150
|
||||
#define L2CAP_MTU 5000
|
||||
|
||||
const BLEAdvertisedDevice* theDevice = NULL;
|
||||
BLEClient* theClient = NULL;
|
||||
BLEL2CAPChannel* theChannel = NULL;
|
||||
|
||||
size_t bytesSent = 0;
|
||||
size_t bytesReceived = 0;
|
||||
|
||||
class L2CAPChannelCallbacks: public BLEL2CAPChannelCallbacks {
|
||||
|
||||
public:
|
||||
void onConnect(NimBLEL2CAPChannel* channel) {
|
||||
printf("L2CAP connection established\n");
|
||||
}
|
||||
|
||||
void onMTUChange(NimBLEL2CAPChannel* channel, uint16_t mtu) {
|
||||
printf("L2CAP MTU changed to %d\n", mtu);
|
||||
}
|
||||
|
||||
void onRead(NimBLEL2CAPChannel* channel, std::vector<uint8_t>& data) {
|
||||
printf("L2CAP read %d bytes\n", data.size());
|
||||
}
|
||||
void onDisconnect(NimBLEL2CAPChannel* channel) {
|
||||
printf("L2CAP disconnected\n");
|
||||
}
|
||||
};
|
||||
|
||||
class MyClientCallbacks: public BLEClientCallbacks {
|
||||
|
||||
void onConnect(BLEClient* pClient) {
|
||||
printf("GAP connected\n");
|
||||
pClient->setDataLen(251);
|
||||
|
||||
theChannel = BLEL2CAPChannel::connect(pClient, L2CAP_CHANNEL, L2CAP_MTU, new L2CAPChannelCallbacks());
|
||||
}
|
||||
|
||||
void onDisconnect(BLEClient* pClient, int reason) {
|
||||
printf("GAP disconnected (reason: %d)\n", reason);
|
||||
theDevice = NULL;
|
||||
theChannel = NULL;
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
BLEDevice::getScan()->start(5 * 1000, true);
|
||||
}
|
||||
};
|
||||
|
||||
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
|
||||
|
||||
void onResult(const BLEAdvertisedDevice* advertisedDevice) {
|
||||
if (theDevice) { return; }
|
||||
printf("BLE Advertised Device found: %s\n", advertisedDevice->toString().c_str());
|
||||
|
||||
if (!advertisedDevice->haveServiceUUID()) { return; }
|
||||
if (!advertisedDevice->isAdvertisingService(serviceUUID)) { return; }
|
||||
|
||||
printf("Found the device we're interested in!\n");
|
||||
BLEDevice::getScan()->stop();
|
||||
|
||||
// Hand over the device to the other task
|
||||
theDevice = advertisedDevice;
|
||||
}
|
||||
};
|
||||
|
||||
void connectTask(void *pvParameters) {
|
||||
|
||||
uint8_t sequenceNumber = 0;
|
||||
|
||||
while (true) {
|
||||
|
||||
if (!theDevice) {
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!theClient) {
|
||||
theClient = BLEDevice::createClient();
|
||||
theClient->setConnectionParams(6, 6, 0, 42);
|
||||
|
||||
auto callbacks = new MyClientCallbacks();
|
||||
theClient->setClientCallbacks(callbacks);
|
||||
|
||||
auto success = theClient->connect(theDevice);
|
||||
if (!success) {
|
||||
printf("Error: Could not connect to device\n");
|
||||
break;
|
||||
}
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!theChannel) {
|
||||
printf("l2cap channel not initialized\n");
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!theChannel->isConnected()) {
|
||||
printf("l2cap channel not connected\n");
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
continue;
|
||||
}
|
||||
|
||||
while (theChannel->isConnected()) {
|
||||
|
||||
/*
|
||||
static auto initialDelay = true;
|
||||
if (initialDelay) {
|
||||
printf("Waiting gracefully 3 seconds before sending data\n");
|
||||
vTaskDelay(3000 / portTICK_PERIOD_MS);
|
||||
initialDelay = false;
|
||||
};
|
||||
*/
|
||||
std::vector<uint8_t> data(5000, sequenceNumber++);
|
||||
if (theChannel->write(data)) {
|
||||
bytesSent += data.size();
|
||||
} else {
|
||||
printf("failed to send!\n");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C"
|
||||
void app_main(void) {
|
||||
printf("Starting L2CAP client example\n");
|
||||
|
||||
xTaskCreate(connectTask, "connectTask", 5000, NULL, 1, NULL);
|
||||
|
||||
BLEDevice::init("L2CAP-Client");
|
||||
BLEDevice::setMTU(BLE_ATT_MTU_MAX);
|
||||
|
||||
auto scan = BLEDevice::getScan();
|
||||
auto callbacks = new MyAdvertisedDeviceCallbacks();
|
||||
scan->setScanCallbacks(callbacks);
|
||||
scan->setInterval(1349);
|
||||
scan->setWindow(449);
|
||||
scan->setActiveScan(true);
|
||||
scan->start(25 * 1000, false);
|
||||
|
||||
int numberOfSeconds = 0;
|
||||
|
||||
while (bytesSent == 0) {
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
int bytesSentPerSeconds = bytesSent / ++numberOfSeconds;
|
||||
printf("Bandwidth: %d b/sec = %d KB/sec\n", bytesSentPerSeconds, bytesSentPerSeconds / 1024);
|
||||
}
|
||||
}
|
||||
13
examples/L2CAP/L2CAP_Client/sdkconfig.defaults
Normal file
13
examples/L2CAP/L2CAP_Client/sdkconfig.defaults
Normal file
@@ -0,0 +1,13 @@
|
||||
# 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
|
||||
CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM=1
|
||||
7
examples/L2CAP/L2CAP_Server/CMakeLists.txt
Normal file
7
examples/L2CAP/L2CAP_Server/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
# 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)
|
||||
set(SUPPORTED_TARGETS esp32 esp32s3 esp32c3 esp32c6)
|
||||
project(L2CAP_server)
|
||||
3
examples/L2CAP/L2CAP_Server/Makefile
Normal file
3
examples/L2CAP/L2CAP_Server/Makefile
Normal file
@@ -0,0 +1,3 @@
|
||||
PROJECT_NAME := L2CAP_server
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
4
examples/L2CAP/L2CAP_Server/main/CMakeLists.txt
Normal file
4
examples/L2CAP/L2CAP_Server/main/CMakeLists.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
set(COMPONENT_SRCS "main.cpp")
|
||||
set(COMPONENT_ADD_INCLUDEDIRS ".")
|
||||
|
||||
register_component()
|
||||
4
examples/L2CAP/L2CAP_Server/main/component.mk
Normal file
4
examples/L2CAP/L2CAP_Server/main/component.mk
Normal file
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
3
examples/L2CAP/L2CAP_Server/main/idf_component.yml
Normal file
3
examples/L2CAP/L2CAP_Server/main/idf_component.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
dependencies:
|
||||
local/esp-nimble-cpp:
|
||||
path: ../../../../../esp-nimble-cpp/
|
||||
90
examples/L2CAP/L2CAP_Server/main/main.cpp
Normal file
90
examples/L2CAP/L2CAP_Server/main/main.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
#include <NimBLEDevice.h>
|
||||
|
||||
// See the following for generating UUIDs:
|
||||
// https://www.uuidgenerator.net/
|
||||
|
||||
#define SERVICE_UUID "dcbc7255-1e9e-49a0-a360-b0430b6c6905"
|
||||
#define CHARACTERISTIC_UUID "371a55c8-f251-4ad2-90b3-c7c195b049be"
|
||||
#define L2CAP_CHANNEL 150
|
||||
#define L2CAP_MTU 5000
|
||||
|
||||
class GATTCallbacks: public BLEServerCallbacks {
|
||||
|
||||
public:
|
||||
void onConnect(BLEServer* pServer, BLEConnInfo& info) {
|
||||
/// Booster #1
|
||||
pServer->setDataLen(info.getConnHandle(), 251);
|
||||
/// Booster #2 (especially for Apple devices)
|
||||
BLEDevice::getServer()->updateConnParams(info.getConnHandle(), 12, 12, 0, 200);
|
||||
}
|
||||
};
|
||||
|
||||
class L2CAPChannelCallbacks: public BLEL2CAPChannelCallbacks {
|
||||
|
||||
public:
|
||||
bool connected = false;
|
||||
size_t numberOfReceivedBytes;
|
||||
uint8_t nextSequenceNumber;
|
||||
|
||||
public:
|
||||
void onConnect(NimBLEL2CAPChannel* channel) {
|
||||
printf("L2CAP connection established\n");
|
||||
connected = true;
|
||||
numberOfReceivedBytes = nextSequenceNumber = 0;
|
||||
}
|
||||
|
||||
void onRead(NimBLEL2CAPChannel* channel, std::vector<uint8_t>& data) {
|
||||
numberOfReceivedBytes += data.size();
|
||||
size_t sequenceNumber = data[0];
|
||||
printf("L2CAP read %d bytes w/ sequence number %d", data.size(), sequenceNumber);
|
||||
if (sequenceNumber != nextSequenceNumber) {
|
||||
printf("(wrong sequence number %d, expected %d)\n", sequenceNumber, nextSequenceNumber);
|
||||
} else {
|
||||
printf("\n");
|
||||
nextSequenceNumber++;
|
||||
}
|
||||
}
|
||||
void onDisconnect(NimBLEL2CAPChannel* channel) {
|
||||
printf("L2CAP disconnected\n");
|
||||
connected = false;
|
||||
}
|
||||
};
|
||||
|
||||
extern "C"
|
||||
void app_main(void) {
|
||||
printf("Starting L2CAP server example [%lu free] [%lu min]\n", esp_get_free_heap_size(), esp_get_minimum_free_heap_size());
|
||||
|
||||
BLEDevice::init("L2CAP-Server");
|
||||
BLEDevice::setMTU(BLE_ATT_MTU_MAX);
|
||||
|
||||
auto cocServer = BLEDevice::createL2CAPServer();
|
||||
auto l2capChannelCallbacks = new L2CAPChannelCallbacks();
|
||||
auto channel = cocServer->createService(L2CAP_CHANNEL, L2CAP_MTU, l2capChannelCallbacks);
|
||||
|
||||
auto server = BLEDevice::createServer();
|
||||
server->setCallbacks(new GATTCallbacks());
|
||||
auto service = server->createService(SERVICE_UUID);
|
||||
auto characteristic = service->createCharacteristic(CHARACTERISTIC_UUID, NIMBLE_PROPERTY::READ);
|
||||
characteristic->setValue(L2CAP_CHANNEL);
|
||||
service->start();
|
||||
auto advertising = BLEDevice::getAdvertising();
|
||||
advertising->addServiceUUID(SERVICE_UUID);
|
||||
advertising->enableScanResponse(true);
|
||||
|
||||
BLEDevice::startAdvertising();
|
||||
printf("Server waiting for connection requests [%lu free] [%lu min]\n", esp_get_free_heap_size(), esp_get_minimum_free_heap_size());
|
||||
|
||||
// Wait until transfer actually starts...
|
||||
while (!l2capChannelCallbacks->numberOfReceivedBytes) {
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
}
|
||||
printf("\n\n\n");
|
||||
int numberOfSeconds = 0;
|
||||
|
||||
while (true) {
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
if (!l2capChannelCallbacks->connected) { continue; }
|
||||
int bps = l2capChannelCallbacks->numberOfReceivedBytes / ++numberOfSeconds;
|
||||
printf("Bandwidth: %d b/sec = %d KB/sec [%lu free] [%lu min]\n", bps, bps / 1024, esp_get_free_heap_size(), esp_get_minimum_free_heap_size());
|
||||
}
|
||||
}
|
||||
13
examples/L2CAP/L2CAP_Server/sdkconfig.defaults
Normal file
13
examples/L2CAP/L2CAP_Server/sdkconfig.defaults
Normal file
@@ -0,0 +1,13 @@
|
||||
# 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
|
||||
CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM=1
|
||||
@@ -1,5 +1,5 @@
|
||||
## IDF Component Manager Manifest File
|
||||
version: "2.1.1"
|
||||
version: "2.3.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.1.1",
|
||||
"version": "2.2.1",
|
||||
"description": "C++ wrapper for the NimBLE BLE stack",
|
||||
"keywords": [
|
||||
"BLE",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
@@ -68,7 +68,7 @@ class NimBLE2904 : public NimBLEDescriptor {
|
||||
static const uint8_t FORMAT_OPAQUE = 27;
|
||||
static const uint8_t FORMAT_MEDASN1 = 28;
|
||||
|
||||
void setDescription(uint16_t);
|
||||
void setDescription(uint16_t description);
|
||||
void setExponent(int8_t exponent);
|
||||
void setFormat(uint8_t format);
|
||||
void setNamespace(uint8_t namespace_value);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
@@ -780,6 +780,16 @@ bool NimBLEAdvertisedDevice::isLegacyAdvertisement() const {
|
||||
# endif
|
||||
} // isLegacyAdvertisement
|
||||
|
||||
/**
|
||||
* @brief Convenience operator to convert this NimBLEAdvertisedDevice to NimBLEAddress representation.
|
||||
* @details This allows passing NimBLEAdvertisedDevice to functions
|
||||
* that accept NimBLEAddress and/or or it's methods as a parameter.
|
||||
*/
|
||||
NimBLEAdvertisedDevice::operator NimBLEAddress() const {
|
||||
NimBLEAddress address(getAddress());
|
||||
return address;
|
||||
} // operator NimBLEAddress
|
||||
|
||||
/**
|
||||
* @brief Get the payload advertised by the device.
|
||||
* @return The advertisement payload.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
@@ -93,6 +93,7 @@ class NimBLEAdvertisedDevice {
|
||||
uint8_t getSecondaryPhy() const;
|
||||
uint16_t getPeriodicInterval() const;
|
||||
# endif
|
||||
operator NimBLEAddress() const;
|
||||
|
||||
const std::vector<uint8_t>& getPayload() const;
|
||||
const std::vector<uint8_t>::const_iterator begin() const;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
@@ -38,7 +38,7 @@ class NimBLEAdvertisementData {
|
||||
bool addData(const uint8_t* data, size_t length);
|
||||
bool addData(const std::vector<uint8_t>& data);
|
||||
bool setAppearance(uint16_t appearance);
|
||||
bool setFlags(uint8_t);
|
||||
bool setFlags(uint8_t flag);
|
||||
bool addTxPower();
|
||||
bool setPreferredParams(uint16_t minInterval, uint16_t maxInterval);
|
||||
bool addServiceUUID(const NimBLEUUID& serviceUUID);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
@@ -105,7 +105,8 @@ void NimBLEAttValue::deepCopy(const NimBLEAttValue& source) {
|
||||
|
||||
// Set the value of the attribute.
|
||||
bool NimBLEAttValue::setValue(const uint8_t* value, uint16_t len) {
|
||||
m_attr_len = 0; // Just set the value length to 0 and append instead of repeating code.
|
||||
m_attr_len = 0; // Just set the value length to 0 and append instead of repeating code.
|
||||
m_attr_value[0] = '\0'; // Set the first byte to 0 incase the len of the new value is 0.
|
||||
append(value, len);
|
||||
return memcmp(m_attr_value, value, len) == 0 && m_attr_len == len;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
@@ -305,9 +305,6 @@ class NimBLEAttValue {
|
||||
*/
|
||||
template <typename T>
|
||||
T getValue(time_t* timestamp = nullptr, bool skipSizeCheck = false) const {
|
||||
if (!skipSizeCheck && size() < sizeof(T)) {
|
||||
return T();
|
||||
}
|
||||
if (timestamp != nullptr) {
|
||||
# if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED
|
||||
*timestamp = m_timestamp;
|
||||
@@ -316,6 +313,9 @@ class NimBLEAttValue {
|
||||
# endif
|
||||
}
|
||||
|
||||
if (!skipSizeCheck && size() < sizeof(T)) {
|
||||
return T();
|
||||
}
|
||||
return *(reinterpret_cast<const T*>(m_attr_value));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
@@ -268,17 +268,32 @@ bool NimBLECharacteristic::sendValue(const uint8_t* value, size_t length, bool i
|
||||
int rc = 0;
|
||||
|
||||
if (value != nullptr && length > 0) { // custom notification value
|
||||
// Notify all connected peers unless a specific handle is provided
|
||||
for (const auto& ch : NimBLEDevice::getServer()->getPeerDevices()) {
|
||||
if (connHandle != BLE_HS_CONN_HANDLE_NONE && ch != connHandle) {
|
||||
continue; // only send to the specific handle, minor inefficiency but saves code.
|
||||
os_mbuf* om = nullptr;
|
||||
|
||||
if (connHandle != BLE_HS_CONN_HANDLE_NONE) { // only sending to specific peer
|
||||
om = ble_hs_mbuf_from_flat(value, length);
|
||||
if (!om) {
|
||||
rc = BLE_HS_ENOMEM;
|
||||
goto done;
|
||||
}
|
||||
|
||||
// Null buffer will read the value from the characteristic
|
||||
if (isNotification) {
|
||||
rc = ble_gattc_notify_custom(connHandle, m_handle, om);
|
||||
} else {
|
||||
rc = ble_gattc_indicate_custom(connHandle, m_handle, om);
|
||||
}
|
||||
|
||||
goto done;
|
||||
}
|
||||
|
||||
// Notify all connected peers unless a specific handle is provided
|
||||
for (const auto& ch : NimBLEDevice::getServer()->getPeerDevices()) {
|
||||
// Must re-create the data buffer on each iteration because it is freed by the calls bellow.
|
||||
os_mbuf* om = ble_hs_mbuf_from_flat(value, length);
|
||||
om = ble_hs_mbuf_from_flat(value, length);
|
||||
if (!om) {
|
||||
NIMBLE_LOGE(LOG_TAG, "<< sendValue: failed to allocate mbuf");
|
||||
return false;
|
||||
rc = BLE_HS_ENOMEM;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (isNotification) {
|
||||
@@ -286,24 +301,25 @@ bool NimBLECharacteristic::sendValue(const uint8_t* value, size_t length, bool i
|
||||
} else {
|
||||
rc = ble_gattc_indicate_custom(ch, m_handle, om);
|
||||
}
|
||||
|
||||
if (rc != 0) {
|
||||
NIMBLE_LOGE(LOG_TAG, "<< sendValue: failed to send value, rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (connHandle != BLE_HS_CONN_HANDLE_NONE) { // only sending to specific peer
|
||||
} else if (connHandle != BLE_HS_CONN_HANDLE_NONE) {
|
||||
// Null buffer will read the value from the characteristic
|
||||
if (isNotification) {
|
||||
rc = ble_gattc_notify_custom(connHandle, m_handle, NULL);
|
||||
rc = ble_gattc_notify_custom(connHandle, m_handle, nullptr);
|
||||
} else {
|
||||
rc = ble_gattc_indicate_custom(connHandle, m_handle, NULL);
|
||||
rc = ble_gattc_indicate_custom(connHandle, m_handle, nullptr);
|
||||
}
|
||||
} else { // Notify or indicate to all connected peers the characteristic value
|
||||
ble_gatts_chr_updated(m_handle);
|
||||
}
|
||||
|
||||
return rc == 0;
|
||||
done:
|
||||
if (rc != 0) {
|
||||
NIMBLE_LOGE(LOG_TAG, "failed to send value, rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} // sendValue
|
||||
|
||||
void NimBLECharacteristic::readEvent(NimBLEConnInfo& connInfo) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
@@ -87,7 +87,9 @@ class NimBLECharacteristic : public NimBLELocalValueAttribute {
|
||||
# ifdef _DOXYGEN_
|
||||
bool
|
||||
# else
|
||||
typename std::enable_if<!std::is_pointer<T>::value && !Has_c_str_length<T>::value && !Has_data_size<T>::value, bool>::type
|
||||
typename std::enable_if<!std::is_pointer<T>::value && !std::is_array<T>::value && !Has_c_str_length<T>::value &&
|
||||
!Has_data_size<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), sizeof(T), connHandle);
|
||||
@@ -133,7 +135,9 @@ class NimBLECharacteristic : public NimBLELocalValueAttribute {
|
||||
# ifdef _DOXYGEN_
|
||||
bool
|
||||
# else
|
||||
typename std::enable_if<!std::is_pointer<T>::value && !Has_c_str_length<T>::value && !Has_data_size<T>::value, bool>::type
|
||||
typename std::enable_if<!std::is_pointer<T>::value && !std::is_array<T>::value && !Has_c_str_length<T>::value &&
|
||||
!Has_data_size<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), sizeof(T), connHandle);
|
||||
@@ -182,8 +186,8 @@ class NimBLECharacteristic : public NimBLELocalValueAttribute {
|
||||
* @note This function is only available if the type T is not a pointer.
|
||||
*/
|
||||
template <typename T>
|
||||
typename std::enable_if<!std::is_pointer<T>::value, bool>::type notify(const T& value,
|
||||
uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const {
|
||||
typename std::enable_if<!std::is_pointer<T>::value && !std::is_array<T>::value, bool>::type notify(
|
||||
const T& value, uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const {
|
||||
if constexpr (Has_data_size<T>::value) {
|
||||
return notify(reinterpret_cast<const uint8_t*>(value.data()), value.size(), connHandle);
|
||||
} else if constexpr (Has_c_str_length<T>::value) {
|
||||
@@ -204,7 +208,7 @@ class NimBLECharacteristic : public NimBLELocalValueAttribute {
|
||||
* @note This function is only available if the type T is not a pointer.
|
||||
*/
|
||||
template <typename T>
|
||||
typename std::enable_if<!std::is_pointer<T>::value, bool>::type indicate(
|
||||
typename std::enable_if<!std::is_pointer<T>::value && !std::is_array<T>::value, bool>::type indicate(
|
||||
const T& value, uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const {
|
||||
if constexpr (Has_data_size<T>::value) {
|
||||
return indicate(reinterpret_cast<const uint8_t*>(value.data()), value.size(), connHandle);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
@@ -126,6 +126,22 @@ size_t NimBLEClient::deleteService(const NimBLEUUID& uuid) {
|
||||
return m_svcVec.size();
|
||||
} // deleteService
|
||||
|
||||
/**
|
||||
* @brief Connect to an advertising device.
|
||||
* @param [in] pDevice A pointer to the advertised device instance to connect to.
|
||||
* @param [in] deleteAttributes If true this will delete any attribute objects this client may already\n
|
||||
* have created when last connected.
|
||||
* @param [in] asyncConnect If true, the connection will be made asynchronously and this function will return immediately.\n
|
||||
* If false, this function will block until the connection is established or the connection attempt times out.
|
||||
* @param [in] exchangeMTU If true, the client will attempt to exchange MTU with the server after connection.\n
|
||||
* If false, the client will use the default MTU size and the application will need to call exchangeMTU() later.
|
||||
* @return true on success.
|
||||
*/
|
||||
bool NimBLEClient::connect(const NimBLEAdvertisedDevice* pDevice, bool deleteAttributes, bool asyncConnect, bool exchangeMTU) {
|
||||
NimBLEAddress address(pDevice->getAddress());
|
||||
return connect(address, deleteAttributes, asyncConnect, exchangeMTU);
|
||||
} // connect
|
||||
|
||||
/**
|
||||
* @brief Connect to the BLE Server using the address of the last connected device, or the address\n
|
||||
* passed to the constructor.
|
||||
@@ -141,22 +157,6 @@ bool NimBLEClient::connect(bool deleteAttributes, bool asyncConnect, bool exchan
|
||||
return connect(m_peerAddress, deleteAttributes, asyncConnect, exchangeMTU);
|
||||
} // connect
|
||||
|
||||
/**
|
||||
* @brief Connect to an advertising device.
|
||||
* @param [in] device The device to connect to.
|
||||
* @param [in] deleteAttributes If true this will delete any attribute objects this client may already\n
|
||||
* have created when last connected.
|
||||
* @param [in] asyncConnect If true, the connection will be made asynchronously and this function will return immediately.\n
|
||||
* If false, this function will block until the connection is established or the connection attempt times out.
|
||||
* @param [in] exchangeMTU If true, the client will attempt to exchange MTU with the server after connection.\n
|
||||
* If false, the client will use the default MTU size and the application will need to call exchangeMTU() later.
|
||||
* @return true on success.
|
||||
*/
|
||||
bool NimBLEClient::connect(const NimBLEAdvertisedDevice* device, bool deleteAttributes, bool asyncConnect, bool exchangeMTU) {
|
||||
NimBLEAddress address(device->getAddress());
|
||||
return connect(address, deleteAttributes, asyncConnect, exchangeMTU);
|
||||
} // connect
|
||||
|
||||
/**
|
||||
* @brief Connect to a BLE Server by address.
|
||||
* @param [in] address The address of the server.
|
||||
@@ -408,6 +408,7 @@ void NimBLEClient::setConfig(NimBLEClient::Config config) {
|
||||
void NimBLEClient::setConnectPhy(uint8_t mask) {
|
||||
m_phyMask = mask;
|
||||
} // setConnectPhy
|
||||
# endif
|
||||
|
||||
/**
|
||||
* @brief Request a change to the PHY used for this peer connection.
|
||||
@@ -450,7 +451,6 @@ bool NimBLEClient::getPhy(uint8_t* txPhy, uint8_t* rxPhy) {
|
||||
|
||||
return rc == 0;
|
||||
} // getPhy
|
||||
# endif
|
||||
|
||||
/**
|
||||
* @brief Set the connection parameters to use when connecting to a server.
|
||||
@@ -1141,7 +1141,6 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
|
||||
break;
|
||||
} // BLE_GAP_EVENT_IDENTITY_RESOLVED
|
||||
|
||||
# if CONFIG_BT_NIMBLE_EXT_ADV
|
||||
case BLE_GAP_EVENT_PHY_UPDATE_COMPLETE: {
|
||||
NimBLEConnInfo peerInfo;
|
||||
rc = ble_gap_conn_find(event->phy_updated.conn_handle, &peerInfo.m_desc);
|
||||
@@ -1152,7 +1151,6 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
|
||||
pClient->m_pClientCallbacks->onPhyUpdate(pClient, event->phy_updated.tx_phy, event->phy_updated.rx_phy);
|
||||
return 0;
|
||||
} // BLE_GAP_EVENT_PHY_UPDATE_COMPLETE
|
||||
# endif
|
||||
|
||||
case BLE_GAP_EVENT_MTU: {
|
||||
if (pClient->m_connHandle != event->mtu.conn_handle) {
|
||||
@@ -1298,10 +1296,9 @@ void NimBLEClientCallbacks::onMTUChange(NimBLEClient* pClient, uint16_t mtu) {
|
||||
NIMBLE_LOGD(CB_TAG, "onMTUChange: default");
|
||||
} // onMTUChange
|
||||
|
||||
# if CONFIG_BT_NIMBLE_EXT_ADV
|
||||
void NimBLEClientCallbacks::onPhyUpdate(NimBLEClient* pClient, uint8_t txPhy, uint8_t rxPhy) {
|
||||
NIMBLE_LOGD(CB_TAG, "onPhyUpdate: default, txPhy: %d, rxPhy: %d", txPhy, rxPhy);
|
||||
} // onPhyUpdate
|
||||
# endif
|
||||
#
|
||||
|
||||
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
@@ -95,9 +95,9 @@ class NimBLEClient {
|
||||
|
||||
# if CONFIG_BT_NIMBLE_EXT_ADV
|
||||
void setConnectPhy(uint8_t phyMask);
|
||||
# endif
|
||||
bool updatePhy(uint8_t txPhysMask, uint8_t rxPhysMask, uint16_t phyOptions = 0);
|
||||
bool getPhy(uint8_t* txPhy, uint8_t* rxPhy);
|
||||
# endif
|
||||
|
||||
struct Config {
|
||||
uint8_t deleteCallbacks : 1; // Delete the callback object when the client is deleted.
|
||||
@@ -213,7 +213,6 @@ class NimBLEClientCallbacks {
|
||||
*/
|
||||
virtual void onMTUChange(NimBLEClient* pClient, uint16_t MTU);
|
||||
|
||||
# if CONFIG_BT_NIMBLE_EXT_ADV
|
||||
/**
|
||||
* @brief Called when the PHY update procedure is complete.
|
||||
* @param [in] pClient A pointer to the client whose PHY was updated.
|
||||
@@ -226,7 +225,6 @@ class NimBLEClientCallbacks {
|
||||
* * BLE_GAP_LE_PHY_CODED
|
||||
*/
|
||||
virtual void onPhyUpdate(NimBLEClient* pClient, uint8_t txPhy, uint8_t rxPhy);
|
||||
# endif
|
||||
};
|
||||
|
||||
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
@@ -65,6 +65,9 @@
|
||||
|
||||
# if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
|
||||
# include "NimBLEServer.h"
|
||||
# if CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM > 0
|
||||
# include "NimBLEL2CAPServer.h"
|
||||
# endif
|
||||
# endif
|
||||
|
||||
# include "NimBLELog.h"
|
||||
@@ -76,12 +79,18 @@ extern "C" void ble_store_config_init(void);
|
||||
/**
|
||||
* Singletons for the NimBLEDevice.
|
||||
*/
|
||||
NimBLEDeviceCallbacks NimBLEDevice::defaultDeviceCallbacks{};
|
||||
NimBLEDeviceCallbacks* NimBLEDevice::m_pDeviceCallbacks = &defaultDeviceCallbacks;
|
||||
|
||||
# if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
|
||||
NimBLEScan* NimBLEDevice::m_pScan = nullptr;
|
||||
# endif
|
||||
|
||||
# if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
|
||||
NimBLEServer* NimBLEDevice::m_pServer = nullptr;
|
||||
# if CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM > 0
|
||||
NimBLEL2CAPServer* NimBLEDevice::m_pL2CAPServer = nullptr;
|
||||
# endif
|
||||
# endif
|
||||
|
||||
# if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER)
|
||||
@@ -93,7 +102,7 @@ NimBLEAdvertising* NimBLEDevice::m_bleAdvertising = nullptr;
|
||||
# endif
|
||||
|
||||
# if defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
|
||||
std::array<NimBLEClient*, NIMBLE_MAX_CONNECTIONS> NimBLEDevice::m_pClients{nullptr};
|
||||
std::array<NimBLEClient*, NIMBLE_MAX_CONNECTIONS> NimBLEDevice::m_pClients{};
|
||||
# endif
|
||||
|
||||
bool NimBLEDevice::m_initialized{false};
|
||||
@@ -104,9 +113,18 @@ std::vector<NimBLEAddress> NimBLEDevice::m_whiteList{};
|
||||
uint8_t NimBLEDevice::m_ownAddrType{BLE_OWN_ADDR_PUBLIC};
|
||||
|
||||
# ifdef ESP_PLATFORM
|
||||
# ifdef CONFIG_BTDM_BLE_SCAN_DUPL
|
||||
# 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};
|
||||
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};
|
||||
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);
|
||||
extern "C" int ble_vhci_disc_duplicate_mode_enable(int mode);
|
||||
# endif
|
||||
# endif
|
||||
|
||||
@@ -137,6 +155,27 @@ NimBLEServer* NimBLEDevice::createServer() {
|
||||
NimBLEServer* NimBLEDevice::getServer() {
|
||||
return m_pServer;
|
||||
} // getServer
|
||||
|
||||
# if CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM > 0
|
||||
/**
|
||||
* @brief Create an instance of a L2CAP server.
|
||||
* @return A pointer to the instance of the L2CAP server.
|
||||
*/
|
||||
NimBLEL2CAPServer* NimBLEDevice::createL2CAPServer() {
|
||||
if (NimBLEDevice::m_pL2CAPServer == nullptr) {
|
||||
NimBLEDevice::m_pL2CAPServer = new NimBLEL2CAPServer();
|
||||
}
|
||||
return m_pL2CAPServer;
|
||||
} // createL2CAPServer
|
||||
|
||||
/**
|
||||
* @brief Get the instance of the L2CAP server.
|
||||
* @return A pointer to the L2CAP server instance or nullptr if none have been created.
|
||||
*/
|
||||
NimBLEL2CAPServer* NimBLEDevice::getL2CAPServer() {
|
||||
return m_pL2CAPServer;
|
||||
} // getL2CAPServer
|
||||
# endif
|
||||
# endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
@@ -228,7 +267,7 @@ NimBLEScan* NimBLEDevice::getScan() {
|
||||
} // getScan
|
||||
|
||||
# ifdef ESP_PLATFORM
|
||||
# ifdef CONFIG_BTDM_BLE_SCAN_DUPL
|
||||
# if CONFIG_BTDM_BLE_SCAN_DUPL || CONFIG_BT_LE_SCAN_DUPL
|
||||
/**
|
||||
* @brief Set the duplicate filter cache size for filtering scanned devices.
|
||||
* @param [in] size The number of advertisements filtered before the cache is reset.\n
|
||||
@@ -275,7 +314,26 @@ void NimBLEDevice::setScanFilterMode(uint8_t mode) {
|
||||
|
||||
m_scanFilterMode = mode;
|
||||
}
|
||||
# endif // CONFIG_BTDM_BLE_SCAN_DUPL
|
||||
|
||||
/**
|
||||
* @brief Set the time in seconds to reset the duplicate cache.
|
||||
* @param [in] time The time in seconds to reset the cache.
|
||||
* @details When the cache is reset all scanned devices will be reported again
|
||||
* even if already seen in the current scan. If set to 0 the cache will never be reset.
|
||||
*/
|
||||
void NimBLEDevice::setScanDuplicateCacheResetTime(uint16_t time) {
|
||||
if (m_initialized) {
|
||||
NIMBLE_LOGE(LOG_TAG, "Cannot change scan cache reset time while initialized");
|
||||
return;
|
||||
} else if (time > 1000) {
|
||||
NIMBLE_LOGE(LOG_TAG, "Invalid scan cache reset time");
|
||||
return;
|
||||
}
|
||||
|
||||
NIMBLE_LOGD(LOG_TAG, "Set duplicate cache reset time to: %u", time);
|
||||
m_scanDuplicateResetTime = time;
|
||||
}
|
||||
# endif // CONFIG_BTDM_BLE_SCAN_DUPL || CONFIG_BT_LE_SCAN_DUPL
|
||||
# endif // ESP_PLATFORM
|
||||
# endif // #if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
|
||||
|
||||
@@ -473,14 +531,14 @@ bool NimBLEDevice::setPower(int8_t dbm, NimBLETxPowerType type) {
|
||||
dbm++; // round up to the next multiple of 3 to be able to target 20dbm
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
esp_power_level_t espPwr = static_cast<esp_power_level_t>(dbm / 3 + ESP_PWR_LVL_N0);
|
||||
bool success = false;
|
||||
esp_power_level_t espPwr = static_cast<esp_power_level_t>(dbm / 3 + ESP_PWR_LVL_N0);
|
||||
if (type == NimBLETxPowerType::All) {
|
||||
success = setPowerLevel(espPwr, ESP_BLE_PWR_TYPE_ADV);
|
||||
success &= setPowerLevel(espPwr, ESP_BLE_PWR_TYPE_SCAN);
|
||||
success &= setPowerLevel(espPwr, ESP_BLE_PWR_TYPE_DEFAULT);
|
||||
} else if (type == NimBLETxPowerType::Advertise) {
|
||||
success = setPowerLevel(espPwr, ESP_BLE_PWR_TYPE_ADV);
|
||||
success = setPowerLevel(espPwr, ESP_BLE_PWR_TYPE_ADV);
|
||||
} else if (type == NimBLETxPowerType::Scan) {
|
||||
success = setPowerLevel(espPwr, ESP_BLE_PWR_TYPE_SCAN);
|
||||
} else if (type == NimBLETxPowerType::Connection) {
|
||||
@@ -492,15 +550,13 @@ bool NimBLEDevice::setPower(int8_t dbm, NimBLETxPowerType type) {
|
||||
# else
|
||||
(void)type; // unused
|
||||
NIMBLE_LOGD(LOG_TAG, ">> setPower: %d", dbm);
|
||||
ble_hci_vs_set_tx_pwr_cp cmd{dbm};
|
||||
ble_hci_vs_set_tx_pwr_rp rsp{0};
|
||||
int rc = ble_hs_hci_send_vs_cmd(BLE_HCI_OCF_VS_SET_TX_PWR, &cmd, sizeof(cmd), &rsp, sizeof(rsp));
|
||||
int rc = ble_phy_tx_power_set(dbm);
|
||||
if (rc) {
|
||||
NIMBLE_LOGE(LOG_TAG, "failed to set TX power, rc: %04x\n", rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
NIMBLE_LOGD(LOG_TAG, "TX power set to %d dBm\n", rsp.tx_power);
|
||||
NIMBLE_LOGD(LOG_TAG, "TX power set to %d dBm\n", dbm);
|
||||
return true;
|
||||
# endif
|
||||
} // setPower
|
||||
@@ -536,7 +592,7 @@ int NimBLEDevice::getPower(NimBLETxPowerType type) {
|
||||
# endif
|
||||
# else
|
||||
(void)type; // unused
|
||||
return ble_phy_txpwr_get();
|
||||
return ble_phy_tx_power_get();
|
||||
# endif
|
||||
} // getPower
|
||||
|
||||
@@ -736,7 +792,6 @@ NimBLEAddress NimBLEDevice::getWhiteListAddress(size_t index) {
|
||||
/* STACK FUNCTIONS */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
# if CONFIG_BT_NIMBLE_EXT_ADV || defined(_DOXYGEN_)
|
||||
/**
|
||||
* @brief Set the preferred default phy to use for connections.
|
||||
* @param [in] txPhyMask TX PHY. Can be mask of following:
|
||||
@@ -759,7 +814,6 @@ bool NimBLEDevice::setDefaultPhy(uint8_t txPhyMask, uint8_t rxPhyMask) {
|
||||
|
||||
return rc == 0;
|
||||
}
|
||||
# endif
|
||||
|
||||
/**
|
||||
* @brief Host reset, we pass the message so we don't make calls until re-synced.
|
||||
@@ -841,7 +895,7 @@ bool NimBLEDevice::init(const std::string& deviceName) {
|
||||
if (!m_initialized) {
|
||||
# ifdef ESP_PLATFORM
|
||||
|
||||
# ifdef CONFIG_ENABLE_ARDUINO_DEPENDS
|
||||
# if defined(CONFIG_ENABLE_ARDUINO_DEPENDS) && SOC_BT_SUPPORTED
|
||||
// make sure the linker includes esp32-hal-bt.c so Arduino init doesn't release BLE memory.
|
||||
btStarted();
|
||||
# endif
|
||||
@@ -863,18 +917,24 @@ bool NimBLEDevice::init(const std::string& deviceName) {
|
||||
esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
|
||||
# endif
|
||||
|
||||
# if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) | !defined(CONFIG_NIMBLE_CPP_IDF)
|
||||
# if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) || !defined(CONFIG_NIMBLE_CPP_IDF)
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
# if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
bt_cfg.bluetooth_mode = ESP_BT_MODE_BLE;
|
||||
# else
|
||||
# if defined(CONFIG_IDF_TARGET_ESP32)
|
||||
bt_cfg.mode = ESP_BT_MODE_BLE;
|
||||
bt_cfg.ble_max_conn = CONFIG_BT_NIMBLE_MAX_CONNECTIONS;
|
||||
# elif defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
bt_cfg.ble_max_act = CONFIG_BT_NIMBLE_MAX_CONNECTIONS;
|
||||
# else
|
||||
bt_cfg.nimble_max_connections = CONFIG_BT_NIMBLE_MAX_CONNECTIONS;
|
||||
# endif
|
||||
|
||||
# ifdef CONFIG_BTDM_BLE_SCAN_DUPL
|
||||
bt_cfg.normal_adv_size = m_scanDuplicateSize;
|
||||
bt_cfg.scan_duplicate_type = m_scanFilterMode;
|
||||
# if CONFIG_BTDM_BLE_SCAN_DUPL
|
||||
bt_cfg.normal_adv_size = m_scanDuplicateSize;
|
||||
bt_cfg.scan_duplicate_type = m_scanFilterMode;
|
||||
bt_cfg.dup_list_refresh_period = m_scanDuplicateResetTime;
|
||||
# elif CONFIG_BT_LE_SCAN_DUPL
|
||||
bt_cfg.ble_ll_rsp_dup_list_count = m_scanDuplicateSize;
|
||||
bt_cfg.ble_ll_adv_dup_list_count = m_scanDuplicateSize;
|
||||
# endif
|
||||
err = esp_bt_controller_init(&bt_cfg);
|
||||
if (err != ESP_OK) {
|
||||
@@ -882,17 +942,38 @@ bool NimBLEDevice::init(const std::string& deviceName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
# if CONFIG_BT_LE_SCAN_DUPL
|
||||
int mode = (1UL << 4); // FILTER_DUPLICATE_EXCEPTION_FOR_MESH
|
||||
switch (m_scanFilterMode) {
|
||||
case 1:
|
||||
mode |= (1UL << 3); // FILTER_DUPLICATE_ADVDATA
|
||||
break;
|
||||
case 2:
|
||||
mode |= ((1UL << 2) | (1UL << 3)); // FILTER_DUPLICATE_ADDRESS | FILTER_DUPLICATE_ADVDATA
|
||||
break;
|
||||
default:
|
||||
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
|
||||
|
||||
err = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (err != ESP_OK) {
|
||||
NIMBLE_LOGE(LOG_TAG, "esp_bt_controller_enable() failed; err=%d", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
# if CONFIG_BT_NIMBLE_LEGACY_VHCI_ENABLE
|
||||
err = esp_nimble_hci_init();
|
||||
if (err != ESP_OK) {
|
||||
NIMBLE_LOGE(LOG_TAG, "esp_nimble_hci_init() failed; err=%d", err);
|
||||
return false;
|
||||
}
|
||||
# endif
|
||||
# endif
|
||||
# endif
|
||||
nimble_port_init();
|
||||
@@ -900,7 +981,9 @@ bool NimBLEDevice::init(const std::string& deviceName) {
|
||||
// Setup callbacks for host events
|
||||
ble_hs_cfg.reset_cb = NimBLEDevice::onReset;
|
||||
ble_hs_cfg.sync_cb = NimBLEDevice::onSync;
|
||||
ble_hs_cfg.store_status_cb = ble_store_util_status_rr; /*TODO: Implement handler for this*/
|
||||
ble_hs_cfg.store_status_cb = [](struct ble_store_status_event* event, void* arg) {
|
||||
return m_pDeviceCallbacks->onStoreStatus(event, arg);
|
||||
};
|
||||
|
||||
// Set initial security capabilities
|
||||
ble_hs_cfg.sm_io_cap = BLE_HS_IO_NO_INPUT_OUTPUT;
|
||||
@@ -960,6 +1043,12 @@ bool NimBLEDevice::deinit(bool clearAll) {
|
||||
delete NimBLEDevice::m_pServer;
|
||||
NimBLEDevice::m_pServer = nullptr;
|
||||
}
|
||||
# if CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM > 0
|
||||
if (NimBLEDevice::m_pL2CAPServer != nullptr) {
|
||||
delete NimBLEDevice::m_pL2CAPServer;
|
||||
NimBLEDevice::m_pL2CAPServer = nullptr;
|
||||
}
|
||||
# endif
|
||||
# endif
|
||||
|
||||
# if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER)
|
||||
@@ -1033,18 +1122,16 @@ bool NimBLEDevice::setOwnAddrType(uint8_t type) {
|
||||
|
||||
m_ownAddrType = type;
|
||||
|
||||
# if MYNEWT_VAL(BLE_HOST_BASED_PRIVACY)
|
||||
if (type == BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT || type == BLE_OWN_ADDR_RPA_RANDOM_DEFAULT) {
|
||||
# ifdef CONFIG_IDF_TARGET_ESP32
|
||||
// esp32 controller does not support RPA so we must use the random static for calls to the stack
|
||||
// the host will take care of the random private address generation/setting.
|
||||
m_ownAddrType = BLE_OWN_ADDR_RANDOM;
|
||||
rc = ble_hs_pvcy_rpa_config(NIMBLE_HOST_ENABLE_RPA);
|
||||
# endif
|
||||
} else {
|
||||
# ifdef CONFIG_IDF_TARGET_ESP32
|
||||
rc = ble_hs_pvcy_rpa_config(NIMBLE_HOST_DISABLE_PRIVACY);
|
||||
# endif
|
||||
}
|
||||
# endif
|
||||
|
||||
return rc == 0;
|
||||
} // setOwnAddrType
|
||||
@@ -1262,4 +1349,13 @@ void nimble_cpp_assert(const char* file, unsigned line) {
|
||||
}
|
||||
# endif // CONFIG_NIMBLE_CPP_DEBUG_ASSERT_ENABLED
|
||||
|
||||
void NimBLEDevice::setDeviceCallbacks(NimBLEDeviceCallbacks* cb) {
|
||||
m_pDeviceCallbacks = cb ? cb : &defaultDeviceCallbacks;
|
||||
}
|
||||
|
||||
int NimBLEDeviceCallbacks::onStoreStatus(struct ble_store_status_event* event, void* arg) {
|
||||
NIMBLE_LOGD("NimBLEDeviceCallbacks", "onStoreStatus: default");
|
||||
return ble_store_util_status_rr(event, arg);
|
||||
}
|
||||
|
||||
#endif // CONFIG_BT_ENABLED
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
@@ -59,6 +59,9 @@ class NimBLEAdvertising;
|
||||
|
||||
# if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
|
||||
class NimBLEServer;
|
||||
# if CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM > 0
|
||||
class NimBLEL2CAPServer;
|
||||
# endif
|
||||
# endif
|
||||
|
||||
# if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) || defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
|
||||
@@ -66,6 +69,7 @@ class NimBLEConnInfo;
|
||||
# endif
|
||||
|
||||
class NimBLEAddress;
|
||||
class NimBLEDeviceCallbacks;
|
||||
|
||||
# define BLEDevice NimBLEDevice
|
||||
# define BLEClient NimBLEClient
|
||||
@@ -94,6 +98,13 @@ class NimBLEAddress;
|
||||
# define BLEEddystoneTLM NimBLEEddystoneTLM
|
||||
# define BLEEddystoneURL NimBLEEddystoneURL
|
||||
# define BLEConnInfo NimBLEConnInfo
|
||||
# define BLEL2CAPServer NimBLEL2CAPServer
|
||||
# define BLEL2CAPService NimBLEL2CAPService
|
||||
# define BLEL2CAPServiceCallbacks NimBLEL2CAPServiceCallbacks
|
||||
# define BLEL2CAPClient NimBLEL2CAPClient
|
||||
# define BLEL2CAPClientCallbacks NimBLEL2CAPClientCallbacks
|
||||
# define BLEL2CAPChannel NimBLEL2CAPChannel
|
||||
# define BLEL2CAPChannelCallbacks NimBLEL2CAPChannelCallbacks
|
||||
|
||||
# ifdef CONFIG_BT_NIMBLE_MAX_CONNECTIONS
|
||||
# define NIMBLE_MAX_CONNECTIONS CONFIG_BT_NIMBLE_MAX_CONNECTIONS
|
||||
@@ -101,12 +112,7 @@ class NimBLEAddress;
|
||||
# define NIMBLE_MAX_CONNECTIONS CONFIG_NIMBLE_MAX_CONNECTIONS
|
||||
# endif
|
||||
|
||||
enum class NimBLETxPowerType {
|
||||
All = 0,
|
||||
Advertise = 1,
|
||||
Scan = 2,
|
||||
Connection = 3
|
||||
};
|
||||
enum class NimBLETxPowerType { All = 0, Advertise = 1, Scan = 2, Connection = 3 };
|
||||
|
||||
typedef int (*gap_event_handler)(ble_gap_event* event, void* arg);
|
||||
|
||||
@@ -129,8 +135,10 @@ class NimBLEDevice {
|
||||
static bool setOwnAddrType(uint8_t type);
|
||||
static bool setOwnAddr(const NimBLEAddress& addr);
|
||||
static bool setOwnAddr(const uint8_t* addr);
|
||||
static void setDeviceCallbacks(NimBLEDeviceCallbacks* cb);
|
||||
static void setScanDuplicateCacheSize(uint16_t cacheSize);
|
||||
static void setScanFilterMode(uint8_t type);
|
||||
static void setScanDuplicateCacheResetTime(uint16_t time);
|
||||
static bool setCustomGapHandler(gap_event_handler handler);
|
||||
static void setSecurityAuth(bool bonding, bool mitm, bool sc);
|
||||
static void setSecurityAuth(uint8_t auth);
|
||||
@@ -147,6 +155,7 @@ class NimBLEDevice {
|
||||
static void host_task(void* param);
|
||||
static int getPower(NimBLETxPowerType type = NimBLETxPowerType::All);
|
||||
static bool setPower(int8_t dbm, NimBLETxPowerType type = NimBLETxPowerType::All);
|
||||
static bool setDefaultPhy(uint8_t txPhyMask, uint8_t rxPhyMask);
|
||||
|
||||
# ifdef ESP_PLATFORM
|
||||
# ifndef CONFIG_IDF_TARGET_ESP32P4
|
||||
@@ -155,10 +164,6 @@ class NimBLEDevice {
|
||||
# endif
|
||||
# endif
|
||||
|
||||
# if CONFIG_BT_NIMBLE_EXT_ADV
|
||||
static bool setDefaultPhy(uint8_t txPhyMask, uint8_t rxPhyMask);
|
||||
# endif
|
||||
|
||||
# if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
|
||||
static NimBLEScan* getScan();
|
||||
# endif
|
||||
@@ -166,6 +171,10 @@ class NimBLEDevice {
|
||||
# if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
|
||||
static NimBLEServer* createServer();
|
||||
static NimBLEServer* getServer();
|
||||
# if CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM > 0
|
||||
static NimBLEL2CAPServer* createL2CAPServer();
|
||||
static NimBLEL2CAPServer* getL2CAPServer();
|
||||
# endif
|
||||
# endif
|
||||
|
||||
# if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) || defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
|
||||
@@ -213,6 +222,8 @@ class NimBLEDevice {
|
||||
static ble_gap_event_listener m_listener;
|
||||
static uint8_t m_ownAddrType;
|
||||
static std::vector<NimBLEAddress> m_whiteList;
|
||||
static NimBLEDeviceCallbacks* m_pDeviceCallbacks;
|
||||
static NimBLEDeviceCallbacks defaultDeviceCallbacks;
|
||||
|
||||
# if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
|
||||
static NimBLEScan* m_pScan;
|
||||
@@ -220,6 +231,9 @@ class NimBLEDevice {
|
||||
|
||||
# if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
|
||||
static NimBLEServer* m_pServer;
|
||||
# if CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM > 0
|
||||
static NimBLEL2CAPServer* m_pL2CAPServer;
|
||||
# endif
|
||||
# endif
|
||||
|
||||
# if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER)
|
||||
@@ -235,9 +249,10 @@ class NimBLEDevice {
|
||||
# endif
|
||||
|
||||
# ifdef ESP_PLATFORM
|
||||
# ifdef CONFIG_BTDM_BLE_SCAN_DUPL
|
||||
# if CONFIG_BTDM_BLE_SCAN_DUPL || CONFIG_BT_LE_SCAN_DUPL
|
||||
static uint16_t m_scanDuplicateSize;
|
||||
static uint8_t m_scanFilterMode;
|
||||
static uint16_t m_scanDuplicateResetTime;
|
||||
# endif
|
||||
# endif
|
||||
|
||||
@@ -279,6 +294,10 @@ class NimBLEDevice {
|
||||
# include "NimBLEService.h"
|
||||
# include "NimBLECharacteristic.h"
|
||||
# include "NimBLEDescriptor.h"
|
||||
# if CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM > 0
|
||||
# include "NimBLEL2CAPServer.h"
|
||||
# include "NimBLEL2CAPChannel.h"
|
||||
# endif
|
||||
# endif
|
||||
|
||||
# if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER)
|
||||
@@ -295,5 +314,27 @@ class NimBLEDevice {
|
||||
|
||||
# include "NimBLEUtils.h"
|
||||
|
||||
/**
|
||||
* @brief Callbacks associated with a BLE device.
|
||||
*/
|
||||
class NimBLEDeviceCallbacks {
|
||||
public:
|
||||
virtual ~NimBLEDeviceCallbacks() {};
|
||||
|
||||
/**
|
||||
* @brief Indicates an inability to perform a store operation.
|
||||
* This callback should do one of two things:
|
||||
* -Address the problem and return 0, indicating that the store operation
|
||||
* should proceed.
|
||||
* -Return nonzero to indicate that the store operation should be aborted.
|
||||
* @param event Describes the store event being reported.
|
||||
* BLE_STORE_EVENT_FULL; or
|
||||
* BLE_STORE_EVENT_OVERFLOW
|
||||
* @return 0 if the store operation should proceed;
|
||||
* nonzero if the store operation should be aborted.
|
||||
*/
|
||||
virtual int onStoreStatus(struct ble_store_status_event* event, void* arg);
|
||||
};
|
||||
|
||||
#endif // CONFIG_BT_ENABLED
|
||||
#endif // NIMBLE_CPP_DEVICE_H_
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
|
||||
304
src/NimBLEL2CAPChannel.cpp
Normal file
304
src/NimBLEL2CAPChannel.cpp
Normal file
@@ -0,0 +1,304 @@
|
||||
//
|
||||
// (C) Dr. Michael 'Mickey' Lauer <mickey@vanille-media.de>
|
||||
//
|
||||
#include "NimBLEL2CAPChannel.h"
|
||||
|
||||
#include "NimBLEClient.h"
|
||||
#include "NimBLELog.h"
|
||||
#include "NimBLEUtils.h"
|
||||
|
||||
// L2CAP buffer block size
|
||||
#define L2CAP_BUF_BLOCK_SIZE (250)
|
||||
#define L2CAP_BUF_SIZE_MTUS_PER_CHANNEL (3)
|
||||
// Round-up integer division
|
||||
#define CEIL_DIVIDE(a, b) (((a) + (b) - 1) / (b))
|
||||
#define ROUND_DIVIDE(a, b) (((a) + (b) / 2) / (b))
|
||||
// Retry
|
||||
constexpr uint32_t RetryTimeout = 50;
|
||||
constexpr int RetryCounter = 3;
|
||||
|
||||
NimBLEL2CAPChannel::NimBLEL2CAPChannel(uint16_t psm, uint16_t mtu, NimBLEL2CAPChannelCallbacks* callbacks)
|
||||
: psm(psm), mtu(mtu), callbacks(callbacks) {
|
||||
assert(mtu); // fail here, if MTU is too little
|
||||
assert(callbacks); // fail here, if no callbacks are given
|
||||
assert(setupMemPool()); // fail here, if the memory pool could not be setup
|
||||
|
||||
NIMBLE_LOGI(LOG_TAG, "L2CAP COC 0x%04X initialized w/ L2CAP MTU %i", this->psm, this->mtu);
|
||||
};
|
||||
|
||||
NimBLEL2CAPChannel::~NimBLEL2CAPChannel() {
|
||||
teardownMemPool();
|
||||
|
||||
NIMBLE_LOGI(LOG_TAG, "L2CAP COC 0x%04X shutdown and freed.", this->psm);
|
||||
}
|
||||
|
||||
bool NimBLEL2CAPChannel::setupMemPool() {
|
||||
const size_t buf_blocks = CEIL_DIVIDE(mtu, L2CAP_BUF_BLOCK_SIZE) * L2CAP_BUF_SIZE_MTUS_PER_CHANNEL;
|
||||
NIMBLE_LOGD(LOG_TAG, "Computed number of buf_blocks = %d", buf_blocks);
|
||||
|
||||
_coc_memory = malloc(OS_MEMPOOL_SIZE(buf_blocks, L2CAP_BUF_BLOCK_SIZE) * sizeof(os_membuf_t));
|
||||
if (_coc_memory == 0) {
|
||||
NIMBLE_LOGE(LOG_TAG, "Can't allocate _coc_memory: %d", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto rc = os_mempool_init(&_coc_mempool, buf_blocks, L2CAP_BUF_BLOCK_SIZE, _coc_memory, "appbuf");
|
||||
if (rc != 0) {
|
||||
NIMBLE_LOGE(LOG_TAG, "Can't os_mempool_init: %d", rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto rc2 = os_mbuf_pool_init(&_coc_mbuf_pool, &_coc_mempool, L2CAP_BUF_BLOCK_SIZE, buf_blocks);
|
||||
if (rc2 != 0) {
|
||||
NIMBLE_LOGE(LOG_TAG, "Can't os_mbuf_pool_init: %d", rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
this->receiveBuffer = (uint8_t*)malloc(mtu);
|
||||
if (!this->receiveBuffer) {
|
||||
NIMBLE_LOGE(LOG_TAG, "Can't malloc receive buffer: %d, %s", errno, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void NimBLEL2CAPChannel::teardownMemPool() {
|
||||
if (this->callbacks) {
|
||||
delete this->callbacks;
|
||||
}
|
||||
if (this->receiveBuffer) {
|
||||
free(this->receiveBuffer);
|
||||
}
|
||||
if (_coc_memory) {
|
||||
free(_coc_memory);
|
||||
}
|
||||
}
|
||||
|
||||
int NimBLEL2CAPChannel::writeFragment(std::vector<uint8_t>::const_iterator begin, std::vector<uint8_t>::const_iterator end) {
|
||||
auto toSend = end - begin;
|
||||
|
||||
if (stalled) {
|
||||
NIMBLE_LOGD(LOG_TAG, "L2CAP Channel waiting for unstall...");
|
||||
NimBLETaskData taskData;
|
||||
m_pTaskData = &taskData;
|
||||
NimBLEUtils::taskWait(taskData, BLE_NPL_TIME_FOREVER);
|
||||
m_pTaskData = nullptr;
|
||||
stalled = false;
|
||||
NIMBLE_LOGD(LOG_TAG, "L2CAP Channel unstalled!");
|
||||
}
|
||||
|
||||
struct ble_l2cap_chan_info info;
|
||||
ble_l2cap_get_chan_info(channel, &info);
|
||||
// Take the minimum of our and peer MTU
|
||||
auto mtu = info.peer_coc_mtu < info.our_coc_mtu ? info.peer_coc_mtu : info.our_coc_mtu;
|
||||
|
||||
if (toSend > mtu) {
|
||||
return -BLE_HS_EBADDATA;
|
||||
}
|
||||
|
||||
auto retries = RetryCounter;
|
||||
|
||||
while (retries--) {
|
||||
auto txd = os_mbuf_get_pkthdr(&_coc_mbuf_pool, 0);
|
||||
if (!txd) {
|
||||
NIMBLE_LOGE(LOG_TAG, "Can't os_mbuf_get_pkthdr.");
|
||||
return -BLE_HS_ENOMEM;
|
||||
}
|
||||
auto append = os_mbuf_append(txd, &(*begin), toSend);
|
||||
if (append != 0) {
|
||||
NIMBLE_LOGE(LOG_TAG, "Can't os_mbuf_append: %d", append);
|
||||
return append;
|
||||
}
|
||||
|
||||
auto res = ble_l2cap_send(channel, txd);
|
||||
switch (res) {
|
||||
case 0:
|
||||
NIMBLE_LOGD(LOG_TAG, "L2CAP COC 0x%04X sent %d bytes.", this->psm, toSend);
|
||||
return 0;
|
||||
|
||||
case BLE_HS_ESTALLED:
|
||||
stalled = true;
|
||||
NIMBLE_LOGD(LOG_TAG, "L2CAP COC 0x%04X sent %d bytes.", this->psm, toSend);
|
||||
NIMBLE_LOGW(LOG_TAG,
|
||||
"ble_l2cap_send returned BLE_HS_ESTALLED. Next send will wait for unstalled event...");
|
||||
return 0;
|
||||
|
||||
case BLE_HS_ENOMEM:
|
||||
case BLE_HS_EAGAIN:
|
||||
case BLE_HS_EBUSY:
|
||||
NIMBLE_LOGD(LOG_TAG, "ble_l2cap_send returned %d. Retrying shortly...", res);
|
||||
os_mbuf_free_chain(txd);
|
||||
ble_npl_time_delay(ble_npl_time_ms_to_ticks32(RetryTimeout));
|
||||
continue;
|
||||
|
||||
default:
|
||||
NIMBLE_LOGE(LOG_TAG, "ble_l2cap_send failed: %d", res);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
NIMBLE_LOGE(LOG_TAG, "Retries exhausted, dropping %d bytes to send.", toSend);
|
||||
return -BLE_HS_EREJECT;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
|
||||
NimBLEL2CAPChannel* NimBLEL2CAPChannel::connect(NimBLEClient* client,
|
||||
uint16_t psm,
|
||||
uint16_t mtu,
|
||||
NimBLEL2CAPChannelCallbacks* callbacks) {
|
||||
if (!client->isConnected()) {
|
||||
NIMBLE_LOGE(
|
||||
LOG_TAG,
|
||||
"Client is not connected. Before connecting via L2CAP, a GAP connection must have been established");
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
auto channel = new NimBLEL2CAPChannel(psm, mtu, callbacks);
|
||||
|
||||
auto sdu_rx = os_mbuf_get_pkthdr(&channel->_coc_mbuf_pool, 0);
|
||||
if (!sdu_rx) {
|
||||
NIMBLE_LOGE(LOG_TAG, "Can't allocate SDU buffer: %d, %s", errno, strerror(errno));
|
||||
return nullptr;
|
||||
}
|
||||
auto rc = ble_l2cap_connect(client->getConnHandle(), psm, mtu, sdu_rx, NimBLEL2CAPChannel::handleL2capEvent, channel);
|
||||
if (rc != 0) {
|
||||
NIMBLE_LOGE(LOG_TAG, "ble_l2cap_connect failed: %d", rc);
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
#endif // CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL
|
||||
|
||||
bool NimBLEL2CAPChannel::write(const std::vector<uint8_t>& bytes) {
|
||||
if (!this->channel) {
|
||||
NIMBLE_LOGW(LOG_TAG, "L2CAP Channel not open");
|
||||
return false;
|
||||
}
|
||||
|
||||
struct ble_l2cap_chan_info info;
|
||||
ble_l2cap_get_chan_info(channel, &info);
|
||||
auto mtu = info.peer_coc_mtu < info.our_coc_mtu ? info.peer_coc_mtu : info.our_coc_mtu;
|
||||
|
||||
auto start = bytes.begin();
|
||||
while (start != bytes.end()) {
|
||||
auto end = start + mtu < bytes.end() ? start + mtu : bytes.end();
|
||||
if (writeFragment(start, end) < 0) {
|
||||
return false;
|
||||
}
|
||||
start = end;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// private
|
||||
int NimBLEL2CAPChannel::handleConnectionEvent(struct ble_l2cap_event* event) {
|
||||
channel = event->connect.chan;
|
||||
struct ble_l2cap_chan_info info;
|
||||
ble_l2cap_get_chan_info(channel, &info);
|
||||
NIMBLE_LOGI(LOG_TAG,
|
||||
"L2CAP COC 0x%04X connected. Local MTU = %d [%d], remote MTU = %d [%d].",
|
||||
psm,
|
||||
info.our_coc_mtu,
|
||||
info.our_l2cap_mtu,
|
||||
info.peer_coc_mtu,
|
||||
info.peer_l2cap_mtu);
|
||||
if (info.our_coc_mtu > info.peer_coc_mtu) {
|
||||
NIMBLE_LOGW(LOG_TAG, "L2CAP COC 0x%04X connected, but local MTU is bigger than remote MTU.", psm);
|
||||
}
|
||||
auto mtu = info.peer_coc_mtu < info.our_coc_mtu ? info.peer_coc_mtu : info.our_coc_mtu;
|
||||
callbacks->onConnect(this, mtu);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int NimBLEL2CAPChannel::handleAcceptEvent(struct ble_l2cap_event* event) {
|
||||
NIMBLE_LOGI(LOG_TAG, "L2CAP COC 0x%04X accept.", psm);
|
||||
if (!callbacks->shouldAcceptConnection(this)) {
|
||||
NIMBLE_LOGI(LOG_TAG, "L2CAP COC 0x%04X refused by delegate.", psm);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct os_mbuf* sdu_rx = os_mbuf_get_pkthdr(&_coc_mbuf_pool, 0);
|
||||
assert(sdu_rx != NULL);
|
||||
ble_l2cap_recv_ready(event->accept.chan, sdu_rx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int NimBLEL2CAPChannel::handleDataReceivedEvent(struct ble_l2cap_event* event) {
|
||||
NIMBLE_LOGD(LOG_TAG, "L2CAP COC 0x%04X data received.", psm);
|
||||
|
||||
struct os_mbuf* rxd = event->receive.sdu_rx;
|
||||
assert(rxd != NULL);
|
||||
|
||||
int rx_len = (int)OS_MBUF_PKTLEN(rxd);
|
||||
assert(rx_len <= (int)mtu);
|
||||
|
||||
int res = os_mbuf_copydata(rxd, 0, rx_len, receiveBuffer);
|
||||
assert(res == 0);
|
||||
|
||||
NIMBLE_LOGD(LOG_TAG, "L2CAP COC 0x%04X received %d bytes.", psm, rx_len);
|
||||
|
||||
res = os_mbuf_free_chain(rxd);
|
||||
assert(res == 0);
|
||||
|
||||
std::vector<uint8_t> incomingData(receiveBuffer, receiveBuffer + rx_len);
|
||||
callbacks->onRead(this, incomingData);
|
||||
|
||||
struct os_mbuf* next = os_mbuf_get_pkthdr(&_coc_mbuf_pool, 0);
|
||||
assert(next != NULL);
|
||||
|
||||
res = ble_l2cap_recv_ready(channel, next);
|
||||
assert(res == 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int NimBLEL2CAPChannel::handleTxUnstalledEvent(struct ble_l2cap_event* event) {
|
||||
if (m_pTaskData != nullptr) {
|
||||
NimBLEUtils::taskRelease(*m_pTaskData, event->tx_unstalled.status);
|
||||
}
|
||||
|
||||
NIMBLE_LOGI(LOG_TAG, "L2CAP COC 0x%04X transmit unstalled.", psm);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int NimBLEL2CAPChannel::handleDisconnectionEvent(struct ble_l2cap_event* event) {
|
||||
NIMBLE_LOGI(LOG_TAG, "L2CAP COC 0x%04X disconnected.", psm);
|
||||
channel = NULL;
|
||||
callbacks->onDisconnect(this);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* STATIC */
|
||||
int NimBLEL2CAPChannel::handleL2capEvent(struct ble_l2cap_event* event, void* arg) {
|
||||
NIMBLE_LOGD(LOG_TAG, "handleL2capEvent: handling l2cap event %d", event->type);
|
||||
NimBLEL2CAPChannel* self = reinterpret_cast<NimBLEL2CAPChannel*>(arg);
|
||||
|
||||
int returnValue = 0;
|
||||
|
||||
switch (event->type) {
|
||||
case BLE_L2CAP_EVENT_COC_CONNECTED:
|
||||
returnValue = self->handleConnectionEvent(event);
|
||||
break;
|
||||
|
||||
case BLE_L2CAP_EVENT_COC_DISCONNECTED:
|
||||
returnValue = self->handleDisconnectionEvent(event);
|
||||
break;
|
||||
|
||||
case BLE_L2CAP_EVENT_COC_ACCEPT:
|
||||
returnValue = self->handleAcceptEvent(event);
|
||||
break;
|
||||
|
||||
case BLE_L2CAP_EVENT_COC_DATA_RECEIVED:
|
||||
returnValue = self->handleDataReceivedEvent(event);
|
||||
break;
|
||||
|
||||
case BLE_L2CAP_EVENT_COC_TX_UNSTALLED:
|
||||
returnValue = self->handleTxUnstalledEvent(event);
|
||||
break;
|
||||
|
||||
default:
|
||||
NIMBLE_LOGW(LOG_TAG, "Unhandled l2cap event %d", event->type);
|
||||
break;
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
124
src/NimBLEL2CAPChannel.h
Normal file
124
src/NimBLEL2CAPChannel.h
Normal file
@@ -0,0 +1,124 @@
|
||||
//
|
||||
// (C) Dr. Michael 'Mickey' Lauer <mickey@vanille-media.de>
|
||||
//
|
||||
#pragma once
|
||||
#ifndef NIMBLEL2CAPCHANNEL_H
|
||||
# define NIMBLEL2CAPCHANNEL_H
|
||||
|
||||
# include "nimconfig.h"
|
||||
|
||||
# include "inttypes.h"
|
||||
# if defined(CONFIG_NIMBLE_CPP_IDF)
|
||||
# include "host/ble_l2cap.h"
|
||||
# include "os/os_mbuf.h"
|
||||
# else
|
||||
# include "nimble/nimble/host/include/host/ble_l2cap.h"
|
||||
# include "nimble/porting/nimble/include/os/os_mbuf.h"
|
||||
# endif
|
||||
|
||||
/**** FIX COMPILATION ****/
|
||||
# undef min
|
||||
# undef max
|
||||
/**************************/
|
||||
|
||||
# include <vector>
|
||||
# include <atomic>
|
||||
|
||||
class NimBLEClient;
|
||||
class NimBLEL2CAPChannelCallbacks;
|
||||
struct NimBLETaskData;
|
||||
|
||||
/**
|
||||
* @brief Encapsulates a L2CAP channel.
|
||||
*
|
||||
* This class is used to encapsulate a L2CAP connection oriented channel, both
|
||||
* from the "server" (which waits for the connection to be opened) and the "client"
|
||||
* (which opens the connection) point of view.
|
||||
*/
|
||||
class NimBLEL2CAPChannel {
|
||||
public:
|
||||
/// @brief Open an L2CAP channel via the specified PSM and MTU.
|
||||
/// @param[in] psm The PSM to use.
|
||||
/// @param[in] mtu The MTU to use. Note that this is the local MTU. Upon opening the channel,
|
||||
/// the final MTU will be negotiated to be the minimum of local and remote.
|
||||
/// @param[in] callbacks The callbacks to use. NOTE that these callbacks are called from the
|
||||
/// context of the NimBLE bluetooth task (`nimble_host`) and MUST be handled as fast as possible.
|
||||
/// @return True if the channel was opened successfully, false otherwise.
|
||||
static NimBLEL2CAPChannel* connect(NimBLEClient* client, uint16_t psm, uint16_t mtu, NimBLEL2CAPChannelCallbacks* callbacks);
|
||||
|
||||
/// @brief Write data to the channel.
|
||||
///
|
||||
/// If the size of the data exceeds the MTU, the data will be split into multiple fragments.
|
||||
/// @return true on success, after the data has been sent.
|
||||
/// @return false, if the data can't be sent.
|
||||
///
|
||||
/// NOTE: This function will block until the data has been sent or an error occurred.
|
||||
bool write(const std::vector<uint8_t>& bytes);
|
||||
|
||||
/// @return True, if the channel is connected. False, otherwise.
|
||||
bool isConnected() const { return !!channel; }
|
||||
|
||||
protected:
|
||||
NimBLEL2CAPChannel(uint16_t psm, uint16_t mtu, NimBLEL2CAPChannelCallbacks* callbacks);
|
||||
~NimBLEL2CAPChannel();
|
||||
|
||||
int handleConnectionEvent(struct ble_l2cap_event* event);
|
||||
int handleAcceptEvent(struct ble_l2cap_event* event);
|
||||
int handleDataReceivedEvent(struct ble_l2cap_event* event);
|
||||
int handleTxUnstalledEvent(struct ble_l2cap_event* event);
|
||||
int handleDisconnectionEvent(struct ble_l2cap_event* event);
|
||||
|
||||
private:
|
||||
friend class NimBLEL2CAPServer;
|
||||
static constexpr const char* LOG_TAG = "NimBLEL2CAPChannel";
|
||||
|
||||
const uint16_t psm; // PSM of the channel
|
||||
const uint16_t mtu; // The requested (local) MTU of the channel, might be larger than negotiated MTU
|
||||
struct ble_l2cap_chan* channel = nullptr;
|
||||
NimBLEL2CAPChannelCallbacks* callbacks;
|
||||
uint8_t* receiveBuffer = nullptr; // buffers a full (local) MTU
|
||||
|
||||
// NimBLE memory pool
|
||||
void* _coc_memory = nullptr;
|
||||
struct os_mempool _coc_mempool;
|
||||
struct os_mbuf_pool _coc_mbuf_pool;
|
||||
|
||||
// Runtime handling
|
||||
std::atomic<bool> stalled{false};
|
||||
NimBLETaskData* m_pTaskData{nullptr};
|
||||
|
||||
// Allocate / deallocate NimBLE memory pool
|
||||
bool setupMemPool();
|
||||
void teardownMemPool();
|
||||
|
||||
// Writes data up to the size of the negotiated MTU to the channel.
|
||||
int writeFragment(std::vector<uint8_t>::const_iterator begin, std::vector<uint8_t>::const_iterator end);
|
||||
|
||||
// L2CAP event handler
|
||||
static int handleL2capEvent(struct ble_l2cap_event* event, void* arg);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Callbacks base class for the L2CAP channel.
|
||||
*/
|
||||
class NimBLEL2CAPChannelCallbacks {
|
||||
public:
|
||||
NimBLEL2CAPChannelCallbacks() = default;
|
||||
virtual ~NimBLEL2CAPChannelCallbacks() = default;
|
||||
|
||||
/// Called when the client attempts to open a channel on the server.
|
||||
/// You can choose to accept or deny the connection.
|
||||
/// Default implementation returns true.
|
||||
virtual bool shouldAcceptConnection(NimBLEL2CAPChannel* channel) { return true; }
|
||||
/// Called after a connection has been made.
|
||||
/// Default implementation does nothing.
|
||||
virtual void onConnect(NimBLEL2CAPChannel* channel, uint16_t negotiatedMTU) {};
|
||||
/// Called when data has been read from the channel.
|
||||
/// Default implementation does nothing.
|
||||
virtual void onRead(NimBLEL2CAPChannel* channel, std::vector<uint8_t>& data) {};
|
||||
/// Called after the channel has been disconnected.
|
||||
/// Default implementation does nothing.
|
||||
virtual void onDisconnect(NimBLEL2CAPChannel* channel) {};
|
||||
};
|
||||
|
||||
#endif
|
||||
35
src/NimBLEL2CAPServer.cpp
Normal file
35
src/NimBLEL2CAPServer.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// (C) Dr. Michael 'Mickey' Lauer <mickey@vanille-media.de>
|
||||
//
|
||||
#include "NimBLEL2CAPServer.h"
|
||||
#include "NimBLEL2CAPChannel.h"
|
||||
#include "NimBLEDevice.h"
|
||||
#include "NimBLELog.h"
|
||||
|
||||
static const char* LOG_TAG = "NimBLEL2CAPServer";
|
||||
|
||||
NimBLEL2CAPServer::NimBLEL2CAPServer() {
|
||||
// Nothing to do here...
|
||||
}
|
||||
|
||||
NimBLEL2CAPServer::~NimBLEL2CAPServer() {
|
||||
// Delete all services
|
||||
for (auto service : this->services) {
|
||||
delete service;
|
||||
}
|
||||
}
|
||||
|
||||
NimBLEL2CAPChannel* NimBLEL2CAPServer::createService(const uint16_t psm,
|
||||
const uint16_t mtu,
|
||||
NimBLEL2CAPChannelCallbacks* callbacks) {
|
||||
auto service = new NimBLEL2CAPChannel(psm, mtu, callbacks);
|
||||
auto rc = ble_l2cap_create_server(psm, mtu, NimBLEL2CAPChannel::handleL2capEvent, service);
|
||||
|
||||
if (rc != 0) {
|
||||
NIMBLE_LOGE(LOG_TAG, "Could not ble_l2cap_create_server: %d", rc);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
this->services.push_back(service);
|
||||
return service;
|
||||
}
|
||||
38
src/NimBLEL2CAPServer.h
Normal file
38
src/NimBLEL2CAPServer.h
Normal file
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// (C) Dr. Michael 'Mickey' Lauer <mickey@vanille-media.de>
|
||||
//
|
||||
#ifndef NIMBLEL2CAPSERVER_H
|
||||
#define NIMBLEL2CAPSERVER_H
|
||||
#pragma once
|
||||
|
||||
#include "inttypes.h"
|
||||
#include <vector>
|
||||
|
||||
class NimBLEL2CAPChannel;
|
||||
class NimBLEL2CAPChannelCallbacks;
|
||||
|
||||
/**
|
||||
* @brief L2CAP server class.
|
||||
*
|
||||
* Encapsulates a L2CAP server that can hold multiple services. Every service is represented by a channel object
|
||||
* and an assorted set of callbacks.
|
||||
*/
|
||||
class NimBLEL2CAPServer {
|
||||
public:
|
||||
/// @brief Register a new L2CAP service instance.
|
||||
/// @param psm The port multiplexor service number.
|
||||
/// @param mtu The maximum transmission unit.
|
||||
/// @param callbacks The callbacks for this service.
|
||||
/// @return the newly created object, if the server registration was successful.
|
||||
NimBLEL2CAPChannel* createService(const uint16_t psm, const uint16_t mtu, NimBLEL2CAPChannelCallbacks* callbacks);
|
||||
|
||||
private:
|
||||
NimBLEL2CAPServer();
|
||||
~NimBLEL2CAPServer();
|
||||
std::vector<NimBLEL2CAPChannel*> services;
|
||||
|
||||
friend class NimBLEL2CAPChannel;
|
||||
friend class NimBLEDevice;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
@@ -48,30 +48,18 @@ typedef enum {
|
||||
} NIMBLE_PROPERTY;
|
||||
|
||||
# include "NimBLELocalAttribute.h"
|
||||
# include "NimBLEValueAttribute.h"
|
||||
# include "NimBLEAttValue.h"
|
||||
# include <vector>
|
||||
class NimBLEConnInfo;
|
||||
|
||||
class NimBLELocalValueAttribute : public NimBLELocalAttribute {
|
||||
class NimBLELocalValueAttribute : public NimBLELocalAttribute, public NimBLEValueAttribute {
|
||||
public:
|
||||
/**
|
||||
* @brief Get the properties of the attribute.
|
||||
*/
|
||||
uint16_t getProperties() const { return m_properties; }
|
||||
|
||||
/**
|
||||
* @brief Get the length of the attribute value.
|
||||
* @return The length of the attribute value.
|
||||
*/
|
||||
size_t getLength() const { return m_value.size(); }
|
||||
|
||||
/**
|
||||
* @brief Get a copy of the value of the attribute value.
|
||||
* @param [in] timestamp (Optional) A pointer to a time_t struct to get the time the value set.
|
||||
* @return A copy of the attribute value.
|
||||
*/
|
||||
NimBLEAttValue getValue(time_t* timestamp = nullptr) const { return m_value; }
|
||||
|
||||
/**
|
||||
* @brief Set the value of the attribute value.
|
||||
* @param [in] data The data to set the value to.
|
||||
@@ -100,19 +88,6 @@ class NimBLELocalValueAttribute : public NimBLELocalAttribute {
|
||||
m_value.setValue<T>(val);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Template to convert the data to <type\>.
|
||||
* @tparam T The type to convert the data to.
|
||||
* @param [in] timestamp (Optional) A pointer to a time_t struct to get the time the value set.
|
||||
* @param [in] skipSizeCheck (Optional) If true it will skip checking if the data size is less than <tt>sizeof(<type\>)</tt>.
|
||||
* @return The data converted to <type\> or NULL if skipSizeCheck is false and the data is less than <tt>sizeof(<type\>)</tt>.
|
||||
* @details <b>Use:</b> <tt>getValue<type>(×tamp, skipSizeCheck);</tt>
|
||||
*/
|
||||
template <typename T>
|
||||
T getValue(time_t* timestamp = nullptr, bool skipSizeCheck = false) const {
|
||||
return m_value.getValue<T>(timestamp, skipSizeCheck);
|
||||
}
|
||||
|
||||
protected:
|
||||
friend class NimBLEServer;
|
||||
|
||||
@@ -127,8 +102,7 @@ class NimBLELocalValueAttribute : public NimBLELocalAttribute {
|
||||
uint16_t handle,
|
||||
uint16_t maxLen,
|
||||
uint16_t initLen = CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH)
|
||||
: NimBLELocalAttribute(uuid, handle), m_value(initLen, maxLen) {}
|
||||
|
||||
: NimBLELocalAttribute(uuid, handle), NimBLEValueAttribute(maxLen, initLen) {}
|
||||
/**
|
||||
* @brief Destroy the NimBLELocalValueAttribute object.
|
||||
*/
|
||||
@@ -163,8 +137,7 @@ class NimBLELocalValueAttribute : public NimBLELocalAttribute {
|
||||
*/
|
||||
void setProperties(uint16_t properties) { m_properties = properties; }
|
||||
|
||||
NimBLEAttValue m_value{};
|
||||
uint16_t m_properties{0};
|
||||
uint16_t m_properties{0};
|
||||
};
|
||||
|
||||
#endif // CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
@@ -110,16 +110,16 @@
|
||||
|
||||
# define NIMBLE_CPP_LOG_FORMAT(letter, format) NIMBLE_CPP_LOG_COLOR_##letter #letter " (%lu) %s: " format LOG_RESET_COLOR "\n"
|
||||
|
||||
# define NIMBLE_CPP_LOG_LEVEL_LOCAL(level, tag, format, ...) \
|
||||
do { \
|
||||
if (level==ESP_LOG_ERROR) { esp_log_write(ESP_LOG_ERROR, tag, NIMBLE_CPP_LOG_FORMAT(E, format), esp_log_timestamp(), tag __VA_OPT__(,) __VA_ARGS__); } \
|
||||
# define NIMBLE_CPP_LOG_LEVEL_LOCAL(level, tag, format, ...) \
|
||||
do { \
|
||||
if (level==ESP_LOG_ERROR) { esp_log_write(ESP_LOG_ERROR, tag, NIMBLE_CPP_LOG_FORMAT(E, format), esp_log_timestamp(), tag __VA_OPT__(,) __VA_ARGS__); } \
|
||||
else if (level==ESP_LOG_WARN) { esp_log_write(ESP_LOG_WARN, tag, NIMBLE_CPP_LOG_FORMAT(W, format), esp_log_timestamp(), tag __VA_OPT__(,) __VA_ARGS__); } \
|
||||
else if (level==ESP_LOG_INFO) { esp_log_write(ESP_LOG_INFO, tag, NIMBLE_CPP_LOG_FORMAT(I, format), esp_log_timestamp(), tag __VA_OPT__(,) __VA_ARGS__); } \
|
||||
else { esp_log_write(ESP_LOG_DEBUG, tag, NIMBLE_CPP_LOG_FORMAT(D, format), esp_log_timestamp(), tag __VA_OPT__(,) __VA_ARGS__); } \
|
||||
} while(0)
|
||||
|
||||
# define NIMBLE_CPP_LOG_PRINT(level, tag, format, ...) \
|
||||
do { \
|
||||
# define NIMBLE_CPP_LOG_PRINT(level, tag, format, ...) \
|
||||
do { \
|
||||
if (CONFIG_NIMBLE_CPP_LOG_LEVEL >= level) NIMBLE_CPP_LOG_LEVEL_LOCAL(level, tag, format, ##__VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
@@ -172,5 +172,12 @@
|
||||
# endif
|
||||
|
||||
# endif /* CONFIG_NIMBLE_CPP_IDF */
|
||||
|
||||
# define NIMBLE_LOGD_IF(cond, tag, format, ...) { if (cond) { NIMBLE_LOGD(tag, format, ##__VA_ARGS__); }}
|
||||
# define NIMBLE_LOGI_IF(cond, tag, format, ...) { if (cond) { NIMBLE_LOGI(tag, format, ##__VA_ARGS__); }}
|
||||
# define NIMBLE_LOGW_IF(cond, tag, format, ...) { if (cond) { NIMBLE_LOGW(tag, format, ##__VA_ARGS__); }}
|
||||
# define NIMBLE_LOGE_IF(cond, tag, format, ...) { if (cond) { NIMBLE_LOGE(tag, format, ##__VA_ARGS__); }}
|
||||
# define NIMBLE_LOGE_RC(rc, tag, format, ...) { if (rc) { NIMBLE_LOGE(tag, format "; rc=%d %s", ##__VA_ARGS__, rc, NimBLEUtils::returnCodeToString(rc)); }}
|
||||
|
||||
#endif /* CONFIG_BT_ENABLED */
|
||||
#endif /* NIMBLE_CPP_LOG_H_ */
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
@@ -63,11 +63,8 @@ NimBLERemoteCharacteristic::~NimBLERemoteCharacteristic() {
|
||||
/**
|
||||
* @brief Callback used by the API when a descriptor is discovered or search complete.
|
||||
*/
|
||||
int NimBLERemoteCharacteristic::descriptorDiscCB(uint16_t connHandle,
|
||||
const ble_gatt_error* error,
|
||||
uint16_t chrHandle,
|
||||
const ble_gatt_dsc* dsc,
|
||||
void* arg) {
|
||||
int NimBLERemoteCharacteristic::descriptorDiscCB(
|
||||
uint16_t connHandle, const ble_gatt_error* error, uint16_t chrHandle, const ble_gatt_dsc* dsc, void* arg) {
|
||||
int rc = error->status;
|
||||
auto filter = (NimBLEDescriptorFilter*)arg;
|
||||
auto pTaskData = (NimBLETaskData*)filter->taskData;
|
||||
@@ -77,8 +74,7 @@ int NimBLERemoteCharacteristic::descriptorDiscCB(uint16_t connHandle,
|
||||
|
||||
// Results for chrHandle added until rc != 0
|
||||
// Must find specified UUID if filter is used
|
||||
if (rc == 0 && pChr->getHandle() == chrHandle
|
||||
&& (!uuid || 0 == ble_uuid_cmp(uuid->getBase(), &dsc->uuid.u))) {
|
||||
if (rc == 0 && pChr->getHandle() == chrHandle && (!uuid || 0 == ble_uuid_cmp(uuid->getBase(), &dsc->uuid.u))) {
|
||||
// Return BLE_HS_EDONE if the descriptor was found, stop the search
|
||||
pChr->m_vDescriptors.push_back(new NimBLERemoteDescriptor(pChr, dsc));
|
||||
rc = !!uuid * BLE_HS_EDONE;
|
||||
@@ -93,10 +89,10 @@ int NimBLERemoteCharacteristic::descriptorDiscCB(uint16_t connHandle,
|
||||
|
||||
/**
|
||||
* @brief Populate the descriptors (if any) for this characteristic.
|
||||
* @param [in] filter Structure containing pointers to descriptor, UUID, and task data.
|
||||
* @param [in] pFilter Pointer to a filter containing pointers to descriptor, UUID, and task data.
|
||||
* @return True if successfully retrieved, success = BLE_HS_EDONE.
|
||||
*/
|
||||
bool NimBLERemoteCharacteristic::retrieveDescriptors(NimBLEDescriptorFilter* filter) const {
|
||||
bool NimBLERemoteCharacteristic::retrieveDescriptors(NimBLEDescriptorFilter* pFilter) const {
|
||||
NIMBLE_LOGD(LOG_TAG, ">> retrieveDescriptors() for characteristic: %s", getUUID().toString().c_str());
|
||||
|
||||
// If this is the last handle then there are no descriptors
|
||||
@@ -105,25 +101,35 @@ bool NimBLERemoteCharacteristic::retrieveDescriptors(NimBLEDescriptorFilter* fil
|
||||
return true;
|
||||
}
|
||||
|
||||
NimBLETaskData taskData(const_cast<NimBLERemoteCharacteristic*>(this));
|
||||
NimBLEDescriptorFilter defaultFilter{nullptr, nullptr, &taskData};
|
||||
if (pFilter == nullptr) {
|
||||
pFilter = &defaultFilter;
|
||||
}
|
||||
|
||||
int rc = ble_gattc_disc_all_dscs(getClient()->getConnHandle(),
|
||||
getHandle(),
|
||||
getRemoteService()->getEndHandle(),
|
||||
NimBLERemoteCharacteristic::descriptorDiscCB,
|
||||
filter);
|
||||
pFilter);
|
||||
if (rc != 0) {
|
||||
NIMBLE_LOGE(LOG_TAG, "ble_gattc_disc_all_dscs: rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
|
||||
return false;
|
||||
}
|
||||
|
||||
NimBLEUtils::taskWait(filter->taskData, BLE_NPL_TIME_FOREVER);
|
||||
rc = ((NimBLETaskData*)filter->taskData)->m_flags;
|
||||
auto prevDscCount = m_vDescriptors.size();
|
||||
NimBLEUtils::taskWait(taskData, BLE_NPL_TIME_FOREVER);
|
||||
rc = ((NimBLETaskData*)pFilter->taskData)->m_flags;
|
||||
if (rc != BLE_HS_EDONE) {
|
||||
NIMBLE_LOGE(LOG_TAG, "<< retrieveDescriptors(): failed: rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
|
||||
return false;
|
||||
}
|
||||
|
||||
filter->dsc = m_vDescriptors.back();
|
||||
NIMBLE_LOGD(LOG_TAG, "<< retrieveDescriptors(): found %d descriptors.", m_vDescriptors.size());
|
||||
if (m_vDescriptors.size() > prevDscCount) {
|
||||
pFilter->dsc = m_vDescriptors.back();
|
||||
}
|
||||
|
||||
NIMBLE_LOGD(LOG_TAG, "<< retrieveDescriptors(): found %d descriptors.", m_vDescriptors.size() - prevDscCount);
|
||||
return true;
|
||||
} // retrieveDescriptors
|
||||
|
||||
@@ -134,9 +140,9 @@ bool NimBLERemoteCharacteristic::retrieveDescriptors(NimBLEDescriptorFilter* fil
|
||||
*/
|
||||
NimBLERemoteDescriptor* NimBLERemoteCharacteristic::getDescriptor(const NimBLEUUID& uuid) const {
|
||||
NIMBLE_LOGD(LOG_TAG, ">> getDescriptor: uuid: %s", uuid.toString().c_str());
|
||||
NimBLEUUID uuidTmp{uuid};
|
||||
NimBLETaskData taskData(const_cast<NimBLERemoteCharacteristic*>(this));
|
||||
NimBLEDescriptorFilter filter = {nullptr, &uuid, &taskData};
|
||||
NimBLEUUID uuidTmp;
|
||||
NimBLEDescriptorFilter filter{nullptr, &uuidTmp, &taskData};
|
||||
|
||||
for (const auto& dsc : m_vDescriptors) {
|
||||
if (dsc->getUUID() == uuid) {
|
||||
@@ -148,16 +154,18 @@ NimBLERemoteDescriptor* NimBLERemoteCharacteristic::getDescriptor(const NimBLEUU
|
||||
if (!retrieveDescriptors(&filter) || filter.dsc) {
|
||||
goto Done;
|
||||
}
|
||||
// Try again with 128 bit uuid if request succeeded with no uuid found.
|
||||
if (uuid.bitSize() == BLE_UUID_TYPE_16 || uuid.bitSize() == BLE_UUID_TYPE_32) {
|
||||
uuidTmp = NimBLEUUID(uuid).to128();
|
||||
|
||||
// Try again with 128 bit uuid if request succeeded but no descriptor found.
|
||||
if (uuid.bitSize() != BLE_UUID_TYPE_128) {
|
||||
uuidTmp.to128();
|
||||
retrieveDescriptors(&filter);
|
||||
goto Done;
|
||||
}
|
||||
// Try again with 16 bit uuid if request succeeded with no uuid found.
|
||||
// If the uuid was 128 bit but not of the BLE base type this check will fail.
|
||||
uuidTmp = NimBLEUUID(uuid).to16();
|
||||
|
||||
// If the uuid was 128 bit, try again with 16 bit uuid.
|
||||
uuidTmp.to16();
|
||||
if (uuidTmp.bitSize() == BLE_UUID_TYPE_16) {
|
||||
filter.uuid = &uuidTmp;
|
||||
retrieveDescriptors(&filter);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
@@ -67,7 +67,7 @@ class NimBLERemoteCharacteristic : public NimBLERemoteValueAttribute {
|
||||
~NimBLERemoteCharacteristic();
|
||||
|
||||
bool setNotify(uint16_t val, notify_callback notifyCallback = nullptr, bool response = true) const;
|
||||
bool retrieveDescriptors(NimBLEDescriptorFilter* filter = nullptr) const;
|
||||
bool retrieveDescriptors(NimBLEDescriptorFilter* pFilter = nullptr) const;
|
||||
|
||||
static int descriptorDiscCB(
|
||||
uint16_t connHandle, const ble_gatt_error* error, uint16_t chrHandle, const ble_gatt_dsc* dsc, void* arg);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
@@ -122,7 +122,7 @@ int NimBLERemoteValueAttribute::onWriteCB(uint16_t conn_handle, const ble_gatt_e
|
||||
* @param [in] timestamp A pointer to a time_t struct to store the time the value was read.
|
||||
* @return The value of the remote characteristic.
|
||||
*/
|
||||
NimBLEAttValue NimBLERemoteValueAttribute::readValue(time_t* timestamp) const {
|
||||
NimBLEAttValue NimBLERemoteValueAttribute::readValue(time_t* timestamp) {
|
||||
NIMBLE_LOGD(LOG_TAG, ">> readValue()");
|
||||
|
||||
NimBLEAttValue value{};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
@@ -32,32 +32,19 @@
|
||||
# undef max
|
||||
/**************************/
|
||||
|
||||
# include "NimBLEAttribute.h"
|
||||
# include "NimBLEValueAttribute.h"
|
||||
# include "NimBLEAttValue.h"
|
||||
|
||||
class NimBLEClient;
|
||||
|
||||
class NimBLERemoteValueAttribute : public NimBLEAttribute {
|
||||
class NimBLERemoteValueAttribute : public NimBLEValueAttribute, public NimBLEAttribute {
|
||||
public:
|
||||
/**
|
||||
* @brief Read the value of the remote attribute.
|
||||
* @param [in] timestamp A pointer to a time_t struct to store the time the value was read.
|
||||
* @return The value of the remote attribute.
|
||||
*/
|
||||
NimBLEAttValue readValue(time_t* timestamp = nullptr) const;
|
||||
|
||||
/**
|
||||
* @brief Get the length of the remote attribute value.
|
||||
* @return The length of the remote attribute value.
|
||||
*/
|
||||
size_t getLength() const { return m_value.size(); }
|
||||
|
||||
/**
|
||||
* @brief Get the value of the remote attribute.
|
||||
* @return The value of the remote attribute.
|
||||
* @details This returns a copy of the value to avoid potential race conditions.
|
||||
*/
|
||||
NimBLEAttValue getValue() const { return m_value; }
|
||||
NimBLEAttValue readValue(time_t* timestamp = nullptr);
|
||||
|
||||
/**
|
||||
* Get the client instance that owns this attribute.
|
||||
@@ -153,20 +140,6 @@ class NimBLERemoteValueAttribute : public NimBLEAttribute {
|
||||
}
|
||||
# endif
|
||||
|
||||
/**
|
||||
* @brief Template to convert the remote characteristic data to <type\>.
|
||||
* @tparam T The type to convert the data to.
|
||||
* @param [in] timestamp A pointer to a time_t struct to store the time the value was read.
|
||||
* @param [in] skipSizeCheck If true it will skip checking if the data size is less than <tt>sizeof(<type\>)</tt>.
|
||||
* @return The data converted to <type\> or NULL if skipSizeCheck is false and the data is
|
||||
* less than <tt>sizeof(<type\>)</tt>.
|
||||
* @details <b>Use:</b> <tt>getValue<type>(×tamp, skipSizeCheck);</tt>
|
||||
*/
|
||||
template <typename T>
|
||||
T getValue(time_t* timestamp = nullptr, bool skipSizeCheck = false) const {
|
||||
return m_value.getValue<T>(timestamp, skipSizeCheck);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Template to convert the remote characteristic data to <type\>.
|
||||
* @tparam T The type to convert the data to.
|
||||
@@ -177,16 +150,16 @@ class NimBLERemoteValueAttribute : public NimBLEAttribute {
|
||||
* @details <b>Use:</b> <tt>readValue<type>(×tamp, skipSizeCheck);</tt>
|
||||
*/
|
||||
template <typename T>
|
||||
T readValue(time_t* timestamp = nullptr, bool skipSizeCheck = false) const {
|
||||
T readValue(time_t* timestamp = nullptr, bool skipSizeCheck = false) {
|
||||
readValue();
|
||||
return m_value.getValue<T>(timestamp, skipSizeCheck);
|
||||
return getValue<T>(timestamp, skipSizeCheck);
|
||||
}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Construct a new NimBLERemoteValueAttribute object.
|
||||
*/
|
||||
NimBLERemoteValueAttribute(const ble_uuid_any_t& uuid, uint16_t handle) : NimBLEAttribute(uuid, handle) {}
|
||||
NimBLERemoteValueAttribute(const ble_uuid_any_t& uuid, uint16_t handle) : NimBLEAttribute{uuid, handle} {}
|
||||
|
||||
/**
|
||||
* @brief Destroy the NimBLERemoteValueAttribute object.
|
||||
@@ -195,8 +168,6 @@ class NimBLERemoteValueAttribute : public NimBLEAttribute {
|
||||
|
||||
static int onReadCB(uint16_t conn_handle, const ble_gatt_error* error, ble_gatt_attr* attr, void* arg);
|
||||
static int onWriteCB(uint16_t conn_handle, const ble_gatt_error* error, ble_gatt_attr* attr, void* arg);
|
||||
|
||||
mutable NimBLEAttValue m_value{};
|
||||
};
|
||||
|
||||
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
@@ -57,6 +57,11 @@ int NimBLEScan::handleGapEvent(ble_gap_event* event, void* arg) {
|
||||
switch (event->type) {
|
||||
case BLE_GAP_EVENT_EXT_DISC:
|
||||
case BLE_GAP_EVENT_DISC: {
|
||||
if (!pScan->isScanning()) {
|
||||
NIMBLE_LOGI(LOG_TAG, "Scan stopped, ignoring event");
|
||||
return 0;
|
||||
}
|
||||
|
||||
# if CONFIG_BT_NIMBLE_EXT_ADV
|
||||
const auto& disc = event->ext_disc;
|
||||
const bool isLegacyAdv = disc.props & BLE_HCI_ADV_LEGACY_MASK;
|
||||
@@ -483,11 +488,11 @@ void NimBLEScan::clearResults() {
|
||||
* @brief Dump the scan results to the log.
|
||||
*/
|
||||
void NimBLEScanResults::dump() const {
|
||||
#if CONFIG_NIMBLE_CPP_LOG_LEVEL >=3
|
||||
# if CONFIG_NIMBLE_CPP_LOG_LEVEL >= 3
|
||||
for (const auto& dev : m_deviceVec) {
|
||||
NIMBLE_LOGI(LOG_TAG, "- %s", dev->toString().c_str());
|
||||
}
|
||||
#endif
|
||||
# endif
|
||||
} // dump
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
@@ -445,7 +445,7 @@ int NimBLEServer::handleGapEvent(ble_gap_event* event, void* arg) {
|
||||
|
||||
chr->m_pCallbacks->onSubscribe(chr,
|
||||
peerInfo,
|
||||
event->subscribe.cur_notify + event->subscribe.cur_indicate);
|
||||
event->subscribe.cur_notify + (event->subscribe.cur_indicate << 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -544,7 +544,6 @@ int NimBLEServer::handleGapEvent(ble_gap_event* event, void* arg) {
|
||||
break;
|
||||
} // BLE_GAP_EVENT_IDENTITY_RESOLVED
|
||||
|
||||
# if CONFIG_BT_NIMBLE_EXT_ADV
|
||||
case BLE_GAP_EVENT_PHY_UPDATE_COMPLETE: {
|
||||
rc = ble_gap_conn_find(event->phy_updated.conn_handle, &peerInfo.m_desc);
|
||||
if (rc != 0) {
|
||||
@@ -554,7 +553,6 @@ int NimBLEServer::handleGapEvent(ble_gap_event* event, void* arg) {
|
||||
pServer->m_pServerCallbacks->onPhyUpdate(peerInfo, event->phy_updated.tx_phy, event->phy_updated.rx_phy);
|
||||
return 0;
|
||||
} // BLE_GAP_EVENT_PHY_UPDATE_COMPLETE
|
||||
# endif
|
||||
|
||||
case BLE_GAP_EVENT_PASSKEY_ACTION: {
|
||||
struct ble_sm_io pkey = {0, 0};
|
||||
@@ -620,13 +618,10 @@ int NimBLEServer::handleGattEvent(uint16_t connHandle, uint16_t attrHandle, ble_
|
||||
switch (ctxt->op) {
|
||||
case BLE_GATT_ACCESS_OP_READ_DSC:
|
||||
case BLE_GATT_ACCESS_OP_READ_CHR: {
|
||||
// Don't call readEvent if this is an internal read (handle is NONE)
|
||||
if (connHandle != BLE_HS_CONN_HANDLE_NONE) {
|
||||
// If the packet header is only 8 bytes then this is a follow up of a long read
|
||||
// so we don't want to call the onRead() callback again.
|
||||
if (ctxt->om->om_pkthdr_len > 8 || val.size() <= (ble_att_mtu(connHandle) - 3)) {
|
||||
pAtt->readEvent(peerInfo);
|
||||
}
|
||||
// Don't call readEvent if the buffer len is 0 (this is a follow up to a previous read),
|
||||
// or if this is an internal read (handle is NONE)
|
||||
if (ctxt->om->om_len > 0 && connHandle != BLE_HS_CONN_HANDLE_NONE) {
|
||||
pAtt->readEvent(peerInfo);
|
||||
}
|
||||
|
||||
ble_npl_hw_enter_critical();
|
||||
@@ -638,12 +633,12 @@ int NimBLEServer::handleGattEvent(uint16_t connHandle, uint16_t attrHandle, ble_
|
||||
case BLE_GATT_ACCESS_OP_WRITE_DSC:
|
||||
case BLE_GATT_ACCESS_OP_WRITE_CHR: {
|
||||
uint16_t maxLen = val.max_size();
|
||||
if (ctxt->om->om_len > maxLen) {
|
||||
uint16_t len = ctxt->om->om_len;
|
||||
if (len > maxLen) {
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
|
||||
uint8_t buf[maxLen];
|
||||
uint16_t len = ctxt->om->om_len;
|
||||
uint8_t buf[maxLen];
|
||||
memcpy(buf, ctxt->om->om_data, len);
|
||||
|
||||
os_mbuf* next;
|
||||
@@ -791,29 +786,6 @@ void NimBLEServer::resetGATT() {
|
||||
m_gattsStarted = false;
|
||||
} // resetGATT
|
||||
|
||||
# if CONFIG_BT_NIMBLE_EXT_ADV
|
||||
/**
|
||||
* @brief Start advertising.
|
||||
* @param [in] instId The extended advertisement instance ID to start.
|
||||
* @param [in] duration How long to advertise for in milliseconds, 0 = forever (default).
|
||||
* @param [in] maxEvents Maximum number of advertisement events to send, 0 = no limit (default).
|
||||
* @return True if advertising started successfully.
|
||||
* @details Start the server advertising its existence. This is a convenience function and is equivalent to
|
||||
* retrieving the advertising object and invoking start upon it.
|
||||
*/
|
||||
bool NimBLEServer::startAdvertising(uint8_t instId, int duration, int maxEvents) const {
|
||||
return getAdvertising()->start(instId, duration, maxEvents);
|
||||
} // startAdvertising
|
||||
|
||||
/**
|
||||
* @brief Convenience function to stop advertising a data set.
|
||||
* @param [in] instId The extended advertisement instance ID to stop advertising.
|
||||
* @return True if advertising stopped successfully.
|
||||
*/
|
||||
bool NimBLEServer::stopAdvertising(uint8_t instId) const {
|
||||
return getAdvertising()->stop(instId);
|
||||
} // stopAdvertising
|
||||
|
||||
/**
|
||||
* @brief Request an update to the PHY used for a peer connection.
|
||||
* @param [in] connHandle the connection handle to the update the PHY for.
|
||||
@@ -857,6 +829,30 @@ bool NimBLEServer::getPhy(uint16_t connHandle, uint8_t* txPhy, uint8_t* rxPhy) {
|
||||
|
||||
return rc == 0;
|
||||
} // getPhy
|
||||
|
||||
# if CONFIG_BT_NIMBLE_EXT_ADV
|
||||
/**
|
||||
* @brief Start advertising.
|
||||
* @param [in] instId The extended advertisement instance ID to start.
|
||||
* @param [in] duration How long to advertise for in milliseconds, 0 = forever (default).
|
||||
* @param [in] maxEvents Maximum number of advertisement events to send, 0 = no limit (default).
|
||||
* @return True if advertising started successfully.
|
||||
* @details Start the server advertising its existence. This is a convenience function and is equivalent to
|
||||
* retrieving the advertising object and invoking start upon it.
|
||||
*/
|
||||
bool NimBLEServer::startAdvertising(uint8_t instId, int duration, int maxEvents) const {
|
||||
return getAdvertising()->start(instId, duration, maxEvents);
|
||||
} // startAdvertising
|
||||
|
||||
/**
|
||||
* @brief Convenience function to stop advertising a data set.
|
||||
* @param [in] instId The extended advertisement instance ID to stop advertising.
|
||||
* @return True if advertising stopped successfully.
|
||||
*/
|
||||
bool NimBLEServer::stopAdvertising(uint8_t instId) const {
|
||||
return getAdvertising()->stop(instId);
|
||||
} // stopAdvertising
|
||||
|
||||
# endif
|
||||
|
||||
# if !CONFIG_BT_NIMBLE_EXT_ADV || defined(_DOXYGEN_)
|
||||
@@ -1019,10 +1015,8 @@ void NimBLEServerCallbacks::onConnParamsUpdate(NimBLEConnInfo& connInfo) {
|
||||
NIMBLE_LOGD("NimBLEServerCallbacks", "onConnParamsUpdate: default");
|
||||
} // onConnParamsUpdate
|
||||
|
||||
# if CONFIG_BT_NIMBLE_EXT_ADV
|
||||
void NimBLEServerCallbacks::onPhyUpdate(NimBLEConnInfo& connInfo, uint8_t txPhy, uint8_t rxPhy) {
|
||||
NIMBLE_LOGD("NimBLEServerCallbacks", "onPhyUpdate: default, txPhy: %d, rxPhy: %d", txPhy, rxPhy);
|
||||
} // onPhyUpdate
|
||||
# endif
|
||||
|
||||
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
@@ -79,6 +79,8 @@ class NimBLEServer {
|
||||
NimBLEConnInfo getPeerInfoByHandle(uint16_t connHandle) const;
|
||||
void advertiseOnDisconnect(bool enable);
|
||||
void setDataLen(uint16_t connHandle, uint16_t tx_octets) const;
|
||||
bool updatePhy(uint16_t connHandle, uint8_t txPhysMask, uint8_t rxPhysMask, uint16_t phyOptions);
|
||||
bool getPhy(uint16_t connHandle, uint8_t* txPhy, uint8_t* rxPhy);
|
||||
|
||||
# if defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
|
||||
NimBLEClient* getClient(uint16_t connHandle);
|
||||
@@ -90,8 +92,6 @@ class NimBLEServer {
|
||||
NimBLEExtAdvertising* getAdvertising() const;
|
||||
bool startAdvertising(uint8_t instanceId, int duration = 0, int maxEvents = 0) const;
|
||||
bool stopAdvertising(uint8_t instanceId) const;
|
||||
bool updatePhy(uint16_t connHandle, uint8_t txPhysMask, uint8_t rxPhysMask, uint16_t phyOptions);
|
||||
bool getPhy(uint16_t connHandle, uint8_t* txPhy, uint8_t* rxPhy);
|
||||
# endif
|
||||
|
||||
# if !CONFIG_BT_NIMBLE_EXT_ADV || defined(_DOXYGEN_)
|
||||
@@ -203,7 +203,6 @@ class NimBLEServerCallbacks {
|
||||
*/
|
||||
virtual void onConnParamsUpdate(NimBLEConnInfo& connInfo);
|
||||
|
||||
# if CONFIG_BT_NIMBLE_EXT_ADV
|
||||
/**
|
||||
* @brief Called when the PHY update procedure is complete.
|
||||
* @param [in] connInfo A reference to a NimBLEConnInfo instance with information
|
||||
@@ -216,7 +215,6 @@ class NimBLEServerCallbacks {
|
||||
* * BLE_GAP_LE_PHY_CODED
|
||||
*/
|
||||
virtual void onPhyUpdate(NimBLEConnInfo& connInfo, uint8_t txPhy, uint8_t rxPhy);
|
||||
# endif
|
||||
}; // NimBLEServerCallbacks
|
||||
|
||||
#endif // CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
@@ -511,22 +511,24 @@ const char* NimBLEUtils::gapEventToString(uint8_t eventType) {
|
||||
return "BLE_GAP_EVENT_PATHLOSS_THRESHOLD";
|
||||
case BLE_GAP_EVENT_TRANSMIT_POWER: // 26
|
||||
return "BLE_GAP_EVENT_TRANSMIT_POWER";
|
||||
case BLE_GAP_EVENT_SUBRATE_CHANGE: // 27
|
||||
case BLE_GAP_EVENT_PARING_COMPLETE: // 27
|
||||
return "BLE_GAP_EVENT_PARING_COMPLETE";
|
||||
case BLE_GAP_EVENT_SUBRATE_CHANGE: // 28
|
||||
return "BLE_GAP_EVENT_SUBRATE_CHANGE";
|
||||
case BLE_GAP_EVENT_VS_HCI: // 28
|
||||
case BLE_GAP_EVENT_VS_HCI: // 29
|
||||
return "BLE_GAP_EVENT_VS_HCI";
|
||||
case BLE_GAP_EVENT_REATTEMPT_COUNT: // 29
|
||||
case BLE_GAP_EVENT_REATTEMPT_COUNT: // 31
|
||||
return "BLE_GAP_EVENT_REATTEMPT_COUNT";
|
||||
case BLE_GAP_EVENT_AUTHORIZE: // 30
|
||||
case BLE_GAP_EVENT_AUTHORIZE: // 32
|
||||
return "BLE_GAP_EVENT_AUTHORIZE";
|
||||
case BLE_GAP_EVENT_TEST_UPDATE: // 31
|
||||
case BLE_GAP_EVENT_TEST_UPDATE: // 33
|
||||
return "BLE_GAP_EVENT_TEST_UPDATE";
|
||||
# ifdef BLE_GAP_EVENT_DATA_LEN_CHG
|
||||
case BLE_GAP_EVENT_DATA_LEN_CHG: // 32
|
||||
case BLE_GAP_EVENT_DATA_LEN_CHG: // 34
|
||||
return "BLE_GAP_EVENT_DATA_LEN_CHG";
|
||||
# endif
|
||||
# ifdef BLE_GAP_EVENT_LINK_ESTAB
|
||||
case BLE_GAP_EVENT_LINK_ESTAB: // 33
|
||||
case BLE_GAP_EVENT_LINK_ESTAB: // 38
|
||||
return "BLE_GAP_EVENT_LINK_ESTAB";
|
||||
# endif
|
||||
# endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||
* 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");
|
||||
|
||||
86
src/NimBLEValueAttribute.h
Normal file
86
src/NimBLEValueAttribute.h
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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_VALUE_ATTRIBUTE_H_
|
||||
#define NIMBLE_CPP_VALUE_ATTRIBUTE_H_
|
||||
|
||||
#include "nimconfig.h"
|
||||
#if defined(CONFIG_BT_ENABLED) && (defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) || defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL))
|
||||
|
||||
# include "NimBLEAttribute.h"
|
||||
# include "NimBLEAttValue.h"
|
||||
|
||||
class NimBLEValueAttribute {
|
||||
public:
|
||||
NimBLEValueAttribute(uint16_t maxLen = BLE_ATT_ATTR_MAX_LEN, uint16_t initLen = CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH)
|
||||
: m_value(initLen, maxLen) {}
|
||||
|
||||
/**
|
||||
* @brief Get a copy of the value of the attribute value.
|
||||
* @param [in] timestamp (Optional) A pointer to a time_t struct to get the time the value set.
|
||||
* @return A copy of the attribute value.
|
||||
*/
|
||||
NimBLEAttValue getValue(time_t* timestamp) const { return m_value.getValue(timestamp); }
|
||||
|
||||
/**
|
||||
* @brief Get a copy of the value of the attribute value.
|
||||
* @return A copy of the attribute value.
|
||||
*/
|
||||
NimBLEAttValue getValue() const { return m_value; }
|
||||
|
||||
/**
|
||||
* @brief Get the length of the attribute value.
|
||||
* @return The length of the attribute value.
|
||||
*/
|
||||
size_t getLength() const { return m_value.size(); }
|
||||
|
||||
/**
|
||||
* @brief Template to convert the data to <type\>.
|
||||
* @tparam T The type to convert the data to.
|
||||
* @param [in] timestamp (Optional) A pointer to a time_t struct to get the time the value set.
|
||||
* @param [in] skipSizeCheck (Optional) If true it will skip checking if the data size is less than <tt>sizeof(<type\>)</tt>.
|
||||
* @return The data converted to <type\> or NULL if skipSizeCheck is false and the data is less than <tt>sizeof(<type\>)</tt>.
|
||||
* @details <b>Use:</b> <tt>getValue<type>(×tamp, skipSizeCheck);</tt>
|
||||
* Used for types that are trivially copyable and convertible to NimBLEAttValue.
|
||||
*/
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_trivially_copyable<T>::value, T>::type
|
||||
getValue(time_t* timestamp = nullptr, bool skipSizeCheck = false) const {
|
||||
return m_value.getValue<T>(timestamp, skipSizeCheck);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Template to convert the data to <type\>.
|
||||
* @tparam T The type to convert the data to.
|
||||
* @param [in] timestamp (Optional) A pointer to a time_t struct to get the time the value set.
|
||||
* @param [in] skipSizeCheck (Optional) If true it will skip checking if the data size is less than <tt>sizeof(<type\>)</tt>.
|
||||
* @return The data converted to <type\> or NULL if skipSizeCheck is false and the data is less than <tt>sizeof(<type\>)</tt>.
|
||||
* @details <b>Use:</b> <tt>getValue<type>(×tamp, skipSizeCheck);</tt>
|
||||
* Used for types that are not trivially copyable and convertible to NimBLEAttValue via it's operators.
|
||||
*/
|
||||
template <typename T>
|
||||
typename std::enable_if<!std::is_trivially_copyable<T>::value && std::is_convertible<T, NimBLEAttValue>::value, T>::type
|
||||
getValue(time_t* timestamp = nullptr, bool skipSizeCheck = false) const {
|
||||
return m_value;
|
||||
}
|
||||
|
||||
protected:
|
||||
NimBLEAttValue m_value{};
|
||||
};
|
||||
|
||||
#endif // CONFIG_BT_ENABLED && (CONFIG_BT_NIMBLE_ROLE_PERIPHERAL || CONFIG_BT_NIMBLE_ROLE_CENTRAL)
|
||||
#endif // NIMBLE_CPP_ATTRIBUTE_H_
|
||||
@@ -79,3 +79,15 @@
|
||||
#if !defined(CONFIG_BTDM_SCAN_DUPL_TYPE_DATA_DEVICE) && defined(CONFIG_BT_CTRL_SCAN_DUPL_TYPE_DATA_DEVICE)
|
||||
#define CONFIG_BTDM_SCAN_DUPL_TYPE_DATA_DEVICE CONFIG_BT_CTRL_SCAN_DUPL_TYPE_DATA_DEVICE
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_BT_LE_LL_CFG_FEAT_LE_CODED_PHY
|
||||
#define CONFIG_BT_NIMBLE_LL_CFG_FEAT_LE_CODED_PHY CONFIG_BT_LE_LL_CFG_FEAT_LE_CODED_PHY
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_BT_LE_LL_CFG_FEAT_LE_2M_PHY
|
||||
#define CONFIG_BT_NIMBLE_LL_CFG_FEAT_LE_2M_PHY CONFIG_BT_LE_LL_CFG_FEAT_LE_2M_PHY
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_BT_LE_50_FEATURE_SUPPORT
|
||||
#define CONFIG_BT_NIMBLE_50_FEATURE_SUPPORT CONFIG_BT_LE_50_FEATURE_SUPPORT
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user