Skip to content

Commit

Permalink
Merge branch 'feature/walkthrough_for_coc_ble_cent_v5.1' into 'releas…
Browse files Browse the repository at this point in the history
…e/v5.1'

doc (nimble): Added the tutorial for coc_blecent example. (v5.1)

See merge request espressif/esp-idf!26204
  • Loading branch information
jack0c committed Sep 27, 2023
2 parents 10793c3 + 5caf3b5 commit b5891b6
Showing 1 changed file with 304 additions and 0 deletions.
@@ -0,0 +1,304 @@
# COC BLECENT Example Walkthrough

## Introduction

This example is designed to demonstrate the process of BLE service discovery, connection establishment, and data transfer using the L2CAP layer's connection-oriented channels. It begins by conducting a passive scan to identify nearby peripheral devices. If a device is found that both advertises its connectability and supports connection-oriented channels, the example proceeds to establish a connection with that device.

## Includes

This example is located in the examples folder of the ESP-IDF under the [COC_BLECENT/main](../main). The [main.c](../main/main.c) file located in the main folder contains all the functionality that we are going to review. The header files contained in [main.c](../main/main.c) are:

```c
#include "esp_log.h"
#include "nvs_flash.h"
/* BLE */
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "host/util/util.h"
#include "console/console.h"
#include "services/gap/ble_svc_gap.h"
#include "coc_blecent.h"
```
These `includes` are required for the FreeRTOS and underlying system components to run, including the logging functionality and a library to store data in non-volatile flash memory. We are interested in `“nimble_port.h”`, `“nimble_port_freertos.h”`, `"ble_hs.h"`, `“ble_svc_gap.h”` and `“coc_blecent.h”` which expose the BLE APIs required to implement this example.

* `nimble_port.h`: Includes the declaration of functions required for the initialization of the nimble stack.
* `nimble_port_freertos.h`: Initializes and enables nimble host task.
* `ble_hs.h`: Defines the functionalities to handle the host event.
* `ble_svc_gap.h`: Defines the macros for device name, and device appearance and declares the function to set them.
* `coc_blecent.h`: Provides necessary definitions and forward declarations for the coc_blecent example's functionality.

## Main Entry Point

The program's entry point is the app_main() function:
```c
void
app_main(void)
{
int rc;
/* Initialize NVS — it is used to store PHY calibration data */
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);

ret = nimble_port_init();
assert(ret == 0);

/* Configure the host. */
ble_hs_cfg.reset_cb = blecent_on_reset;
ble_hs_cfg.sync_cb = blecent_on_sync;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;

#if MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM) >= 1
blecent_l2cap_coc_mem_init();
xTaskCreate(ble_coc_cent_task, "ble_coc_cent_task", 4096, NULL, 10, NULL);
#endif

/* Initialize data structures to track connected peers. */
rc = peer_init(MYNEWT_VAL(BLE_MAX_CONNECTIONS), 64, 64, 64);
assert(rc == 0);

/* Set the default device name. */
rc = ble_svc_gap_device_name_set("blecent-l2coc");
assert(rc == 0);

/* XXX Need to have template for store */
ble_store_config_init();

nimble_port_freertos_init(blecent_host_task);
}
```
The main function starts by initializing the non-volatile storage library. This library allows us to save the key-value pairs in flash memory. `nvs_flash_init()` stores the PHY calibration data. In a Bluetooth Low Energy (BLE) device, cryptographic keys used for encryption and authentication are often stored in Non-Volatile Storage (NVS). BLE stores the peer keys, CCCD keys, peer records, etc on NVS. By storing these keys in NVS, the BLE device can quickly retrieve them when needed, without the need for time-consuming key generations.
```c
esp_err_t ret = nvs_flash_init();
```

## BT Controller and Stack Initialization
The main function calls `nimble_port_init()` to initialize the BT Controller and nimble stack. This function initializes the BT controller by first creating its configuration structure named `esp_bt_controller_config_t` with default settings generated by the `BT_CONTROLLER_INIT_CONFIG_DEFAULT()` macro. It implements the Host Controller Interface (HCI) on the controller side, the Link Layer (LL), and the Physical Layer (PHY). The BT Controller is invisible to the user applications and deals with the lower layers of the BLE stack. The controller configuration includes setting the BT controller stack size, priority, and HCI baud rate. With the settings created, the BT controller is initialized and enabled with the `esp_bt_controller_init()` and `esp_bt_controller_enable()` functions:

```c
esp_bt_controller_config_t config_opts = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&config_opts);
```
Next, the controller is enabled in BLE Mode.

```c
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
```
The controller should be enabled in `ESP_BT_MODE_BLE` if you want to use the BLE mode.

There are four Bluetooth modes supported:

1. `ESP_BT_MODE_IDLE`: Bluetooth not running
2. `ESP_BT_MODE_BLE`: BLE mode
3. `ESP_BT_MODE_CLASSIC_BT`: BT Classic mode
4. `ESP_BT_MODE_BTDM`: Dual mode (BLE + BT Classic)

After the initialization of the BT controller, the nimble stack, which includes the common definitions and APIs for BLE, is initialized by using `esp_nimble_init()`:

```c
esp_err_t esp_nimble_init(void)
{
#if !SOC_ESP_NIMBLE_CONTROLLER
/* Initialize the function pointers for OS porting */
npl_freertos_funcs_init();

npl_freertos_mempool_init();

if(esp_nimble_hci_init() != ESP_OK) {
ESP_LOGE(NIMBLE_PORT_LOG_TAG, "hci inits failed\n");
return ESP_FAIL;
}

/* Initialize default event queue */
ble_npl_eventq_init(&g_eventq_dflt);
/* Initialize the global memory pool */
os_mempool_module_init();
os_msys_init();

#endif
/* Initialize the host */
ble_transport_hs_init();

return ESP_OK;
}
```
The host is configured by setting up the callbacks for Stack-reset, Stack-sync, and Storage status.
```c
ble_hs_cfg.reset_cb = blecent_on_reset;
ble_hs_cfg.sync_cb = blecent_on_sync;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
```
## Task creation for l2cap_coc data sending.
```c
#if MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM) >= 1
blecent_l2cap_coc_mem_init();
xTaskCreate(ble_coc_cent_task, "ble_coc_cent_task", 4096, NULL, 10, NULL);
#endif
```
`BLE_L2CAP_COC_MAX_NUM` defines a maximum number of BLE Connection-Oriented Channels. When set to (0), BLE COC is not compiled in.
If the example supports l2cap_coc then allocate mempool and mbuf_pools for allocating the data packets.Then`xTaskCreate` Creates a new task and adds it to the list of tasks that are ready to run with `ble_coc_cent_task` as a pointer to the task entry function, `ble_coc_cent_task` as the name of the task, `4096` as a task stack size in bytes.

The main function invokes `peer_init()` to initialize memory pools to manage peer, service, characteristics, and descriptor objects in BLE.
```c
rc = peer_init(MYNEWT_VAL(BLE_MAX_CONNECTIONS), 64, 64, 64);
```

## Setting device name
The main function calls `ble_svc_gap_device_name_set()` to set the default device name.'blecent-l2coc' is passed as the default device name to this function.
```c
rc = ble_svc_gap_device_name_set("blecent-l2coc");
```

## Ble store configuration
The main function calls `ble_store_config_init()` to configure the host by setting up the storage callbacks which handle the read, write, and deletion of security material.
```c
/* XXX Need to have a template for store */
ble_store_config_init();
```
## Thread model
The main function ends by creating a task where nimble will run using `nimble_port_freertos_init()`. This enables the nimble stack by using `esp_nimble_enable()`.
```c
nimble_port_freertos_init(blecent_host_task);
```
`esp_nimble_enable()` creates a task where the nimble host will run. The nimble stack runs in a separate thread with its own context to handle async operations from the controller and post HCI commands to the controller.
## L2CAP_COC_CENT host task
```c
void ble_coc_cent_task(void *pvParameters)
{
while (1) {
if (coc_chan) {
for (int i = 0; i < 5; i++) {
blecent_l2cap_coc_send_data(coc_chan);
vTaskDelay(500 / portTICK_PERIOD_MS);
}
coc_chan = NULL;
}
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
```
This task runs indefinitely and is responsible for periodically sending data over a Connection-Oriented Channel (COC) established using L2CAP.This task continuously checks for the presence of an L2CAP COC represented by the coc_chan variable. If such a channel exists, it sends data over the channel multiple times with a 500-millisecond delay between each transmission. After sending the data, it resets the coc_chan to NULL to indicate that the channel is available for a new connection. This loop then repeats, with a 1-second pause between iterations, effectively allowing the task to periodically attempt data transmissions over an L2CAP COC when available.

## L2CAP_COC memory initialization (mempool and mbuf_pool)
The purpose of the above function is to initialize memory pools and buffer pools used for managing memory buffers for be_l2cap_coc.It inlvoes 2 impoertant allocations i.e mempool_init and mbuf_pool_init
- Memory Pool Initialization:
`os_mempool_init`: This function initializes a memory pool named `sdu_coc_mbuf_mempool`. A memory pool is a reserved area of memory that can be used for dynamic memory allocation.

- Memory Buffer Pool Initialization:
`os_mbuf_pool_init`: This function initializes a mbuf (memory buffer) pool named `sdu_os_mbuf_pool`. A mbuf is a data structure typically used to manage data buffers efficiently.

## L2CAP_COC event callback
This callback function is responsible for handling L2CAP events related to COCs in a BLE application. It logs information about successful COC connections and disconnections, including relevant details such as channel information and status. Additionally, it sets a global variable (coc_chan) to the connected channel pointer, which can be useful for further interaction with the COC in the application.

- a. `BLE_L2CAP_EVENT_COC_CONNECTED`: This case is executed when a COC (Connection-Oriented Channel) is successfully connected.
- It checks event->connect. status to see if the connection was established successfully. If there's an error (status is nonzero), it prints an error message and returns 0 (indicating a failure).
- It obtains information about the connected COC using ble_l2cap_get_chan_info and stores it in the chan_info structure.
- It prints information about the connected COC, including connection handle, channel pointer, PSM (Protocol/Service Multiplexer), source and destination channel IDs, Maximum Packet Size (MPS), and Maximum Transmission Unit (MTU).
- It stores the channel pointer in the global variable coc_chan for future reference.
- b. `BLE_L2CAP_EVENT_COC_DISCONNECTED`: This case is executed when a COC is disconnected.
- It prints a message indicating that the COC has been disconnected.
- c. `Default Case`: If the event type doesn't match the two handled cases, the function returns 0, indicating that it has successfully handled the event. The default case is used to ignore unhandled event types.

## L2CAP connect after completion of the discovery procedure
```c
static void
blecent_l2cap_coc_on_disc_complete(const struct peer *peer, int status, void *arg)
{
uint16_t psm = 0x1002;
struct os_mbuf *sdu_rx;

sdu_rx = os_mbuf_get_pkthdr(&sdu_os_mbuf_pool, 0);
ble_l2cap_connect(conn_handle_coc, psm, MTU, sdu_rx, blecent_l2cap_coc_event_cb,
NULL);
}
```
This function is called when the service discovery process is completed, and its purpose is to initiate a connection at the L2CAP. It specifies the PSM, MTU, and other parameters required for the connection, and it sets up a callback function (blecent_l2cap_coc_event_cb) to handle L2CAP events related to this connection.
## Send data on l2cap channel.
A function named `blecent_l2cap_coc_send_data` is involved in sending data over a BLE (Bluetooth Low Energy) connection using L2CAP (Logical Link Control and Adaptation Protocol) Connection-Oriented Channels (COC). Below is it explanation.
1. **Function Signature**: The function takes a single argument, `struct ble_l2cap_chan *chan`, which represents the L2CAP channel over which data will be sent.
```c
static void blecent_l2cap_coc_send_data(struct ble_l2cap_chan *chan)
```

2. **Variable Declarations**:
- `struct os_mbuf *sdu_rx_data;`: Declares a pointer to an OS mbuf, which is a data structure typically used for managing memory buffers.
- `int rc = 0;`: Initializes an integer variable `rc` to 0, which will be used to store the return code of various operations.
- `int len = 512;`: Sets the variable `len` to 512, which represents the length of the data buffer to be sent.
- `uint8_t value[len];`: Declares an array `value` of type `uint8_t` with a length of `len` (512 in this case).
```c
struct os_mbuf *sdu_rx_data;
int rc = 0;
int len = 512;
uint8_t value[len];
```

3. **Data Preparation**:
- A loop initializes the `value` array with values from 0 to 511. This loop fills the `value` array with consecutive numbers starting from 0.
```c
for (int i = 0; i < len; i++) {
value[i] = i;
}
```

4. **Memory Allocation and Data Copying**:
- A `do-while` loop is used to allocate an OS mbuf from a pool named `sdu_os_mbuf_pool`. If the allocation fails (sdu_rx_data is `NULL`), the code waits for 10 milliseconds (`vTaskDelay`) and tries again.
- Once a valid mbuf (`sdu_rx_data`) is obtained, the data from the `value` array is appended to this mbuf using `os_mbuf_append`.
```c
do {
sdu_rx_data = os_mbuf_get_pkthdr(&sdu_os_mbuf_pool, 0);
if (sdu_rx_data == NULL) {
vTaskDelay(10 / portTICK_PERIOD_MS);
sdu_rx_data = os_mbuf_get_pkthdr(&sdu_os_mbuf_pool, 0);
}
} while (sdu_rx_data == NULL);

os_mbuf_append(sdu_rx_data, value, len);
```

5. **Printing Mbuf Data**: The function `print_mbuf_data` is called to print the data contained in the `sdu_rx_data` mbuf. This is likely a debugging or logging step to verify the content.


6. **Data Transmission**:
- The code attempts to send the data using the `ble_l2cap_send` function, passing the `chan` (L2CAP channel) and the mbuf (`sdu_rx_data`) as arguments.
- If the initial send operation returns `BLE_HS_ESTALLED`, indicating that the channel is stalled or busy, the code enters a loop where it waits for 100 milliseconds (`vTaskDelay`) and retries the send operation until it succeeds or encounters an error other than `BLE_HS_ESTALLED`.

7. **Result Reporting**:
- After exiting the loop, the code checks the value of `rc`:
- If `rc` is 0, it logs a message indicating that the data was sent successfully.
- If `rc` is not 0, it logs a message indicating that data sending failed and includes the value of `rc` for further debugging.

8. **Memory Cleanup**: Finally, the allocated mbuf (`sdu_rx_data`) is freed using `os_mbuf_free` to release the memory.

This code is essentially preparing data in the `value` array, encapsulating it in an OS mbuf, attempting to send it over an L2CAP channel, handling stalls, and reporting the success or failure of the operation. It is part of a BLE application running on an embedded system.

## BLECENT_GAP_EVENT ( setting discovery callback as `blecent_l2cap_coc_on_disc_complete` )
Below is an explanation of `GAP_CONNECT_EVENT` where the application sets the discovery call back as `blecent_l2cap_coc_on_disc_complete` if the maximum number of BLE Connection-Oriented Channels is greater than 0.
```c
case BLE_GAP_EVENT_CONNECT:
/* A new connection was established or a connection attempt failed. */
if (event->connect.status == 0) {
/* Connection successfully established. */
MODLOG_DFLT(INFO, "Connection established ");

rc = ble_gap_conn_find(event->connect.conn_handle, &desc);
assert(rc == 0);
print_conn_desc(&desc);
MODLOG_DFLT(INFO, "\n");

#if MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM) >= 1
conn_handle_coc = event->connect.conn_handle;
disc_cb = blecent_l2cap_coc_on_disc_complete;
```
For More info related to `BLECENT_GAP_EVENT` here is the complete tutorial for [blecent_walkthrough.md](../../../blecent/tutorial/blecent_walkthrough.md)
## Conclusion
- This example is developed to illustrate the sequence of BLE operations, including service discovery, connection setup, and data exchange via the L2CAP layer's connection-oriented channels.
- It commences by performing a passive scan to detect peripheral devices in the vicinity.
- If a suitable device is detected, one that both advertises its readiness for connection and offers support for connection-oriented channels, the example advances to initiate a connection with that specific device.

0 comments on commit b5891b6

Please sign in to comment.