-
Notifications
You must be signed in to change notification settings - Fork 8.1k
doc: add simple L2CAP server example #96896
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,53 @@ configuration option: :kconfig:option:`CONFIG_BT_L2CAP_DYNAMIC_CHANNEL`. These c | |
support segmentation and reassembly transparently, they also support credit | ||
based flow control making it suitable for data streams. | ||
|
||
Channels instances are represented by the :c:struct:`bt_l2cap_chan` struct which | ||
contains the callbacks in the :c:struct:`bt_l2cap_chan_ops` struct to inform | ||
when the channel has been connected, disconnected or when the encryption has | ||
changed. | ||
In addition to that it also contains the ``recv`` callback which is called | ||
whenever an incoming data has been received. Data received this way can be | ||
marked as processed by returning 0 or using | ||
:c:func:`bt_l2cap_chan_recv_complete` API if processing is asynchronous. | ||
|
||
.. note:: | ||
The ``recv`` callback is called directly from RX Thread thus it is not | ||
recommended to block for long periods of time. | ||
|
||
For sending data the :c:func:`bt_l2cap_chan_send` API can be used noting that | ||
it may block if no credits are available, and resuming as soon as more credits | ||
are available. | ||
|
||
Servers can be registered using :c:func:`bt_l2cap_server_register` API passing | ||
the :c:struct:`bt_l2cap_server` struct which informs what ``psm`` it should | ||
listen to, the required security level ``sec_level``, and the callback | ||
``accept`` which is called to authorize incoming connection requests and | ||
allocate channel instances. | ||
|
||
Creating a simple L2CAP server | ||
------------------------------- | ||
|
||
A complete working example demonstrating L2CAP dynamic channels is available | ||
in the samples directory: | ||
|
||
- Acceptor (server) sample: :zephyr:code-sample:`bluetooth_l2cap_coc_acceptor` | ||
- Initiator (client) sample: :zephyr:code-sample:`bluetooth_l2cap_coc_initiator` | ||
|
||
The acceptor sample shows how to register an L2CAP server and handle incoming | ||
connections: | ||
|
||
.. literalinclude:: ../../../../samples/bluetooth/l2cap_coc_acceptor/src/main.c | ||
:language: c | ||
:linenos: | ||
|
||
.. note:: | ||
The sample demonstrates allocating one channel per connection using | ||
``CONFIG_BT_MAX_CONN`` and ``bt_conn_index(conn)``. See the initiator sample | ||
for how to open a channel and send data to the acceptor. | ||
Comment on lines
+50
to
+53
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this note doesn't belong to |
||
|
||
Fixed Channels | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a good change, thanks! |
||
-------------- | ||
|
||
The user can also define fixed channels using the :c:macro:`BT_L2CAP_FIXED_CHANNEL_DEFINE` | ||
macro. Fixed channels are initialized upon connection, and do not support segmentation. An example | ||
of how to define a fixed channel is shown below. | ||
|
@@ -42,32 +89,12 @@ of how to define a fixed channel is shown below. | |
.accept = l2cap_fixed_accept, | ||
}; | ||
|
||
Channels instances are represented by the :c:struct:`bt_l2cap_chan` struct which | ||
contains the callbacks in the :c:struct:`bt_l2cap_chan_ops` struct to inform | ||
when the channel has been connected, disconnected or when the encryption has | ||
changed. | ||
In addition to that it also contains the ``recv`` callback which is called | ||
whenever an incoming data has been received. Data received this way can be | ||
marked as processed by returning 0 or using | ||
:c:func:`bt_l2cap_chan_recv_complete` API if processing is asynchronous. | ||
|
||
.. note:: | ||
The ``recv`` callback is called directly from RX Thread thus it is not | ||
recommended to block for long periods of time. | ||
|
||
For sending data the :c:func:`bt_l2cap_chan_send` API can be used noting that | ||
it may block if no credits are available, and resuming as soon as more credits | ||
are available. | ||
|
||
Servers can be registered using :c:func:`bt_l2cap_server_register` API passing | ||
the :c:struct:`bt_l2cap_server` struct which informs what ``psm`` it should | ||
listen to, the required security level ``sec_level``, and the callback | ||
``accept`` which is called to authorize incoming connection requests and | ||
allocate channel instances. | ||
Client Channels | ||
--------------- | ||
|
||
Client channels can be initiated with use of :c:func:`bt_l2cap_chan_connect` | ||
API and can be disconnected with the :c:func:`bt_l2cap_chan_disconnect` API. | ||
Note that the later can also disconnect channel instances created by servers. | ||
Note that the latter can also disconnect channel instances created by servers. | ||
|
||
API Reference | ||
************* | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
cmake_minimum_required(VERSION 3.20.0) | ||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) | ||
project(l2cap_server_simple) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Needs to be updated |
||
|
||
target_sources(app PRIVATE src/main.c) |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs to follow the template for samples There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not addressed - please use the template There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. bump |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
.. _bluetooth_l2cap_coc_acceptor: | ||
|
||
L2CAP Connection Oriented Channels (Acceptor) | ||
############################################## | ||
|
||
Overview | ||
******** | ||
|
||
This sample demonstrates how to create an L2CAP Connection Oriented Channel | ||
(CoC) server that listens for incoming L2CAP connections on a fixed PSM | ||
(Protocol/Service Multiplexer). When a client connects, the server accepts | ||
the connection and receives data sent over the L2CAP channel. | ||
|
||
The sample uses a fixed allocation strategy where one L2CAP channel instance is | ||
pre-allocated for each possible Bluetooth connection (based on | ||
``CONFIG_BT_MAX_CONN``). This approach avoids dynamic memory allocation and | ||
simplifies resource management. | ||
|
||
Requirements | ||
************ | ||
|
||
* A board with Bluetooth Low Energy (BLE) support | ||
* Bluetooth controller with L2CAP Connection Oriented Channels support | ||
|
||
Building and Running | ||
******************** | ||
|
||
This sample can be built and run on any board with Bluetooth LE support. | ||
|
||
To build the sample for the nRF52840 DK: | ||
|
||
.. zephyr-app-commands:: | ||
:zephyr-app: samples/bluetooth/l2cap_coc_acceptor | ||
:board: nrf52840dk/nrf52840 | ||
:goals: build flash | ||
:compact: | ||
|
||
For testing with native_posix (simulation): | ||
|
||
.. zephyr-app-commands:: | ||
:zephyr-app: samples/bluetooth/l2cap_coc_acceptor | ||
:board: native_posix | ||
:goals: build | ||
:compact: | ||
|
||
After flashing, the device will register an L2CAP server on PSM 0x29 and wait | ||
for incoming connections. Pair this sample with the | ||
:zephyr:code-sample:`bluetooth_l2cap_coc_initiator` sample to establish a | ||
connection and exchange data. | ||
|
||
Sample Output | ||
============= | ||
|
||
Upon successful initialization: | ||
|
||
.. code-block:: console | ||
|
||
L2CAP server registered, PSM 41 | ||
|
||
When a client connects: | ||
|
||
.. code-block:: console | ||
|
||
L2CAP channel accepted, assigned chan[0] | ||
L2CAP channel connected | ||
|
||
When data is received: | ||
|
||
.. code-block:: console | ||
|
||
L2CAP channel received 17 bytes | ||
|
||
Testing | ||
******* | ||
|
||
This sample is designed to work with the | ||
:zephyr:code-sample:`bluetooth_l2cap_coc_initiator` sample. Run the acceptor | ||
sample on one device and the initiator sample on another device within BLE range. | ||
|
||
The initiator will scan, connect, and establish an L2CAP channel to send periodic | ||
messages to the acceptor. | ||
|
||
References | ||
********** | ||
|
||
* :ref:`bluetooth_api` | ||
* :ref:`bluetooth-samples` | ||
* `Bluetooth Core Specification v5.4, Vol 3, Part A (L2CAP) | ||
<https://www.bluetooth.com/specifications/specs/core-specification-5-4/>`_ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
CONFIG_BT=y | ||
CONFIG_BT_PERIPHERAL=y | ||
CONFIG_BT_SMP=y | ||
CONFIG_BT_L2CAP_DYNAMIC_CHANNEL=y | ||
# Optional, adjust for sample: | ||
CONFIG_BT_MAX_CONN=2 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as here: #96896 (comment) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
sample: | ||
name: Bluetooth L2CAP dynamic channels sample (acceptor role) | ||
description: Sample demonstrating L2CAP dynamic channels (acceptor role). | ||
tests: | ||
sample.bluetooth.l2cap_coc_acceptor: | ||
harness: bluetooth | ||
platform_allow: | ||
- qemu_x86 | ||
integration_platforms: | ||
- qemu_x86 | ||
tags: bluetooth | ||
Check warning on line 11 in samples/bluetooth/l2cap_coc_acceptor/sample.yaml
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,83 @@ | ||||||
/* | ||||||
* SPDX-License-Identifier: Apache-2.0 | ||||||
* Copyright The Zephyr Project Contributors | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://docs.zephyrproject.org/latest/contribute/guidelines.html#copyright-and-license-notices
Suggested change
|
||||||
*/ | ||||||
|
||||||
#include <zephyr/bluetooth/bluetooth.h> | ||||||
#include <zephyr/bluetooth/conn.h> | ||||||
#include <zephyr/bluetooth/l2cap.h> | ||||||
#include <zephyr/sys/printk.h> | ||||||
|
||||||
#define PSM 0x29 /* example PSM */ | ||||||
|
||||||
static int l2cap_recv(struct bt_l2cap_chan *chan, struct net_buf *buf); | ||||||
static void l2cap_connected(struct bt_l2cap_chan *chan); | ||||||
static void l2cap_disconnected(struct bt_l2cap_chan *chan); | ||||||
|
||||||
/* ops used for every channel instance */ | ||||||
static struct bt_l2cap_chan_ops l2cap_ops = { | ||||||
.recv = l2cap_recv, | ||||||
.connected = l2cap_connected, | ||||||
.disconnected = l2cap_disconnected, | ||||||
}; | ||||||
|
||||||
/* Fixed array of channel instances, one per possible connection */ | ||||||
static struct bt_l2cap_chan fixed_chan[CONFIG_BT_MAX_CONN]; | ||||||
|
||||||
static int accept_cb(struct bt_conn *conn, struct bt_l2cap_server *server, | ||||||
struct bt_l2cap_chan **chan) | ||||||
{ | ||||||
uint8_t conn_index = bt_conn_index(conn); | ||||||
|
||||||
/* initialize the chosen entry */ | ||||||
fixed_chan[conn_index] = (struct bt_l2cap_chan){ | ||||||
.ops = &l2cap_ops, | ||||||
}; | ||||||
|
||||||
*chan = &fixed_chan[conn_index]; | ||||||
|
||||||
printk("L2CAP channel accepted, assigned chan[%d]\n", conn_index); | ||||||
|
||||||
return 0; /* accept */ | ||||||
} | ||||||
|
||||||
static struct bt_l2cap_server server = { | ||||||
.psm = PSM, | ||||||
.sec_level = BT_SECURITY_L1, | ||||||
.accept = accept_cb, | ||||||
}; | ||||||
|
||||||
static void l2cap_connected(struct bt_l2cap_chan *chan) | ||||||
{ | ||||||
printk("L2CAP channel connected\n"); | ||||||
} | ||||||
|
||||||
static void l2cap_disconnected(struct bt_l2cap_chan *chan) | ||||||
{ | ||||||
printk("L2CAP channel disconnected\n"); | ||||||
} | ||||||
|
||||||
static int l2cap_recv(struct bt_l2cap_chan *chan, struct net_buf *buf) | ||||||
{ | ||||||
printk("L2CAP channel received %u bytes\n", buf->len); | ||||||
/* For synchronous processing, return 0 and the stack frees buf. */ | ||||||
return 0; | ||||||
} | ||||||
|
||||||
int main(void) | ||||||
{ | ||||||
int err = bt_enable(NULL); | ||||||
if (err) { | ||||||
printk("Bluetooth init failed: %d\n", err); | ||||||
return err; | ||||||
} | ||||||
|
||||||
err = bt_l2cap_server_register(&server); | ||||||
if (err) { | ||||||
printk("L2CAP server registration failed: %d\n", err); | ||||||
return err; | ||||||
} | ||||||
|
||||||
printk("L2CAP server registered, PSM %u\n", PSM); | ||||||
return 0; | ||||||
} | ||||||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
cmake_minimum_required(VERSION 3.20.0) | ||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) | ||
project(l2cap_client_simple) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Needs to be updated |
||
|
||
target_sources(app PRIVATE src/main.c) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
.. _bluetooth_l2cap_coc_initiator: | ||
|
||
L2CAP Connection Oriented Channels (Initiator) | ||
############################################### | ||
|
||
Overview | ||
******** | ||
|
||
This sample demonstrates how to create an L2CAP Connection Oriented Channel | ||
(CoC) client that scans for Bluetooth devices, connects to one, and establishes | ||
an L2CAP channel to communicate with a server. | ||
|
||
The sample periodically sends short text messages over the L2CAP channel to | ||
demonstrate basic data transmission. It uses a fixed allocation strategy for | ||
the L2CAP channel to avoid dynamic memory allocation. | ||
|
||
Requirements | ||
************ | ||
|
||
* A board with Bluetooth Low Energy (BLE) support | ||
* Bluetooth controller with L2CAP Connection Oriented Channels support | ||
* A peer device running an L2CAP server (such as the | ||
:zephyr:code-sample:`bluetooth_l2cap_coc_acceptor` sample) | ||
|
||
Building and Running | ||
******************** | ||
|
||
This sample can be built and run on any board with Bluetooth LE support. | ||
|
||
To build the sample for the nRF52840 DK: | ||
|
||
.. zephyr-app-commands:: | ||
:zephyr-app: samples/bluetooth/l2cap_coc_initiator | ||
:board: nrf52840dk/nrf52840 | ||
:goals: build flash | ||
:compact: | ||
|
||
For testing with native_posix (simulation): | ||
|
||
.. zephyr-app-commands:: | ||
:zephyr-app: samples/bluetooth/l2cap_coc_initiator | ||
:board: native_posix | ||
:goals: build | ||
:compact: | ||
|
||
After flashing, the device will scan for nearby Bluetooth devices, connect to | ||
the first one it finds, and attempt to establish an L2CAP channel on PSM 0x29. | ||
Once connected, it will send messages periodically. | ||
|
||
Sample Output | ||
============= | ||
|
||
Upon successful connection and channel establishment: | ||
|
||
.. code-block:: console | ||
|
||
Scanning... | ||
Found device: XX:XX:XX:XX:XX:XX (RSSI -45) | ||
Connecting to XX:XX:XX:XX:XX:XX ... | ||
Connected | ||
L2CAP channel connection initiated | ||
L2CAP channel connected | ||
Sent: Hello from client | ||
|
||
The message "Hello from client" will be sent periodically to demonstrate | ||
ongoing data transmission. | ||
|
||
Testing | ||
******* | ||
|
||
This sample is designed to work with the | ||
:zephyr:code-sample:`bluetooth_l2cap_coc_acceptor` sample: | ||
|
||
1. Flash the acceptor sample on one device | ||
2. Flash the initiator sample on another device | ||
3. Place both devices within BLE range | ||
4. The initiator will automatically scan, connect, and establish the L2CAP channel | ||
5. Monitor the serial output on both devices to observe the connection and data | ||
exchange | ||
|
||
Notes | ||
***** | ||
|
||
* This sample is intentionally simple and is designed for testing and | ||
demonstration purposes | ||
* The initiator connects to the first advertising device it finds - in a | ||
production environment, you would want to filter devices by name, address, or | ||
advertised services | ||
* You may need to adjust Bluetooth settings in the ``prj.conf`` file to match | ||
your platform's capabilities | ||
|
||
References | ||
********** | ||
|
||
* :ref:`bluetooth_api` | ||
* :ref:`bluetooth-samples` | ||
* `Bluetooth Core Specification v5.4, Vol 3, Part A (L2CAP) | ||
<https://www.bluetooth.com/specifications/specs/core-specification-5-4/>`_ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should point to exact lines not to the entire file. What should be demonstrated (based on your previous change) are line 17-48 from l2cap_coc_acceptor/src/main.c.
I also suggest to move the
literalinclude
up to line 34 (right after paragraph that starts at line 28) and remove the existing lines 34-35 (Creating a simple L2CAP server...
).Then paragraph at line 37 that refers to samples passes perfectly to the entire section. But! I suggest to keep only reference to acceptor (server) role there and move the initiator (client) reference to the end of
Client Channels
section that you have added (after line 97 at the current change).