Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ idf_component_register(
"src/NimBLEEddystoneTLM.cpp"
"src/NimBLEExtAdvertising.cpp"
"src/NimBLEHIDDevice.cpp"
"src/NimBLEL2CAPChannel.cpp"
"src/NimBLEL2CAPServer.cpp"
"src/NimBLERemoteCharacteristic.cpp"
"src/NimBLERemoteDescriptor.cpp"
"src/NimBLERemoteService.cpp"
Expand Down
5 changes: 5 additions & 0 deletions examples/L2CAP/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.vscode
build
sdkconfig
sdkconfig.old
dependencies.lock
7 changes: 7 additions & 0 deletions examples/L2CAP/L2CAP_Client/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
3 changes: 3 additions & 0 deletions examples/L2CAP/L2CAP_Client/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
PROJECT_NAME := L2CAP_client

include $(IDF_PATH)/make/project.mk
4 changes: 4 additions & 0 deletions examples/L2CAP/L2CAP_Client/main/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
set(COMPONENT_SRCS "main.cpp")
set(COMPONENT_ADD_INCLUDEDIRS ".")

register_component()
4 changes: 4 additions & 0 deletions examples/L2CAP/L2CAP_Client/main/component.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
3 changes: 3 additions & 0 deletions examples/L2CAP/L2CAP_Client/main/idf_component.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dependencies:
local/esp-nimble-cpp:
path: ../../../../../esp-nimble-cpp/
165 changes: 165 additions & 0 deletions examples/L2CAP/L2CAP_Client/main/main.cpp
Original file line number Diff line number Diff line change
@@ -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);
}
}
13 changes: 13 additions & 0 deletions examples/L2CAP/L2CAP_Client/sdkconfig.defaults
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions examples/L2CAP/L2CAP_Server/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
3 changes: 3 additions & 0 deletions examples/L2CAP/L2CAP_Server/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
PROJECT_NAME := L2CAP_server

include $(IDF_PATH)/make/project.mk
4 changes: 4 additions & 0 deletions examples/L2CAP/L2CAP_Server/main/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
set(COMPONENT_SRCS "main.cpp")
set(COMPONENT_ADD_INCLUDEDIRS ".")

register_component()
4 changes: 4 additions & 0 deletions examples/L2CAP/L2CAP_Server/main/component.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
3 changes: 3 additions & 0 deletions examples/L2CAP/L2CAP_Server/main/idf_component.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dependencies:
local/esp-nimble-cpp:
path: ../../../../../esp-nimble-cpp/
90 changes: 90 additions & 0 deletions examples/L2CAP/L2CAP_Server/main/main.cpp
Original file line number Diff line number Diff line change
@@ -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());
}
}
13 changes: 13 additions & 0 deletions examples/L2CAP/L2CAP_Server/sdkconfig.defaults
Original file line number Diff line number Diff line change
@@ -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
Loading