Skip to content

Introduce L2CAP infrastructure. #954

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 6, 2025
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
143 changes: 143 additions & 0 deletions examples/L2CAP/L2CAP_Client/L2CAP_Client.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
//
// (C) Dr. Michael 'Mickey' Lauer <mickey@vanille-media.de>
//

#include <Arduino.h>
#include <NimBLEDevice.h>

#if CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM <= 0
# error "CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM must be set to 1 or greater"
#endif

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

// The remote service we wish to connect to.
static NimBLEUUID serviceUUID("dcbc7255-1e9e-49a0-a360-b0430b6c6905");
// The characteristic of the remote service we are interested in.
static NimBLEUUID charUUID("371a55c8-f251-4ad2-90b3-c7c195b049be");

#define L2CAP_CHANNEL 150
#define L2CAP_MTU 5000

const NimBLEAdvertisedDevice* theDevice = NULL;
NimBLEClient* theClient = NULL;
NimBLEL2CAPChannel* theChannel = NULL;

size_t bytesSent = 0;
size_t bytesReceived = 0;
size_t numberOfSeconds = 0;

class L2CAPChannelCallbacks : public NimBLEL2CAPChannelCallbacks {
public:
void onConnect(NimBLEL2CAPChannel* channel) { Serial.println("L2CAP connection established"); }

void onMTUChange(NimBLEL2CAPChannel* channel, uint16_t mtu) { Serial.printf("L2CAP MTU changed to %d\n", mtu); }

void onRead(NimBLEL2CAPChannel* channel, std::vector<uint8_t>& data) {
Serial.printf("L2CAP read %d bytes\n", data.size());
}
void onDisconnect(NimBLEL2CAPChannel* channel) { Serial.println("L2CAP disconnected"); }
} L2Callbacks;

class ClientCallbacks : public NimBLEClientCallbacks {
void onConnect(NimBLEClient* pClient) {
Serial.println("GAP connected");
pClient->setDataLen(251);

theChannel = NimBLEL2CAPChannel::connect(pClient, L2CAP_CHANNEL, L2CAP_MTU, &L2Callbacks);
}

void onDisconnect(NimBLEClient* pClient, int reason) {
printf("GAP disconnected (reason: %d)\n", reason);
theDevice = NULL;
theChannel = NULL;
NimBLEDevice::getScan()->start(5 * 1000, true);
}
} clientCallbacks;

class ScanCallbacks : public NimBLEScanCallbacks {
void onResult(const NimBLEAdvertisedDevice* advertisedDevice) {
if (theDevice) {
return;
}
Serial.printf("BLE Advertised Device found: %s\n", advertisedDevice->toString().c_str());

if (!advertisedDevice->haveServiceUUID()) {
return;
}
if (!advertisedDevice->isAdvertisingService(serviceUUID)) {
return;
}

Serial.println("Found the device we're interested in!");
NimBLEDevice::getScan()->stop();

// Hand over the device to the other task
theDevice = advertisedDevice;
}
} scanCallbacks;

void setup() {
Serial.begin(115200);
Serial.println("Starting L2CAP client example");

NimBLEDevice::init("L2CAP-Client");
NimBLEDevice::setMTU(BLE_ATT_MTU_MAX);

auto scan = NimBLEDevice::getScan();
scan->setScanCallbacks(&scanCallbacks);
scan->setInterval(1349);
scan->setWindow(449);
scan->setActiveScan(true);
scan->start(25 * 1000, false);
}

void loop() {
static uint8_t sequenceNumber = 0;
static unsigned long firstBytesTime = 0;
auto now = millis();

if (!theDevice) {
delay(1000);
return;
}

if (!theClient) {
theClient = NimBLEDevice::createClient();
theClient->setConnectionParams(6, 6, 0, 42);
theClient->setClientCallbacks(&clientCallbacks);
if (!theClient->connect(theDevice)) {
Serial.println("Error: Could not connect to device");
return;
}
delay(2000);
}

if (!theChannel) {
Serial.println("l2cap channel not initialized");
delay(2000);
return;
}

if (!theChannel->isConnected()) {
Serial.println("l2cap channel not connected\n");
delay(2000);
return;
}

std::vector<uint8_t> data(5000, sequenceNumber++);
if (theChannel->write(data)) {
if (bytesSent == 0) {
firstBytesTime = now;
}
bytesSent += data.size();
if (now - firstBytesTime > 1000) {
int bytesSentPerSeconds = bytesSent / ((now - firstBytesTime) / 1000);
printf("Bandwidth: %d b/sec = %d KB/sec\n", bytesSentPerSeconds, bytesSentPerSeconds / 1024);
}
} else {
Serial.println("failed to send!");
abort();
}
}
97 changes: 97 additions & 0 deletions examples/L2CAP/L2CAP_Server/L2CAP_Server.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//
// (C) Dr. Michael 'Mickey' Lauer <mickey@vanille-media.de>
//

#include <Arduino.h>
#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 NimBLEServerCallbacks {
public:
void onConnect(NimBLEServer* pServer, NimBLEConnInfo& info) {
/// Booster #1
pServer->setDataLen(info.getConnHandle(), 251);
/// Booster #2 (especially for Apple devices)
NimBLEDevice::getServer()->updateConnParams(info.getConnHandle(), 12, 12, 0, 200);
}
} gattCallbacks;

class L2CAPChannelCallbacks : public NimBLEL2CAPChannelCallbacks {
public:
bool connected = false;
size_t numberOfReceivedBytes;
uint8_t nextSequenceNumber;
int numberOfSeconds;

public:
void onConnect(NimBLEL2CAPChannel* channel) {
Serial.println("L2CAP connection established");
connected = true;
numberOfSeconds = numberOfReceivedBytes = nextSequenceNumber = 0;
}

void onRead(NimBLEL2CAPChannel* channel, std::vector<uint8_t>& data) {
numberOfReceivedBytes += data.size();
size_t sequenceNumber = data[0];
Serial.printf("L2CAP read %d bytes w/ sequence number %d", data.size(), sequenceNumber);
if (sequenceNumber != nextSequenceNumber) {
Serial.printf("(wrong sequence number %d, expected %d)\n", sequenceNumber, nextSequenceNumber);
} else {
nextSequenceNumber++;
}
}

void onDisconnect(NimBLEL2CAPChannel* channel) {
Serial.println("L2CAP disconnected");
connected = false;
}
} l2capCallbacks;

void setup() {
Serial.begin(115200);
Serial.println("Starting L2CAP server example");

NimBLEDevice::init("L2CAP-Server");
NimBLEDevice::setMTU(BLE_ATT_MTU_MAX);

auto cocServer = NimBLEDevice::createL2CAPServer();
auto channel = cocServer->createService(L2CAP_CHANNEL, L2CAP_MTU, &l2capCallbacks);

auto server = NimBLEDevice::createServer();
server->setCallbacks(&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);

NimBLEDevice::startAdvertising();
Serial.println("Server waiting for connection requests");
}

void loop() {
// Wait until transfer actually starts...
if (!l2capCallbacks.numberOfReceivedBytes) {
delay(10);
return;
}

delay(1000);
if (!l2capCallbacks.connected) {
return;
}

int bps = l2capCallbacks.numberOfReceivedBytes / ++l2capCallbacks.numberOfSeconds;
Serial.printf("Bandwidth: %d b/sec = %d KB/sec\n", bps, bps / 1024);
}
33 changes: 33 additions & 0 deletions src/NimBLEDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@

# if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
# include "NimBLEServer.h"
# if CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM > 0
# include "NimBLEL2CAPServer.h"
# endif
# endif

# include "NimBLELog.h"
Expand All @@ -85,6 +88,9 @@ NimBLEScan* NimBLEDevice::m_pScan = nullptr;

# if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
NimBLEServer* NimBLEDevice::m_pServer = nullptr;
# if CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM > 0
NimBLEL2CAPServer* NimBLEDevice::m_pL2CAPServer = nullptr;
# endif
# endif

# if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER)
Expand Down Expand Up @@ -140,6 +146,27 @@ NimBLEServer* NimBLEDevice::createServer() {
NimBLEServer* NimBLEDevice::getServer() {
return m_pServer;
} // getServer

# if CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM > 0
/**
* @brief Create an instance of a L2CAP server.
* @return A pointer to the instance of the L2CAP server.
*/
NimBLEL2CAPServer* NimBLEDevice::createL2CAPServer() {
if (NimBLEDevice::m_pL2CAPServer == nullptr) {
NimBLEDevice::m_pL2CAPServer = new NimBLEL2CAPServer();
}
return m_pL2CAPServer;
} // createL2CAPServer

/**
* @brief Get the instance of the L2CAP server.
* @return A pointer to the L2CAP server instance or nullptr if none have been created.
*/
NimBLEL2CAPServer* NimBLEDevice::getL2CAPServer() {
return m_pL2CAPServer;
} // getL2CAPServer
# endif
# endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)

/* -------------------------------------------------------------------------- */
Expand Down Expand Up @@ -965,6 +992,12 @@ bool NimBLEDevice::deinit(bool clearAll) {
delete NimBLEDevice::m_pServer;
NimBLEDevice::m_pServer = nullptr;
}
# if CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM > 0
if (NimBLEDevice::m_pL2CAPServer != nullptr) {
delete NimBLEDevice::m_pL2CAPServer;
NimBLEDevice::m_pL2CAPServer = nullptr;
}
# endif
# endif

# if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER)
Expand Down
21 changes: 21 additions & 0 deletions src/NimBLEDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ class NimBLEAdvertising;

# if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
class NimBLEServer;
# if CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM > 0
class NimBLEL2CAPServer;
# endif
# endif

# if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) || defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
Expand Down Expand Up @@ -95,6 +98,13 @@ class NimBLEDeviceCallbacks;
# define BLEEddystoneTLM NimBLEEddystoneTLM
# define BLEEddystoneURL NimBLEEddystoneURL
# define BLEConnInfo NimBLEConnInfo
# define BLEL2CAPServer NimBLEL2CAPServer
# define BLEL2CAPService NimBLEL2CAPService
# define BLEL2CAPServiceCallbacks NimBLEL2CAPServiceCallbacks
# define BLEL2CAPClient NimBLEL2CAPClient
# define BLEL2CAPClientCallbacks NimBLEL2CAPClientCallbacks
# define BLEL2CAPChannel NimBLEL2CAPChannel
# define BLEL2CAPChannelCallbacks NimBLEL2CAPChannelCallbacks

# ifdef CONFIG_BT_NIMBLE_MAX_CONNECTIONS
# define NIMBLE_MAX_CONNECTIONS CONFIG_BT_NIMBLE_MAX_CONNECTIONS
Expand Down Expand Up @@ -160,6 +170,10 @@ class NimBLEDevice {
# if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
static NimBLEServer* createServer();
static NimBLEServer* getServer();
# if CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM > 0
static NimBLEL2CAPServer* createL2CAPServer();
static NimBLEL2CAPServer* getL2CAPServer();
# endif
# endif

# if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) || defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
Expand Down Expand Up @@ -216,6 +230,9 @@ class NimBLEDevice {

# if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
static NimBLEServer* m_pServer;
# if CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM > 0
static NimBLEL2CAPServer* m_pL2CAPServer;
# endif
# endif

# if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER)
Expand Down Expand Up @@ -275,6 +292,10 @@ class NimBLEDevice {
# include "NimBLEService.h"
# include "NimBLECharacteristic.h"
# include "NimBLEDescriptor.h"
# if CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM > 0
# include "NimBLEL2CAPServer.h"
# include "NimBLEL2CAPChannel.h"
# endif
# endif

# if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER)
Expand Down
Loading