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)

examples/L2CAP/L2CAP_Client/Makefile

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)

examples/L2CAP/L2CAP_Server/Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
PROJECT_NAME := L2CAP_server
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: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#include <NimBLEDevice.h>
2+
3+
// See the following for generating UUIDs:
4+
// https://www.uuidgenerator.net/
5+
6+
#define SERVICE_UUID "dcbc7255-1e9e-49a0-a360-b0430b6c6905"
7+
#define CHARACTERISTIC_UUID "371a55c8-f251-4ad2-90b3-c7c195b049be"
8+
#define L2CAP_CHANNEL 150
9+
#define L2CAP_MTU 5000
10+
11+
class GATTCallbacks: public BLEServerCallbacks {
12+
13+
public:
14+
void onConnect(BLEServer* pServer, BLEConnInfo& info) {
15+
/// Booster #1
16+
pServer->setDataLen(info.getConnHandle(), 251);
17+
/// Booster #2 (especially for Apple devices)
18+
BLEDevice::getServer()->updateConnParams(info.getConnHandle(), 12, 12, 0, 200);
19+
}
20+
};
21+
22+
class L2CAPChannelCallbacks: public BLEL2CAPChannelCallbacks {
23+
24+
public:
25+
bool connected = false;
26+
size_t numberOfReceivedBytes;
27+
uint8_t nextSequenceNumber;
28+
29+
public:
30+
void onConnect(NimBLEL2CAPChannel* channel) {
31+
printf("L2CAP connection established\n");
32+
connected = true;
33+
numberOfReceivedBytes = nextSequenceNumber = 0;
34+
}
35+
36+
void onRead(NimBLEL2CAPChannel* channel, std::vector<uint8_t>& data) {
37+
numberOfReceivedBytes += data.size();
38+
size_t sequenceNumber = data[0];
39+
printf("L2CAP read %d bytes w/ sequence number %d", data.size(), sequenceNumber);
40+
if (sequenceNumber != nextSequenceNumber) {
41+
printf("(wrong sequence number %d, expected %d)\n", sequenceNumber, nextSequenceNumber);
42+
} else {
43+
printf("\n");
44+
nextSequenceNumber++;
45+
}
46+
}
47+
void onDisconnect(NimBLEL2CAPChannel* channel) {
48+
printf("L2CAP disconnected\n");
49+
connected = false;
50+
}
51+
};
52+
53+
extern "C"
54+
void app_main(void) {
55+
printf("Starting L2CAP server example [%lu free] [%lu min]\n", esp_get_free_heap_size(), esp_get_minimum_free_heap_size());
56+
57+
BLEDevice::init("L2CAP-Server");
58+
BLEDevice::setMTU(BLE_ATT_MTU_MAX);
59+
60+
auto cocServer = BLEDevice::createL2CAPServer();
61+
auto l2capChannelCallbacks = new L2CAPChannelCallbacks();
62+
auto channel = cocServer->createService(L2CAP_CHANNEL, L2CAP_MTU, l2capChannelCallbacks);
63+
64+
auto server = BLEDevice::createServer();
65+
server->setCallbacks(new GATTCallbacks());
66+
auto service = server->createService(SERVICE_UUID);
67+
auto characteristic = service->createCharacteristic(CHARACTERISTIC_UUID, NIMBLE_PROPERTY::READ);
68+
characteristic->setValue(L2CAP_CHANNEL);
69+
service->start();
70+
auto advertising = BLEDevice::getAdvertising();
71+
advertising->addServiceUUID(SERVICE_UUID);
72+
advertising->enableScanResponse(true);
73+
74+
BLEDevice::startAdvertising();
75+
printf("Server waiting for connection requests [%lu free] [%lu min]\n", esp_get_free_heap_size(), esp_get_minimum_free_heap_size());
76+
77+
// Wait until transfer actually starts...
78+
while (!l2capChannelCallbacks->numberOfReceivedBytes) {
79+
vTaskDelay(10 / portTICK_PERIOD_MS);
80+
}
81+
printf("\n\n\n");
82+
int numberOfSeconds = 0;
83+
84+
while (true) {
85+
vTaskDelay(1000 / portTICK_PERIOD_MS);
86+
if (!l2capChannelCallbacks->connected) { continue; }
87+
int bps = l2capChannelCallbacks->numberOfReceivedBytes / ++numberOfSeconds;
88+
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());
89+
}
90+
}
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

0 commit comments

Comments
 (0)