component/bt: Update BLE examples tutorials and ReadMe files
- Make docs compatible with GitHub Markdown - Add tutorials links in programming doc - Modify ReadMe files
This commit is contained in:
+91
-87
@@ -2,17 +2,17 @@
|
||||
|
||||
## Introduction
|
||||
|
||||
This document presents a walkthrough of the GATT Server Service Table example code for the ESP32. This example implements a Bluetooth Low Energy (BLE) Generic Attribute (GATT) Server using a table-like data structure to define the server services and characteristics such as the one shown in Fig. 1. Therefore, it demonstrates a practical way to define the server functionality in one place instead of adding services and characteristics one by one.
|
||||
This document presents a walkthrough of the GATT Server Service Table example code for the ESP32. This example implements a Bluetooth Low Energy (BLE) Generic Attribute (GATT) Server using a table-like data structure to define the server services and characteristics such as the one shown in the figure below Therefore, it demonstrates a practical way to define the server functionality in one place instead of adding services and characteristics one by one.
|
||||
|
||||
This example implements the *Heart Rate Profile* as defined by the [Traditional Profile Specifications](https://www.bluetooth.com/specifications/profiles-overview).
|
||||
|
||||

|
||||
<div align="center"><img src="image/Heart_Rate_Service.png" width = "450" alt="Table-like data structure representing the Heart Rate Service" align=center /> </div>
|
||||
|
||||
## Includes
|
||||
|
||||
Let’s start by taking a look at the included headers in the ``gatts_table_creat_demo.c`` file:
|
||||
Let’s start by taking a look at the included headers in the [gatts_table_creat_demo.c](../main/gatts_table_creat_demo.c) file:
|
||||
|
||||
```
|
||||
```c
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
@@ -38,9 +38,9 @@ These includes are required for the *FreeRTOS* and underlaying system components
|
||||
|
||||
## Service Table
|
||||
|
||||
The header file ``gatts_table_creat_demo.h`` is where an enumeration of the services and characteristics is created:
|
||||
The header file [gatts_table_creat_demo.h](../main/gatts_table_creat_demo.h) is where an enumeration of the services and characteristics is created:
|
||||
|
||||
```
|
||||
```c
|
||||
enum
|
||||
{
|
||||
HRS_IDX_SVC,
|
||||
@@ -60,21 +60,21 @@ enum
|
||||
```
|
||||
The enumeration elements are set up in the same order as the Heart Rate Profile attributes, starting with the service followed by the characteristics of that service. In addition, the Heart Rate Measurement characteristic has a Client Characteristic Configuration (CCC) descriptor which is an additional attribute that describes if the characteristic has notifications enabled. The enumeration index can be used to identify each element later when creating the actual attributes table. In summary, the elements are described as follows:
|
||||
|
||||
* HRS_IDX_SVC: Heart Rate Service index
|
||||
* HRS_IDX_HR_MEAS_CHAR: Heart Rate Measurement characteristic index
|
||||
* HRS_IDX_HR_MEAS_VAL: Heart Rate Measurement characteristic value index
|
||||
* HRS_IDX_HR_MEAS_NTF_CFG: Heart Rate Measurement notifications configuration (CCC) index
|
||||
* HRS_IDX_BOBY_SENSOR_LOC_CHAR: Heart Rate Body Sensor Location characteristic index
|
||||
* HRS_IDX_BOBY_SENSOR_LOC_VAL: Heart Rate Body Sensor Location characteristic value index
|
||||
* HRS_IDX_HR_CTNL_PT_CHAR: Heart Rate Control Point characteristic index
|
||||
* HRS_IDX_HR_CTNL_PT_VAL: Heart Rate Control Point characteristic value index
|
||||
* HRS_IDX_NB: Number of table elements.
|
||||
* ``HRS_IDX_SVC``: Heart Rate Service index
|
||||
* ``HRS_IDX_HR_MEAS_CHAR``: Heart Rate Measurement characteristic index
|
||||
* ``HRS_IDX_HR_MEAS_VAL``: Heart Rate Measurement characteristic value index
|
||||
* ``HRS_IDX_HR_MEAS_NTF_CFG``: Heart Rate Measurement notifications configuration (CCC) index
|
||||
* ``HRS_IDX_BOBY_SENSOR_LOC_CHAR``: Heart Rate Body Sensor Location characteristic index
|
||||
* ``HRS_IDX_BOBY_SENSOR_LOC_VAL``: Heart Rate Body Sensor Location characteristic value index
|
||||
* ``HRS_IDX_HR_CTNL_PT_CHAR``: Heart Rate Control Point characteristic index
|
||||
* ``HRS_IDX_HR_CTNL_PT_VAL``: Heart Rate Control Point characteristic value index
|
||||
* ``HRS_IDX_NB``: Number of table elements.
|
||||
|
||||
## Main Entry Point
|
||||
|
||||
The entry point to this example is the ``app_main()`` function:
|
||||
|
||||
```
|
||||
```c
|
||||
void app_main()
|
||||
{
|
||||
esp_err_t ret;
|
||||
@@ -94,7 +94,7 @@ void app_main()
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BTDM);
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (ret) {
|
||||
ESP_LOGE(GATTS_TABLE_TAG, "%s enable controller failed\n", __func__);
|
||||
return;
|
||||
@@ -121,20 +121,20 @@ void app_main()
|
||||
|
||||
The main function starts by initializing the non-volatile storage library in order to be able to save parameters in flash memory.
|
||||
|
||||
```
|
||||
```c
|
||||
ret = nvs_flash_init();
|
||||
```
|
||||
|
||||
## BT Controller and Stack Initialization
|
||||
|
||||
See this section in **GATT Server Example Walkthrough**.
|
||||
See this section in [GATT Server Example Walkthrough](../../gatt_server/tutorial/Gatt_Server_Example_Walkthrough.md).
|
||||
|
||||
|
||||
## Application Profiles
|
||||
|
||||
This example implements one Application Profile for the Heart Rate Service. An Application Profile is a way to group functionality which is designed to be used by one client application, for example one smartphone mobile app. In this way, different types of profiles can be accommodated in one server. The Application Profile ID, which is an user-assigned number to identify each profile, is used to register the profile in the stack, in this example the ID is 0x55.
|
||||
|
||||
```
|
||||
```c
|
||||
#define HEART_PROFILE_NUM 1
|
||||
#define HEART_PROFILE_APP_IDX 0
|
||||
#define ESP_HEART_RATE_APP_ID 0x55
|
||||
@@ -142,7 +142,7 @@ This example implements one Application Profile for the Heart Rate Service. An A
|
||||
|
||||
The profiles are stored in the ``heart_rate_profile_tab`` array. Since there is only one profile in this example, one element is stored in the array with index zero as defined by the ``HEART_PROFILE_APP_IDX``. Additionally, the profile event handler callback function is initialized. Each application on the GATT server uses a different interface, represented by the gatts_if parameter. For initialization, this parameter is set to ``ESP_GATT_IF_NONE``, later when the application is registered, the gatts_if parameter is updated with the corresponding interface generated by the stack.
|
||||
|
||||
```
|
||||
```c
|
||||
/* One gatt-based profile one app_id and one gatts_if, this array will store the gatts_if returned by ESP_GATTS_REG_EVT */
|
||||
static struct gatts_profile_inst heart_rate_profile_tab[HEART_PROFILE_NUM] = {
|
||||
[HEART_PROFILE_APP_IDX] = {
|
||||
@@ -155,7 +155,7 @@ static struct gatts_profile_inst heart_rate_profile_tab[HEART_PROFILE_NUM] = {
|
||||
|
||||
The application registration takes place inside ``app_main()`` using the ``esp_ble_gatts_app_register()`` function:
|
||||
|
||||
```
|
||||
```c
|
||||
esp_ble_gatts_app_register(ESP_HEART_RATE_APP_ID);
|
||||
```
|
||||
|
||||
@@ -168,27 +168,27 @@ The register application event is the first one that is triggered during the lif
|
||||
|
||||
The function used to configure standard Bluetooth Specification advertisement parameters is ``esp_ble_gap_config_adv_data()`` which takes a pointer to an ``esp_ble_adv_data_t`` structure. The ``esp_ble_adv_data_t`` data structure for advertising data has the following definition:
|
||||
|
||||
```
|
||||
```c
|
||||
typedef struct {
|
||||
bool set_scan_rsp; /*!< Set this advertising data as scan response or not*/
|
||||
bool include_name; /*!< Advertising data include device name or not */
|
||||
bool set_scan_rsp; /*!< Set this advertising data as scan response or not*/
|
||||
bool include_name; /*!< Advertising data include device name or not */
|
||||
bool include_txpower; /*!< Advertising data include TX power */
|
||||
int min_interval; /*!< Advertising data show advertising min interval */
|
||||
int max_interval; /*!< Advertising data show advertising max interval */
|
||||
int appearance; /*!< External appearance of device */
|
||||
int min_interval; /*!< Advertising data show advertising min interval */
|
||||
int max_interval; /*!< Advertising data show advertising max interval */
|
||||
int appearance; /*!< External appearance of device */
|
||||
uint16_t manufacturer_len; /*!< Manufacturer data length */
|
||||
uint8_t *p_manufacturer_data; /*!< Manufacturer data point */
|
||||
uint16_t service_data_len; /*!< Service data length */
|
||||
uint8_t *p_service_data; /*!< Service data point */
|
||||
uint16_t service_uuid_len; /*!< Service uuid length */
|
||||
uint8_t *p_service_uuid; /*!< Service uuid array point */
|
||||
uint8_t flag; /*!< Advertising flag of discovery mode, see BLE_ADV_DATA_FLAG detail */
|
||||
uint16_t service_data_len; /*!< Service data length */
|
||||
uint8_t *p_service_data; /*!< Service data point */
|
||||
uint16_t service_uuid_len; /*!< Service uuid length */
|
||||
uint8_t *p_service_uuid; /*!< Service uuid array point */
|
||||
uint8_t flag; /*!< Advertising flag of discovery mode, see BLE_ADV_DATA_FLAG detail */
|
||||
} esp_ble_adv_data_t;
|
||||
```
|
||||
|
||||
In this example, the structure is initialized as follows:
|
||||
|
||||
```
|
||||
```c
|
||||
static esp_ble_adv_data_t heart_rate_adv_config = {
|
||||
.set_scan_rsp = false,
|
||||
.include_name = true,
|
||||
@@ -210,19 +210,18 @@ The minimum and maximum advertisement intervals are set in units of 0.625 ms. In
|
||||
|
||||
An advertising payload can be up to 31 bytes of data. It is possible that some of the parameters surpass the 31-byte advertisement packet limit which causes the stack to cut the message and leave some of the parameters out. To solve this, usually the longer parameters are stored in the scan response, which can be configured using the same ``esp_ble_gap_config_adv_data()`` function and an additional esp_ble_adv_data_t type structure with the .set_scan_rsp parameter is set to true. Finally, to set the device name the ``esp_ble_gap_set_device_name()`` function is used. The registering event handler is shown as follows:
|
||||
|
||||
```
|
||||
static void gatts_profile_event_handler(esp_gatts_cb_event_t event,
|
||||
esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
|
||||
```c
|
||||
static void gatts_profile_event_handler(esp_gatts_cb_event_t event,
|
||||
esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
|
||||
{
|
||||
ESP_LOGE(GATTS_TABLE_TAG, "event = %x\n",event);
|
||||
switch (event) {
|
||||
case ESP_GATTS_REG_EVT:
|
||||
ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__);
|
||||
esp_ble_gap_set_device_name(SAMPLE_DEVICE_NAME);
|
||||
ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__);
|
||||
esp_ble_gap_config_adv_data(&heart_rate_adv_config);
|
||||
|
||||
ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__);
|
||||
ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__);
|
||||
esp_ble_gap_set_device_name(SAMPLE_DEVICE_NAME);
|
||||
ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__);
|
||||
esp_ble_gap_config_adv_data(&heart_rate_adv_config);
|
||||
ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__);
|
||||
…
|
||||
```
|
||||
|
||||
@@ -230,7 +229,7 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event,
|
||||
|
||||
Once the advertising data have been set, the ``ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT`` is triggered and managed by the GAP event handler. Moreover, an ``ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT`` is triggered as well if the scan response is also set. Once the configuration of the advertising and scan response data has been set, the handler can use any of these events to start advertising, which is done using the ``esp_ble_gap_start_advertising()`` function:
|
||||
|
||||
```
|
||||
```c
|
||||
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
|
||||
{
|
||||
ESP_LOGE(GATTS_TABLE_TAG, "GAP_EVT, event %d\n", event);
|
||||
@@ -253,27 +252,25 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param
|
||||
|
||||
The function to start advertising takes a structure of type ``esp_ble_adv_params_t`` with the advertising parameters required.
|
||||
|
||||
```
|
||||
```c
|
||||
/// Advertising parameters
|
||||
typedef struct {
|
||||
uint16_t adv_int_min; /*!< Minimum advertising interval for
|
||||
undirected and low duty cycle directed advertising.
|
||||
Range: 0x0020 to 0x4000
|
||||
Default: N = 0x0800 (1.28 second)
|
||||
Time = N * 0.625 msec
|
||||
Time Range: 20 ms to 10.24 sec */
|
||||
uint16_t adv_int_max; /*!< Maximum advertising interval for undirected and low duty
|
||||
cycle directed advertising.
|
||||
Range: 0x0020 to 0x4000
|
||||
Default: N = 0x0800 (1.28 second)
|
||||
Time = N * 0.625 msec
|
||||
Time Range: 20 ms to 10.24 sec */
|
||||
esp_ble_adv_type_t adv_type; /*!< Advertising type */
|
||||
esp_ble_addr_type_t own_addr_type; /*!< Owner bluetooth device address type */
|
||||
esp_bd_addr_t peer_addr; /*!< Peer device bluetooth device address */
|
||||
esp_ble_addr_type_t peer_addr_type;/*!< Peer device bluetooth device address type */
|
||||
esp_ble_adv_channel_t channel_map; /*!< Advertising channel map */
|
||||
esp_ble_adv_filter_t adv_filter_policy; /*!< Advertising filter policy */
|
||||
uint16_t adv_int_min; /*!< Minimum advertising interval for undirected and low duty cycle directed advertising.
|
||||
Range: 0x0020 to 0x4000
|
||||
Default: N = 0x0800 (1.28 second)
|
||||
Time = N * 0.625 msec
|
||||
Time Range: 20 ms to 10.24 sec */
|
||||
uint16_t adv_int_max; /*!< Maximum advertising interval for undirected and low duty cycle directed advertising.
|
||||
Range: 0x0020 to 0x4000
|
||||
Default: N = 0x0800 (1.28 second)
|
||||
Time = N * 0.625 msec
|
||||
Time Range: 20 ms to 10.24 sec */
|
||||
esp_ble_adv_type_t adv_type; /*!< Advertising type */
|
||||
esp_ble_addr_type_t own_addr_type; /*!< Owner bluetooth device address type */
|
||||
esp_bd_addr_t peer_addr; /*!< Peer device bluetooth device address */
|
||||
esp_ble_addr_type_t peer_addr_type; /*!< Peer device bluetooth device address type */
|
||||
esp_ble_adv_channel_t channel_map; /*!< Advertising channel map */
|
||||
esp_ble_adv_filter_t adv_filter_policy; /*!< Advertising filter policy */
|
||||
} esp_ble_adv_params_t;
|
||||
```
|
||||
|
||||
@@ -281,7 +278,7 @@ Note that ``esp_ble_gap_config_adv_data()`` configures the data that is advertis
|
||||
|
||||
For this example, the advertisement parameters are initialized as follows:
|
||||
|
||||
```
|
||||
```c
|
||||
static esp_ble_adv_params_t heart_rate_adv_params = {
|
||||
.adv_int_min = 0x20,
|
||||
.adv_int_max = 0x40,
|
||||
@@ -298,7 +295,7 @@ These parameters configure the advertising interval between 20 ms to 40 ms. The
|
||||
|
||||
If the advertising started successfully, an ``ESP_GAP_BLE_ADV_START_COMPLETE_EVT`` event is generated which in this example is used to check if the advertising status is indeed advertising or otherwise print an error message.
|
||||
|
||||
```
|
||||
```c
|
||||
…
|
||||
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
|
||||
//advertising start complete event to indicate advertising start successfully or failed
|
||||
@@ -313,14 +310,15 @@ If the advertising started successfully, an ``ESP_GAP_BLE_ADV_START_COMPLETE_EVT
|
||||
|
||||
When an Application Profile is registered, an ``ESP_GATTS_REG_EVT`` event is triggered. The parameters of the ``ESP_GATTS_REG_EVT`` are:
|
||||
|
||||
* esp_gatt_status_t status; /*!< Operation status */
|
||||
* uint16_t app_id; /*!< Application id which input in register API */
|
||||
```c
|
||||
esp_gatt_status_t status; /*!< Operation status */
|
||||
uint16_t app_id; /*!< Application id which input in register API */
|
||||
```
|
||||
|
||||
In addition to the previous parameters, the event also contains the GATT interface assigned by the BLE stack. The event is captured by the ``gatts_event_handler()`` which stores the generated interface in the profile table and then forwards it to the corresponding profile event handler.
|
||||
|
||||
```
|
||||
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
|
||||
esp_ble_gatts_cb_param_t *param)
|
||||
```c
|
||||
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
|
||||
{
|
||||
ESP_LOGI(GATTS_TABLE_TAG, "EVT %d, gatts if %d\n", event, gatts_if);
|
||||
|
||||
@@ -340,7 +338,7 @@ static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_
|
||||
int idx;
|
||||
for (idx = 0; idx < HEART_PROFILE_NUM; idx++) {
|
||||
if (gatts_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
|
||||
gatts_if == heart_rate_profile_tab[idx].gatts_if) {
|
||||
gatts_if == heart_rate_profile_tab[idx].gatts_if) {
|
||||
if (heart_rate_profile_tab[idx].gatts_cb) {
|
||||
heart_rate_profile_tab[idx].gatts_cb(event, gatts_if, param);
|
||||
}
|
||||
@@ -356,23 +354,27 @@ The register event is used to create a table of profile attributes by employing
|
||||
|
||||
The ``esp_gatts_attr_db_t`` structure has two members:
|
||||
|
||||
* esp_attr_control_t attr_control; /*!< The attribute control type*/
|
||||
* esp_attr_desc_t att_desc; /*!< The attribute type*/
|
||||
```c
|
||||
esp_attr_control_t attr_control; /*!< The attribute control type*/
|
||||
esp_attr_desc_t att_desc; /*!< The attribute type*/
|
||||
```
|
||||
|
||||
The attr_control is the auto-respond parameter which can be set as ``ESP_GATT_AUTO_RSP`` to allow the BLE stack to take care of responding messages when read or write events arrive. The other option is ``ESP_GATT_RSP_BY_APP`` which allows to manually respond to messages using the ``esp_ble_gatts_send_response()`` function.
|
||||
|
||||
The ``att_desc`` is the attribute description which is made of:
|
||||
|
||||
* ``uint16_t uuid_length``; /*!< UUID length */
|
||||
* ``uint8_t *uuid_p``; /*!< UUID value */
|
||||
* ``uint16_t perm``; /*!< Attribute permission */
|
||||
* ``uint16_t max_length``; /*!< Maximum length of the element*/
|
||||
* ``uint16_t length``; /*!< Current length of the element*/
|
||||
* ``uint8_t *value``; /*!< Element value array*/
|
||||
```c
|
||||
uint16_t uuid_length; /*!< UUID length */
|
||||
uint8_t *uuid_p; /*!< UUID value */
|
||||
uint16_t perm; /*!< Attribute permission */
|
||||
uint16_t max_length; /*!< Maximum length of the element*/
|
||||
uint16_t length; /*!< Current length of the element*/
|
||||
uint8_t *value; /*!< Element value array*/
|
||||
```
|
||||
|
||||
For example, the first element of the table in this example is the service attribute:
|
||||
|
||||
```
|
||||
```c
|
||||
[HRS_IDX_SVC] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ,
|
||||
sizeof(uint16_t), sizeof(heart_rate_svc), (uint8_t *)&heart_rate_svc}},
|
||||
@@ -391,7 +393,7 @@ The initialization values are:
|
||||
|
||||
The rest of the attributes is initialized in the same way. Some attributes also have the *NOTIFY* property which is set by ``&char_prop_notify``. The complete table structure is initialized as follows:
|
||||
|
||||
```
|
||||
```c
|
||||
/// Full HRS Database Description - Used to add attributes into the database
|
||||
static const esp_gatts_attr_db_t heart_rate_gatt_db[HRS_IDX_NB] =
|
||||
{
|
||||
@@ -440,14 +442,16 @@ static const esp_gatts_attr_db_t heart_rate_gatt_db[HRS_IDX_NB] =
|
||||
## Starting the Service
|
||||
When the attribute table is created, an ``ESP_GATTS_CREAT_ATTR_TAB_EVT`` event is triggered. This event has the following parameters:
|
||||
|
||||
* ``esp_gatt_status_t status``; /*!< Operation status */
|
||||
* ``esp_bt_uuid_t svc_uuid``; /*!< Service uuid type */
|
||||
* ``uint16_t num_handle``; /*!< The number of the attribute handle to be added to the gatts database */
|
||||
* ``uint16_t *handles``; /*!< The number to the handles */
|
||||
```c
|
||||
esp_gatt_status_t status; /*!< Operation status */
|
||||
esp_bt_uuid_t svc_uuid; /*!< Service uuid type */
|
||||
uint16_t num_handle; /*!< The number of the attribute handle to be added to the gatts database */
|
||||
uint16_t *handles; /*!< The number to the handles */
|
||||
```
|
||||
|
||||
This example uses this event to print information and to check that the size of the created table equals the number of elements in the enumeration HRS_IDX_NB. If the table is correctly created, the attribute handles are copied into the handle table heart_rate_handle_table and the service is started using the ``esp_ble_gatts_start_service()`` function:
|
||||
|
||||
```
|
||||
```c
|
||||
case ESP_GATTS_CREAT_ATTR_TAB_EVT:{
|
||||
ESP_LOGI(GATTS_TABLE_TAG, "The number handle =%x\n",param->add_attr_tab.num_handle);
|
||||
if (param->add_attr_tab.status != ESP_GATT_OK){
|
||||
@@ -468,7 +472,7 @@ The handles stored in the handles pointer of the event parameters are numbers th
|
||||
|
||||
Finally, the heart_rate_handle_table contains the Application Profile in the form of a structure with information about the attribute parameters as well as GATT interface, connection ID, permissions and application ID. The profile structure is shown as follows, note that not all members are used in this example:
|
||||
|
||||
```
|
||||
```c
|
||||
struct gatts_profile_inst {
|
||||
esp_gatts_cb_t gatts_cb;
|
||||
uint16_t gatts_if;
|
||||
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
Reference in New Issue
Block a user