Merge branch 'feature/walkthrough_for_htp_examples_v5.1' into 'release/v5.1'

Nimble: Added Example Walkthrough tutorial for HTP example in nimble (v5.1)

See merge request espressif/esp-idf!24534
pull/12369/head
Island 2023-07-05 10:07:37 +08:00
commit c6bc222d54
2 zmienionych plików z 579 dodań i 0 usunięć

Wyświetl plik

@ -0,0 +1,298 @@
# BLE Central HTP Example Walkthrough
## Introduction
In this tutorial, the ble_htp central example code for the espressif chipsets with BLE5.0 support is reviewed. This example creates GATT client and performs passive scan, it then connects to peripheral device if the device advertises connectability and the device advertises support for the Health Thermometer service (0x1809) as primary service UUID. After connection it enables bonding and link encryprion if the `Enable Link Encryption` flag is set in the example config.
## Includes
This example is located in the examples folder of the ESP-IDF under the [ble_htp/htp_cent/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 "ble_htp_cent.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"` and `“ble_svc_gap.h”`, `“ble_htp_cent.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 ,device apperance and declare the function to set them.
* `ble_htp_cent.h`: Defines the 16 bit HTP SVC UUID.
## Main Entry Point
The programs 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);
nimble_port_init();
/* Configure the host. */
ble_hs_cfg.reset_cb = ble_htp_cent_on_reset;
ble_hs_cfg.sync_cb = ble_htp_cent_on_sync;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
/* 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("nimble-htp-cent");
assert(rc == 0);
/* XXX Need to have template for store */
ble_store_config_init();
nimble_port_freertos_init(ble_htp_cent_host_task);
}
```
The main function starts by initializing the non-volatile storage library. This library allows 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();
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 );
```
## BT Controller and Stack Initialization
The main function calls `nimble_port_init()` to initialize 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 = ble_htp_cent_on_reset;
ble_hs_cfg.sync_cb = ble_htp_cent_on_sync;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
```
Further Data Structures are created and initialized to track connected peers using `peer_init()`. This function creates memory buffers to generate the memory pools like `peer_pool`, `peer_svc_pool`, `peer_chr_pool`, and `peer_dsc_pool`.
```c
rc = peer_init(MYNEWT_VAL(BLE_MAX_CONNECTIONS), 64, 64, 64);
```
## Structure of Peer
The structure of a peer includes fields such as its connection handle, a pointer to the next peer, a list of discovered gatt services, tracking parameters for the service discovery process, and the callbacks that get executed when service discovery completes.
```c
struct peer {
SLIST_ENTRY(peer) next;
uint16_t conn_handle;
struct peer_svc_list svcs;
uint16_t disc_prev_chr_val;
struct peer_svc *cur_svc;
peer_disc_fn *disc_cb;
void *disc_cb_arg;
};
```
The main function calls `ble_svc_gap_device_name_set()` to set the default device name. 'nimble-htp-cent' is passed as the default device name to this function.
```c
rc = ble_svc_gap_device_name_set("nimble-htp-cent");
```
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();
```
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(ble_htp_cent_host_task);
```
`esp_nimble_enable()` create a task where the nimble host will run. It is not strictly necessary to have a separate task for the nimble host, but since something needs to handle the default queue, it is easier to create a separate task.
## Overall code flow.
It performs four GATT operations against the specified peer:
* Reads the HTP temperature type characteristic.
* After the read operation is completed, writes the HTP measurement interval characteristic.
* After the write operation is completed, subscribes to indications for the HTP temperature measurement characteristic.
* After the subscribe operation is completed, it subscribes to notifications for the HTP intermediate temperature characteristic.
## Read Operation
`ble_htp_cent_read_write_subscribe` reads the supported HTP characteristic.If the peer does not support a required service, characteristic, or descriptor OR if a GATT procedure fails , then this function immediately terminates the connection.
```c
static void
ble_htp_cent_read_write_subscribe(const struct peer *peer)
{
const struct peer_chr *chr;
int rc;
/* Read the Temparature Type characteristic. */
chr = peer_chr_find_uuid(peer,
BLE_UUID16_DECLARE(BLE_SVC_HTP_UUID16),
BLE_UUID16_DECLARE(BLE_SVC_HTP_CHR_UUID16_TEMP_TYPE));
if (chr == NULL) {
MODLOG_DFLT(ERROR, "Error: Peer doesn't support the Temparature Type"
" characteristic\n");
goto err;
}
rc = ble_gattc_read(peer->conn_handle, chr->chr.val_handle,
ble_htp_cent_on_read, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "Error: Failed to read characteristic; rc=%d\n",
rc);
goto err;
}
return;
err:
/* Terminate the connection. */
ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
}
```
## BLE GAP Connect Event
Once the connection is established `BLE_GAP_EVENT_CONNECT` event occurs. Once connected, it proceeds to perform the service discovery. If the connection attempt is failed then central start scanning again using the `ble_htp_cent_scan` function.
```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");
/* Remember peer. */
rc = peer_add(event->connect.conn_handle);
if (rc != 0) {
MODLOG_DFLT(ERROR, "Failed to add peer; rc=%d\n", rc);
return 0;
}
< snip >
} else {
/* Connection attempt failed; resume scanning. */
MODLOG_DFLT(ERROR, "Error: Connection failed; status=%d\n",
event->connect.status);
ble_htp_cent_scan();
}
return 0;
```
## BLE GAP Disconnect Event
The connection between Central and Peripheral is terminated when the service discovery is failed or the GATT procedure is completed. `ble_gap_terminate` function is used to terminate the connection which results in the event called `BLE_GAP_EVENT_DISCONNECT`.
```c
case BLE_GAP_EVENT_DISCONNECT:
/* Connection terminated. */
MODLOG_DFLT(INFO, "disconnect; reason=%d ", event->disconnect.reason);
print_conn_desc(&event->disconnect.conn);
MODLOG_DFLT(INFO, "\n");
/* Forget about peer. */
peer_delete(event->disconnect.conn.conn_handle);
ble_htp_cent_scan();
return 0;
```
## Conclusion
This Walkthrough covers the code explanation of the BLE_HTP_CENTRAL example. The following points are concluded through this walkthrough.
1. First a connection is made with a device supporting the HTP service.
2. Gatt operations are performed on the characteristic and results displayed.

Wyświetl plik

@ -0,0 +1,281 @@
# BLE Peripheral HTP Example Walkthrough
## Introduction
In this tutorial, the ble_htp peripheral example code for the espressif chipsets with BLE5.0 support is reviewed. This example creates GATT server demonstrating standard Health Thermometer profile. It simulates temperature measurement and notifies to client when the notifications are enabled. It advertises support for the Health Thermometer service (0x1809) as primary service UUID. When internmediate temperature characteristic is subscribed, the timer resets and it sends notifications to client every 1 sec.
## Includes
This example is located in the examples folder of the ESP-IDF under the [ble_htp/htp_prph/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"
#include "freertos/FreeRTOSConfig.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 "ble_htp_prph.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"` and `“ble_svc_gap.h”`, `“ble_htp_prph.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.
* `ble_htp_prph.h`: Defines the profiles specific macros and functions
## Main Entry Point
The programs 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) {
MODLOG_DFLT(ERROR, "Failed to init nimble %d \n", ret);
return;
}
/* Initialize the NimBLE host configuration */
ble_hs_cfg.sync_cb = ble_htp_prph_on_sync;
ble_hs_cfg.reset_cb = ble_htp_prph_on_reset;
/* Enable bonding */
ble_hs_cfg.sm_bonding = 1;
ble_hs_cfg.sm_our_key_dist |= BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID;
ble_hs_cfg.sm_their_key_dist |= BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID;
ble_hs_cfg.sm_sc = 1;
ble_hs_cfg.sm_mitm = 1;
/* name, period/time, auto reload, timer ID, callback */
ble_htp_prph_tx_timer = xTimerCreate("ble_htp_prph_tx_timer", pdMS_TO_TICKS(1000), pdTRUE,
(void *)0, ble_htp_prph_tx);
rc = gatt_svr_init();
assert(rc == 0);
/* Set the default device name */
rc = ble_svc_gap_device_name_set(device_name);
assert(rc == 0);
/* Start the task */
nimble_port_freertos_init(ble_htp_prph_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();
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 );
```
## BT Controller and Stack Initialization
The main function calls `nimble_port_init()` to initialize 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 on Stack-reset, Stack-sync, registration of each GATT resource, and storage status.
```c
ble_hs_cfg.sync_cb = ble_htp_prph_on_sync;
ble_hs_cfg.reset_cb = ble_htp_prph_on_reset;
```
The Security Manager is configured by setting up the following SM's flag and attributes of the host
1. sm_bonding: It represents Security Manager Bond Flag.
2. sm_our_key_dist: It represents Security Manager's local key distribution mask.
3. sm_their_key_dist: It represents Security Manager remote key distribution mask.
4. sm_sc: It represents Security Manager Secure Connections Flag.
5. sm_mitm : It represents the Security Manager MITM Flag which results in requiring Man-In-The-Middle protection while pairing if it is set.
```c
ble_hs_cfg.sm_bonding = 1;
ble_hs_cfg.sm_our_key_dist |= BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID;
ble_hs_cfg.sm_their_key_dist |= BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID;
ble_hs_cfg.sm_sc = 1;
ble_hs_cfg.sm_mitm = 1;
```
A timer with value of 1 second is then started. After every second, when the callback 'ble_htp_prph_tx' is called, it will attempt to send a notification to remote client.
```c
static void
ble_htp_prph_tx(TimerHandle_t ev)
{
int rc;
float temp;
/* If temperature value to be indicated is in Fahrenheit set temp_unit as true or else false
for Celsius */
bool temp_unit = true;
if (!ble_svc_htp_is_subscribed(conn_handle, INTERMEDIATE_TEMP)) {
ble_htp_prph_tx_htp_stop();
return;
}
temp = 0; /* 0 Fahrenheit */
rc = ble_svc_htp_notify(conn_handle, temp, temp_unit);
if (rc == 0) {
MODLOG_DFLT(INFO, "Notification sent successfully");
} else {
MODLOG_DFLT(INFO, "Error in sending notification");
}
```
The main function then calls `ble_svc_gap_device_name_set()` to set the default device name. 'ble_htp_phy' is passed as the default device name to this function.
```c
static const char *device_name = "ble_htp_prph"
rc = ble_svc_gap_device_name_set(device_name);
```
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(ble_htp_prph_host_task);
```
`esp_nimble_enable()` create a task where the nimble host will run. It is not strictly necessary to have a separate task for the nimble host, but since something needs to handle the default queue, it is easier to create a separate task.
## Advertisement Functionalities
To support increased messaging, BLE5.0 is added with extended advertising PDUs. These PDUs are broadcast-only events (like electronic beacons). The following function is used to make extended advertisements. In Bluetooth® 4.0, all advertising was done on 3 of the 40 – 2.4GHz ISM band channels. With Bluetooth 5 there are now two sets of advertising channels: primary and secondary. The primary advertising channels are the original 3 of the 40 advertising channels defined in Bluetooth® 4.0 While the secondary advertising channels use the 37 fixed channels previously reserved for data.
```c
static void
ext_ble_htp_prph_advertise(void)
{
struct ble_gap_ext_adv_params params;
struct os_mbuf *data;
uint8_t instance = 0;
int rc;
/* First check if any instance is already active */
if (ble_gap_ext_adv_active(instance)) {
return;
}
/* use defaults for non-set params */
memset (&params, 0, sizeof(params));
/* enable connectable advertising */
params.connectable = 1;
/* advertise using random addr */
params.own_addr_type = BLE_OWN_ADDR_PUBLIC;
params.primary_phy = BLE_HCI_LE_PHY_1M;
params.secondary_phy = BLE_HCI_LE_PHY_2M;
params.sid = 1;
params.itvl_min = BLE_GAP_ADV_FAST_INTERVAL1_MIN;
params.itvl_max = BLE_GAP_ADV_FAST_INTERVAL1_MIN;
/* configure instance 0 */
rc = ble_gap_ext_adv_configure(instance, &params, NULL,
ble_htp_prph_gap_event, NULL);
assert (rc == 0);
/* in this case only scan response is allowed */
/* get mbuf for scan rsp data */
data = os_msys_get_pkthdr(sizeof(ext_adv_pattern_1), 0);
assert(data);
/* fill mbuf with scan rsp data */
rc = os_mbuf_append(data, ext_adv_pattern_1, sizeof(ext_adv_pattern_1));
assert(rc == 0);
rc = ble_gap_ext_adv_set_data(instance, data);
assert (rc == 0);
/* start advertising */
rc = ble_gap_ext_adv_start(instance, 0, 0);
assert (rc == 0);
}
```
## Conclusion
This Walkthrough covers the code explanation of the BLE_HTP_PRPH example. The following points are concluded through this walkthrough.
1. First a connection is allowed to a device which requests for HTP service.
2. Gatt operations are responded on the characteristics and results displayed.