Skip to content

Commit

Permalink
Merge branch 'feature/walkthrough_for_coc_ble_prph_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_bleprph example. (v5.1)

See merge request espressif/esp-idf!26480
  • Loading branch information
rahult-github committed Nov 18, 2023
2 parents 54e3737 + 7ac37cc commit 5709078
Showing 1 changed file with 321 additions and 0 deletions.
@@ -0,0 +1,321 @@
# COC BLEPRPH Example Walkthrough

## Introduction

This example illustrates the process of advertising data, accepting connection from a central device, and enabling connection-oriented channels. It accomplishes two key tasks with the designated peer i.e. establishing L2CAP connection-oriented channels and receiving data via these established channels. The primary goal of the example is to demonstrate the functionality of BLE service discovery, connection establishment, and data transmission over the L2CAP layer using connection-oriented channels.

## Includes

This example is located in the examples folder of the ESP-IDF under the [COC_BLEPRPH/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_bleprph.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_bleprph.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_bleprph.h`: This file defines structures for BLE configuration and GATT registration context, includes an L2CAP COC UUID, and provides functions for registering GATT services and initializing the GATT server.

## 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();
if (ret != ESP_OK) {
ESP_LOGE(tag, "Failed to init nimble %d ", ret);
return;
}

/* Initialize the NimBLE host configuration. */
ble_hs_cfg.reset_cb = bleprph_on_reset;
ble_hs_cfg.sync_cb = bleprph_on_sync;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;

ble_hs_cfg.sm_io_cap = CONFIG_EXAMPLE_IO_TYPE;
#ifdef CONFIG_EXAMPLE_BONDING
ble_hs_cfg.sm_bonding = 1;
#endif
#ifdef CONFIG_EXAMPLE_MITM
ble_hs_cfg.sm_mitm = 1;
#endif
#ifdef CONFIG_EXAMPLE_USE_SC
ble_hs_cfg.sm_sc = 1;
#else
ble_hs_cfg.sm_sc = 0;
#endif
#ifdef CONFIG_EXAMPLE_BONDING
ble_hs_cfg.sm_our_key_dist = 1;
ble_hs_cfg.sm_their_key_dist = 1;
#endif

#if MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM) >= 1
bleprph_l2cap_coc_mem_init();
#endif

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

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

nimble_port_freertos_init(bleprph_host_task);

/* Initialize command line interface to accept input from user */
rc = scli_init();
if (rc != ESP_OK) {
ESP_LOGE(tag, "scli_init() failed");
}
}
```
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;
```
## Security Manager Configuration
Security Manager (sm_ members) is configurable at runtime to simplify security testing. Defaults for those are configured by selecting proper options via menuconfig for example configurations.

### I/O capabilities
```c
ble_hs_cfg.sm_io_cap = CONFIG_EXAMPLE_IO_TYPE; // Security Manager Local Input Output Capabilities is set to 3 which represents no input and no output.
```
### Use bonding
```c
#ifdef CONFIG_EXAMPLE_BONDING
ble_hs_cfg.sm_bonding = 1;
ble_hs_cfg.sm_our_key_dist |= BLE_SM_PAIR_KEY_DIST_ENC;
ble_hs_cfg.sm_their_key_dist |= BLE_SM_PAIR_KEY_DIST_ENC;
#endif
```
`CONFIG_EXAMPLE_BONDING` is set to enable the bonding on coc_bleprph. By default this flag is disabled. It can be configured through the sdkconfig file or from the menuconfig options.

### MITM protection
```c
#ifdef CONFIG_EXAMPLE_MITM
ble_hs_cfg.sm_mitm = 1;
#endif
```
When the `CONFIG_EXAMPLE_MITM` flag is set during pairing, it will enable the MITM bit in the auth req field of the pairing request, which in turn necessitates protection against Man-In-The-Middle attacks during the pairing process. The flag can be configured through sdkconfig or menuconfig as mentioned above.

### Secure connection feature
```c
#ifdef CONFIG_EXAMPLE_USE_SC
ble_hs_cfg.sm_sc = 1;
#else
ble_hs_cfg.sm_sc = 0;
#endif
```
When the `CONFIG_EXAMPLE_USE_SC` flag is set in the Pairing Request/Response, it enables the usage of LE Secure Connections for pairing, provided that the remote device also supports it. If the remote device does not support LE Secure Connections, the pairing process falls back to using legacy pairing.

### Allocate mempool and mbufpools for data packets.
```c
#if MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM) >= 1
bleprph_l2cap_coc_mem_init();
#endif
```
`BLE_L2CAP_COC_MAX_NUM` defines a maximum number of BLE Connection-Oriented Channels. When set to (0), the BLE L2CAP COC feature is not used. Instead legacy l2cap connections are used.

## Setting device name
The main function calls `ble_svc_gap_device_name_set()` to set the default device name. 'bleprph-l2coc' is passed as the default device name to this function.
```c
rc = ble_svc_gap_device_name_set("bleprph-l2coc");
```
## Ble store configuration
main function calls `ble_store_config_init()` to configure the host by setting up the storage callbacks that 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 creates 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(bleprph_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 controller and post HCI commands to controller.
## Setting command-line interface
```c
rc = scli_init();
if (rc != ESP_OK) {
ESP_LOGE(tag, "scli_init() failed");
}
```
To initialize a command line interface (CLI) that accepts input from the user, the function scli_init() is used. This function registers the CLI command "key value" to receive input from the user during the pairing process. The pairing process is facilitated using the ble_register_cli() function.

```c
static void
bleprph_l2cap_coc_mem_init(void)
{
int rc;
rc = os_mempool_init(&sdu_coc_mbuf_mempool, COC_BUF_COUNT, MTU, sdu_coc_mem,
"coc_sdu_pool");
assert(rc == 0);
rc = os_mbuf_pool_init(&sdu_os_mbuf_pool, &sdu_coc_mbuf_mempool, MTU,
COC_BUF_COUNT);
assert(rc == 0);
}
```
## 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 involves 2 important 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.
## BLEPRPH_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 created.
- 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 link which uses COC is disconnected.
- It prints a message indicating that the COC has been disconnected.
- c. `BLE_L2CAP_EVENT_COC_DATA_RECEIVED`:This case handles the event when data is received over an connection created by L2CAP COC.
- It checks if event->receive.sdu_rx is not NULL (indicating that data has been received).
- It then logs the received data by iterating through the bytes in the received SDU and printing them.
- Finally, it frees the memory associated with the received SDU and calls bleprph_l2cap_coc_accept to indicate that the COC is ready to receive more data.
- d. `BLE_L2CAP_EVENT_COC_ACCEPT`:This case handles the event when an L2CAP COC connection is accepted.
- It retrieves the peer's SDU size from event->accept.peer_sdu_size.
- It then calls bleprph_l2cap_coc_accept to indicate that the COC is ready for data transfer.
- 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.
## Accept the data on L2CAP_COC
```c
static int
bleprph_l2cap_coc_accept(uint16_t conn_handle, uint16_t peer_mtu,
struct ble_l2cap_chan *chan)
{
struct os_mbuf *sdu_rx;
console_printf("LE CoC accepting, chan: 0x%08lx, peer_mtu %d\n",
(uint32_t) chan, peer_mtu);
sdu_rx = os_mbuf_get_pkthdr(&sdu_os_mbuf_pool, 0);
if (!sdu_rx) {
return BLE_HS_ENOMEM;
}
return ble_l2cap_recv_ready(chan, sdu_rx);
}
```
This function prepares an L2CAP COC for data reception by allocating an SDU buffer, printing relevant information to the console, and signaling readiness to receive data on the specified channel. If memory allocation fails, it returns an out-of-memory error code.

`Parameters`:
- uint16_t conn_handle: This parameter represents the connection handle, which is a unique identifier for the BLE connection associated with the incoming COC.
- uint16_t peer_mtu: It represents the Maximum Transmission Unit (MTU) negotiated with the peer device for this COC. The MTU specifies the maximum size of data packets that can be exchanged over the channel.
- struct ble_l2cap_chan *chan: This parameter is a pointer to a structure that represents the L2CAP channel associated with the COC.

`Console Output`:
- The function starts by printing a message to the console using console_printf. This message indicates that the COC is being accepted and includes details such as the channel's memory address (chan) and the negotiated peer MTU (peer_mtu).

`Memory Allocation`:
- Next, the function attempts to allocate memory for an outgoing Service Data Unit (SDU) using os_mbuf_get_pkthdr. An SDU is a data buffer used to receive data from the peer over the COC. The function checks if memory allocation succeeds.

`Memory Allocation Check`:
- If memory allocation fails (i.e., sdu_rx is NULL), it returns an error code BLE_HS_ENOMEM to indicate that there was not enough memory to allocate the SDU.
L2CAP Receive Ready:
- If memory allocation is successful, the function proceeds to indicate that the COC is ready to receive data by calling ble_l2cap_recv_ready(chan, sdu_rx). This function prepares the L2CAP channel for data reception, passing in the channel pointer (chan) and the allocated SDU (sdu_rx).

## BLEPRPH Gap Event
The below event handler is responsible for responding to BLE connection events. It logs connection status, retrieves and prints connection information, resumes advertising if the connection fails, and creates an L2CAP server if the connection is successful
function is responsible for creating a new L2CAP COC server with the specified PSM, MTU, callback function, and callback argument. It allocates memory for the server, initializes its attributes, ensures it hasn't been inserted previously, inserts it into a list of COC servers, and returns a success code if everything goes smoothly
```c
case BLE_GAP_EVENT_CONNECT:
.
.
.
.
#if MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM) >= 1
rc = ble_l2cap_create_server(psm, MTU, bleprph_l2cap_coc_event_cb, NULL);
#endif
}
return 0;
```

## Conclusion
This example demonstrates advertising data, connecting to a central device, enabling connection-oriented channels, and transmitting data over the L2CAP layer. Its key objectives are to showcase BLE service discovery, connection setup, and data transfer using connection-oriented channels.

0 comments on commit 5709078

Please sign in to comment.