Skip to content

Commit 22bfe0d

Browse files
committed
feat(L2CAP): add disconnect API and harden CoC send/error handling
- Add NimBLEL2CAPChannel::disconnect() and getConnHandle(). - Fix CoC TX mbuf ownership handling in writeFragment(): treat BLE_HS_ENOMEM / BLE_HS_EAGAIN as consumed buffer, only free local tx mbuf on BLE_HS_EBUSY. - Refresh L2CAP client/server examples for stress testing and runtime stats. - Pin example dependency mickeyl/esp-hpl to tag 1.1.0. - Clean trailing whitespace in updated example sources. Closes #391
1 parent dd32365 commit 22bfe0d

6 files changed

Lines changed: 322 additions & 78 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
dependencies:
22
local/esp-nimble-cpp:
33
path: ../../../../../esp-nimble-cpp/
4+
mickeyl/esp-hpl:
5+
git: https://github.com/mickeyl/esp-hpl.git
6+
version: "1.1.0"

examples/L2CAP/L2CAP_Client/main/main.cpp

Lines changed: 127 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,28 @@
11
#include <NimBLEDevice.h>
2+
#include <esp_hpl.hpp>
3+
#include <esp_timer.h>
24

3-
// See the following for generating UUIDs:
4-
// https://www.uuidgenerator.net/
5-
6-
// The remote service we wish to connect to.
7-
static BLEUUID serviceUUID("dcbc7255-1e9e-49a0-a360-b0430b6c6905");
8-
// The characteristic of the remote service we are interested in.
9-
static BLEUUID charUUID("371a55c8-f251-4ad2-90b3-c7c195b049be");
10-
11-
#define L2CAP_CHANNEL 150
5+
#define L2CAP_PSM 192
126
#define L2CAP_MTU 5000
7+
#define INITIAL_PAYLOAD_SIZE 64
8+
#define BLOCKS_BEFORE_DOUBLE 50
9+
#define MAX_PAYLOAD_SIZE 4900
1310

1411
const BLEAdvertisedDevice* theDevice = NULL;
1512
BLEClient* theClient = NULL;
1613
BLEL2CAPChannel* theChannel = NULL;
1714

1815
size_t bytesSent = 0;
1916
size_t bytesReceived = 0;
17+
size_t currentPayloadSize = INITIAL_PAYLOAD_SIZE;
18+
uint32_t blocksSent = 0;
19+
uint64_t startTime = 0;
20+
21+
// Heap monitoring
22+
size_t initialHeap = 0;
23+
size_t lastHeap = 0;
24+
size_t heapDecreaseCount = 0;
25+
const size_t HEAP_LEAK_THRESHOLD = 10; // Warn after 10 consecutive decreases
2026

2127
class L2CAPChannelCallbacks: public BLEL2CAPChannelCallbacks {
2228

@@ -43,7 +49,7 @@ class MyClientCallbacks: public BLEClientCallbacks {
4349
printf("GAP connected\n");
4450
pClient->setDataLen(251);
4551

46-
theChannel = BLEL2CAPChannel::connect(pClient, L2CAP_CHANNEL, L2CAP_MTU, new L2CAPChannelCallbacks());
52+
theChannel = BLEL2CAPChannel::connect(pClient, L2CAP_PSM, L2CAP_MTU, new L2CAPChannelCallbacks());
4753
}
4854

4955
void onDisconnect(BLEClient* pClient, int reason) {
@@ -61,23 +67,72 @@ class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
6167
if (theDevice) { return; }
6268
printf("BLE Advertised Device found: %s\n", advertisedDevice->toString().c_str());
6369

64-
if (!advertisedDevice->haveServiceUUID()) { return; }
65-
if (!advertisedDevice->isAdvertisingService(serviceUUID)) { return; }
70+
// Look for device named "l2cap"
71+
if (advertisedDevice->haveName() && advertisedDevice->getName() == "l2cap") {
72+
printf("Found l2cap device!\n");
73+
BLEDevice::getScan()->stop();
74+
theDevice = advertisedDevice;
75+
}
76+
}
77+
};
78+
79+
void statusTask(void *pvParameters) {
80+
while (true) {
81+
vTaskDelay(1000 / portTICK_PERIOD_MS);
82+
83+
if (startTime > 0 && blocksSent > 0) {
84+
uint64_t currentTime = esp_timer_get_time();
85+
double elapsedSeconds = (currentTime - startTime) / 1000000.0;
86+
double bytesPerSecond = 0.0;
87+
double kbPerSecond = 0.0;
88+
if (elapsedSeconds > 0.0) {
89+
bytesPerSecond = bytesSent / elapsedSeconds;
90+
kbPerSecond = bytesPerSecond / 1024.0;
91+
}
6692

67-
printf("Found the device we're interested in!\n");
68-
BLEDevice::getScan()->stop();
93+
// Heap monitoring
94+
size_t currentHeap = esp_get_free_heap_size();
95+
size_t minHeap = esp_get_minimum_free_heap_size();
6996

70-
// Hand over the device to the other task
71-
theDevice = advertisedDevice;
97+
// Track heap for leak detection
98+
if (initialHeap == 0) {
99+
initialHeap = currentHeap;
100+
lastHeap = currentHeap;
101+
}
102+
103+
// Check for consistent heap decrease
104+
if (currentHeap < lastHeap) {
105+
heapDecreaseCount++;
106+
if (heapDecreaseCount >= HEAP_LEAK_THRESHOLD) {
107+
printf("\n⚠️ WARNING: POSSIBLE MEMORY LEAK DETECTED! ⚠️\n");
108+
printf("Heap has decreased %zu times in a row\n", heapDecreaseCount);
109+
printf("Initial heap: %zu, Current heap: %zu, Lost: %zu bytes\n",
110+
initialHeap, currentHeap, initialHeap - currentHeap);
111+
}
112+
} else if (currentHeap >= lastHeap) {
113+
heapDecreaseCount = 0; // Reset counter if heap stabilizes or increases
114+
}
115+
lastHeap = currentHeap;
116+
117+
printf("\n=== STATUS UPDATE ===\n");
118+
printf("Blocks sent: %lu\n", (unsigned long)blocksSent);
119+
printf("Total bytes sent: %zu\n", bytesSent);
120+
printf("Current payload size: %zu bytes\n", currentPayloadSize);
121+
printf("Elapsed time: %.1f seconds\n", elapsedSeconds);
122+
printf("Bandwidth: %.2f KB/s (%.2f Mbps)\n", kbPerSecond, (bytesPerSecond * 8) / 1000000.0);
123+
printf("Heap: %zu free (min: %zu), Used since start: %zu\n",
124+
currentHeap, minHeap, initialHeap > 0 ? initialHeap - currentHeap : 0);
125+
printf("==================\n\n");
126+
}
72127
}
73-
};
128+
}
74129

75130
void connectTask(void *pvParameters) {
76131

77132
uint8_t sequenceNumber = 0;
78133

79134
while (true) {
80-
135+
81136
if (!theDevice) {
82137
vTaskDelay(1000 / portTICK_PERIOD_MS);
83138
continue;
@@ -96,7 +151,7 @@ void connectTask(void *pvParameters) {
96151
break;
97152
}
98153
vTaskDelay(2000 / portTICK_PERIOD_MS);
99-
continue;
154+
continue;
100155
}
101156

102157
if (!theChannel) {
@@ -112,22 +167,58 @@ void connectTask(void *pvParameters) {
112167
}
113168

114169
while (theChannel->isConnected()) {
170+
// Create framed packet: [seqno 8bit] [16bit payload length] [payload]
171+
std::vector<uint8_t> packet;
172+
packet.reserve(3 + currentPayloadSize);
173+
174+
// Add sequence number (8 bits)
175+
packet.push_back(sequenceNumber);
176+
177+
// Add payload length (16 bits, big endian - network byte order)
178+
uint16_t payloadLen = currentPayloadSize;
179+
packet.push_back((payloadLen >> 8) & 0xFF); // High byte first
180+
packet.push_back(payloadLen & 0xFF); // Low byte second
115181

116-
/*
117-
static auto initialDelay = true;
118-
if (initialDelay) {
119-
printf("Waiting gracefully 3 seconds before sending data\n");
120-
vTaskDelay(3000 / portTICK_PERIOD_MS);
121-
initialDelay = false;
122-
};
123-
*/
124-
std::vector<uint8_t> data(5000, sequenceNumber++);
125-
if (theChannel->write(data)) {
126-
bytesSent += data.size();
182+
// Add payload
183+
for (size_t i = 0; i < currentPayloadSize; i++) {
184+
packet.push_back(i & 0xFF);
185+
}
186+
187+
if (theChannel->write(packet)) {
188+
if (startTime == 0) {
189+
startTime = esp_timer_get_time();
190+
}
191+
bytesSent += packet.size();
192+
blocksSent++;
193+
194+
// Print every block since we're sending slowly now
195+
printf("Sent block %lu (seq=%d, payload=%zu bytes, frame_size=%zu)\n",
196+
(unsigned long)blocksSent, sequenceNumber, currentPayloadSize, packet.size());
197+
198+
sequenceNumber++;
199+
200+
// After every 50 blocks, double payload size
201+
if (blocksSent % BLOCKS_BEFORE_DOUBLE == 0) {
202+
size_t newSize = currentPayloadSize * 2;
203+
204+
// Cap at maximum safe payload size
205+
if (newSize > MAX_PAYLOAD_SIZE) {
206+
if (currentPayloadSize < MAX_PAYLOAD_SIZE) {
207+
currentPayloadSize = MAX_PAYLOAD_SIZE;
208+
printf("\n=== Reached maximum payload size of %zu bytes after %lu blocks ===\n", currentPayloadSize, (unsigned long)blocksSent);
209+
}
210+
// Already at max, don't increase further
211+
} else {
212+
currentPayloadSize = newSize;
213+
printf("\n=== Doubling payload size to %zu bytes after %lu blocks ===\n", currentPayloadSize, (unsigned long)blocksSent);
214+
}
215+
}
127216
} else {
128217
printf("failed to send!\n");
129-
abort();
218+
abort();
130219
}
220+
221+
// No delay - send as fast as possible
131222
}
132223

133224
vTaskDelay(1000 / portTICK_PERIOD_MS);
@@ -136,9 +227,13 @@ void connectTask(void *pvParameters) {
136227

137228
extern "C"
138229
void app_main(void) {
230+
// Install high performance logging before any output
231+
esp_hpl::HighPerformanceLogger::init();
232+
139233
printf("Starting L2CAP client example\n");
140234

141235
xTaskCreate(connectTask, "connectTask", 5000, NULL, 1, NULL);
236+
xTaskCreate(statusTask, "statusTask", 3000, NULL, 1, NULL);
142237

143238
BLEDevice::init("L2CAP-Client");
144239
BLEDevice::setMTU(BLE_ATT_MTU_MAX);
@@ -151,15 +246,8 @@ void app_main(void) {
151246
scan->setActiveScan(true);
152247
scan->start(25 * 1000, false);
153248

154-
int numberOfSeconds = 0;
155-
156-
while (bytesSent == 0) {
157-
vTaskDelay(10 / portTICK_PERIOD_MS);
158-
}
159-
249+
// Main task just waits
160250
while (true) {
161251
vTaskDelay(1000 / portTICK_PERIOD_MS);
162-
int bytesSentPerSeconds = bytesSent / ++numberOfSeconds;
163-
printf("Bandwidth: %d b/sec = %d KB/sec\n", bytesSentPerSeconds, bytesSentPerSeconds / 1024);
164252
}
165253
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
dependencies:
22
local/esp-nimble-cpp:
33
path: ../../../../../esp-nimble-cpp/
4+
mickeyl/esp-hpl:
5+
git: https://github.com/mickeyl/esp-hpl.git
6+
version: "1.1.0"

0 commit comments

Comments
 (0)