72 Commits

Author SHA1 Message Date
h2zero
24a89199a1 Return error if calling function will block host task
This will prevent applications from hanging forever when calling a blocking function from the host task.
2026-03-30 13:31:31 -06:00
copilot-swe-agent[bot]
dd32365625 Fix setValue() using sizeof instead of strlen for char array arguments
Agent-Logs-Url: https://github.com/h2zero/NimBLE-Arduino/sessions/42ace199-2049-4c37-8f0a-30443045b8b2

Co-authored-by: h2zero <32826625+h2zero@users.noreply.github.com>
2026-03-29 17:06:10 -06:00
h2zero
8bb9578b41 Fix backward compatability issue with remote charactertistic sorting 2026-03-29 15:53:34 -06:00
h2zero
28d49a0d28 Add ANCS example 2026-03-29 15:34:30 -06:00
h2zero
b11d34ee06 [Bugfix] get remote characteristic by uuid returning the wrong instance.
This fixes a bug introduced when sorting the characteristic vector where the last item on in the vector was
returned but the characteristc wasn't at the end anymore.

This reverts the previous sorting of the vector after retrieval and instead sorts as the characteristics are found and also
provides a pointer to the characteristic from the retrieval callback.
2026-03-29 14:21:42 -06:00
h2zero
67fa4776f4 [Bugfix] Remote descriptor not found when char handles out of order
This resolves the issue of retrieving characteristics out of handle order and then trying to subscribe or
do some other fetch of a descriptor belonging to that characteristic and the search handle was limited
to the handle of the next characteristic in the vector.

This ensures that the vector is sorted in ascending order based on handle values so the next characteristic
in the vector will always have a higher handle value and the desciptor serach will function as intended.
2026-03-29 11:14:56 -06:00
h2zero
0db2215c2f [Bugfix] Set server created client connection status on creation. 2026-03-29 10:40:10 -06:00
h2zero
182796d756 [Bugfix] HCI response to disconnect of unknown ID should return success.
* Update macro use to use conversion macros.
2026-03-28 15:53:01 -06:00
h2zero
afe12212ff [Feat] Wait for connections to establish before callback, retry on fail
* Delay onConnect until connection is likely established using a timer (7x conn interval), with early completion on peer activity.
* Suppress onDisconnect for 0x3e and route terminal 0x3e failures to onConnectFail.
* Add configurable 0x3e reconnect attempts via `setConnectRetries` or client config (default 2).
* Retry logic suppresses callbacks while retrying; callbacks fire only on final outcome.
* Refactor connect-start path into a shared helper used by initial connect and retries.
2026-03-27 15:56:34 -06:00
h2zero
5d48f9479b [Bugfix] Arduino detection for headers. 2026-03-25 13:30:51 -06:00
h2zero
0dbca80bcf [Bugfix] Incorrect Client connection state tracking and self delete
This adds better client state tracking so that functions like NimBLEDevice::getDisconnectedClient get a more accurate state and will not return a connecting client.

This also fixes the client self delete on connection error where function call errors did not delete the client
2026-03-25 12:35:26 -06:00
h2zero
74443d93ee Add NimBLEServer::sendServiceChangedIndication
This reverts changing NimBLEServer::serviceChanged from private to public and adds a new function that is more user firendly to indicate to clients that the services should be re-discovered.

* Renames serviceChanged to setServiceChanged to better indicate its function.
2026-03-24 12:51:25 -06:00
h2zero
7b897b1551 Add a timer for scan responses
This ensures that the callback will be called within the configured time (in ms) when devices fail to respond to a scan response request within that time.

* Adds stats when debug logging to help tune the scan response timeout/scan parameters.
2026-03-24 12:26:18 -06:00
Chris Morgan
bc6bb9836d NimBLEServer::serviceChanged() should be public to enable it being called outside of the library to indicate GATT changes 2026-03-21 07:40:19 -06:00
h2zero
2cb68e242d [Bugfix] getBondedAddress index could go out of array bounds. 2026-03-18 09:08:01 -06:00
h2zero
6f16880c25 Cleanup compiler warnings when using HID device or bonds disabled. 2026-03-18 09:08:01 -06:00
copilot-swe-agent[bot]
a672ecd86b Add NimBLECppVersion.h with version macros and runtime version function
Co-authored-by: h2zero <32826625+h2zero@users.noreply.github.com>
2026-03-17 18:52:55 -06:00
Ryan Powell
3aace03f54 [Bugfix] NimBLEDevice::createServer can crash if stack not initialized.
* [Bugfix] NimBLEDevice::createServer can crash if stack not initialized.

This removes the gatts/gap reset calls from NimBLEDevice::createServer so that it can be safely used before initializing the stack.

This also deprecates NimBLEService::start the the services will now be added/created only when the server is started.
2026-03-17 18:52:55 -06:00
copilot-swe-agent[bot]
494f647e1f Add onPassKeyEntry to NimBLEServerCallbacks and onPassKeyDisplay to NimBLEClientCallbacks
Co-authored-by: h2zero <32826625+h2zero@users.noreply.github.com>
2026-03-17 18:52:55 -06:00
h2zero
22e7d6fe8d [Bugfix] Delete all bonds does not allow re-pairing.
This change iterates through each bond and unpairs it rather than just deleting the bond data in nvs, allowing a connected peer to rebond.
2026-03-16 19:07:31 -06:00
h2zero
c731adc441 [Bugfix] Missing notification data when length > 255 bytes
When the ACL buffer is less than the MTU, the data arrives in more than one mbuf.
This combines the data from the mbuf chain and stores it before calling the appliation callback, ensuring it has all the data.
2026-03-16 18:15:20 -06:00
h2zero
42c33fcdb5 Update build workflow - remove IDF v4.x builds 2026-03-16 08:56:09 -06:00
h2zero
59ef15a218 Add BLE stream classes.
Co-authored-by: doudar <17362216+doudar@users.noreply.github.com>
2026-03-16 08:56:09 -06:00
Stijn Eijndhoven
a0a8db992c Restore comment for disable_observer_mode field 2026-03-14 16:09:41 -06:00
Stijn Eijndhoven
c64ca74c34 Conditionally guard disable_observer_mode behind ESP_PLATFORM and CONFIG_USING_NIMBLE_COMPONENT 2026-03-14 16:09:41 -06:00
Stijn Eijndhoven
4cb427ef51 Use designated initializer for NimBLEScan::m_scanParams and guard disable_observer_mode field for ESP-IDF >= 5.4.2
The NimBLEScan constructor previously used positional struct initialization,
which no longer matches the ble_gap_disc_params layout in newer ESP-IDF
versions (>= 5.4.2) where the field `disable_observer_mode` was added.

This is caused by -Wmissing-field-initializers.

Switch to designated initializers to make the field assignments explicit
and more robust across ESP-IDF/NimBLE revisions. The new field is only
initialized when building against ESP-IDF 5.4.2 or later to maintain
backwards compatibility.
2026-03-14 16:09:41 -06:00
copilot-swe-agent[bot]
04a7a792ac [Bugfix] whitelist bounds checks
Co-authored-by: doudar <17362216+doudar@users.noreply.github.com>
2026-03-14 08:23:46 -06:00
h2zero
540ebfcb64 Properly set attribute handles and improve dynamic service changes
This changes how attribute handles are set so they can be correctly identified when there is more than one attribute with the same UUID.
Instead of reading from the stack by UUID to get the handles this will now use the registration callback to set them correctly.

This also improves handling of dynamic service changes by properly removing characteristics/descriptors when required and resetting the GATT when advertising is started instead of after the last client disconnects.

* Adds NimBLEUUID constructor overload for ble_uuid_t*.
* NimBLECharacteristic::getDescriptorByUUID now takes an optional index value to support multiple same-uuid descriptors.
2026-03-13 15:35:35 -06:00
h2zero
437e651e0c Add NimBLEConnInfo::toString method. 2026-03-13 14:18:54 -06:00
h2zero
ea7075a2f7 Use a single macro to detect if scan duplicate filtering is enabled 2026-03-13 14:16:00 -06:00
h2zero
002abf91e9 Fix kconfig warnings
Redefining the kconfig BLE options for the esp32p4 is no longer needed as
bluetooth support is enabled in newer esp-idf versions and can be added to
the project config if older versions are used.
2026-03-03 10:40:14 -07:00
h2zero
3266951aee Release 2.3.4 2025-12-27 08:34:13 -07:00
h2zero
4c07a1d892 Add void pointer argument to setCustomGapHandler. 2025-12-08 17:12:44 -07:00
hjlee
9a7db6a16e Fix descriptor search range in retrieveDescriptors()
The previous implementation incorrectly used the service's end handle
when searching for descriptors, which caused it to retrieve descriptors
from subsequent characteristics as well.

This fix calculates the correct end handle by finding the next
characteristic's handle and using (next_handle - 1) as the search limit.
This ensures only descriptors belonging to the current characteristic
are retrieved.

Fixes incorrect descriptor retrieval when multiple characteristics
exist in the same service.
2025-12-03 14:18:04 -07:00
iranl
25af28bcad Add support for esp32c61 2025-11-24 05:35:43 -07:00
h2zero
f216e95770 Add characteristic callbacks onStatus overload with conn info.
Adds a new overloaded callback to NimBLECharacteristicCallbacks for the notification/indication onStatus method that provides a NimBLEConnInfo reference.
2025-11-17 19:21:13 -07:00
h2zero
222f1590ed Refactor notify/indicate
This refactors the handling of sending notifications and indications for greater efficiency.
* Adds client subscription state tracking to NimBLECharacteristic rather than relying on the stack.
* Notifications/indications are now sent directly, no longer calling the callback to read the values.
  This avoids delays and flash writes in the stack, allowing for greater throughput.
2025-11-17 19:21:13 -07:00
srgg
149716a506 correct container byte size calculation to writeValue, notify, and indicate 2025-11-16 09:22:40 -07:00
srgg
4199c52af1 fix: correct byte size calculation for ATT values set from containers 2025-11-16 09:22:37 -07:00
h2zero
20158d62d0 [Bugfix] make sure the notify event is sent to server created clients 2025-10-24 13:19:42 -06:00
Quentin F
f6c8728ca3 Update 1.x_to2.x_migration_guide.md 2025-10-24 13:19:03 -06:00
h2zero
e0d3c4be39 Update workflows + add release publish 2025-10-24 13:09:46 -06:00
Guo-Rong
d163a9fdc6 Find client by handle during disconnect event.
If the peer has RPA enabled, searching by address fails due to address
resolution.
If this occurs, attempt to find the client by connection handle.
2025-10-23 11:55:24 -06:00
Chris Morgan
133c1a5da4 Usage_tips.md - Note that the library is threadsafe. 2025-10-23 11:54:37 -06:00
Chris Morgan
f622cdff0c README.md - Add a note about threadsafety 2025-10-23 11:54:37 -06:00
Chris Morgan
68068677ab Usage_tips.md - 'Device Local Name' information to help guide setting the GATT Device Name or Advertising name. 2025-09-28 19:31:04 -06:00
Chris Morgan
2c6ab706b3 Usage_tips.md - Detail persisted bonds limitations and considerations relative to CONFIG_BT_NIMBLE_MAX_CCCDS 2025-09-23 21:04:11 -06:00
h2zero
6f0b9ddf5d Convert NIMBLE_CPP macros to MYNEWT. 2025-09-06 16:59:55 -06:00
h2zero
8f9e85a46a Release 2.3.3 2025-09-05 16:11:11 -06:00
h2zero
7706f5a6b2 Support up to 1650 bytes of advertisement with extended advertising. 2025-09-05 15:44:47 -06:00
h2zero
1ffd013794 [Bugfix] Extended advertisements not reporting full data.
Extended advertisement reports would be truncated incorrectly as the handler was not checking the data status.

Correct advertisement length and set status on update.
2025-09-05 15:39:46 -06:00
h2zero
e8f7147ac5 [Bugfix] NimBLEAdvertisedDevice::isConnectable incorrect result 2025-09-05 15:11:38 -06:00
h2zero
6ee2a951f5 Release 2.3.2 2025-09-02 14:54:24 -06:00
h2zero
4b74939b6d Improve macros for code enablement 2025-09-02 14:36:02 -06:00
h2zero
9f7b9042e0 Fix docs build 2025-09-02 14:36:02 -06:00
h2zero
2cd5dc2aa2 Fix build with idf v5.5+ and specific roles are defined. 2025-08-25 09:11:13 -06:00
h2zero
9df8cc7dd1 Refactor to use MYNEWT_VAL macros.
This replaces the previously prefixed CONFIG_BT_X config macros with the underlying MYNEWT_VAL_X config macros that they affected.
2025-08-24 16:35:38 -06:00
iranl
88df909cfb Fix undefined reference to ble_svc_gap_device_name_set when GATT server is disabled (#349)
* Fix undefined reference to ble_svc_gap_device_name_set when GATT server is disabled

* Do not affect ESP-IDF <5.5.0
2025-08-24 16:07:59 -06:00
h2zero
8cefc0a562 [Bugfix] OnConnectfail not called when connection not established.
Workaround for when the disconnect event is sent when no connection has been established.
Espressif changed this from a connect event with error code to disconnect event.
2025-08-01 17:08:44 -06:00
h2zero
8af38e7eb9 Change default security settings to BLE secure connections off.
Fixing some connection issues when enabled, users should enable if desired.
2025-06-27 18:08:42 -06:00
h2zero
e7fead903c [Bugfix](workaround) OnConnect not being called.
Upstream changes have resulted in a possible status of BLE_ERR_UNSUPP_REM_FEATURE, this resulted in the onConnect callback not being called despite the connection actually being created.
This works around that bug to ensure that the connections are correctly tracked.
2025-06-27 18:07:46 -06:00
h2zero
a57c45e1de Fix build with idf versions < 5.x 2025-06-27 18:07:41 -06:00
h2zero
edfc838bef [Bugfix] allow peripheral and central roles without broadcast/scan. 2025-06-20 10:41:28 -06:00
h2zero
f1ead9959d Bump idf_component version 2025-06-11 11:40:42 -06:00
h2zero
b30421c19d Fix library.json version 2025-06-11 11:34:10 -06:00
h2zero
bdb868d125 Release 2.3.1 2025-06-11 11:14:52 -06:00
h2zero
503939c66f Update docs 2025-06-11 11:11:58 -06:00
Larry Davis
2640c44b45 Support passing data directly from NimBLEBeacon.getData() to NimBLEAdvertisementData.setManufacturerData() 2025-06-11 11:11:11 -06:00
h2zero
fec2d7a279 [Bugfix] NimBLEScan delete.
Calling NimBLEDevice::deint with the `clearAll` parameter set to `true` will delete the scan and any scan results but it was calling `clearall` which uses critical sections, this could cause a crash because the stack has already been de-initialized.
2025-06-09 17:13:30 -06:00
h2zero
e2cee2d994 Fix server client read/write not returning when encryption is used.
When the client created by the server reads or writes to an attribute and it triggers a pairing action the task will not be released because the client does not get the event.
This passes the event to the client to prevent the task from being hung.
2025-06-09 17:13:12 -06:00
h2zero
39f974625c Fix builds when exluding roles 2025-06-02 18:06:40 -06:00
John Boiles
169290f047 Allow esp_wifi_remote >= 0.5.3
`esp_wifi_remote` >= v0.10.0 is necessary to use esp-nimble-cpp with the latest ESP-IDF master branch.
2025-05-21 11:14:43 -06:00
41 changed files with 276 additions and 489 deletions

View File

@@ -1,49 +1,6 @@
# Changelog
All notable changes to this project will be documented in this file.
## [2.5.0] 2026-04-01
## Fixed
- `NimBLEClient` connection state tracking.
- Calling disconnect will no longer return false if the HCI response is "Unknown ID".
- Remote descriptors not found when characteristic vector handles out of order.
- `setValue` with char inputs now calculates the data length correctly.
## Added
- `NimBLEServer::sendServiceChangedIndication` Sends the service changed indication to peers so they refresh their database.
- `NimBLEScan` user configuarable scan response timer added to prevent unreported devices on long duration scans.
- `NimBLEClient` Connection retry on connection establishment failure, retry count configurable by app, default 2.
- ANCS Example
- `l2Cap` Disconnect API
## [2.4.0] 2026-03-20
## Fixed
- GATT attribute handles are now assigned from the registration callback so duplicate UUID attributes are identified correctly.
- Dynamic service changes now properly remove characteristics/descriptors and reset the GATT database when advertising starts.
- Missing notification/indication payload data when the value spans multiple mbufs, such as values larger than 255 bytes with small ACL buffers.
- `NimBLEDevice::createServer` will longer crash when called before the stack is initialized.
- Re-pairing after deleting all bonds now works by unpairing each stored bond instead of only deleting NVS data.
- Whitelist bounds checks.
- `NimBLEDevice::getBondedAddress` index bounds validation.
- Compiler warnings when bonds are disabled.
- kconfig warnings, redefined macros.
## Added
- `NimBLEStream`, `NimBLEStreamClient`, and `NimBLEStreamServer` classes and examples.
- `NimBLECppVersion.h` with compile-time version macros.
- `NimBLEDevice::getVersion` runtime version string helper.
- Matching passkey callbacks for both roles: `NimBLEServerCallbacks::onPassKeyEntry` and `NimBLEClientCallbacks::onPassKeyDisplay`.
- Bond migration helpers to convert bond storage between v1 and current formats while preserving existing bonds.
- `NimBLEUUID` constructor overload for `ble_uuid_t*`.
- Optional `index` parameter for `NimBLECharacteristic::getDescriptorByUUID` to access multiple descriptors with the same UUID.
- `NimBLEConnInfo::toString` method to get a string representation of the connection info.
## Changed
- `NimBLEService::start` is deprecated; services are now added when the server starts.
- `NimBLEHIDDevice::startServices()` is deprecated; services are now added when the server starts.
## [2.3.4] 2025-12-27
## Fixed

View File

@@ -204,4 +204,8 @@ config NIMBLE_CPP_FREERTOS_TASK_BLOCK_BIT
Configure the bit to set in the task notification value when a task is blocked waiting for an event.
This should be set to a bit that is not used by other notifications in the system.
config NIMBLE_CPP_IDF
bool
default BT_NIMBLE_ENABLED
endmenu

View File

@@ -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.5.0
PROJECT_NUMBER = 2.3.3
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
# quick idea about the purpose of the project. Keep the description short.

View File

@@ -1,6 +1,3 @@
dependencies:
local/esp-nimble-cpp:
path: ../../../../../esp-nimble-cpp/
mickeyl/esp-hpl:
git: https://github.com/mickeyl/esp-hpl.git
version: "1.1.0"

View File

@@ -1,12 +1,15 @@
#include <NimBLEDevice.h>
#include <esp_hpl.hpp>
#include <esp_timer.h>
#define L2CAP_PSM 192
// 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
#define INITIAL_PAYLOAD_SIZE 64
#define BLOCKS_BEFORE_DOUBLE 50
#define MAX_PAYLOAD_SIZE 4900
const BLEAdvertisedDevice* theDevice = NULL;
BLEClient* theClient = NULL;
@@ -14,15 +17,6 @@ BLEL2CAPChannel* theChannel = NULL;
size_t bytesSent = 0;
size_t bytesReceived = 0;
size_t currentPayloadSize = INITIAL_PAYLOAD_SIZE;
uint32_t blocksSent = 0;
uint64_t startTime = 0;
// Heap monitoring
size_t initialHeap = 0;
size_t lastHeap = 0;
size_t heapDecreaseCount = 0;
const size_t HEAP_LEAK_THRESHOLD = 10; // Warn after 10 consecutive decreases
class L2CAPChannelCallbacks: public BLEL2CAPChannelCallbacks {
@@ -49,7 +43,7 @@ class MyClientCallbacks: public BLEClientCallbacks {
printf("GAP connected\n");
pClient->setDataLen(251);
theChannel = BLEL2CAPChannel::connect(pClient, L2CAP_PSM, L2CAP_MTU, new L2CAPChannelCallbacks());
theChannel = BLEL2CAPChannel::connect(pClient, L2CAP_CHANNEL, L2CAP_MTU, new L2CAPChannelCallbacks());
}
void onDisconnect(BLEClient* pClient, int reason) {
@@ -67,72 +61,23 @@ class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
if (theDevice) { return; }
printf("BLE Advertised Device found: %s\n", advertisedDevice->toString().c_str());
// Look for device named "l2cap"
if (advertisedDevice->haveName() && advertisedDevice->getName() == "l2cap") {
printf("Found l2cap device!\n");
BLEDevice::getScan()->stop();
theDevice = advertisedDevice;
}
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 statusTask(void *pvParameters) {
while (true) {
vTaskDelay(1000 / portTICK_PERIOD_MS);
if (startTime > 0 && blocksSent > 0) {
uint64_t currentTime = esp_timer_get_time();
double elapsedSeconds = (currentTime - startTime) / 1000000.0;
double bytesPerSecond = 0.0;
double kbPerSecond = 0.0;
if (elapsedSeconds > 0.0) {
bytesPerSecond = bytesSent / elapsedSeconds;
kbPerSecond = bytesPerSecond / 1024.0;
}
// Heap monitoring
size_t currentHeap = esp_get_free_heap_size();
size_t minHeap = esp_get_minimum_free_heap_size();
// Track heap for leak detection
if (initialHeap == 0) {
initialHeap = currentHeap;
lastHeap = currentHeap;
}
// Check for consistent heap decrease
if (currentHeap < lastHeap) {
heapDecreaseCount++;
if (heapDecreaseCount >= HEAP_LEAK_THRESHOLD) {
printf("\n⚠️ WARNING: POSSIBLE MEMORY LEAK DETECTED! ⚠️\n");
printf("Heap has decreased %zu times in a row\n", heapDecreaseCount);
printf("Initial heap: %zu, Current heap: %zu, Lost: %zu bytes\n",
initialHeap, currentHeap, initialHeap - currentHeap);
}
} else if (currentHeap >= lastHeap) {
heapDecreaseCount = 0; // Reset counter if heap stabilizes or increases
}
lastHeap = currentHeap;
printf("\n=== STATUS UPDATE ===\n");
printf("Blocks sent: %lu\n", (unsigned long)blocksSent);
printf("Total bytes sent: %zu\n", bytesSent);
printf("Current payload size: %zu bytes\n", currentPayloadSize);
printf("Elapsed time: %.1f seconds\n", elapsedSeconds);
printf("Bandwidth: %.2f KB/s (%.2f Mbps)\n", kbPerSecond, (bytesPerSecond * 8) / 1000000.0);
printf("Heap: %zu free (min: %zu), Used since start: %zu\n",
currentHeap, minHeap, initialHeap > 0 ? initialHeap - currentHeap : 0);
printf("==================\n\n");
}
}
}
void connectTask(void *pvParameters) {
uint8_t sequenceNumber = 0;
while (true) {
if (!theDevice) {
vTaskDelay(1000 / portTICK_PERIOD_MS);
continue;
@@ -151,7 +96,7 @@ void connectTask(void *pvParameters) {
break;
}
vTaskDelay(2000 / portTICK_PERIOD_MS);
continue;
continue;
}
if (!theChannel) {
@@ -167,58 +112,22 @@ void connectTask(void *pvParameters) {
}
while (theChannel->isConnected()) {
// Create framed packet: [seqno 8bit] [16bit payload length] [payload]
std::vector<uint8_t> packet;
packet.reserve(3 + currentPayloadSize);
// Add sequence number (8 bits)
packet.push_back(sequenceNumber);
// Add payload length (16 bits, big endian - network byte order)
uint16_t payloadLen = currentPayloadSize;
packet.push_back((payloadLen >> 8) & 0xFF); // High byte first
packet.push_back(payloadLen & 0xFF); // Low byte second
// Add payload
for (size_t i = 0; i < currentPayloadSize; i++) {
packet.push_back(i & 0xFF);
}
if (theChannel->write(packet)) {
if (startTime == 0) {
startTime = esp_timer_get_time();
}
bytesSent += packet.size();
blocksSent++;
// Print every block since we're sending slowly now
printf("Sent block %lu (seq=%d, payload=%zu bytes, frame_size=%zu)\n",
(unsigned long)blocksSent, sequenceNumber, currentPayloadSize, packet.size());
sequenceNumber++;
// After every 50 blocks, double payload size
if (blocksSent % BLOCKS_BEFORE_DOUBLE == 0) {
size_t newSize = currentPayloadSize * 2;
// Cap at maximum safe payload size
if (newSize > MAX_PAYLOAD_SIZE) {
if (currentPayloadSize < MAX_PAYLOAD_SIZE) {
currentPayloadSize = MAX_PAYLOAD_SIZE;
printf("\n=== Reached maximum payload size of %zu bytes after %lu blocks ===\n", currentPayloadSize, (unsigned long)blocksSent);
}
// Already at max, don't increase further
} else {
currentPayloadSize = newSize;
printf("\n=== Doubling payload size to %zu bytes after %lu blocks ===\n", currentPayloadSize, (unsigned long)blocksSent);
}
}
/*
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();
abort();
}
// No delay - send as fast as possible
}
vTaskDelay(1000 / portTICK_PERIOD_MS);
@@ -227,13 +136,9 @@ void connectTask(void *pvParameters) {
extern "C"
void app_main(void) {
// Install high performance logging before any output
esp_hpl::HighPerformanceLogger::init();
printf("Starting L2CAP client example\n");
xTaskCreate(connectTask, "connectTask", 5000, NULL, 1, NULL);
xTaskCreate(statusTask, "statusTask", 3000, NULL, 1, NULL);
BLEDevice::init("L2CAP-Client");
BLEDevice::setMTU(BLE_ATT_MTU_MAX);
@@ -246,8 +151,15 @@ void app_main(void) {
scan->setActiveScan(true);
scan->start(25 * 1000, false);
// Main task just waits
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);
}
}

View File

@@ -1,6 +1,3 @@
dependencies:
local/esp-nimble-cpp:
path: ../../../../../esp-nimble-cpp/
mickeyl/esp-hpl:
git: https://github.com/mickeyl/esp-hpl.git
version: "1.1.0"

View File

@@ -1,16 +1,13 @@
#include <NimBLEDevice.h>
#include <esp_hpl.hpp>
#include <esp_timer.h>
#define L2CAP_PSM 192
// 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
// Heap monitoring
size_t initialHeap = 0;
size_t lastHeap = 0;
size_t heapDecreaseCount = 0;
const size_t HEAP_LEAK_THRESHOLD = 10; // Warn after 10 consecutive decreases
class GATTCallbacks: public BLEServerCallbacks {
public:
@@ -26,179 +23,68 @@ class L2CAPChannelCallbacks: public BLEL2CAPChannelCallbacks {
public:
bool connected = false;
size_t totalBytesReceived = 0;
size_t totalFramesReceived = 0;
size_t totalPayloadBytes = 0;
uint8_t expectedSequenceNumber = 0;
size_t sequenceErrors = 0;
size_t frameErrors = 0;
uint64_t startTime = 0;
std::vector<uint8_t> buffer; // Buffer for incomplete frames
size_t numberOfReceivedBytes;
uint8_t nextSequenceNumber;
public:
void onConnect(NimBLEL2CAPChannel* channel) {
printf("L2CAP connection established on PSM %d\n", L2CAP_PSM);
printf("L2CAP connection established\n");
connected = true;
totalBytesReceived = 0;
totalFramesReceived = 0;
totalPayloadBytes = 0;
expectedSequenceNumber = 0;
sequenceErrors = 0;
frameErrors = 0;
startTime = esp_timer_get_time();
buffer.clear();
numberOfReceivedBytes = nextSequenceNumber = 0;
}
void onRead(NimBLEL2CAPChannel* channel, std::vector<uint8_t>& data) {
// Append new data to buffer
buffer.insert(buffer.end(), data.begin(), data.end());
totalBytesReceived += data.size();
if (startTime == 0) {
startTime = esp_timer_get_time(); // start measuring once data flows
}
// Process complete frames from buffer
while (buffer.size() >= 3) { // Minimum frame size: seqno(1) + len(2)
// Parse frame header
uint8_t seqno = buffer[0];
uint16_t payloadLen = (buffer[1] << 8) | buffer[2]; // Big-endian
size_t frameSize = 3 + payloadLen;
// Check if we have complete frame
if (buffer.size() < frameSize) {
break; // Wait for more data
}
// Validate and process frame
totalFramesReceived++;
totalPayloadBytes += payloadLen;
// Check sequence number
if (seqno != expectedSequenceNumber) {
sequenceErrors++;
printf("Frame %zu: Sequence error - got %d, expected %d (payload=%d bytes)\n",
totalFramesReceived, seqno, expectedSequenceNumber, payloadLen);
}
// Update expected sequence number (wraps at 256)
expectedSequenceNumber = (seqno + 1) & 0xFF;
// Remove processed frame from buffer
buffer.erase(buffer.begin(), buffer.begin() + frameSize);
// Print progress every 100 frames
if (totalFramesReceived % 100 == 0) {
double elapsedSeconds = (esp_timer_get_time() - startTime) / 1000000.0;
double bytesPerSecond = elapsedSeconds > 0 ? totalBytesReceived / elapsedSeconds : 0.0;
printf("Received %zu frames (%zu payload bytes) - Bandwidth: %.2f KB/s (%.2f Mbps)\n",
totalFramesReceived, totalPayloadBytes,
bytesPerSecond / 1024.0, (bytesPerSecond * 8) / 1000000.0);
}
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("\nL2CAP disconnected\n");
double elapsedSeconds = startTime > 0 ? (esp_timer_get_time() - startTime) / 1000000.0 : 0.0;
double bytesPerSecond = elapsedSeconds > 0 ? totalBytesReceived / elapsedSeconds : 0.0;
printf("Final statistics:\n");
printf(" Total frames: %zu\n", totalFramesReceived);
printf(" Total bytes: %zu\n", totalBytesReceived);
printf(" Payload bytes: %zu\n", totalPayloadBytes);
printf(" Sequence errors: %zu\n", sequenceErrors);
printf(" Frame errors: %zu\n", frameErrors);
printf(" Bandwidth: %.2f KB/s (%.2f Mbps)\n", bytesPerSecond / 1024.0, (bytesPerSecond * 8) / 1000000.0);
// Reset state for the next connection
buffer.clear();
totalBytesReceived = 0;
totalFramesReceived = 0;
totalPayloadBytes = 0;
expectedSequenceNumber = 0;
sequenceErrors = 0;
frameErrors = 0;
startTime = 0;
printf("L2CAP disconnected\n");
connected = false;
// Restart advertising so another client can connect
BLEDevice::startAdvertising();
}
};
extern "C"
void app_main(void) {
// Install high performance logging before any other output
esp_hpl::HighPerformanceLogger::init();
printf("Starting L2CAP server example [%lu free] [%lu min]\n", esp_get_free_heap_size(), esp_get_minimum_free_heap_size());
BLEDevice::init("l2cap"); // Match the name the client is looking for
BLEDevice::init("L2CAP-Server");
BLEDevice::setMTU(BLE_ATT_MTU_MAX);
auto cocServer = BLEDevice::createL2CAPServer();
auto l2capChannelCallbacks = new L2CAPChannelCallbacks();
auto channel = cocServer->createService(L2CAP_PSM, L2CAP_MTU, l2capChannelCallbacks);
(void)channel; // prevent unused warning
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();
NimBLEAdvertisementData scanData;
scanData.setName("l2cap");
advertising->setScanResponseData(scanData);
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());
// Status reporting loop
// 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 && l2capChannelCallbacks->totalBytesReceived > 0) {
uint64_t currentTime = esp_timer_get_time();
double elapsedSeconds = (currentTime - l2capChannelCallbacks->startTime) / 1000000.0;
if (elapsedSeconds > 0) {
double bytesPerSecond = l2capChannelCallbacks->totalBytesReceived / elapsedSeconds;
double framesPerSecond = l2capChannelCallbacks->totalFramesReceived / elapsedSeconds;
// Heap monitoring
size_t currentHeap = esp_get_free_heap_size();
size_t minHeap = esp_get_minimum_free_heap_size();
// Track heap for leak detection
if (initialHeap == 0) {
initialHeap = currentHeap;
lastHeap = currentHeap;
}
// Check for consistent heap decrease
if (currentHeap < lastHeap) {
heapDecreaseCount++;
if (heapDecreaseCount >= HEAP_LEAK_THRESHOLD) {
printf("\n⚠️ WARNING: POSSIBLE MEMORY LEAK DETECTED! ⚠️\n");
printf("Heap has decreased %zu times in a row\n", heapDecreaseCount);
printf("Initial heap: %zu, Current heap: %zu, Lost: %zu bytes\n",
initialHeap, currentHeap, initialHeap - currentHeap);
}
} else if (currentHeap >= lastHeap) {
heapDecreaseCount = 0; // Reset counter if heap stabilizes or increases
}
lastHeap = currentHeap;
printf("\n=== STATUS UPDATE ===\n");
printf("Frames received: %zu (%.1f fps)\n", l2capChannelCallbacks->totalFramesReceived, framesPerSecond);
printf("Total bytes: %zu\n", l2capChannelCallbacks->totalBytesReceived);
printf("Payload bytes: %zu\n", l2capChannelCallbacks->totalPayloadBytes);
printf("Bandwidth: %.2f KB/s (%.2f Mbps)\n", bytesPerSecond / 1024.0, (bytesPerSecond * 8) / 1000000.0);
printf("Sequence errors: %zu\n", l2capChannelCallbacks->sequenceErrors);
printf("Heap: %zu free (min: %zu), Used since start: %zu\n",
currentHeap, minHeap, initialHeap > 0 ? initialHeap - currentHeap : 0);
printf("==================\n");
}
}
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());
}
}

View File

@@ -1,5 +1,5 @@
## IDF Component Manager Manifest File
version: "2.5.0"
version: "2.3.4"
license: "Apache-2.0"
description: "C++ wrapper for the NimBLE BLE stack"
url: "https://github.com/h2zero/esp-nimble-cpp"

View File

@@ -1,6 +1,6 @@
{
"name": "esp-nimble-cpp",
"version": "2.5.0",
"version": "2.3.4",
"description": "C++ wrapper for the NimBLE BLE stack",
"keywords": [
"BLE",

View File

@@ -21,10 +21,10 @@
#include "syscfg/syscfg.h"
#if CONFIG_BT_NIMBLE_ENABLED
# ifdef USING_NIMBLE_ARDUINO_HEADERS
# include "nimble/nimble/include/nimble/ble.h"
# else
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "nimble/ble.h"
# else
# include "nimble/nimble/include/nimble/ble.h"
# endif
/**** FIX COMPILATION ****/
@@ -63,8 +63,8 @@ class NimBLEAddress : private ble_addr_t {
const NimBLEAddress& reverseByteOrder();
bool operator==(const NimBLEAddress& rhs) const;
bool operator!=(const NimBLEAddress& rhs) const;
operator std::string() const;
operator uint64_t() const;
operator std::string() const;
operator uint64_t() const;
};
#endif // CONFIG_BT_NIMBLE_ENABLED

View File

@@ -25,12 +25,12 @@
# include "NimBLEScan.h"
# include "NimBLEUUID.h"
# ifdef USING_NIMBLE_ARDUINO_HEADERS
# include "nimble/nimble/host/include/host/ble_hs_adv.h"
# include "nimble/nimble/host/include/host/ble_gap.h"
# else
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "host/ble_hs_adv.h"
# include "host/ble_gap.h"
# else
# include "nimble/nimble/host/include/host/ble_hs_adv.h"
# include "nimble/nimble/host/include/host/ble_gap.h"
# endif
# include <vector>
@@ -158,13 +158,13 @@ class NimBLEAdvertisedDevice {
uint8_t findAdvField(uint8_t type, uint8_t index = 0, size_t* data_loc = nullptr) const;
size_t findServiceData(uint8_t index, uint8_t* bytes) const;
NimBLEAddress m_address{};
uint8_t m_advType{};
int8_t m_rssi{};
uint8_t m_callbackSent{};
uint16_t m_advLength{};
ble_npl_time_t m_time{};
NimBLEAdvertisedDevice* m_pNextWaiting{}; // intrusive list node; self-pointer means "not in list", set in ctor
NimBLEAddress m_address{};
uint8_t m_advType{};
int8_t m_rssi{};
uint8_t m_callbackSent{};
uint16_t m_advLength{};
ble_npl_time_t m_time{};
NimBLEAdvertisedDevice* m_pNextWaiting{}; // intrusive list node; self-pointer means "not in list", set in ctor
# if MYNEWT_VAL(BLE_EXT_ADV)
bool m_isLegacyAdv{};

View File

@@ -23,10 +23,10 @@
# include "NimBLEUUID.h"
# include "NimBLELog.h"
#ifdef USING_NIMBLE_ARDUINO_HEADERS
# include "nimble/nimble/host/include/host/ble_hs_adv.h"
# else
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "host/ble_hs_adv.h"
# else
# include "nimble/nimble/host/include/host/ble_hs_adv.h"
# endif
static const char* LOG_TAG = "NimBLEAdvertisementData";

View File

@@ -18,12 +18,11 @@
#include "NimBLEAdvertising.h"
#if (CONFIG_BT_NIMBLE_ENABLED && MYNEWT_VAL(BLE_ROLE_BROADCASTER) && !MYNEWT_VAL(BLE_EXT_ADV)) || defined(_DOXYGEN_)
#ifdef USING_NIMBLE_ARDUINO_HEADERS
# include "nimble/nimble/host/services/gap/include/services/gap/ble_svc_gap.h"
# else
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "services/gap/ble_svc_gap.h"
# else
# include "nimble/nimble/host/services/gap/include/services/gap/ble_svc_gap.h"
# endif
# include "NimBLEDevice.h"
# include "NimBLEServer.h"
# include "NimBLEUtils.h"

View File

@@ -21,10 +21,10 @@
#include "syscfg/syscfg.h"
#if (CONFIG_BT_NIMBLE_ENABLED && MYNEWT_VAL(BLE_ROLE_BROADCASTER) && !MYNEWT_VAL(BLE_EXT_ADV)) || defined(_DOXYGEN_)
#ifdef USING_NIMBLE_ARDUINO_HEADERS
# include "nimble/nimble/host/include/host/ble_gap.h"
# else
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "host/ble_gap.h"
# else
# include "nimble/nimble/host/include/host/ble_gap.h"
# endif
/**** FIX COMPILATION ****/

View File

@@ -18,10 +18,10 @@
#include "NimBLEAttValue.h"
#if CONFIG_BT_NIMBLE_ENABLED
# ifdef USING_NIMBLE_ARDUINO_HEADERS
# include "nimble/nimble/include/nimble/nimble_npl.h"
# else
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "nimble/nimble_npl.h"
# else
# include "nimble/nimble/include/nimble/nimble_npl.h"
# endif
# include "NimBLEUtils.h"

View File

@@ -18,7 +18,7 @@
#include "NimBLECharacteristic.h"
#if CONFIG_BT_NIMBLE_ENABLED && MYNEWT_VAL(BLE_ROLE_PERIPHERAL)
# ifndef USING_NIMBLE_ARDUINO_HEADERS
# if defined(CONFIG_NIMBLE_CPP_IDF)
# if !defined(ESP_IDF_VERSION_MAJOR) || ESP_IDF_VERSION_MAJOR < 5
# define ble_gatts_notify_custom ble_gattc_notify_custom
# define ble_gatts_indicate_custom ble_gattc_indicate_custom

View File

@@ -23,10 +23,10 @@
# include "NimBLEDevice.h"
# include "NimBLELog.h"
# ifdef USING_NIMBLE_ARDUINO_HEADERS
# include "nimble/porting/nimble/include/nimble/nimble_port.h"
# else
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "nimble/nimble_port.h"
# else
# include "nimble/porting/nimble/include/nimble/nimble_port.h"
# endif
# include <climits>
@@ -270,6 +270,12 @@ bool NimBLEClient::connect(const NimBLEAddress& address, bool deleteAttributes,
goto error;
}
if (!asyncConnect && NimBLEUtils::inHostTask()) {
NIMBLE_LOGE(LOG_TAG, "Cannot connect synchronously from host task");
rc = BLE_HS_EREJECT;
goto error;
}
m_connStatus = CONNECTING;
m_peerAddress = address;
m_config.asyncConnect = asyncConnect;
@@ -334,6 +340,11 @@ error:
*/
bool NimBLEClient::secureConnection(bool async) const {
NIMBLE_LOGD(LOG_TAG, ">> secureConnection()");
if (!async && NimBLEUtils::inHostTask()) {
NIMBLE_LOGE(LOG_TAG, "Cannot secure connection synchronously from host task");
m_lastErr = BLE_HS_EREJECT;
return false;
}
int rc = 0;
if (async && !NimBLEDevice::startSecurity(m_connHandle, &rc)) {
@@ -550,7 +561,7 @@ bool NimBLEClient::updateConnParams(uint16_t minInterval, uint16_t maxInterval,
* @param [in] txOctets The preferred number of payload octets to use (Range 0x001B-0x00FB).
*/
bool NimBLEClient::setDataLen(uint16_t txOctets) {
# if !defined(USING_NIMBLE_ARDUINO_HEADERS) && !defined(ESP_IDF_VERSION) || \
# if defined(CONFIG_NIMBLE_CPP_IDF) && !defined(ESP_IDF_VERSION) || \
(ESP_IDF_VERSION_MAJOR * 100 + ESP_IDF_VERSION_MINOR * 10 + ESP_IDF_VERSION_PATCH) < 432
return false;
# else
@@ -669,6 +680,10 @@ NimBLERemoteService* NimBLEClient::getService(const char* uuid) {
*/
NimBLERemoteService* NimBLEClient::getService(const NimBLEUUID& uuid) {
NIMBLE_LOGD(LOG_TAG, ">> getService: uuid: %s", uuid.toString().c_str());
if (NimBLEUtils::inHostTask()) {
NIMBLE_LOGE(LOG_TAG, "getService cannot be called from host task");
return nullptr;
}
for (auto& it : m_svcVec) {
if (it->getUUID() == uuid) {

View File

@@ -21,10 +21,10 @@
#include "syscfg/syscfg.h"
#if CONFIG_BT_NIMBLE_ENABLED && MYNEWT_VAL(BLE_ROLE_CENTRAL)
# ifdef USING_NIMBLE_ARDUINO_HEADERS
# include "nimble/nimble/host/include/host/ble_gap.h"
# else
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "host/ble_gap.h"
# else
# include "nimble/nimble/host/include/host/ble_gap.h"
# endif
# include "NimBLEAddress.h"
@@ -118,7 +118,7 @@ class NimBLEClient {
* - deleteOnConnectFail: false
* - asyncConnect: false
* - exchangeMTU: true
* - connectFailRetries: 2
* - connectFailRetries: 2
*/
Config()
: deleteCallbacks(0),

View File

@@ -18,10 +18,10 @@
#ifndef NIMBLE_CPP_CONNINFO_H_
#define NIMBLE_CPP_CONNINFO_H_
#ifdef USING_NIMBLE_ARDUINO_HEADERS
# include "nimble/nimble/host/include/host/ble_gap.h"
#else
#if defined(CONFIG_NIMBLE_CPP_IDF)
# include "host/ble_gap.h"
#else
# include "nimble/nimble/host/include/host/ble_gap.h"
#endif
#include "NimBLEAddress.h"

View File

@@ -22,10 +22,10 @@
#define NIMBLE_CPP_VERSION_MAJOR 2
/** @brief NimBLE-Arduino library minor version number. */
#define NIMBLE_CPP_VERSION_MINOR 5
#define NIMBLE_CPP_VERSION_MINOR 3
/** @brief NimBLE-Arduino library patch version number. */
#define NIMBLE_CPP_VERSION_PATCH 0
#define NIMBLE_CPP_VERSION_PATCH 9
/**
* @brief Macro to create a version number for comparison.

View File

@@ -24,7 +24,7 @@
# include "esp_bt.h"
# endif
# include "nvs_flash.h"
# ifndef USING_NIMBLE_ARDUINO_HEADERS
# if defined(CONFIG_NIMBLE_CPP_IDF)
# if (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) || CONFIG_BT_NIMBLE_LEGACY_VHCI_ENABLE)
# include "esp_nimble_hci.h"
# endif
@@ -35,14 +35,14 @@
# include "host/util/util.h"
# include "services/gap/ble_svc_gap.h"
# include "services/gatt/ble_svc_gatt.h"
# else // USING_NIMBLE_ARDUINO_HEADERS
# else
# include "nimble/esp_port/esp-hci/include/esp_nimble_hci.h"
# endif
# else
# include "nimble/nimble/controller/include/controller/ble_phy.h"
# endif
# ifdef USING_NIMBLE_ARDUINO_HEADERS
# ifndef CONFIG_NIMBLE_CPP_IDF
# include "nimble/porting/nimble/include/nimble/nimble_port.h"
# include "nimble/porting/npl/freertos/include/nimble/nimble_port_freertos.h"
# include "nimble/nimble/host/include/host/ble_hs.h"
@@ -876,6 +876,7 @@ void NimBLEDevice::onSync(void) {
*/
void NimBLEDevice::host_task(void* param) {
NIMBLE_LOGI(LOG_TAG, "NimBLE Started!");
NimBLEUtils::m_hostTaskHandle = ble_npl_get_current_task_id();
nimble_port_run(); // This function will return only when nimble_port_stop() is executed
nimble_port_freertos_deinit();
} // host_task
@@ -911,7 +912,7 @@ 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(USING_NIMBLE_ARDUINO_HEADERS)
# 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_ESP32)
bt_cfg.mode = ESP_BT_MODE_BLE;
@@ -1025,7 +1026,7 @@ bool NimBLEDevice::deinit(bool clearAll) {
rc = nimble_port_stop();
if (rc == 0) {
nimble_port_deinit();
# ifndef USING_NIMBLE_ARDUINO_HEADERS
# ifdef CONFIG_NIMBLE_CPP_IDF
# if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
rc = esp_nimble_hci_and_controller_deinit();
if (rc != ESP_OK) {

View File

@@ -29,10 +29,10 @@
(CONFIG_BTDM_BLE_SCAN_DUPL || CONFIG_BT_LE_SCAN_DUPL || CONFIG_BT_CTRL_BLE_SCAN_DUPL)
# endif
# ifdef USING_NIMBLE_ARDUINO_HEADERS
# include "nimble/nimble/host/include/host/ble_gap.h"
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include <host/ble_gap.h>
# else
# include "host/ble_gap.h"
# include <nimble/nimble/host/include/host/ble_gap.h>
# endif
/**** FIX COMPILATION ****/

View File

@@ -18,10 +18,10 @@
#include "NimBLEExtAdvertising.h"
#if CONFIG_BT_NIMBLE_ENABLED && MYNEWT_VAL(BLE_ROLE_BROADCASTER) && MYNEWT_VAL(BLE_EXT_ADV)
#ifdef USING_NIMBLE_ARDUINO_HEADERS
# include "nimble/nimble/host/services/gap/include/services/gap/ble_svc_gap.h"
#else
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "services/gap/ble_svc_gap.h"
# else
# include "nimble/nimble/host/services/gap/include/services/gap/ble_svc_gap.h"
# endif
# include "NimBLEDevice.h"

View File

@@ -21,10 +21,10 @@
#include "syscfg/syscfg.h"
#if CONFIG_BT_NIMBLE_ENABLED && MYNEWT_VAL(BLE_ROLE_BROADCASTER) && MYNEWT_VAL(BLE_EXT_ADV)
# ifdef USING_NIMBLE_ARDUINO_HEADERS
# include "nimble/nimble/host/include/host/ble_gap.h"
# else
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "host/ble_gap.h"
# else
# include "nimble/nimble/host/include/host/ble_gap.h"
# endif
/**** FIX COMPILATION ****/

View File

@@ -9,10 +9,10 @@
# include "NimBLELog.h"
# include "NimBLEUtils.h"
# ifdef USING_NIMBLE_ARDUINO_HEADERS
# include "nimble/nimble/host/include/host/ble_gap.h"
# else
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "host/ble_gap.h"
# else
# include "nimble/nimble/host/include/host/ble_gap.h"
# endif
// L2CAP buffer block size
@@ -134,14 +134,8 @@ int NimBLEL2CAPChannel::writeFragment(std::vector<uint8_t>::const_iterator begin
case BLE_HS_ENOMEM:
case BLE_HS_EAGAIN:
/* ble_l2cap_send already consumed and freed txd on these errors */
NIMBLE_LOGD(LOG_TAG, "ble_l2cap_send returned %d (consumed buffer). Retrying shortly...", res);
ble_npl_time_delay(ble_npl_time_ms_to_ticks32(RetryTimeout));
continue;
case BLE_HS_EBUSY:
/* Channel busy; txd not consumed */
NIMBLE_LOGD(LOG_TAG, "ble_l2cap_send returned %d (busy). Retrying shortly...", res);
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;
@@ -203,28 +197,6 @@ bool NimBLEL2CAPChannel::write(const std::vector<uint8_t>& bytes) {
return true;
}
bool NimBLEL2CAPChannel::disconnect() {
if (!this->channel) {
NIMBLE_LOGW(LOG_TAG, "L2CAP Channel not open");
return false;
}
int rc = ble_l2cap_disconnect(this->channel);
if (rc != 0 && rc != BLE_HS_ENOTCONN && rc != BLE_HS_EALREADY) {
NIMBLE_LOGE(LOG_TAG, "ble_l2cap_disconnect failed: rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
return false;
}
return true;
}
uint16_t NimBLEL2CAPChannel::getConnHandle() const {
if (!this->channel) {
return BLE_HS_CONN_HANDLE_NONE;
}
return ble_l2cap_get_conn_handle(this->channel);
}
// private
int NimBLEL2CAPChannel::handleConnectionEvent(struct ble_l2cap_event* event) {
channel = event->connect.chan;

View File

@@ -9,12 +9,12 @@
#if CONFIG_BT_NIMBLE_ENABLED && MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM)
# include "inttypes.h"
# ifdef USING_NIMBLE_ARDUINO_HEADERS
# include "nimble/nimble/host/include/host/ble_l2cap.h"
# include "nimble/porting/nimble/include/os/os_mbuf.h"
# else
# 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 ****/
@@ -56,14 +56,6 @@ class NimBLEL2CAPChannel {
/// NOTE: This function will block until the data has been sent or an error occurred.
bool write(const std::vector<uint8_t>& bytes);
/// @brief Disconnect this L2CAP channel.
/// @return true on success, false on failure.
bool disconnect();
/// @brief Get the connection handle associated with this channel.
/// @return Connection handle, or BLE_HS_CONN_HANDLE_NONE if not connected.
uint16_t getConnHandle() const;
/// @return True, if the channel is connected. False, otherwise.
bool isConnected() const { return !!channel; }

View File

@@ -21,10 +21,10 @@
#include "syscfg/syscfg.h"
#if CONFIG_BT_NIMBLE_ENABLED && MYNEWT_VAL(BLE_ROLE_PERIPHERAL)
#ifdef USING_NIMBLE_ARDUINO_HEADERS
# include "nimble/nimble/host/include/host/ble_hs.h"
# else
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "host/ble_hs.h"
# else
# include "nimble/nimble/host/include/host/ble_hs.h"
# endif
/**** FIX COMPILATION ****/

View File

@@ -33,7 +33,7 @@
# endif
# endif
# ifndef USING_NIMBLE_ARDUINO_HEADERS
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "esp_log.h"
# include "console/console.h"
@@ -172,7 +172,7 @@
# define NIMBLE_LOGE(tag, format, ...) (void)tag
# endif
# endif /* !USING_NIMBLE_ARDUINO_HEADERS */
# 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__); }}

View File

@@ -155,6 +155,11 @@ bool NimBLERemoteCharacteristic::retrieveDescriptors(NimBLEDescriptorFilter* pFi
*/
NimBLERemoteDescriptor* NimBLERemoteCharacteristic::getDescriptor(const NimBLEUUID& uuid) const {
NIMBLE_LOGD(LOG_TAG, ">> getDescriptor: uuid: %s", uuid.toString().c_str());
if (NimBLEUtils::inHostTask()) {
NIMBLE_LOGE(LOG_TAG, "getDescriptor cannot be called from host task");
return nullptr;
}
NimBLEUUID uuidTmp{uuid};
NimBLETaskData taskData(const_cast<NimBLERemoteCharacteristic*>(this));
NimBLEDescriptorFilter filter{nullptr, &uuidTmp, &taskData};

View File

@@ -75,6 +75,11 @@ NimBLERemoteCharacteristic* NimBLERemoteService::getCharacteristic(const char* u
*/
NimBLERemoteCharacteristic* NimBLERemoteService::getCharacteristic(const NimBLEUUID& uuid) const {
NIMBLE_LOGD(LOG_TAG, ">> getCharacteristic: uuid: %s", uuid.toString().c_str());
if (NimBLEUtils::inHostTask()) {
NIMBLE_LOGE(LOG_TAG, "getCharacteristic cannot be called from host task");
return nullptr;
}
NimBLERemoteCharacteristic* pChar = nullptr;
for (const auto& it : m_vChars) {
@@ -116,6 +121,11 @@ NimBLERemoteCharacteristic* NimBLERemoteService::getCharacteristic(const NimBLEU
* @return A read-only reference to the vector of characteristics retrieved for this service.
*/
const std::vector<NimBLERemoteCharacteristic*>& NimBLERemoteService::getCharacteristics(bool refresh) const {
if (refresh && NimBLEUtils::inHostTask()) {
NIMBLE_LOGE(LOG_TAG, "cannot refresh characteristics from host task");
return m_vChars;
}
if (refresh) {
deleteCharacteristics();
retrieveCharacteristics();

View File

@@ -42,6 +42,11 @@ bool NimBLERemoteValueAttribute::writeValue(const uint8_t* data, size_t length,
goto Done;
}
if (NimBLEUtils::inHostTask()) {
NIMBLE_LOGE(LOG_TAG, "writeValue cannot be called from the host task");
return false;
}
do {
if (length > mtu) {
NIMBLE_LOGI(LOG_TAG, "writeValue: long write");
@@ -123,6 +128,10 @@ int NimBLERemoteValueAttribute::onWriteCB(uint16_t conn_handle, const ble_gatt_e
*/
NimBLEAttValue NimBLERemoteValueAttribute::readValue(time_t* timestamp) {
NIMBLE_LOGD(LOG_TAG, ">> readValue()");
if (NimBLEUtils::inHostTask()) {
NIMBLE_LOGE(LOG_TAG, "readValue cannot be called from the host task");
return NimBLEAttValue();
}
NimBLEAttValue value{};
const NimBLEClient* pClient = getClient();

View File

@@ -21,10 +21,10 @@
#include "syscfg/syscfg.h"
#if CONFIG_BT_NIMBLE_ENABLED && MYNEWT_VAL(BLE_ROLE_CENTRAL)
#ifdef USING_NIMBLE_ARDUINO_HEADERS
# include "nimble/nimble/host/include/host/ble_gatt.h"
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include <host/ble_gatt.h>
# else
# include "host/ble_gatt.h"
# include <nimble/nimble/host/include/host/ble_gatt.h>
# endif
/**** FIX COMPILATION ****/

View File

@@ -20,10 +20,10 @@
# include "NimBLEDevice.h"
# include "NimBLELog.h"
# ifdef USING_NIMBLE_ARDUINO_HEADERS
# include "nimble/porting/nimble/include/nimble/nimble_port.h"
# else
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "nimble/nimble_port.h"
# else
# include "nimble/porting/nimble/include/nimble/nimble_port.h"
# endif
# include <string>
@@ -564,15 +564,15 @@ bool NimBLEScan::start(uint32_t duration, bool isContinue, bool restart) {
scan_params.itvl = m_scanParams.itvl;
scan_params.window = m_scanParams.window;
int rc = ble_gap_ext_disc(NimBLEDevice::m_ownAddrType,
duration / 10, // 10ms units
m_period,
m_scanParams.filter_duplicates,
m_scanParams.filter_policy,
m_scanParams.limited,
m_phy & SCAN_1M ? &scan_params : NULL,
m_phy & SCAN_CODED ? &scan_params : NULL,
NimBLEScan::handleGapEvent,
NULL);
duration / 10, // 10ms units
m_period,
m_scanParams.filter_duplicates,
m_scanParams.filter_policy,
m_scanParams.limited,
m_phy & SCAN_1M ? &scan_params : NULL,
m_phy & SCAN_CODED ? &scan_params : NULL,
NimBLEScan::handleGapEvent,
NULL);
# else
int rc = ble_gap_disc(NimBLEDevice::m_ownAddrType,
duration ? duration : BLE_HS_FOREVER,
@@ -689,6 +689,11 @@ NimBLEScanResults NimBLEScan::getResults(uint32_t duration, bool is_continue) {
return m_scanResults;
}
if (NimBLEUtils::inHostTask()) {
NIMBLE_LOGE(LOG_TAG, "Cannot call blocking getResults from NimBLE host task");
return m_scanResults;
}
NimBLETaskData taskData;
m_pTaskData = &taskData;

View File

@@ -24,10 +24,10 @@
# include "NimBLEAdvertisedDevice.h"
# include "NimBLEUtils.h"
# ifdef USING_NIMBLE_ARDUINO_HEADERS
# include "nimble/nimble/host/include/host/ble_gap.h"
# else
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "host/ble_gap.h"
# else
# include "nimble/nimble/host/include/host/ble_gap.h"
# endif
# include <vector>

View File

@@ -25,12 +25,12 @@
# include "NimBLEClient.h"
# endif
# ifdef USING_NIMBLE_ARDUINO_HEADERS
# include "nimble/nimble/host/services/gap/include/services/gap/ble_svc_gap.h"
# include "nimble/nimble/host/services/gatt/include/services/gatt/ble_svc_gatt.h"
# else
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "services/gap/ble_svc_gap.h"
# include "services/gatt/ble_svc_gatt.h"
# else
# include "nimble/nimble/host/services/gap/include/services/gap/ble_svc_gap.h"
# include "nimble/nimble/host/services/gatt/include/services/gatt/ble_svc_gatt.h"
# endif
# define NIMBLE_SERVER_GET_PEER_NAME_ON_CONNECT_CB 0
@@ -1072,7 +1072,7 @@ void NimBLEServer::updateConnParams(
* @param [in] octets The preferred number of payload octets to use (Range 0x001B-0x00FB).
*/
void NimBLEServer::setDataLen(uint16_t connHandle, uint16_t octets) const {
# if !defined(USING_NIMBLE_ARDUINO_HEADERS) && !defined(ESP_IDF_VERSION) || \
# if defined(CONFIG_NIMBLE_CPP_IDF) && !defined(ESP_IDF_VERSION) || \
(ESP_IDF_VERSION_MAJOR * 100 + ESP_IDF_VERSION_MINOR * 10 + ESP_IDF_VERSION_PATCH) < 432
return;
# else

View File

@@ -21,10 +21,10 @@
#include "syscfg/syscfg.h"
#if CONFIG_BT_NIMBLE_ENABLED && MYNEWT_VAL(BLE_ROLE_PERIPHERAL)
# ifdef USING_NIMBLE_ARDUINO_HEADERS
# include "nimble/nimble/host/include/host/ble_gap.h"
# else
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "host/ble_gap.h"
# else
# include "nimble/nimble/host/include/host/ble_gap.h"
# endif
/**** FIX COMPILATION ****/

View File

@@ -20,12 +20,12 @@
# include "NimBLEDevice.h"
# include "NimBLELog.h"
# ifdef USING_NIMBLE_ARDUINO_HEADERS
# include "nimble/porting/nimble/include/os/os_mbuf.h"
# include "nimble/porting/nimble/include/nimble/nimble_port.h"
# else
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "os/os_mbuf.h"
# include "nimble/nimble_port.h"
# else
# include "nimble/porting/nimble/include/os/os_mbuf.h"
# include "nimble/porting/nimble/include/nimble/nimble_port.h"
# endif
# include <algorithm>
# include <cstdio>

View File

@@ -21,10 +21,10 @@
#include "syscfg/syscfg.h"
#if CONFIG_BT_NIMBLE_ENABLED && (MYNEWT_VAL(BLE_ROLE_PERIPHERAL) || MYNEWT_VAL(BLE_ROLE_CENTRAL))
# ifdef USING_NIMBLE_ARDUINO_HEADERS
# include "nimble/nimble/include/nimble/nimble_npl.h"
# else
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "nimble/nimble_npl.h"
# else
# include "nimble/nimble/include/nimble/nimble_npl.h"
# endif
# include <functional>

View File

@@ -21,10 +21,10 @@
#include "syscfg/syscfg.h"
#if CONFIG_BT_NIMBLE_ENABLED
# ifdef USING_NIMBLE_ARDUINO_HEADERS
# include "nimble/nimble/host/include/host/ble_uuid.h"
# else
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "host/ble_uuid.h"
# else
# include "nimble/nimble/host/include/host/ble_uuid.h"
# endif
/**** FIX COMPILATION ****/
@@ -65,7 +65,7 @@ class NimBLEUUID {
bool operator==(const NimBLEUUID& rhs) const;
bool operator!=(const NimBLEUUID& rhs) const;
operator std::string() const;
operator std::string() const;
private:
ble_uuid_any_t m_uuid{};

View File

@@ -21,10 +21,10 @@
# include "NimBLEAddress.h"
# include "NimBLELog.h"
#ifdef USING_NIMBLE_ARDUINO_HEADERS
# include "nimble/nimble/host/include/host/ble_hs.h"
#else
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "host/ble_hs.h"
# else
# include "nimble/nimble/host/include/host/ble_hs.h"
# endif
/**** FIX COMPILATION ****/
@@ -72,6 +72,8 @@
constexpr uint32_t TASK_BLOCK_BIT = (1 << MYNEWT_VAL(NIMBLE_CPP_FREERTOS_TASK_BLOCK_BIT));
static const char* LOG_TAG = "NimBLEUtils";
void* NimBLEUtils::m_hostTaskHandle = nullptr;
/**
* @brief Construct a NimBLETaskData instance.
* @param [in] pInstance An instance of the class that will be waiting.
@@ -604,4 +606,21 @@ NimBLEAddress NimBLEUtils::generateAddr(bool nrpa) {
return NimBLEAddress{addr};
} // generateAddr
/**
* @brief Get the handle of the task that is running the NimBLE host.
* @return The task handle or nullptr if there was an error.
*/
void* NimBLEUtils::getHostTaskHandle() {
return m_hostTaskHandle;
} // getHostTaskHandle
/**
* @brief Check if the current task is the NimBLE host task.
* @return True if the current task is the host task, false otherwise.
*/
bool NimBLEUtils::inHostTask() {
return ble_npl_get_current_task_id() == NimBLEUtils::getHostTaskHandle();
}
#endif // CONFIG_BT_NIMBLE_ENABLED

View File

@@ -29,17 +29,18 @@
# endif
# endif
#if MYNEWT_VAL(NIMBLE_CPP_DEBUG_ASSERT_ENABLED) && !defined NDEBUG
void nimble_cpp_assert(const char *file, unsigned line) __attribute((weak, noreturn));
# define NIMBLE_ATT_VAL_FILE (__builtin_strrchr(__FILE__, '/') ? \
__builtin_strrchr (__FILE__, '/') + 1 : __FILE__)
# define NIMBLE_CPP_DEBUG_ASSERT(cond) \
if (!(cond)) { \
nimble_cpp_assert(NIMBLE_ATT_VAL_FILE, __LINE__); \
}
#else
# define NIMBLE_CPP_DEBUG_ASSERT(cond) (void(0))
#endif
# if MYNEWT_VAL(NIMBLE_CPP_DEBUG_ASSERT_ENABLED) && !defined NDEBUG
void nimble_cpp_assert(const char* file, unsigned line) __attribute__((weak, noreturn));
# define NIMBLE_ATT_VAL_FILE (__builtin_strrchr(__FILE__, '/') ? __builtin_strrchr(__FILE__, '/') + 1 : __FILE__)
# define NIMBLE_CPP_DEBUG_ASSERT(cond) \
do { \
if (!(cond)) { \
nimble_cpp_assert(NIMBLE_ATT_VAL_FILE, __LINE__); \
} \
} while (0)
# else
# define NIMBLE_CPP_DEBUG_ASSERT(cond) (void(0))
# endif
# include <string>
@@ -74,6 +75,12 @@ class NimBLEUtils {
static NimBLEAddress generateAddr(bool nrpa);
static bool taskWait(const NimBLETaskData& taskData, uint32_t timeout);
static void taskRelease(const NimBLETaskData& taskData, int rc = 0);
static void* getHostTaskHandle();
static bool inHostTask();
private:
friend class NimBLEDevice;
static void* m_hostTaskHandle;
};
#endif // CONFIG_BT_NIMBLE_ENABLED