forked from h2zero/esp-nimble-cpp
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.
This commit is contained in:
committed by
Ryan Powell
parent
59e111ab55
commit
e55ad9019c
@@ -0,0 +1,5 @@
|
||||
.vscode
|
||||
build
|
||||
sdkconfig
|
||||
sdkconfig.old
|
||||
dependencies.lock
|
||||
@@ -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)
|
||||
@@ -0,0 +1,3 @@
|
||||
PROJECT_NAME := L2CAP_client
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
@@ -0,0 +1,4 @@
|
||||
set(COMPONENT_SRCS "main.cpp")
|
||||
set(COMPONENT_ADD_INCLUDEDIRS ".")
|
||||
|
||||
register_component()
|
||||
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
@@ -0,0 +1,3 @@
|
||||
dependencies:
|
||||
local/esp-nimble-cpp:
|
||||
path: ../../../../../esp-nimble-cpp/
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -0,0 +1,3 @@
|
||||
PROJECT_NAME := L2CAP_server
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
@@ -0,0 +1,4 @@
|
||||
set(COMPONENT_SRCS "main.cpp")
|
||||
set(COMPONENT_ADD_INCLUDEDIRS ".")
|
||||
|
||||
register_component()
|
||||
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
@@ -0,0 +1,3 @@
|
||||
dependencies:
|
||||
local/esp-nimble-cpp:
|
||||
path: ../../../../../esp-nimble-cpp/
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user