Merge branch 'feature/dynamic_services' into 'master'

Nimble: Add support for dynamic service addition / deletion

Closes BT-3182

See merge request espressif/esp-idf!22777
This commit is contained in:
Rahul Tank 2023-09-03 00:47:15 +08:00
commit 830acd0e6e
12 changed files with 1009 additions and 1 deletions

View File

@ -208,6 +208,13 @@ config BT_NIMBLE_DEBUG
help
This enables extra runtime asserts and host debugging
config BT_NIMBLE_DYNAMIC_SERVICE
bool "Enable dynamic services"
depends on BT_NIMBLE_ENABLED
help
This enables user to add/remove Gatt services at runtime
config BT_NIMBLE_SVC_GAP_DEVICE_NAME
string "BLE GAP default device name"
depends on BT_NIMBLE_ENABLED

@ -1 +1 @@
Subproject commit d826fefc3ad172beb43e4e986575eb132c2c3936
Subproject commit 63cfb08751664350cb107711809ca24e66b1b7af

View File

@ -475,6 +475,11 @@
#endif
/*** @apache-mynewt-nimble/nimble/host */
#ifndef MYNEWT_VAL_BLE_DYNAMIC_SERVICE
#define MYNEWT_VAL_BLE_DYNAMIC_SERVICE CONFIG_BT_NIMBLE_DYNAMIC_SERVICE
#endif
#ifndef MYNEWT_VAL_BLE_ATT_PREFERRED_MTU
#define MYNEWT_VAL_BLE_ATT_PREFERRED_MTU CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU
#endif

View File

@ -142,6 +142,12 @@ examples/bluetooth/nimble:
temporary: true
reason: The runner doesn't support yet
examples/bluetooth/nimble/ble_dynamic_service:
enable:
- if: IDF_TARGET in ["esp32", "esp32c2", "esp32c3", "esp32c6", "esp32h2", "esp32h4", "esp32s3"]
temporary: true
reason: the other targets are not tested yet
examples/bluetooth/nimble/ble_enc_adv_data:
enable:
- if: IDF_TARGET in ["esp32c2", "esp32c6", "esp32h2"]

View File

@ -0,0 +1,8 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/bluetooth/nimble/common/nimble_peripheral_utils)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(ble_dynamic_service)

View File

@ -0,0 +1,167 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- |
# BLE Dyanamic Service Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example creates GATT server and then starts advertising, waiting to be connected to a GATT client.
In the main thread it keeps on adding and deleting one custom service in the gatt server.
It uses ESP32's Bluetooth controller and NimBLE stack based BLE host.
This example aims at understanding addition and deletion of services at runtime.
Note : Services can be added at the time of init. This example focuses on adding services after stack init. There may be active connections at the time the new service is added/deleted.
To test this demo, any BLE scanner app can be used.
## How to Use Example
Before project configuration and build, be sure to set the correct chip target using:
```bash
idf.py set-target <chip_name>
```
### Configure the project
Open the project configuration menu:
```bash
idf.py menuconfig
```
### Build and Flash
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://idf.espressif.com/) for full steps to configure and use ESP-IDF to build projects.
## Example Output
There is this console output when ble_dynamic_service is connected to ble scanner:
```
I (354) BLE_INIT: BT controller compile version [80abacd]
I (354) phy_init: phy_version 950,f732b06,Feb 15 2023,18:57:12
I (404) BLE_INIT: Bluetooth MAC: 58:cf:79:e9:c1:9e
I (404) NimBLE_BLE_DYNAMIC_SERVER: BLE Host Task Started
I (404) NimBLE: GAP procedure initiated: stop advertising.
I (414) NimBLE: Failed to restore IRKs from store; status=8
I (414) NimBLE: Device Address:
I (424) NimBLE: 58:cf:79:e9:c1:9e
I (424) NimBLE:
I (424) NimBLE: GAP procedure initiated: advertise;
I (434) NimBLE: disc_mode=2
I (434) NimBLE: adv_channel_map=0 own_addr_type=0 adv_filter_policy=0 adv_itvl_min=0 adv_itvl_max=0
I (444) NimBLE:
I (15444) NimBLE: Adding Dynamic service
I (30444) NimBLE: Deleting service
I (45444) NimBLE: Adding Dynamic service
I (60444) NimBLE: Deleting service
I (75444) NimBLE: Adding Dynamic service
I (90444) NimBLE: Deleting service
I (101654) NimBLE: connection established; status=0
I (101654) NimBLE: handle=1 our_ota_addr_type=0 our_ota_addr=
I (101654) NimBLE: 58:cf:79:e9:c1:9e
I (101664) NimBLE: our_id_addr_type=0 our_id_addr=
I (101664) NimBLE: 58:cf:79:e9:c1:9e
I (101674) NimBLE: peer_ota_addr_type=1 peer_ota_addr=
I (101674) NimBLE: 41:c7:05:e9:d9:ce
I (101684) NimBLE: peer_id_addr_type=1 peer_id_addr=
I (101684) NimBLE: 41:c7:05:e9:d9:ce
I (101694) NimBLE: conn_itvl=39 conn_latency=0 supervision_timeout=500 encrypted=0 authenticated=0 bonded=0
I (101704) NimBLE:
I (102284) NimBLE: connection updated; status=0
I (102284) NimBLE: handle=1 our_ota_addr_type=0 our_ota_addr=
I (102284) NimBLE: 58:cf:79:e9:c1:9e
I (102294) NimBLE: our_id_addr_type=0 our_id_addr=
I (102294) NimBLE: 58:cf:79:e9:c1:9e
I (102304) NimBLE: peer_ota_addr_type=1 peer_ota_addr=
I (102304) NimBLE: 41:c7:05:e9:d9:ce
I (102314) NimBLE: peer_id_addr_type=1 peer_id_addr=
I (102314) NimBLE: 41:c7:05:e9:d9:ce
I (102324) NimBLE: conn_itvl=6 conn_latency=0 supervision_timeout=500 encrypted=0 authenticated=0 bonded=0
I (102334) NimBLE:
I (102584) NimBLE: connection updated; status=0
I (102584) NimBLE: handle=1 our_ota_addr_type=0 our_ota_addr=
I (102584) NimBLE: 58:cf:79:e9:c1:9e
I (102584) NimBLE: our_id_addr_type=0 our_id_addr=
I (102594) NimBLE: 58:cf:79:e9:c1:9e
I (102594) NimBLE: peer_ota_addr_type=1 peer_ota_addr=
I (102604) NimBLE: 41:c7:05:e9:d9:ce
I (102604) NimBLE: peer_id_addr_type=1 peer_id_addr=
I (102614) NimBLE: 41:c7:05:e9:d9:ce
I (102614) NimBLE: conn_itvl=39 conn_latency=0 supervision_timeout=500 encrypted=0 authenticated=0 bonded=0
I (102624) NimBLE:
I (104974) NimBLE: subscribe event; conn_handle=1 attr_handle=8 reason=1 prevn=0 curn=0 previ=0 curi=1
I (105444) NimBLE: Adding Dynamic service
I (105444) NimBLE: GATT procedure initiated: indicate;
I (105444) NimBLE: att_handle=8
I (105444) NimBLE: notify_tx event; conn_handle=1 attr_handle=8 status=0 is_indication=1
I (105554) NimBLE: notify_tx event; conn_handle=1 attr_handle=8 status=14 is_indication=1
I (105554) NimBLE: GATT procedure initiated: indicate;
I (105564) NimBLE: att_handle=8
I (105564) NimBLE: notify_tx event; conn_handle=1 attr_handle=8 status=0 is_indication=1
I (105654) NimBLE: notify_tx event; conn_handle=1 attr_handle=8 status=14 is_indication=1
I (105904) NimBLE: connection updated; status=0
I (105904) NimBLE: handle=1 our_ota_addr_type=0 our_ota_addr=
I (105904) NimBLE: 58:cf:79:e9:c1:9e
I (105904) NimBLE: our_id_addr_type=0 our_id_addr=
I (105914) NimBLE: 58:cf:79:e9:c1:9e
I (105914) NimBLE: peer_ota_addr_type=1 peer_ota_addr=
I (105924) NimBLE: 41:c7:05:e9:d9:ce
I (105924) NimBLE: peer_id_addr_type=1 peer_id_addr=
I (105934) NimBLE: 41:c7:05:e9:d9:ce
I (105934) NimBLE: conn_itvl=6 conn_latency=0 supervision_timeout=500 encrypted=0 authenticated=0 bonded=0
I (105944) NimBLE:
I (106334) NimBLE: connection updated; status=0
I (106334) NimBLE: handle=1 our_ota_addr_type=0 our_ota_addr=
I (106334) NimBLE: 58:cf:79:e9:c1:9e
I (106344) NimBLE: our_id_addr_type=0 our_id_addr=
I (106344) NimBLE: 58:cf:79:e9:c1:9e
I (106354) NimBLE: peer_ota_addr_type=1 peer_ota_addr=
I (106354) NimBLE: 41:c7:05:e9:d9:ce
I (106364) NimBLE: peer_id_addr_type=1 peer_id_addr=
I (106364) NimBLE: 41:c7:05:e9:d9:ce
I (106374) NimBLE: conn_itvl=39 conn_latency=0 supervision_timeout=500 encrypted=0 authenticated=0 bonded=0
I (106384) NimBLE:
I (109604) NimBLE: subscribe event; conn_handle=1 attr_handle=40 reason=1 prevn=0 curn=1 previ=0 curi=0
I (110484) NimBLE: subscribe event; conn_handle=1 attr_handle=40 reason=1 prevn=1 curn=0 previ=0 curi=0
I (112484) NimBLE: Characteristic read; conn_handle=1 attr_handle=40
I (120454) NimBLE: Deleting service
I (120454) NimBLE: GATT procedure initiated: indicate;
I (120454) NimBLE: att_handle=8
I (120454) NimBLE: notify_tx event; conn_handle=1 attr_handle=8 status=0 is_indication=1
I (120574) NimBLE: notify_tx event; conn_handle=1 attr_handle=8 status=14 is_indication=1
I (120574) NimBLE: GATT procedure initiated: indicate;
I (120574) NimBLE: att_handle=8
```
## Troubleshooting
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

View File

@ -0,0 +1,5 @@
set(srcs "main.c"
"gatt_svr.c")
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,36 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#ifndef H_BLE_DYNAMIC_SERVICE_
#define H_BLE_DYNAMIC_SERVICE_
#include <stdbool.h>
#include "nimble/ble.h"
#include "modlog/modlog.h"
#include "esp_peripheral.h"
#ifdef __cplusplus
extern "C" {
#endif
struct ble_hs_cfg;
struct ble_gatt_register_ctxt;
/** GATT server. */
#define GATT_SVR_SVC_ALERT_UUID 0x1811
#define GATT_SVR_CHR_SUP_NEW_ALERT_CAT_UUID 0x2A47
#define GATT_SVR_CHR_NEW_ALERT 0x2A46
#define GATT_SVR_CHR_SUP_UNR_ALERT_CAT_UUID 0x2A48
#define GATT_SVR_CHR_UNR_ALERT_STAT_UUID 0x2A45
#define GATT_SVR_CHR_ALERT_NOT_CTRL_PT 0x2A44
void gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg);
int gatt_svr_init(void);
int dynamic_service(const uint8_t operation, const struct ble_gatt_svc_def *svcs, const ble_uuid_t *uuid);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,264 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "host/ble_hs.h"
#include "host/ble_uuid.h"
#include "services/gap/ble_svc_gap.h"
#include "services/gatt/ble_svc_gatt.h"
#include "ble_dynamic_service.h"
#include "services/ans/ble_svc_ans.h"
/*** Maximum number of characteristics with the notify flag ***/
#define MAX_NOTIFY 5
static const ble_uuid128_t gatt_svr_svc_uuid =
BLE_UUID128_INIT(0x2d, 0x71, 0xa2, 0x59, 0xb4, 0x58, 0xc8, 0x12,
0x99, 0x99, 0x43, 0x95, 0x12, 0x2f, 0x46, 0x59);
/* A characteristic that can be subscribed to */
static uint8_t gatt_svr_chr_val;
static uint16_t gatt_svr_chr_val_handle;
static const ble_uuid128_t gatt_svr_chr_uuid =
BLE_UUID128_INIT(0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11,
0x22, 0x22, 0x22, 0x22, 0x33, 0x33, 0x33, 0x33);
/* A custom descriptor */
static uint8_t gatt_svr_dsc_val;
static const ble_uuid128_t gatt_svr_dsc_uuid =
BLE_UUID128_INIT(0x01, 0x01, 0x01, 0x01, 0x12, 0x12, 0x12, 0x12,
0x23, 0x23, 0x23, 0x23, 0x34, 0x34, 0x34, 0x34);
static int
gatt_svc_access(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt,
void *arg);
const struct ble_gatt_svc_def gatt_svr_svcs[] = {
{
/*** Service ***/
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = &gatt_svr_svc_uuid.u,
.characteristics = (struct ble_gatt_chr_def[])
{ {
/*** This characteristic can be subscribed to by writing 0x00 and 0x01 to the CCCD ***/
.uuid = &gatt_svr_chr_uuid.u,
.access_cb = gatt_svc_access,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_INDICATE,
.val_handle = &gatt_svr_chr_val_handle,
.descriptors = (struct ble_gatt_dsc_def[])
{ {
.uuid = &gatt_svr_dsc_uuid.u,
.att_flags = BLE_ATT_F_READ,
.access_cb = gatt_svc_access,
}, {
0, /* No more descriptors in this characteristic */
}
},
}, {
0, /* No more characteristics in this service. */
}
},
},
{
0, /* No more services. */
},
};
static int
gatt_svr_write(struct os_mbuf *om, uint16_t min_len, uint16_t max_len,
void *dst, uint16_t *len)
{
uint16_t om_len;
int rc;
om_len = OS_MBUF_PKTLEN(om);
if (om_len < min_len || om_len > max_len) {
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
rc = ble_hs_mbuf_to_flat(om, dst, max_len, len);
if (rc != 0) {
return BLE_ATT_ERR_UNLIKELY;
}
return 0;
}
/**
* Access callback whenever a characteristic/descriptor is read or written to.
* Here reads and writes need to be handled.
* ctxt->op tells weather the operation is read or write and
* weather it is on a characteristic or descriptor,
* ctxt->dsc->uuid tells which characteristic/descriptor is accessed.
* attr_handle give the value handle of the attribute being accessed.
* Accordingly do:
* Append the value to ctxt->om if the operation is READ
* Write ctxt->om to the value if the operation is WRITE
**/
static int
gatt_svc_access(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
const ble_uuid_t *uuid;
int rc;
switch (ctxt->op) {
case BLE_GATT_ACCESS_OP_READ_CHR:
if (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
MODLOG_DFLT(INFO, "Characteristic read; conn_handle=%d attr_handle=%d\n",
conn_handle, attr_handle);
} else {
MODLOG_DFLT(INFO, "Characteristic read by NimBLE stack; attr_handle=%d\n",
attr_handle);
}
uuid = ctxt->chr->uuid;
if (attr_handle == gatt_svr_chr_val_handle) {
rc = os_mbuf_append(ctxt->om,
&gatt_svr_chr_val,
sizeof(gatt_svr_chr_val));
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
goto unknown;
case BLE_GATT_ACCESS_OP_WRITE_CHR:
if (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
MODLOG_DFLT(INFO, "Characteristic write; conn_handle=%d attr_handle=%d",
conn_handle, attr_handle);
} else {
MODLOG_DFLT(INFO, "Characteristic write by NimBLE stack; attr_handle=%d",
attr_handle);
}
uuid = ctxt->chr->uuid;
if (attr_handle == gatt_svr_chr_val_handle) {
rc = gatt_svr_write(ctxt->om,
sizeof(gatt_svr_chr_val),
sizeof(gatt_svr_chr_val),
&gatt_svr_chr_val, NULL);
ble_gatts_chr_updated(attr_handle);
MODLOG_DFLT(INFO, "Notification/Indication scheduled for "
"all subscribed peers.\n");
return rc;
}
goto unknown;
case BLE_GATT_ACCESS_OP_READ_DSC:
if (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
MODLOG_DFLT(INFO, "Descriptor read; conn_handle=%d attr_handle=%d\n",
conn_handle, attr_handle);
} else {
MODLOG_DFLT(INFO, "Descriptor read by NimBLE stack; attr_handle=%d\n",
attr_handle);
}
uuid = ctxt->dsc->uuid;
if (ble_uuid_cmp(uuid, &gatt_svr_dsc_uuid.u) == 0) {
rc = os_mbuf_append(ctxt->om,
&gatt_svr_dsc_val,
sizeof(gatt_svr_chr_val));
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
goto unknown;
case BLE_GATT_ACCESS_OP_WRITE_DSC:
goto unknown;
default:
goto unknown;
}
unknown:
/* Unknown characteristic/descriptor;
* The NimBLE host should not have called this function;
*/
assert(0);
return BLE_ATT_ERR_UNLIKELY;
}
void
gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg)
{
char buf[BLE_UUID_STR_LEN];
switch (ctxt->op) {
case BLE_GATT_REGISTER_OP_SVC:
MODLOG_DFLT(DEBUG, "registered service %s with handle=%d\n",
ble_uuid_to_str(ctxt->svc.svc_def->uuid, buf),
ctxt->svc.handle);
break;
case BLE_GATT_REGISTER_OP_CHR:
MODLOG_DFLT(DEBUG, "registering characteristic %s with "
"def_handle=%d val_handle=%d\n",
ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf),
ctxt->chr.def_handle,
ctxt->chr.val_handle);
break;
case BLE_GATT_REGISTER_OP_DSC:
MODLOG_DFLT(DEBUG, "registering descriptor %s with handle=%d\n",
ble_uuid_to_str(ctxt->dsc.dsc_def->uuid, buf),
ctxt->dsc.handle);
break;
default:
assert(0);
break;
}
}
/* Adds custom service
*@params
*operation : uint8_t (1 for add and 0 for delete)
*gatt_svr_svcs : struct ble_gatt_svc_def (NULL if operation is delete, else pass the services)
*uuid : const ble_uuid_t * (NULL if operation is add, else pass the uuid of the service to be deleted)
*/
int dynamic_service(const uint8_t operation, const struct ble_gatt_svc_def *svcs, const ble_uuid_t *uuid) {
int rc = 0;
int i = 0;
switch(operation) {
case 1:
/* add services in gatt_svr_svcs */
if(svcs == NULL) {
/* don't add anything gatt_svr_svcs is NULL */
MODLOG_DFLT(ERROR, "No services to add\n");
return ESP_FAIL;
}
rc = ble_gatts_add_dynamic_svcs(svcs);
return rc;
break;
case 0:
/* delete service by uuid*/
if(uuid == NULL) {
MODLOG_DFLT(ERROR, "No service to delete\n");
return ESP_FAIL;
}
rc = ble_gatts_delete_svc(uuid);
if(rc != 0) {
/* not able to delete service return immidietely */
return rc;
}
i++;
return rc;
break;
}
return rc;
}
int
gatt_svr_init(void)
{
ble_svc_gap_init();
ble_svc_gatt_init();
ble_svc_ans_init();
/* Setting a value for the read-only descriptor */
gatt_svr_dsc_val = 0x99;
return 0;
}

View File

@ -0,0 +1,304 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#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_dynamic_service.h"
#define BLE_DYNAMIC_SERVICE_ADD 1
#define BLE_DYNAMIC_SERVICE_DELETE 0
extern const struct ble_gatt_svc_def gatt_svr_svcs[];
static const char *tag = "NimBLE_DYNAMIC_SERVICE";
static int dynamic_service_gap_event(struct ble_gap_event *event, void *arg);
static uint8_t own_addr_type;
/**
* Logs information about a connection to the console.
*/
static void
dynamic_service_print_conn_desc(struct ble_gap_conn_desc *desc)
{
MODLOG_DFLT(INFO, "handle=%d our_ota_addr_type=%d our_ota_addr=",
desc->conn_handle, desc->our_ota_addr.type);
print_addr(desc->our_ota_addr.val);
MODLOG_DFLT(INFO, " our_id_addr_type=%d our_id_addr=",
desc->our_id_addr.type);
print_addr(desc->our_id_addr.val);
MODLOG_DFLT(INFO, " peer_ota_addr_type=%d peer_ota_addr=",
desc->peer_ota_addr.type);
print_addr(desc->peer_ota_addr.val);
MODLOG_DFLT(INFO, " peer_id_addr_type=%d peer_id_addr=",
desc->peer_id_addr.type);
print_addr(desc->peer_id_addr.val);
MODLOG_DFLT(INFO, " conn_itvl=%d conn_latency=%d supervision_timeout=%d "
"encrypted=%d authenticated=%d bonded=%d\n",
desc->conn_itvl, desc->conn_latency,
desc->supervision_timeout,
desc->sec_state.encrypted,
desc->sec_state.authenticated,
desc->sec_state.bonded);
}
/**
* Enables advertising with the following parameters:
* o General discoverable mode.
* o Undirected connectable mode.
*/
static void
dynamic_service_advertise(void)
{
struct ble_gap_adv_params adv_params;
struct ble_hs_adv_fields fields;
const char *name;
int rc;
/**
* Set the advertisement data included in our advertisements:
* o Flags (indicates advertisement type and other general info).
* o Advertising tx power.
* o Device name.
* o 16-bit service UUIDs (alert notifications).
*/
memset(&fields, 0, sizeof fields);
/* Advertise two flags:
* o Discoverability in forthcoming advertisement (general)
* o BLE-only (BR/EDR unsupported).
*/
fields.flags = BLE_HS_ADV_F_DISC_GEN |
BLE_HS_ADV_F_BREDR_UNSUP;
/* Indicate that the TX power level field should be included; have the
* stack fill this value automatically. This is done by assigning the
* special value BLE_HS_ADV_TX_PWR_LVL_AUTO.
*/
fields.tx_pwr_lvl_is_present = 1;
fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
name = ble_svc_gap_device_name();
fields.name = (uint8_t *)name;
fields.name_len = strlen(name);
fields.name_is_complete = 1;
fields.uuids16 = (ble_uuid16_t[]) {
BLE_UUID16_INIT(GATT_SVR_SVC_ALERT_UUID)
};
fields.num_uuids16 = 1;
fields.uuids16_is_complete = 1;
rc = ble_gap_adv_set_fields(&fields);
if (rc != 0) {
MODLOG_DFLT(ERROR, "error setting advertisement data; rc=%d\n", rc);
return;
}
/* Begin advertising. */
memset(&adv_params, 0, sizeof adv_params);
adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;
adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;
rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER,
&adv_params, dynamic_service_gap_event, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "error enabling advertisement; rc=%d\n", rc);
return;
}
}
/**
* The nimble host executes this callback when a GAP event occurs. The
* application associates a GAP event callback with each connection that forms.
* dynamic_service uses the same callback for all connections.
*
* @param event The type of event being signalled.
* @param ctxt Various information pertaining to the event.
* @param arg Application-specified argument; unused by
* dynamic_service.
*
* @return 0 if the application successfully handled the
* event; nonzero on failure. The semantics
* of the return code is specific to the
* particular GAP event being signalled.
*/
static int
dynamic_service_gap_event(struct ble_gap_event *event, void *arg)
{
struct ble_gap_conn_desc desc;
int rc;
switch (event->type) {
case BLE_GAP_EVENT_CONNECT:
/* A new connection was established or a connection attempt failed. */
MODLOG_DFLT(INFO, "connection %s; status=%d ",
event->connect.status == 0 ? "established" : "failed",
event->connect.status);
if (event->connect.status == 0) {
rc = ble_gap_conn_find(event->connect.conn_handle, &desc);
assert(rc == 0);
dynamic_service_print_conn_desc(&desc);
}
MODLOG_DFLT(INFO, "\n");
if (event->connect.status != 0) {
/* Connection failed; resume advertising. */
dynamic_service_advertise();
}
return 0;
case BLE_GAP_EVENT_DISCONNECT:
MODLOG_DFLT(INFO, "disconnect; reason=%d ", event->disconnect.reason);
dynamic_service_print_conn_desc(&event->disconnect.conn);
MODLOG_DFLT(INFO, "\n");
/* Connection terminated; resume advertising. */
dynamic_service_advertise();
return 0;
case BLE_GAP_EVENT_CONN_UPDATE:
/* The central has updated the connection parameters. */
MODLOG_DFLT(INFO, "connection updated; status=%d ",
event->conn_update.status);
rc = ble_gap_conn_find(event->conn_update.conn_handle, &desc);
assert(rc == 0);
dynamic_service_print_conn_desc(&desc);
MODLOG_DFLT(INFO, "\n");
return 0;
case BLE_GAP_EVENT_ADV_COMPLETE:
MODLOG_DFLT(INFO, "advertise complete; reason=%d",
event->adv_complete.reason);
dynamic_service_advertise();
return 0;
case BLE_GAP_EVENT_NOTIFY_TX:
MODLOG_DFLT(INFO, "notify_tx event; conn_handle=%d attr_handle=%d "
"status=%d is_indication=%d",
event->notify_tx.conn_handle,
event->notify_tx.attr_handle,
event->notify_tx.status,
event->notify_tx.indication);
return 0;
case BLE_GAP_EVENT_SUBSCRIBE:
MODLOG_DFLT(INFO, "subscribe event; conn_handle=%d attr_handle=%d "
"reason=%d prevn=%d curn=%d previ=%d curi=%d\n",
event->subscribe.conn_handle,
event->subscribe.attr_handle,
event->subscribe.reason,
event->subscribe.prev_notify,
event->subscribe.cur_notify,
event->subscribe.prev_indicate,
event->subscribe.cur_indicate);
return 0;
case BLE_GAP_EVENT_MTU:
MODLOG_DFLT(INFO, "mtu update event; conn_handle=%d cid=%d mtu=%d\n",
event->mtu.conn_handle,
event->mtu.channel_id,
event->mtu.value);
return 0;
}
return 0;
}
static void
dynamic_service_on_reset(int reason)
{
MODLOG_DFLT(ERROR, "Resetting state; reason=%d\n", reason);
}
static void
dynamic_service_on_sync(void)
{
int rc;
/* Make sure we have proper identity address set (public preferred) */
rc = ble_hs_util_ensure_addr(0);
assert(rc == 0);
/* Figure out address to use while advertising (no privacy for now) */
rc = ble_hs_id_infer_auto(0, &own_addr_type);
if (rc != 0) {
MODLOG_DFLT(ERROR, "error determining address type; rc=%d\n", rc);
return;
}
/* Printing ADDR */
uint8_t addr_val[6] = {0};
rc = ble_hs_id_copy_addr(own_addr_type, addr_val, NULL);
MODLOG_DFLT(INFO, "Device Address: ");
print_addr(addr_val);
MODLOG_DFLT(INFO, "\n");
/* Begin advertising. */
dynamic_service_advertise();
}
void dynamic_service_host_task(void *param)
{
ESP_LOGI(tag, "BLE Host Task Started");
/* This function will return only when nimble_port_stop() is executed */
nimble_port_run();
nimble_port_freertos_deinit();
}
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 = dynamic_service_on_reset;
ble_hs_cfg.sync_cb = dynamic_service_on_sync;
ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
rc = gatt_svr_init();
assert(rc == 0);
/* Set the default device name. */
rc = ble_svc_gap_device_name_set("ble-dynamic-service");
assert(rc == 0);
nimble_port_freertos_init(dynamic_service_host_task);
while(1) {
vTaskDelay(15000 / portTICK_PERIOD_MS);
MODLOG_DFLT(INFO, "Adding Dynamic service");
/* add services defined in gatt_svr_svcs */
dynamic_service(BLE_DYNAMIC_SERVICE_ADD, gatt_svr_svcs, NULL);
/* 15 seconds delay before deleting the service */
vTaskDelay(15000 / portTICK_PERIOD_MS);
MODLOG_DFLT(INFO, "Deleting service");
/* Delete the first service in the list */
dynamic_service(BLE_DYNAMIC_SERVICE_DELETE, NULL, gatt_svr_svcs[0].uuid);
}
}

View File

@ -0,0 +1,13 @@
# Override some defaults so BT stack is enabled
# in this example
#
# BT config
#
CONFIG_BT_ENABLED=y
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
CONFIG_BTDM_CTRL_MODE_BTDM=n
CONFIG_BT_BLUEDROID_ENABLED=n
CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_BT_NIMBLE_DYNAMIC_SERVICE=y

View File

@ -0,0 +1,193 @@
# BLE Dynamic Service Example Walkthrough
## Introduction
In this tutorial, the ble_dynamic_service example code for the espressif chipsets is reviewed. This example creates GATT server and then starts advertising, waiting to be connected to a GATT client.
In the main thread it keeps on adding and deleting one custom service in the gatt server. When any of the connected clients subscribes for the Service Changed Characteristic(0x2a05) in gatt service, then whenever the gatt database is altered due to additition or deletion of the service the service changed indication is sent to the subscribed clients.
## Includes
This example is located in the examples folder of the ESP-IDF under the [ble_dynamic_service/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 "ble_dynamic_service.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_dynamic_service.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_dynamic_service.h`: Defines the example 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) {
ESP_LOGE(tag, "Failed to init nimble %d ", ret);
return;
}
/* Initialize the NimBLE host configuration. */
ble_hs_cfg.reset_cb = dynamic_service_on_reset;
ble_hs_cfg.sync_cb = dynamic_service_on_sync;
ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
rc = gatt_svr_init();
assert(rc == 0);
/* Set the default device name. */
rc = ble_svc_gap_device_name_set("ble-dynamic-service");
assert(rc == 0);
nimble_port_freertos_init(dynamic_service_host_task);
while(1) {
vTaskDelay(15000 / portTICK_PERIOD_MS);
MODLOG_DFLT(INFO, "Adding Dynamic service");
/* add services defined in gatt_svr_svcs */
dynamic_service(BLE_DYNAMIC_SERVICE_ADD, gatt_svr_svcs, NULL);
/* 15 seconds delay before deleting the service */
vTaskDelay(15000 / portTICK_PERIOD_MS);
MODLOG_DFLT(INFO, "Deleting service");
/* Delete the first service in the list */
dynamic_service(BLE_DYNAMIC_SERVICE_DELETE, NULL, gatt_svr_svcs[0].uuid);
}
}
```
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.reset_cb = dynamic_service_on_reset;
ble_hs_cfg.sync_cb = dynamic_service_on_sync;
ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
```
The main function then calls `ble_svc_gap_device_name_set()` to set the default device name. 'ble-dynamic-service' is passed as the default device name to this function.
```c
static const char *device_name = "ble-dynamic-service"
rc = ble_svc_gap_device_name_set(device_name);
```
The main function then 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(dynamic_service_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.
In the while loop, main thread keeps adding a custom service defined in struct `gatt_svr_svcs` and deleting it after 15 seconds.
```c
while(1) {
vTaskDelay(15000 / portTICK_PERIOD_MS);
MODLOG_DFLT(INFO, "Adding Dynamic service");
/* add services defined in gatt_svr_svcs */
dynamic_service(BLE_DYNAMIC_SERVICE_ADD, gatt_svr_svcs, NULL);
/* 15 seconds delay before deleting the service */
vTaskDelay(15000 / portTICK_PERIOD_MS);
MODLOG_DFLT(INFO, "Deleting service");
/* Delete the first service in the list */
dynamic_service(BLE_DYNAMIC_SERVICE_DELETE, NULL, gatt_svr_svcs[0].uuid);
}
```
>Service change indication will be sent to all the subscribed clients after addition or deletion of service as the gatt database is altered.
## Conclusion
This Walkthrough covers the code explanation of the BLE_DYNAMIC_SERVICE example. The following points are concluded through this walkthrough.
1. Gatt server keeps advertising and connections are allowed.
2. The addition and deletion of custom service happens asynchronously and the service change indication is sent to the subscribed clients.