Skip to content

Commit e55ad90

Browse files
mickeylh2zero
authored andcommitted
Introduce L2CAP infrastructure.
L2CAP is the underlying technology powering GATT. BLE 5 exposes L2CAP COC (Connection Oriented Channels) allowing a streaming API that leads to much higher throughputs than you can achieve with updating GATT characteristics. The patch follows the established infrastructure very closely. The main components are: - `NimBLEL2CAPChannel`, encapsulating an L2CAP COC. - `NimBLEL2CAPServer`, encapsulating the L2CAP service. - `Examples/L2CAP`, containing a client and a server application. Apart from these, only minor adjustments to the existing code was necessary.
1 parent 59e111a commit e55ad90

22 files changed

+876
-0
lines changed

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ idf_component_register(
5555
"src/NimBLEEddystoneTLM.cpp"
5656
"src/NimBLEExtAdvertising.cpp"
5757
"src/NimBLEHIDDevice.cpp"
58+
"src/NimBLEL2CAPChannel.cpp"
59+
"src/NimBLEL2CAPServer.cpp"
5860
"src/NimBLERemoteCharacteristic.cpp"
5961
"src/NimBLERemoteDescriptor.cpp"
6062
"src/NimBLERemoteService.cpp"

examples/L2CAP/.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.vscode
2+
build
3+
sdkconfig
4+
sdkconfig.old
5+
dependencies.lock
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# The following lines of boilerplate have to be in your project's
2+
# CMakeLists in this exact order for cmake to work correctly
3+
cmake_minimum_required(VERSION 3.5)
4+
5+
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
6+
set(SUPPORTED_TARGETS esp32 esp32s3 esp32c3 esp32c6)
7+
project(L2CAP_client)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
PROJECT_NAME := L2CAP_client
2+
3+
include $(IDF_PATH)/make/project.mk
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
set(COMPONENT_SRCS "main.cpp")
2+
set(COMPONENT_ADD_INCLUDEDIRS ".")
3+
4+
register_component()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#
2+
# "main" pseudo-component makefile.
3+
#
4+
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
dependencies:
2+
local/esp-nimble-cpp:
3+
path: ../../../../../esp-nimble-cpp/
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
#include <NimBLEDevice.h>
2+
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
12+
#define L2CAP_MTU 5000
13+
14+
const BLEAdvertisedDevice* theDevice = NULL;
15+
BLEClient* theClient = NULL;
16+
BLEL2CAPChannel* theChannel = NULL;
17+
18+
size_t bytesSent = 0;
19+
size_t bytesReceived = 0;
20+
21+
class L2CAPChannelCallbacks: public BLEL2CAPChannelCallbacks {
22+
23+
public:
24+
void onConnect(NimBLEL2CAPChannel* channel) {
25+
printf("L2CAP connection established\n");
26+
}
27+
28+
void onMTUChange(NimBLEL2CAPChannel* channel, uint16_t mtu) {
29+
printf("L2CAP MTU changed to %d\n", mtu);
30+
}
31+
32+
void onRead(NimBLEL2CAPChannel* channel, std::vector<uint8_t>& data) {
33+
printf("L2CAP read %d bytes\n", data.size());
34+
}
35+
void onDisconnect(NimBLEL2CAPChannel* channel) {
36+
printf("L2CAP disconnected\n");
37+
}
38+
};
39+
40+
class MyClientCallbacks: public BLEClientCallbacks {
41+
42+
void onConnect(BLEClient* pClient) {
43+
printf("GAP connected\n");
44+
pClient->setDataLen(251);
45+
46+
theChannel = BLEL2CAPChannel::connect(pClient, L2CAP_CHANNEL, L2CAP_MTU, new L2CAPChannelCallbacks());
47+
}
48+
49+
void onDisconnect(BLEClient* pClient, int reason) {
50+
printf("GAP disconnected (reason: %d)\n", reason);
51+
theDevice = NULL;
52+
theChannel = NULL;
53+
vTaskDelay(1000 / portTICK_PERIOD_MS);
54+
BLEDevice::getScan()->start(5 * 1000, true);
55+
}
56+
};
57+
58+
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
59+
60+
void onResult(const BLEAdvertisedDevice* advertisedDevice) {
61+
if (theDevice) { return; }
62+
printf("BLE Advertised Device found: %s\n", advertisedDevice->toString().c_str());
63+
64+
if (!advertisedDevice->haveServiceUUID()) { return; }
65+
if (!advertisedDevice->isAdvertisingService(serviceUUID)) { return; }
66+
67+
printf("Found the device we're interested in!\n");
68+
BLEDevice::getScan()->stop();
69+
70+
// Hand over the device to the other task
71+
theDevice = advertisedDevice;
72+
}
73+
};
74+
75+
void connectTask(void *pvParameters) {
76+
77+
uint8_t sequenceNumber = 0;
78+
79+
while (true) {
80+
81+
if (!theDevice) {
82+
vTaskDelay(1000 / portTICK_PERIOD_MS);
83+
continue;
84+
}
85+
86+
if (!theClient) {
87+
theClient = BLEDevice::createClient();
88+
theClient->setConnectionParams(6, 6, 0, 42);
89+
90+
auto callbacks = new MyClientCallbacks();
91+
theClient->setClientCallbacks(callbacks);
92+
93+
auto success = theClient->connect(theDevice);
94+
if (!success) {
95+
printf("Error: Could not connect to device\n");
96+
break;
97+
}
98+
vTaskDelay(2000 / portTICK_PERIOD_MS);
99+
continue;
100+
}
101+
102+
if (!theChannel) {
103+
printf("l2cap channel not initialized\n");
104+
vTaskDelay(2000 / portTICK_PERIOD_MS);
105+
continue;
106+
}
107+
108+
if (!theChannel->isConnected()) {
109+
printf("l2cap channel not connected\n");
110+
vTaskDelay(2000 / portTICK_PERIOD_MS);
111+
continue;
112+
}
113+
114+
while (theChannel->isConnected()) {
115+
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();
127+
} else {
128+
printf("failed to send!\n");
129+
abort();
130+
}
131+
}
132+
133+
vTaskDelay(1000 / portTICK_PERIOD_MS);
134+
}
135+
}
136+
137+
extern "C"
138+
void app_main(void) {
139+
printf("Starting L2CAP client example\n");
140+
141+
xTaskCreate(connectTask, "connectTask", 5000, NULL, 1, NULL);
142+
143+
BLEDevice::init("L2CAP-Client");
144+
BLEDevice::setMTU(BLE_ATT_MTU_MAX);
145+
146+
auto scan = BLEDevice::getScan();
147+
auto callbacks = new MyAdvertisedDeviceCallbacks();
148+
scan->setScanCallbacks(callbacks);
149+
scan->setInterval(1349);
150+
scan->setWindow(449);
151+
scan->setActiveScan(true);
152+
scan->start(25 * 1000, false);
153+
154+
int numberOfSeconds = 0;
155+
156+
while (bytesSent == 0) {
157+
vTaskDelay(10 / portTICK_PERIOD_MS);
158+
}
159+
160+
while (true) {
161+
vTaskDelay(1000 / portTICK_PERIOD_MS);
162+
int bytesSentPerSeconds = bytesSent / ++numberOfSeconds;
163+
printf("Bandwidth: %d b/sec = %d KB/sec\n", bytesSentPerSeconds, bytesSentPerSeconds / 1024);
164+
}
165+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Override some defaults so BT stack is enabled
2+
# in this example
3+
4+
#
5+
# BT config
6+
#
7+
CONFIG_BT_ENABLED=y
8+
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
9+
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
10+
CONFIG_BTDM_CTRL_MODE_BTDM=n
11+
CONFIG_BT_BLUEDROID_ENABLED=n
12+
CONFIG_BT_NIMBLE_ENABLED=y
13+
CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM=1
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# The following lines of boilerplate have to be in your project's
2+
# CMakeLists in this exact order for cmake to work correctly
3+
cmake_minimum_required(VERSION 3.5)
4+
5+
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
6+
set(SUPPORTED_TARGETS esp32 esp32s3 esp32c3 esp32c6)
7+
project(L2CAP_server)

0 commit comments

Comments
 (0)