fix(nimble): Improve safety, fix bugs, and update docs across NimBLE examples

- Remove unused headers from examples
- Improve periodic adv/sync example functionality and readability
- Use ble_hs_id_infer_auto() instead of hardcoded BLE_OWN_ADDR_PUBLIC/RANDOM
- Add ble_hs_util_ensure_addr() calls in on_sync for proper address setup
- Use correct ext adv instance (0 instead of 1) in phy_prph, l2cap_coc, multi_conn
- Fix struct name: ble_gap_periodic_adv_enable_params -> ble_gap_periodic_adv_start_params
- Add CONFIG_BT_NIMBLE_GAP_SERVICE guards around ble_svc_gap_device_name_set
- Fix unsafe AD data parsing with bounds checks in central examples
- Fix UUID matching bugs (off-by-one loop condition and byte order) in
  phy_cent, htp_cent, and proximity_sensor_cent
- Fix ble_multi_conn_cent address type to use dynamic inference
- Remove contradictory sm_sc=0 after sm_sc=1 in ble_multi_adv
- Add CONFIG_BT_NIMBLE_EXT_ADV=y to ble_multi_adv sdkconfig defaults
- Check return values for ble_gap_set_host_feat, nimble_port_init
- Update tutorials and READMEs to match code changes
This commit is contained in:
Rahul Tank
2026-02-20 12:47:40 +05:30
parent 273bc6a0db
commit 432cd1ebdd
69 changed files with 1026 additions and 2059 deletions
@@ -646,6 +646,8 @@ ble_ancs_gap_event(struct ble_gap_event *event, void *arg)
if (event->passkey.params.action == BLE_SM_IOACT_DISP) {
pkey.action = event->passkey.params.action;
/* WARNING: Hardcoded passkey for demonstration only.
* In production, generate a random passkey per pairing. */
pkey.passkey = 123456; // This is the passkey to be entered on peer
ESP_LOGI(tag, "Enter passkey %" PRIu32 "on the peer side", pkey.passkey);
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
@@ -683,6 +683,9 @@ blecent_on_sync(void)
/* Make sure we have set Host feature bit for Channel Sounding*/
int rc;
rc = ble_gap_set_host_feat(47,0x01);
if (rc != 0) {
MODLOG_DFLT(ERROR, "error setting host feature; rc=%d\n", rc);
}
/* Make sure we have proper identity address set (public preferred) */
rc = ble_hs_util_ensure_addr(0);
assert(rc == 0);
File diff suppressed because it is too large Load Diff
@@ -40,6 +40,7 @@ static uint8_t ext_adv_pattern_1[] = {
#endif
static const char *tag = "NimBLE_BLE_CHAN_REFLECTOR";
static int bleprph_gap_event(struct ble_gap_event *event, void *arg);
static uint8_t own_addr_type;
void ble_store_config_init(void);
struct ble_cs_event ranging_subevent;
@@ -199,8 +200,8 @@ ext_bleprph_advertise(void)
memset (&params, 0, sizeof(params));
/* enable connectable advertising */
params.connectable = 1;
/* advertise using random addr */
params.own_addr_type = BLE_OWN_ADDR_PUBLIC;
/* advertise using the inferred address type */
params.own_addr_type = own_addr_type;
params.primary_phy = BLE_HCI_LE_PHY_1M;
params.secondary_phy = BLE_HCI_LE_PHY_2M;
params.sid = 1;
@@ -259,10 +260,12 @@ bleprph_advertise(void)
*/
fields.tx_pwr_lvl_is_present = 1;
fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
#if CONFIG_BT_NIMBLE_GAP_SERVICE
name = ble_svc_gap_device_name();
fields.name = (uint8_t *)name;
fields.name_len = strlen(name);
fields.name_is_complete = 1;
#endif
fields.uuids16 = (ble_uuid16_t[]) {
BLE_UUID16_INIT(BLE_UUID_RANGING_SERVICE_VAL)
};
@@ -277,7 +280,7 @@ bleprph_advertise(void)
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(0, NULL, BLE_HS_FOREVER,
rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER,
&adv_params, bleprph_gap_event, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "error enabling advertisement; rc=%d\n", rc);
@@ -412,12 +415,13 @@ static void
bleprph_on_sync(void)
{
int rc;
uint8_t own_addr_type = 0;
/* Make sure we have set Host feature bit for Channel Sounding*/
rc = ble_gap_set_host_feat(47,0x01);
if (rc != 0) {
MODLOG_DFLT(ERROR, "error setting host feature; rc=%d\n", 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);
@@ -490,9 +494,11 @@ app_main(void)
#endif
rc = custom_gatt_svr_init();
assert(rc == 0);
#if CONFIG_BT_NIMBLE_GAP_SERVICE
/* Set the default device name. */
rc = ble_svc_gap_device_name_set("nimble-ble_chan_reflector");
assert(rc == 0);
#endif
/* XXX Need to have template for store */
ble_store_config_init();
nimble_port_freertos_init(bleprph_host_task);
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
@@ -10,9 +10,7 @@
#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 "periodic_adv.h"
#include "host/ble_gap.h"
#include "host/ble_hs_adv.h"
#include "cte_config.h"
@@ -21,25 +19,17 @@
static const char *TAG = "CTE_ADV_EXAMPLE";
static uint8_t s_periodic_adv_raw_data[] = {0x0D, BLE_HS_ADV_TYPE_COMP_NAME, 'C','T','E',' ','P','e','r','i','o','d','i','c'};
/* Configuration based on Kconfig settings */
#if CONFIG_EXAMPLE_RANDOM_ADDR
static uint8_t s_own_addr_type = BLE_OWN_ADDR_RANDOM;
#else
static uint8_t s_own_addr_type;
#endif
/**
* @brief Configure and start periodic advertising with CTE
*/
static void start_periodic_adv_cte(void)
static void start_periodic_adv_cte(uint8_t own_addr_type)
{
int rc;
uint8_t instance = 1;
uint8_t instance = 0;
ble_addr_t addr;
uint8_t addr_type = own_addr_type == BLE_OWN_ADDR_RANDOM ? BLE_ADDR_RANDOM : BLE_ADDR_PUBLIC;
/* Generate random address for instance */
rc = ble_hs_id_gen_rnd(1, &addr);
rc = ble_hs_id_copy_addr(addr_type, addr.val, NULL);
assert(rc == 0);
ESP_LOGI(TAG, "Device Address: ");
@@ -48,26 +38,23 @@ static void start_periodic_adv_cte(void)
/* Configure extended advertising parameters */
struct ble_gap_ext_adv_params ext_adv_params = {
.own_addr_type = BLE_OWN_ADDR_RANDOM,
.own_addr_type = own_addr_type,
.primary_phy = BLE_HCI_LE_PHY_1M,
.secondary_phy = BLE_HCI_LE_PHY_1M,
.sid = 2,
.tx_power = 0
.tx_power = 0x7f
};
rc = ble_gap_ext_adv_configure(instance, &ext_adv_params, NULL, NULL, NULL);
assert(rc == 0);
rc = ble_gap_ext_adv_set_addr(instance, &addr);
assert(rc == 0);
/* Configure advertising data */
struct ble_hs_adv_fields adv_fields = {
.name = (const uint8_t *)"CTE_Periodic_Adv",
.name_len = strlen((char *)adv_fields.name)
};
struct os_mbuf *data = os_msys_get_pkthdr(BLE_HCI_MAX_ADV_DATA_LEN, 0);
struct os_mbuf *data = os_msys_get_pkthdr(BLE_HS_ADV_MAX_FIELD_SZ, 0);
assert(data);
rc = ble_hs_adv_set_fields_mbuf(&adv_fields, data);
@@ -79,8 +66,8 @@ static void start_periodic_adv_cte(void)
/* Configure periodic advertising parameters */
struct ble_gap_periodic_adv_params pparams = {
.include_tx_power = 0,
.itvl_min = BLE_GAP_ADV_ITVL_MS(200),
.itvl_max = BLE_GAP_ADV_ITVL_MS(400)
.itvl_min = BLE_GAP_PERIODIC_ITVL_MS(200),
.itvl_max = BLE_GAP_PERIODIC_ITVL_MS(400)
};
rc = ble_gap_periodic_adv_configure(instance, &pparams);
@@ -98,11 +85,15 @@ static void start_periodic_adv_cte(void)
/* Configure CTE parameters */
#if defined(CONFIG_EXAMPLE_ADV_DIRECTION_FINDING_AOA)
/* Configure CTE parameters */
/* Configure CTE parameters for AoA (receiver does antenna switching).
* A minimal switching pattern is provided to satisfy the NimBLE host
* API; the controller ignores it for AoA transmissions. */
struct ble_gap_periodic_adv_cte_params cte_params = {
.cte_length = 0x14,
.cte_type = BLE_CTE_TYPE_AOA,
.cte_count = 1,
.switching_pattern_length = 2,
.antenna_ids = (uint8_t[]){0, 0},
};
#elif defined(CONFIG_EXAMPLE_ADV_DIRECTION_FINDING_AOD)
struct ble_gap_periodic_adv_cte_params cte_params = {
@@ -151,27 +142,19 @@ static void periodic_adv_on_reset(int reason)
static void periodic_sync_cb(void)
{
int rc;
uint8_t own_addr_type;
#if CONFIG_EXAMPLE_RANDOM_ADDR
ble_addr_t addr;
if (ble_hs_id_gen_rnd(0, &addr) == 0) {
ble_hs_id_set_rnd(addr.val);
}
/* Ensure proper identity address */
rc = ble_hs_util_ensure_addr(1);
#else
rc = ble_hs_util_ensure_addr(0);
#endif
assert(rc == 0);
/* Infer address type */
rc = ble_hs_id_infer_auto(0, &s_own_addr_type);
if (rc != 0) {
ESP_LOGE(TAG, "Failed to infer address type (rc=%d)", rc);
return;
}
rc = ble_hs_id_infer_auto(0, &own_addr_type);
assert(rc == 0);
/* Start advertising */
start_periodic_adv_cte();
start_periodic_adv_cte(own_addr_type);
}
/**
@@ -210,7 +193,7 @@ void app_main(void)
ESP_LOGI(TAG, "%s", direction_finding_logo);
#if defined(CONFIG_EXAMPLE_ADV_DIRECTION_FINDING_AOD)
ESP_LOGI(TAG, "DIRECTION_FINDING Example Periodic Adv AOD Mode");
ble_direction_finding_antenna_init(antenna_use_gpio,CONFIG_EXAMPLE_AOD_GPIO_BIT_COUNT);
ble_direction_finding_antenna_init(antenna_use_gpio,CONFIG_EXAMPLE_ANT_GPIO_BIT_COUNT);
#elif defined(CONFIG_EXAMPLE_ADV_DIRECTION_FINDING_AOA)
ESP_LOGI(TAG, "DIRECTION_FINDING Example Periodic Adv AOA Mode");
#endif
@@ -221,8 +204,10 @@ void app_main(void)
ble_hs_cfg.sync_cb = periodic_sync_cb;
/* Set device name */
#if CONFIG_BT_NIMBLE_GAP_SERVICE
rc = ble_svc_gap_device_name_set("Periodic ADV with CTE");
assert(rc == 0);
#endif
/* Start BLE host task */
nimble_port_freertos_init(periodic_adv_host_task);
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
@@ -13,9 +13,6 @@
#include "host/ble_hs.h"
#include "host/util/util.h"
#include "services/gap/ble_svc_gap.h"
#include "console/console.h"
#include "periodic_sync.h"
#include "cte_config.h"
static const char *TAG = "CTE_SYNC_EXAMPLE";
@@ -36,20 +33,17 @@ static struct ble_gap_cte_sampling_params sync_cte_sampling_params = {
static void periodic_sync_scan(void)
{
uint8_t own_addr_type;
struct ble_gap_disc_params disc_params = {0};
struct ble_gap_ext_disc_params disc_params = {0};
int rc = ble_hs_id_infer_auto(0, &own_addr_type);
if (rc != 0) {
ESP_LOGE(TAG, "Failed to determine address type; rc=%d", rc);
return;
}
/**
* Perform a passive scan. I.e., don't send follow-up scan requests to
* each advertiser.
*/
disc_params.passive = 1;
rc = ble_gap_disc(own_addr_type, BLE_HS_FOREVER, &disc_params,
periodic_sync_gap_event, NULL);
rc = ble_gap_ext_disc(own_addr_type, 0, 0,
0, 0, 0, &disc_params, &disc_params,
periodic_sync_gap_event, NULL);
if (rc != 0) {
ESP_LOGE(TAG, "GAP discovery failed; rc=%d", rc);
}
@@ -146,8 +140,7 @@ static int periodic_sync_gap_event(struct ble_gap_event *event, void *arg)
{
switch (event->type) {
case BLE_GAP_EVENT_EXT_DISC: {
const struct ble_gap_ext_disc_desc *disc = ((struct ble_gap_ext_disc_desc *)(&event->disc));
const struct ble_gap_ext_disc_desc *disc = &event->ext_disc;
if (is_synced) {
return 0;
}
@@ -216,6 +209,11 @@ static int periodic_sync_gap_event(struct ble_gap_event *event, void *arg)
}
case BLE_GAP_EVENT_PERIODIC_SYNC:
if (event->periodic_sync.status != 0) {
ESP_LOGE(TAG, "Periodic Sync Failed; status=%d", event->periodic_sync.status);
is_synced = 0;
return 0;
}
ESP_LOGI(TAG, "Periodic Sync Established");
print_periodic_sync_data(event);
ble_gap_disc_cancel();
@@ -260,7 +258,7 @@ static void periodic_sync_on_sync(void)
rc = ble_hs_util_ensure_addr(0);
assert(rc == 0);
/* Begin scanning for a peripheral to connect to. */
/* Begin scanning for a periodic advertiser to sync with. */
periodic_sync_scan();
}
@@ -296,8 +294,10 @@ void app_main(void)
ble_hs_cfg.sync_cb = periodic_sync_on_sync;
/* Set the default device name. */
#if CONFIG_BT_NIMBLE_GAP_SERVICE
rc = ble_svc_gap_device_name_set("periodic_sync_CTE");
assert(rc == 0);
#endif
#if MYNEWT_VAL(BLE_AOA_AOD)
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -11,6 +11,7 @@
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "host/ble_ead.h"
#include "host/util/util.h"
#include "console/console.h"
#include "services/gap/ble_svc_gap.h"
@@ -253,37 +254,50 @@ enc_adv_data_cent_decrypt(uint8_t length_data, const uint8_t *data, const uint8_
uint8_t op;
uint8_t len, offset = 0;
uint8_t *enc_data;
uint8_t enc_payload_len;
int rc;
uint8_t dec_data_len;
uint8_t temp[BLE_EAD_DECRYPTED_PAYLOAD_SIZE(UINT8_MAX)];
struct ble_store_key_ead key_ead = {0};
struct ble_store_value_ead value_ead = {0};
while (offset < length_data) {
len = data[offset];
/* Bounds check: ensure we can read the type byte and the full AD field */
if (offset + 1 >= length_data) {
break;
}
op = data[offset + 1];
uint8_t temp[len];
if (len == 0 || offset + 1 + len > length_data) {
break;
}
switch (op) {
case BLE_GAP_ENC_ADV_DATA:
enc_data = (uint8_t *) malloc (sizeof(uint8_t) * len);
/* Encrypted payload is AD value (len - 1 bytes, excluding the type byte) */
enc_payload_len = len - 1;
enc_data = (uint8_t *) malloc (sizeof(uint8_t) * enc_payload_len);
if (enc_data == NULL) {
MODLOG_DFLT(ERROR, "Failed to allocate enc_data");
return 0;
}
memcpy(enc_data, data + offset + 2, len);
memcpy(enc_data, data + offset + 2, enc_payload_len);
memcpy(&key_ead.peer_addr.val, peer_addr, PEER_ADDR_VAL_SIZE);
rc = ble_store_read_ead(&key_ead, &value_ead);
if (rc != 0 || !value_ead.km_present) {
MODLOG_DFLT(INFO, "Reading of session key and iv from NVS failed rc = %d", rc);
free(enc_data);
return 0;
} else {
MODLOG_DFLT(INFO, "Read session key and iv from NVS successfully");
}
rc = ble_ead_decrypt(value_ead.km->session_key, value_ead.km->iv, enc_data, len,
temp);
rc = ble_ead_decrypt(value_ead.km->session_key, value_ead.km->iv, enc_data,
enc_payload_len, temp);
if (rc == 0) {
MODLOG_DFLT(INFO, "Decryption of adv data done successfully");
} else {
@@ -593,6 +607,8 @@ enc_adv_data_cent_gap_event(struct ble_gap_event *event, void *arg)
if (event->passkey.params.action == BLE_SM_IOACT_INPUT) {
pkey.action = event->passkey.params.action;
/* WARNING: Hardcoded passkey for demonstration only.
* In production, generate a random passkey per pairing. */
pkey.passkey = 123456;
ESP_LOGI(tag, "Entering passkey %" PRIu32, pkey.passkey);
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
@@ -247,6 +247,8 @@ enc_adv_data_prph_gap_event(struct ble_gap_event *event, void *arg)
/** For now only BLE_SM_IOACT_DISP is handled */
if (event->passkey.params.action == BLE_SM_IOACT_DISP) {
pkey.action = event->passkey.params.action;
/* WARNING: Hardcoded passkey for demonstration only.
* In production, generate a random passkey per pairing. */
pkey.passkey = 123456;
ESP_LOGI(tag, "Enter passkey %" PRIu32 " on the peer side", pkey.passkey);
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -491,9 +491,11 @@ ble_coex_advertise(void)
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;
if (name) {
fields.name = (uint8_t *)name;
fields.name_len = strlen(name);
fields.name_is_complete = 1;
}
fields.uuids16 = (ble_uuid16_t[]) {
BLE_UUID16_INIT(BLECOEX_SVC_ALERT_UUID)
@@ -523,16 +525,19 @@ static void on_sync(void)
{
int rc;
ble_hs_util_ensure_addr(0);
ble_hs_id_infer_auto(0, &own_addr_type);
ble_svc_gap_device_name_set("NimBLE Coex");
ble_coex_advertise();
// Start scanning as a client
rc = ble_hs_util_ensure_addr(0);
assert(rc == 0);
rc = ble_hs_id_infer_auto(0, &own_addr_type);
assert(rc == 0);
#if CONFIG_BT_NIMBLE_GAP_SERVICE
rc = ble_svc_gap_device_name_set("NimBLE Coex");
assert(rc == 0);
#endif
ble_coex_advertise();
/* Start scanning as a client */
ble_coex_scan();
}
@@ -557,7 +562,11 @@ void app_main(void)
}
ESP_ERROR_CHECK(ret);
nimble_port_init();
ret = nimble_port_init();
if (ret != ESP_OK) {
MODLOG_DFLT(ERROR, "Failed to init nimble %d \n", ret);
return;
}
ble_hs_cfg.sync_cb = on_sync;
#if MYNEWT_VAL(BLE_INCL_SVC_DISCOVERY) || MYNEWT_VAL(BLE_GATT_CACHING_INCLUDE_SERVICES)
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -1,10 +1,9 @@
/*
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2026 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"
@@ -80,7 +79,7 @@ ext_bleprph_advertise(void)
{
struct ble_gap_ext_adv_params params;
struct os_mbuf *data;
uint8_t instance = 1;
uint8_t instance = 0;
int rc;
/* use defaults for non-set params */
@@ -89,8 +88,8 @@ ext_bleprph_advertise(void)
/* enable connectable advertising */
params.connectable = 1;
/* advertise using random addr */
params.own_addr_type = BLE_OWN_ADDR_PUBLIC;
/* advertise using the inferred address type */
params.own_addr_type = own_addr_type;
params.primary_phy = BLE_HCI_LE_PHY_1M;
params.secondary_phy = BLE_HCI_LE_PHY_2M;
@@ -150,8 +150,8 @@ esp_err_t esp_nimble_init(void)
```
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.reset_cb = bleprph_on_reset;
ble_hs_cfg.sync_cb = bleprph_on_sync;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
```
## Security Manager Configuration
@@ -4,14 +4,9 @@
* SPDX-License-Identifier: Apache-2.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 "multi_adv.h"
static uint8_t gatt_svr_chr_val = 0x01; /* Example characteristic value */
#define GATT_SVR_UUID16_1 (0xCDAB)
@@ -4,7 +4,6 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_log.h"
#include "nvs_flash.h"
/* BLE */
@@ -12,7 +11,6 @@
#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 "multi_adv.h"
@@ -512,7 +510,6 @@ app_main(void)
ble_hs_cfg.sm_bonding = 1;
ble_hs_cfg.sm_mitm = 1;
ble_hs_cfg.sm_sc = 1;
ble_hs_cfg.sm_sc = 0;
/* Enable the appropriate bit masks to make sure the keys
* that are needed are exchanged
*/
@@ -4,4 +4,6 @@
CONFIG_IDF_TARGET="esp32c2"
CONFIG_BT_ENABLED=y
CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_BT_NIMBLE_HCI_EVT_BUF_SIZE=70
CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=70
CONFIG_BT_NIMBLE_EXT_ADV=y
CONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES=4
@@ -3,4 +3,6 @@
#
CONFIG_BT_ENABLED=y
CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_BT_NIMBLE_HCI_EVT_BUF_SIZE=70
CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=70
CONFIG_BT_NIMBLE_EXT_ADV=y
CONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES=4
@@ -9,7 +9,6 @@ In this tutorial, the ble_multi_adv example code for the espressif chipsets with
This example is located in the examples folder of the ESP-IDF under the [ble_multi_adv/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 */
@@ -17,7 +16,6 @@ This example is located in the examples folder of the ESP-IDF under the [ble_mul
#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 "multi_adv.h"
```
@@ -38,8 +36,6 @@ The programs entry point is the app_main() function:
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) {
@@ -50,10 +46,9 @@ app_main(void)
ret = nimble_port_init();
if (ret != ESP_OK) {
MODLOG_DFLT(ERROR, "Failed to init nimble %d \n", ret);
ESP_LOGE(tag, "Failed to init nimble %d ", ret);
return;
}
/* Initialize the NimBLE host configuration. */
ble_hs_cfg.reset_cb = ble_multi_adv_on_reset;
ble_hs_cfg.sync_cb = ble_multi_adv_on_sync;
@@ -62,8 +57,7 @@ app_main(void)
ble_hs_cfg.sm_bonding = 1;
ble_hs_cfg.sm_mitm = 1;
ble_hs_cfg.sm_sc = 0;
ble_hs_cfg.sm_sc = 1;
/* Enable the appropriate bit masks to make sure the keys
* that are needed are exchanged
*/
@@ -75,12 +69,17 @@ app_main(void)
ble_instance_cb[i].cb = NULL;
}
#if MYNEWT_VAL(BLE_GATTS)
int rc;
rc = gatt_svr_init();
assert(rc == 0);
#if CONFIG_BT_NIMBLE_GAP_SERVICE
/* Set the default device name. */
rc = ble_svc_gap_device_name_set("nimble-multi-adv");
assert(rc == 0);
#endif
#endif
/* XXX Need to have template for store */
ble_store_config_init();
@@ -172,8 +171,7 @@ The Security Manager is configured by setting up the following SM's flag and att
```c
ble_hs_cfg.sm_bonding = 1;
ble_hs_cfg.sm_mitm = 1;
ble_hs_cfg.sm_sc = 0;
ble_hs_cfg.sm_sc = 1;
/* Enable the appropriate bit masks to make sure the keys
* that are needed are exchanged
*/
@@ -181,10 +179,12 @@ The Security Manager is configured by setting up the following SM's flag and att
ble_hs_cfg.sm_their_key_dist = BLE_SM_PAIR_KEY_DIST_ENC;
```
The main function calls `ble_svc_gap_device_name_set()` to set the default device name. 'nimble-multi-adv' is passed as the default device name to this function.
The main function calls `ble_svc_gap_device_name_set()` to set the default device name. 'nimble-multi-adv' is passed as the default device name to this function. This is wrapped with `CONFIG_BT_NIMBLE_GAP_SERVICE` guard as the GAP service may be disabled.
```c
#if CONFIG_BT_NIMBLE_GAP_SERVICE
rc = ble_svc_gap_device_name_set("nimble-multi-adv");
#endif
```
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.
@@ -218,7 +218,7 @@ It provides the following benefits over legacy advertisement.
* Non connectable extended
* Connectable extended
* Scannable legacy
* Legacy withe specified duration(5 sec)
* Legacy with specified duration(5 sec)
For each instance:
* A random address is generated which is associated with the advertising instance
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -35,6 +35,7 @@ static void ble_cent_advertise(void);
static void ble_cent_scan(void);
static void ble_cent_connect(void *disc);
static uint8_t own_addr_type;
static uint8_t s_ble_multi_conn_num = 0;
/**
@@ -234,7 +235,7 @@ ble_cent_advertise(void)
/* Enable connectable advertising */
params.connectable = 1;
params.own_addr_type = BLE_OWN_ADDR_PUBLIC;
params.own_addr_type = own_addr_type;
params.primary_phy = BLE_HCI_LE_PHY_1M;
params.secondary_phy = BLE_HCI_LE_PHY_1M;
params.tx_power = 127;
@@ -257,8 +258,6 @@ ble_cent_advertise(void)
/* Start advertising */
rc = ble_gap_ext_adv_start(instance, 0, 0);
assert(rc == 0);
if (rc) {
ESP_LOGE(TAG, "Failed to enable advertisement; rc=%d\n", rc);
return;
@@ -294,7 +293,7 @@ ble_cent_scan(void)
/* Tell the controller to filter duplicates; we don't want to process
* repeated advertisements from the same device.
*/
rc = ble_gap_ext_disc(BLE_OWN_ADDR_PUBLIC, 0, 0, 1, 0, 0, &uncoded_disc_params,
rc = ble_gap_ext_disc(own_addr_type, 0, 0, 1, 0, 0, &uncoded_disc_params,
&coded_disc_params, ble_cent_client_gap_event, NULL);
if (rc != 0) {
ESP_LOGE(TAG, "Error initiating GAP discovery procedure; rc=%d\n", rc);
@@ -411,6 +410,13 @@ blecent_on_sync(void)
rc = ble_hs_util_ensure_addr(0);
assert(rc == 0);
/* Figure out address to use for advertising and scanning */
rc = ble_hs_id_infer_auto(0, &own_addr_type);
if (rc != 0) {
ESP_LOGE(TAG, "error determining address type; rc=%d\n", rc);
return;
}
/* We will function as both the central and peripheral device, connecting to all peripherals
* with the name of BLE_PEER_NAME. Meanwhile, a connectable advertising will be enabled.
* In this example, we register two gap callback functions.
@@ -466,7 +472,9 @@ app_main(void)
#if MYNEWT_VAL(BLE_GATTS)
rc = gatt_svr_init();
assert(rc == 0);
#endif
#if CONFIG_BT_NIMBLE_GAP_SERVICE
/* Set the default device name. We will act as both central and peripheral. */
rc = ble_svc_gap_device_name_set("esp-ble-role-coex");
assert(rc == 0);
@@ -60,15 +60,25 @@ app_main(void)
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
/* Initialize data structures to track connected peers. */
#if MYNEWT_VAL(BLE_INCL_SVC_DISCOVERY) || MYNEWT_VAL(BLE_GATT_CACHING_INCLUDE_SERVICES)
rc = peer_init(BLE_PEER_MAX_NUM, BLE_PEER_MAX_NUM, BLE_PEER_MAX_NUM, BLE_PEER_MAX_NUM, BLE_PEER_MAX_NUM);
assert(rc == 0);
#else
rc = peer_init(BLE_PEER_MAX_NUM, BLE_PEER_MAX_NUM, BLE_PEER_MAX_NUM, BLE_PEER_MAX_NUM);
assert(rc == 0);
#endif
#if MYNEWT_VAL(BLE_GATTS)
rc = gatt_svr_init();
assert(rc == 0);
#endif
#if CONFIG_BT_NIMBLE_GAP_SERVICE
/* Set the default device name. We will act as both central and peripheral. */
rc = ble_svc_gap_device_name_set("esp-ble-role-coex");
assert(rc == 0);
rc = gatt_svr_init();
assert(rc == 0);
#endif
/* XXX Need to have template for store */
ble_store_config_init();
@@ -144,9 +154,8 @@ esp_err_t esp_nimble_init(void)
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 = ble_multi_adv_on_reset;
ble_hs_cfg.sync_cb = ble_multi_adv_on_sync;
ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb;
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;
```
@@ -166,11 +175,43 @@ 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_multi_adv_host_task);
nimble_port_freertos_init(blecent_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.
## Sync Callback
When the BLE host and controller are synced, the `blecent_on_sync` callback is invoked. It sets up the connection interval common factor, ensures a valid identity address, determines the address type dynamically, and starts both advertising and scanning:
```c
static void
blecent_on_sync(void)
{
int rc;
rc = ble_gap_common_factor_set(true, (BLE_PREF_CONN_ITVL_MS * 1000) / 625);
assert(rc == 0);
/* 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 for advertising and scanning */
rc = ble_hs_id_infer_auto(0, &own_addr_type);
if (rc != 0) {
ESP_LOGE(TAG, "error determining address type; rc=%d\n", rc);
return;
}
ble_cent_advertise();
ble_cent_scan();
}
```
- `ble_hs_util_ensure_addr(0)`: Ensures the device has a valid identity address configured (prefers public address).
- `ble_hs_id_infer_auto(0, &own_addr_type)`: Dynamically determines the best address type to use. The result is stored in the global `own_addr_type` variable, which is then used by `ble_cent_advertise()` and `ble_cent_scan()`.
## Multiple Connections
This example will be executed according to the following steps:
@@ -217,7 +258,7 @@ This example will be executed according to the following steps:
multi_conn_params.scheduling_len_us = BLE_PREF_EVT_LEN_MS * 1000;
multi_conn_params.own_addr_type = BLE_OWN_ADDR_RANDOM;
multi_conn_params.peer_addr = peer_addr;
multi_conn_params.duration_ms = 3000;
multi_conn_params.duration_ms = 8000;
multi_conn_params.phy_mask = BLE_GAP_LE_PHY_1M_MASK | BLE_GAP_LE_PHY_2M_MASK |
BLE_GAP_LE_PHY_CODED_MASK;
multi_conn_params.phy_1m_conn_params = &uncoded_conn_param;
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -10,6 +10,7 @@
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "host/util/util.h"
#include "services/gap/ble_svc_gap.h"
#include "ble_multi_conn_prph.h"
@@ -90,7 +91,6 @@ ble_prph_advertise(void)
/* start advertising */
rc = ble_gap_ext_adv_start(instance, 0, 0);
assert(rc == 0);
#else
struct ble_gap_adv_params adv_params;
struct ble_hs_adv_fields fields;
@@ -131,10 +131,12 @@ ble_prph_advertise(void)
fields.tx_pwr_lvl_is_present = 1;
fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
#if CONFIG_BT_NIMBLE_GAP_SERVICE
name = ble_svc_gap_device_name();
fields.name = (uint8_t *)name;
fields.name_len = strlen(name);
fields.name_is_complete = 1;
#endif
rc = ble_gap_adv_set_fields(&fields);
if (rc != 0) {
@@ -247,6 +249,12 @@ bleprph_on_reset(int reason)
static void
bleprph_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);
/* Begin advertising. */
ble_prph_advertise();
}
@@ -290,7 +298,9 @@ app_main(void)
#if MYNEWT_VAL(BLE_GATTS)
rc = gatt_svr_init();
assert(rc == 0);
#endif
#if CONFIG_BT_NIMBLE_GAP_SERVICE
/* Set the default device name. */
rc = ble_svc_gap_device_name_set("esp-multi-conn");
assert(rc == 0);
@@ -18,6 +18,7 @@ This example is located in the examples folder of the ESP-IDF under the [ble_mul
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "host/util/util.h"
#include "services/gap/ble_svc_gap.h"
#include "ble_multi_conn_prph.h"
```
@@ -62,12 +63,16 @@ app_main(void)
ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
#if MYNEWT_VAL(BLE_GATTS)
rc = gatt_svr_init();
assert(rc == 0);
#endif
#if CONFIG_BT_NIMBLE_GAP_SERVICE
/* Set the default device name. */
rc = ble_svc_gap_device_name_set("esp-multi-conn");
assert(rc == 0);
#endif
/* XXX Need to have template for store */
ble_store_config_init();
@@ -88,7 +93,7 @@ app_main(void)
vTaskDelay(pdMS_TO_TICKS(delay_ms));
ble_prph_advertise();
} else {
ESP_LOGE(TAG, "Failed to take Semaphor");
ESP_LOGE(TAG, "Failed to take Semaphore");
}
}
#endif // CONFIG_EXAMPLE_RESTART_ADV_AFTER_CONNECTED
@@ -162,16 +167,19 @@ esp_err_t esp_nimble_init(void)
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 = ble_multi_adv_on_reset;
ble_hs_cfg.sync_cb = ble_multi_adv_on_sync;
ble_hs_cfg.reset_cb = bleprph_on_reset;
ble_hs_cfg.sync_cb = bleprph_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 calls `ble_svc_gap_device_name_set()` to set the default device name. 'esp-multi-conn' is passed as the default device name to this function.
The main function calls `ble_svc_gap_device_name_set()` to set the default device name, guarded by `CONFIG_BT_NIMBLE_GAP_SERVICE` for cases where the GAP service may be disabled:
```c
rc = ble_svc_gap_device_name_set("esp-multi-conn");
#if CONFIG_BT_NIMBLE_GAP_SERVICE
rc = ble_svc_gap_device_name_set("esp-multi-conn");
assert(rc == 0);
#endif
```
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.
@@ -184,11 +192,32 @@ 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_multi_adv_host_task);
nimble_port_freertos_init(bleprph_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.
## Sync Callback
When the BLE host and controller are synced, the `bleprph_on_sync` callback is invoked. It ensures a valid identity address is set before starting advertising:
```c
static void
bleprph_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);
/* Begin advertising. */
ble_prph_advertise();
}
```
- `ble_hs_util_ensure_addr(0)`: Ensures the device has a valid identity address configured (prefers public address).
## Multiple Connections
This example will be executed according to the following steps:
@@ -217,7 +246,7 @@ This example will be executed according to the following steps:
vTaskDelay(pdMS_TO_TICKS(delay_ms));
ble_prph_advertise();
} else {
ESP_LOGE(TAG, "Failed to take Semaphor");
ESP_LOGE(TAG, "Failed to take Semaphore");
}
}
```
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -9,6 +9,7 @@
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "host/util/util.h"
#define BLE_PAWR_EVENT_INTERVAL (600)
#define BLE_PAWR_PERIODIC_EVENT_INTERVAL_MS (3000)
@@ -85,7 +86,7 @@ gap_event_cb(struct ble_gap_event *event, void *arg)
}
static void
start_periodic_adv(void)
start_periodic_adv(uint8_t own_addr_type)
{
int rc;
uint8_t addr[6];
@@ -96,12 +97,13 @@ start_periodic_adv(void)
uint8_t instance = 0;
#if MYNEWT_VAL(BLE_PERIODIC_ADV_ENH)
struct ble_gap_periodic_adv_enable_params eparams;
struct ble_gap_periodic_adv_start_params eparams;
memset(&eparams, 0, sizeof(eparams));
#endif
/* Get the local public address. */
rc = ble_hs_id_copy_addr(BLE_ADDR_PUBLIC, addr, NULL);
/* Get the local address. */
uint8_t addr_type = own_addr_type == BLE_OWN_ADDR_RANDOM ? BLE_ADDR_RANDOM : BLE_ADDR_PUBLIC;
rc = ble_hs_id_copy_addr(addr_type, addr, NULL);
assert (rc == 0);
ESP_LOGI(TAG, "Device Address %02x:%02x:%02x:%02x:%02x:%02x", addr[5], addr[4], addr[3],
@@ -109,7 +111,7 @@ start_periodic_adv(void)
/* For periodic we use instance with non-connectable advertising */
memset (&params, 0, sizeof(params));
params.own_addr_type = BLE_OWN_ADDR_PUBLIC;
params.own_addr_type = own_addr_type;
params.primary_phy = BLE_HCI_LE_PHY_CODED;
params.secondary_phy = BLE_HCI_LE_PHY_1M;
params.sid = 0;
@@ -173,8 +175,18 @@ on_reset(int reason)
static void
on_sync(void)
{
int rc;
uint8_t own_addr_type;
/* Make sure we have proper identity address set (public preferred) */
rc = ble_hs_util_ensure_addr(0);
assert(rc == 0);
rc = ble_hs_id_infer_auto(0, &own_addr_type);
assert(rc == 0);
/* Begin advertising. */
start_periodic_adv();
start_periodic_adv(own_addr_type);
}
void pawr_host_task(void *param)
@@ -15,6 +15,7 @@ This example is located in the examples folder of the ESP-IDF under the [ble_paw
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "host/util/util.h"
```
These includes provide:
@@ -26,6 +27,8 @@ These includes provide:
- BLE host stack functionality
- BLE utility functions for address management (util.h)
## Main Entry Point
The programs entry point is the app_main() function:
@@ -156,10 +159,10 @@ These parameters control:
## PAwR Advertisement
The start_periodic_adv() function configures and starts PAwR:
The start_periodic_adv() function configures and starts PAwR. It takes `own_addr_type` as a parameter, which is determined dynamically via `ble_hs_id_infer_auto()` in the `on_sync` callback:
```c
static void
start_periodic_adv(void)
start_periodic_adv(uint8_t own_addr_type)
{
int rc;
uint8_t addr[6];
@@ -170,12 +173,13 @@ start_periodic_adv(void)
uint8_t instance = 0;
#if MYNEWT_VAL(BLE_PERIODIC_ADV_ENH)
struct ble_gap_periodic_adv_enable_params eparams;
struct ble_gap_periodic_adv_start_params eparams;
memset(&eparams, 0, sizeof(eparams));
#endif
/* Get the local public address. */
rc = ble_hs_id_copy_addr(BLE_ADDR_PUBLIC, addr, NULL);
/* Get the local address. */
uint8_t addr_type = own_addr_type == BLE_OWN_ADDR_RANDOM ? BLE_ADDR_RANDOM : BLE_ADDR_PUBLIC;
rc = ble_hs_id_copy_addr(addr_type, addr, NULL);
assert (rc == 0);
ESP_LOGI(TAG, "Device Address %02x:%02x:%02x:%02x:%02x:%02x", addr[5], addr[4], addr[3],
@@ -183,7 +187,7 @@ start_periodic_adv(void)
/* For periodic we use instance with non-connectable advertising */
memset (&params, 0, sizeof(params));
params.own_addr_type = BLE_OWN_ADDR_PUBLIC;
params.own_addr_type = own_addr_type;
params.primary_phy = BLE_HCI_LE_PHY_CODED;
params.secondary_phy = BLE_HCI_LE_PHY_1M;
params.sid = 0;
@@ -276,6 +280,32 @@ It processes two main events:
- BLE_GAP_EVENT_PER_SUBEV_RESP: Triggered when responses are received from scanners
## Sync Callback
When the BLE host and controller are synced, the `on_sync` callback is invoked. It ensures a valid identity address is set and determines the appropriate address type before starting PAwR:
```c
static void
on_sync(void)
{
int rc;
uint8_t own_addr_type;
/* Make sure we have proper identity address set (public preferred) */
rc = ble_hs_util_ensure_addr(0);
assert(rc == 0);
rc = ble_hs_id_infer_auto(0, &own_addr_type);
assert(rc == 0);
/* Begin advertising. */
start_periodic_adv(own_addr_type);
}
```
- `ble_hs_util_ensure_addr(0)`: Ensures the device has a valid identity address configured (prefers public address).
- `ble_hs_id_infer_auto(0, &own_addr_type)`: Automatically determines the best address type to use based on what is available on the device.
## Host Task
The pawr_host_task runs the NimBLE stack:
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -108,7 +108,7 @@ gap_event_cb(struct ble_gap_event *event, void *arg)
// choose subevents in range 0 to (num_subevents - 1)
uint8_t subevents[] = {0, 1, 2, 3, 4};
int result = ble_gap_periodic_adv_sync_subev(event->periodic_sync.sync_handle, 0, sizeof(subevents), subevents);
if (result == ESP_OK) {
if (result == 0) {
ESP_LOGI(TAG, "[Subevent Sync OK] sync handle:%d, sync_subevents:%d", event->periodic_sync.sync_handle, sizeof(subevents));
} else {
ESP_LOGE(TAG, "Failed to sync subevents, rc = 0x%x", result);
@@ -172,7 +172,14 @@ start_scan(void)
/* Tell the controller to filter duplicates; we don't want to process
* repeated advertisements from the same device.
*/
rc = ble_gap_ext_disc(BLE_OWN_ADDR_PUBLIC, 0, 0, 1, 0, 0, NULL, &disc_params,
uint8_t own_addr_type;
int rc_addr = ble_hs_id_infer_auto(0, &own_addr_type);
if (rc_addr != 0) {
ESP_LOGE(TAG, "error determining address type; rc=%d\n", rc_addr);
return;
}
rc = ble_gap_ext_disc(own_addr_type, 0, 0, 1, 0, 0, NULL, &disc_params,
gap_event_cb, NULL);
if (rc != 0) {
ESP_LOGE(TAG, "Error initiating GAP discovery procedure; rc=%d\n", rc);
@@ -187,14 +187,25 @@ static int gap_event_cb(struct ble_gap_event *event, void *arg)
```c
static void start_scan(void)
{
struct ble_gap_ext_disc_params disc_params = {
.itvl = BLE_GAP_SCAN_ITVL_MS(600),
.window = BLE_GAP_SCAN_ITVL_MS(300),
.passive = 1
};
struct ble_gap_ext_disc_params disc_params;
ble_gap_ext_disc(BLE_OWN_ADDR_PUBLIC, 0, 0, 1, 0, 0, NULL,
&disc_params, gap_event_cb, NULL);
memset(&disc_params, 0, sizeof(disc_params));
disc_params.itvl = BLE_GAP_SCAN_ITVL_MS(600);
disc_params.window = BLE_GAP_SCAN_ITVL_MS(300);
disc_params.passive = 1;
uint8_t own_addr_type;
int rc_addr = ble_hs_id_infer_auto(0, &own_addr_type);
if (rc_addr != 0) {
ESP_LOGE(TAG, "error determining address type; rc=%d\n", rc_addr);
return;
}
int rc = ble_gap_ext_disc(own_addr_type, 0, 0, 1, 0, 0, NULL, &disc_params,
gap_event_cb, NULL);
if (rc != 0) {
ESP_LOGE(TAG, "Error initiating GAP discovery procedure; rc=%d\n", rc);
}
}
```
Key parameters:
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -9,6 +9,7 @@
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "host/util/util.h"
#define BLE_PAWR_EVENT_PERIODIC_INTERVAL_MS (3000)
#define BLE_PAWR_NUM_SUBEVTS (10)
@@ -23,6 +24,7 @@
static struct ble_gap_set_periodic_adv_subev_data_params sub_data_params[BLE_PAWR_NUM_SUBEVTS];
static uint8_t sub_data_pattern[BLE_PAWR_SUB_DATA_LEN] = {0};
static uint8_t conn;
static uint8_t own_addr_type;
static struct ble_gap_conn_desc desc;
char *
addr_str(const void *addr)
@@ -148,7 +150,7 @@ gap_event_cb(struct ble_gap_event *event, void *arg)
ESP_LOGI(TAG, "data: 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x",
data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9]);
peer_addr.type=0;
peer_addr.type = event->periodic_adv_response.data[8];
memcpy(peer_addr.val,&event->periodic_adv_response.data[2],6);
adv_handle = event->periodic_adv_response.adv_handle;
@@ -156,7 +158,7 @@ gap_event_cb(struct ble_gap_event *event, void *arg)
phy_mask = 0x01;
if (conn == 0) {
rc = ble_gap_connect_with_synced(0,adv_handle,subevent,&peer_addr,30000,phy_mask,NULL,NULL,NULL,gap_event_cb,NULL);
rc = ble_gap_connect_with_synced(own_addr_type,adv_handle,subevent,&peer_addr,30000,phy_mask,NULL,NULL,NULL,gap_event_cb,NULL);
if (rc != 0 ) {
ESP_LOGI(TAG,"Error: Failed to connect to device , rc = %d\n",rc);
}else {
@@ -179,7 +181,7 @@ gap_event_cb(struct ble_gap_event *event, void *arg)
}
static void
start_periodic_adv(void)
start_periodic_adv(uint8_t own_addr_type)
{
int rc;
uint8_t addr[6];
@@ -190,12 +192,13 @@ start_periodic_adv(void)
uint8_t instance = 0;
#if MYNEWT_VAL(BLE_PERIODIC_ADV_ENH)
struct ble_gap_periodic_adv_enable_params eparams;
struct ble_gap_periodic_adv_start_params eparams;
memset(&eparams, 0, sizeof(eparams));
#endif
/* Get the local public address. */
rc = ble_hs_id_copy_addr(BLE_ADDR_PUBLIC, addr, NULL);
/* Get the local address. */
uint8_t addr_type = own_addr_type == BLE_OWN_ADDR_RANDOM ? BLE_ADDR_RANDOM : BLE_ADDR_PUBLIC;
rc = ble_hs_id_copy_addr(addr_type, addr, NULL);
assert (rc == 0);
ESP_LOGI(TAG, "Device Address %02x:%02x:%02x:%02x:%02x:%02x", addr[5], addr[4], addr[3],
@@ -203,7 +206,7 @@ start_periodic_adv(void)
/* For periodic we use instance with non-connectable advertising */
memset (&params, 0, sizeof(params));
params.own_addr_type = BLE_OWN_ADDR_PUBLIC;
params.own_addr_type = own_addr_type;
params.primary_phy = BLE_HCI_LE_PHY_CODED;
params.secondary_phy = BLE_HCI_LE_PHY_1M;
params.sid = 0;
@@ -267,8 +270,17 @@ on_reset(int reason)
static void
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);
rc = ble_hs_id_infer_auto(0, &own_addr_type);
assert(rc == 0);
/* Begin advertising. */
start_periodic_adv();
start_periodic_adv(own_addr_type);
}
void pawr_host_task(void *param)
@@ -14,7 +14,8 @@ This example is located in the examples folder of the ESP-IDF under the [ble_paw
/* BLE */
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h
#include "host/ble_hs.h"
#include "host/util/util.h"
```
These includes provide:
@@ -26,6 +27,8 @@ These includes provide:
- BLE host stack functionality
- BLE utility functions for address management (util.h)
## Main Entry Point
The programs entry point is the app_main() function:
@@ -122,7 +125,6 @@ esp_err_t esp_nimble_init(void)
## PAwR Configuration
```c
#define BLE_PAWR_EVENT_INTERVAL (520)
#define BLE_PAWR_EVENT_PERIODIC_INTERVAL_MS (3000)
#define BLE_PAWR_NUM_SUBEVTS (10)
#define BLE_PAWR_SUB_INTERVAL (52)
@@ -133,7 +135,7 @@ esp_err_t esp_nimble_init(void)
```
These parameters control:
- The interval between periodic advertising events
- The periodic advertising interval in milliseconds
- Number of subevents per periodic interval
@@ -145,20 +147,20 @@ These parameters control:
- num_subevents: Number of subevents per periodic interval (10)
- subevent_interval: Time between subevents (44 × 1.25ms = 55ms)
- subevent_interval: Time between subevents (52 × 1.25ms = 65ms)
- response_slot_delay: First response slot delay (20 × 1.25ms = 25ms)
- response_slot_delay: First response slot delay (5 × 1.25ms = 6.25ms)
- response_slot_spacing: Time between slots (32 × 0.125ms = 4ms)
- response_slot_spacing: Time between slots (10 × 0.125ms = 1.25ms)
- num_response_slots: Number of response slots per subevent (5)
- num_response_slots: Number of response slots per subevent (25)
## PAwR Advertisement
The start_periodic_adv() function configures and starts PAwR:
The start_periodic_adv() function configures and starts PAwR. It takes `own_addr_type` as a parameter, which is determined dynamically via `ble_hs_id_infer_auto()` in the `on_sync` callback:
```c
static void
start_periodic_adv(void)
start_periodic_adv(uint8_t own_addr_type)
{
int rc;
uint8_t addr[6];
@@ -169,12 +171,13 @@ start_periodic_adv(void)
uint8_t instance = 0;
#if MYNEWT_VAL(BLE_PERIODIC_ADV_ENH)
struct ble_gap_periodic_adv_enable_params eparams;
struct ble_gap_periodic_adv_start_params eparams;
memset(&eparams, 0, sizeof(eparams));
#endif
/* Get the local public address. */
rc = ble_hs_id_copy_addr(BLE_ADDR_PUBLIC, addr, NULL);
/* Get the local address. */
uint8_t addr_type = own_addr_type == BLE_OWN_ADDR_RANDOM ? BLE_ADDR_RANDOM : BLE_ADDR_PUBLIC;
rc = ble_hs_id_copy_addr(addr_type, addr, NULL);
assert (rc == 0);
ESP_LOGI(TAG, "Device Address %02x:%02x:%02x:%02x:%02x:%02x", addr[5], addr[4], addr[3],
@@ -182,7 +185,7 @@ start_periodic_adv(void)
/* For periodic we use instance with non-connectable advertising */
memset (&params, 0, sizeof(params));
params.own_addr_type = BLE_OWN_ADDR_PUBLIC;
params.own_addr_type = own_addr_type;
params.primary_phy = BLE_HCI_LE_PHY_CODED;
params.secondary_phy = BLE_HCI_LE_PHY_1M;
params.sid = 0;
@@ -271,12 +274,12 @@ The API ble_gap_connect_with_synced() is a NimBLE API used by a PAwR Advertiser
This is especially useful in use cases where on-demand, peer-to-peer data exchange is needed.
```c
ble_gap_connect_with_synced(
BLE_OWN_ADDR_PUBLIC,
own_addr_type,
adv_handle,
subevent,
&peer_addr,
30000,
BLE_GAP_LE_PHY_1M_MASK,
phy_mask,
NULL, NULL, NULL,
gap_event_cb, NULL);
```
@@ -287,7 +290,32 @@ Highlights:
- Avoids scanning (direct connection)
- Enables faster, deterministic connection
📌 Tip: Choose connection interval as a multiple of subevent interval for optimal scheduling.
> Tip: Choose connection interval as a multiple of subevent interval for optimal scheduling.
## Sync Callback
When the BLE host and controller are synced, the `on_sync` callback is invoked. It ensures a valid identity address is set and determines the appropriate address type before starting PAwR:
```c
static void
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);
rc = ble_hs_id_infer_auto(0, &own_addr_type);
assert(rc == 0);
/* Begin advertising. */
start_periodic_adv(own_addr_type);
}
```
- `ble_hs_util_ensure_addr(0)`: Ensures the device has a valid identity address configured (prefers public address).
- `ble_hs_id_infer_auto(0, &own_addr_type)`: Automatically determines the best address type to use based on what is available on the device.
## Host Task
```c
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -126,9 +126,22 @@ gap_event_cb(struct ble_gap_event *event, void *arg)
// create a special data for checking manually in ADV side
sub_data_pattern[0] = event->periodic_report.subevent;
rc = ble_hs_id_copy_addr(BLE_ADDR_PUBLIC, device_addr, NULL);
uint8_t addr_type;
rc = ble_hs_id_infer_auto(0, &addr_type);
if (rc != 0) {
ESP_LOGE(TAG, "Failed to infer address type; rc=%d", rc);
os_mbuf_free_chain(data);
return 0;
}
rc = ble_hs_id_copy_addr(addr_type, device_addr, NULL);
if (rc != 0) {
ESP_LOGE(TAG, "Failed to copy address; rc=%d", rc);
os_mbuf_free_chain(data);
return 0;
}
sub_data_pattern[1] = param.response_slot;
memcpy(&sub_data_pattern[2],device_addr,BLE_DEV_ADDR_LEN);
sub_data_pattern[8] = addr_type;
os_mbuf_append(data, sub_data_pattern, BLE_PAWR_RSP_DATA_LEN);
@@ -165,7 +178,7 @@ gap_event_cb(struct ble_gap_event *event, void *arg)
// choose subevents in range 0 to (num_subevents - 1)
uint8_t subevents[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int result = ble_gap_periodic_adv_sync_subev(event->periodic_sync.sync_handle, 0, sizeof(subevents), subevents);
if (result == ESP_OK) {
if (result == 0) {
ESP_LOGI(TAG, "[Subevent Sync OK] sync handle:%d, sync_subevents:%d\n", event->periodic_sync.sync_handle, sizeof(subevents));
} else {
ESP_LOGE(TAG, "Failed to sync subevents, rc = 0x%x", result);
@@ -227,7 +240,14 @@ start_scan(void)
/* Tell the controller to filter duplicates; we don't want to process
* repeated advertisements from the same device.
*/
rc = ble_gap_ext_disc(BLE_OWN_ADDR_PUBLIC, 0, 0, 1, 0, 0, NULL, &disc_params,
uint8_t own_addr_type;
int rc_addr = ble_hs_id_infer_auto(0, &own_addr_type);
if (rc_addr != 0) {
ESP_LOGE(TAG, "error determining address type; rc=%d\n", rc_addr);
return;
}
rc = ble_gap_ext_disc(own_addr_type, 0, 0, 1, 0, 0, NULL, &disc_params,
gap_event_cb, NULL);
if (rc != 0) {
ESP_LOGE(TAG, "Error initiating GAP discovery procedure; rc=%d\n", rc);
@@ -112,18 +112,26 @@ esp_err_t esp_nimble_init(void)
### Start Scanning
Configures a passive extended scan to detect periodic advertisers:
Configures a passive extended scan to detect periodic advertisers. The address type is determined dynamically using `ble_hs_id_infer_auto()`:
```c
memset(&disc_params, 0, sizeof(disc_params));
disc_params.itvl = BLE_GAP_SCAN_ITVL_MS(600);
disc_params.window = BLE_GAP_SCAN_ITVL_MS(300);
disc_params.passive = 1;
rc = ble_gap_ext_disc(BLE_OWN_ADDR_PUBLIC, 0, 0, 1, 0, 0, NULL, &disc_params,
uint8_t own_addr_type;
int rc_addr = ble_hs_id_infer_auto(0, &own_addr_type);
if (rc_addr != 0) {
ESP_LOGE(TAG, "error determining address type; rc=%d\n", rc_addr);
return;
}
rc = ble_gap_ext_disc(own_addr_type, 0, 0, 1, 0, 0, NULL, &disc_params,
gap_event_cb, NULL);
```
- gap_event_cb: Processes discovery events (EXT_DISC) to find our target.`
- `ble_hs_id_infer_auto()`: Dynamically determines the best address type to use instead of hardcoding `BLE_OWN_ADDR_PUBLIC`.
- gap_event_cb: Processes discovery events (EXT_DISC) to find our target.
## Create Periodic Sync
@@ -153,13 +161,13 @@ static int create_periodic_sync(struct ble_gap_ext_disc_desc *disc) {
After sync establishment, sync to configurable subevents:
```c
// Choose subevents to listen to
uint8_t subevents[] = {0, 1, 2, 3, 4};
// choose subevents in range 0 to (num_subevents - 1)
uint8_t subevents[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int result = ble_gap_periodic_adv_sync_subev(
event->periodic_sync.sync_handle, 0, sizeof(subevents), subevents);
```
The subevents sync selection depends on the subevent number of the Periodic Advertising device.
The subevents sync selection depends on the subevent number of the Periodic Advertising device. In this example, all 10 subevents are synced.
## Sending Response Data
@@ -187,9 +195,22 @@ case BLE_GAP_EVENT_PERIODIC_REPORT:
// create a special data for checking manually in ADV side
sub_data_pattern[0] = event->periodic_report.subevent;
rc = ble_hs_id_copy_addr(BLE_ADDR_PUBLIC, device_addr, NULL);
uint8_t addr_type;
rc = ble_hs_id_infer_auto(0, &addr_type);
if (rc != 0) {
ESP_LOGE(TAG, "Failed to infer address type; rc=%d", rc);
os_mbuf_free_chain(data);
return 0;
}
rc = ble_hs_id_copy_addr(addr_type, device_addr, NULL);
if (rc != 0) {
ESP_LOGE(TAG, "Failed to copy address; rc=%d", rc);
os_mbuf_free_chain(data);
return 0;
}
sub_data_pattern[1] = param.response_slot;
memcpy(&sub_data_pattern[2],device_addr,BLE_DEV_ADDR_LEN);
sub_data_pattern[8] = addr_type;
os_mbuf_append(data, sub_data_pattern, BLE_PAWR_RSP_DATA_LEN);
@@ -197,7 +218,9 @@ case BLE_GAP_EVENT_PERIODIC_REPORT:
```
- os_msys_get_pkthdr: Allocates memory for the response.
- Payload layout: [subevent, 6-byte address, slot index].
- The device address is obtained using `ble_hs_id_infer_auto()` to dynamically determine the correct address type, followed by `ble_hs_id_copy_addr()` to copy the address. Return values are checked to prevent sending uninitialized data. This ensures compatibility across all ESP32 variants.
- Payload layout: [subevent, slot index, 6-byte address, address type].
- ble_gap_periodic_adv_set_response_data: Transmits response in the next slot.
@@ -237,6 +260,26 @@ gap_event_cb() covers:
- CONNECT/DISCONNECT → Handle connection lifecycle.
## Sync Callback
When the BLE host and controller are synced, the `on_sync` callback is invoked. It ensures a valid identity address is set before starting the scan:
```c
static void
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);
start_scan();
}
```
- `ble_hs_util_ensure_addr(0)`: Ensures the device has a valid identity address configured (prefers public address).
## Host Task
```c
void pawr_host_task(void *param) {
@@ -28,18 +28,6 @@ Before project configuration and build, be sure to set the correct chip target u
idf.py set-target <chip_name>
```
### Configure the project
Open the project configuration menu:
```bash
idf.py menuconfig
```
In the `Example Configuration` menu:
* Select I/O capabilities of device from `Example Configuration --> I/O Capability`, default is `Just_works`.
### Build and Flash
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
@@ -64,7 +52,7 @@ I (373) NimBLE: Device Address:
I (373) NimBLE: d0:42:3a:95:84:05
I (373) NimBLE:
I (383) NimBLE: instance 1 started (periodic)
I (383) NimBLE: instance 0 started (periodic)
```
## Note
@@ -1,16 +1,5 @@
menu "Example Configuration"
config EXAMPLE_EXTENDED_ADV
bool
depends on SOC_BLE_50_SUPPORTED && BT_NIMBLE_50_FEATURE_SUPPORT
default y if SOC_ESP_NIMBLE_CONTROLLER
select BT_NIMBLE_EXT_ADV
prompt "Enable Extended Adv"
help
Use this option to enable extended advertising in the example.
If you disable this option, ensure config BT_NIMBLE_EXT_ADV is
also disabled from Nimble stack menuconfig.
config EXAMPLE_RANDOM_ADDR
bool
prompt "Advertise RANDOM Address"
@@ -1,80 +1,71 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <assert.h>
#include <string.h>
#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 "periodic_adv.h"
#include "host/ble_gap.h"
#include "host/ble_hs_adv.h"
#include "patterns.h"
#include "host/util/util.h"
#include "services/gap/ble_svc_gap.h"
#include "modlog/modlog.h"
#include "esp_peripheral.h"
#if CONFIG_EXAMPLE_EXTENDED_ADV
static uint8_t periodic_adv_raw_data[] = {'E', 'S', 'P', '_', 'P', 'E', 'R', 'I', 'O', 'D', 'I', 'C', '_', 'A', 'D', 'V'};
#endif
static const char *tag = "NimBLE_BLE_PERIODIC_ADV";
#if CONFIG_EXAMPLE_RANDOM_ADDR
static uint8_t own_addr_type = BLE_OWN_ADDR_RANDOM;
#else
static uint8_t own_addr_type;
#endif
void ble_store_config_init(void);
#if CONFIG_EXAMPLE_EXTENDED_ADV
/**
* Enables advertising with the following parameters:
* o General discoverable mode.
* o Undirected connectable mode.
* Configures and starts periodic advertising with extended
* non-connectable advertising on instance 0.
*/
static void
start_periodic_adv(void)
start_periodic_adv(uint8_t own_addr_type)
{
int rc;
struct ble_gap_periodic_adv_params pparams;
struct ble_gap_ext_adv_params params;
struct ble_hs_adv_fields adv_fields;
struct os_mbuf *data;
uint8_t instance = 1;
uint8_t instance = 0;
ble_addr_t addr;
uint8_t addr_type;
#if MYNEWT_VAL(BLE_PERIODIC_ADV_ENH)
struct ble_gap_periodic_adv_enable_params eparams;
struct ble_gap_periodic_adv_start_params eparams;
memset(&eparams, 0, sizeof(eparams));
#endif
/* set random (NRPA) address for instance */
rc = ble_hs_id_gen_rnd(1, &addr);
assert (rc == 0);
MODLOG_DFLT(INFO, "Device Address: ");
print_addr(addr.val);
MODLOG_DFLT(INFO, "\n");
/* For periodic we use instance with non-connectable advertising */
memset (&params, 0, sizeof(params));
/* advertise using random addr */
params.own_addr_type = BLE_OWN_ADDR_RANDOM;
params.own_addr_type = own_addr_type;
params.primary_phy = BLE_HCI_LE_PHY_1M;
params.secondary_phy = BLE_HCI_LE_PHY_2M;
/* Use controller-selected default TX power. */
params.tx_power = 0x7f;
params.sid = 2;
/* configure instance 1 */
/* configure instance 0 */
rc = ble_gap_ext_adv_configure(instance, &params, NULL, NULL, NULL);
assert (rc == 0);
rc = ble_gap_ext_adv_set_addr(instance, &addr );
assert (rc == 0);
addr_type = params.own_addr_type == BLE_OWN_ADDR_RANDOM ? BLE_ADDR_RANDOM : BLE_ADDR_PUBLIC;
rc = ble_hs_id_copy_addr(addr_type, addr.val, NULL);
assert(rc == 0);
MODLOG_DFLT(INFO, "Device Address: ");
print_addr(addr.val);
MODLOG_DFLT(INFO, "\n");
memset(&adv_fields, 0, sizeof(adv_fields));
adv_fields.name = (const uint8_t *)"Periodic ADV";
@@ -82,7 +73,7 @@ start_periodic_adv(void)
/* Default to legacy PDUs size, mbuf chain will be increased if needed
*/
data = os_msys_get_pkthdr(BLE_HCI_MAX_ADV_DATA_LEN, 0);
data = os_msys_get_pkthdr(BLE_HS_ADV_MAX_FIELD_SZ, 0);
assert(data);
rc = ble_hs_adv_set_fields_mbuf(&adv_fields, data);
@@ -129,7 +120,6 @@ start_periodic_adv(void)
MODLOG_DFLT(INFO, "instance %u started (periodic)\n", instance);
}
#endif
static void
periodic_adv_on_reset(int reason)
@@ -137,50 +127,24 @@ periodic_adv_on_reset(int reason)
MODLOG_DFLT(ERROR, "Resetting state; reason=%d\n", reason);
}
#if CONFIG_EXAMPLE_RANDOM_ADDR
static void
periodic_adv_set_addr(void)
{
ble_addr_t addr;
int rc;
/* generate new non-resolvable private address */
rc = ble_hs_id_gen_rnd(0, &addr);
assert(rc == 0);
/* set generated address */
rc = ble_hs_id_set_rnd(addr.val);
assert(rc == 0);
}
#endif
static void
periodic_adv_on_sync(void)
{
int rc;
uint8_t own_addr_type;
#if CONFIG_EXAMPLE_RANDOM_ADDR
/* Generate a non-resolvable private address. */
periodic_adv_set_addr();
/* Make sure we have proper identity address set (public preferred) */
rc = ble_hs_util_ensure_addr(1);
#else
rc = ble_hs_util_ensure_addr(0);
#endif
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;
}
assert(rc == 0);
/* Begin advertising. */
#if CONFIG_EXAMPLE_EXTENDED_ADV
start_periodic_adv();
#endif
start_periodic_adv(own_addr_type);
}
void periodic_adv_host_task(void *param)
@@ -195,7 +159,6 @@ void periodic_adv_host_task(void *param)
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) {
@@ -214,9 +177,11 @@ app_main(void)
ble_hs_cfg.reset_cb = periodic_adv_on_reset;
ble_hs_cfg.sync_cb = periodic_adv_on_sync;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
#if CONFIG_BT_NIMBLE_GAP_SERVICE
/* Set the default device name. */
rc = ble_svc_gap_device_name_set("nimble_periodic_adv");
int rc = ble_svc_gap_device_name_set("nimble_periodic_adv");
assert(rc == 0);
#endif
/* XXX Need to have template for store */
ble_store_config_init();
@@ -1,173 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
static const uint8_t ext_adv_pattern_1[] = {
0x00, 0x02, 0x00, 0x04, 0x00, 0x06, 0x00, 0x08, 0x00, 0x0a,
0x00, 0x0c, 0x00, 0x0e, 0x00, 0x10, 0x00, 0x12, 0x00, 0x14,
0x00, 0x16, 0x00, 0x18, 0x00, 0x1a, 0x00, 0x1c, 0x00, 0x1e,
0x00, 0x20, 0x00, 0x22, 0x00, 0x24, 0x00, 0x26, 0x00, 0x28,
0x00, 0x2a, 0x00, 0x2c, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x32,
0x00, 0x34, 0x00, 0x36, 0x00, 0x38, 0x00, 0x3a, 0x00, 0x3c,
0x00, 0x3e, 0x00, 0x40, 0x00, 0x42, 0x00, 0x44, 0x00, 0x46,
0x00, 0x48, 0x00, 0x4a, 0x00, 0x4c, 0x00, 0x4e, 0x00, 0x50,
0x00, 0x52, 0x00, 0x54, 0x00, 0x56, 0x00, 0x58, 0x00, 0x5a,
0x00, 0x5c, 0x00, 0x5e, 0x00, 0x60, 0x00, 0x62, 0x00, 0x64,
0x00, 0x66, 0x00, 0x68, 0x00, 0x6a, 0x00, 0x6c, 0x00, 0x6e,
0x00, 0x70, 0x00, 0x72, 0x00, 0x74, 0x00, 0x76, 0x00, 0x78,
0x00, 0x7a, 0x00, 0x7c, 0x00, 0x7e, 0x00, 0x80, 0x00, 0x82,
0x00, 0x84, 0x00, 0x86, 0x00, 0x88, 0x00, 0x8a, 0x00, 0x8c,
0x00, 0x8e, 0x00, 0x90, 0x00, 0x92, 0x00, 0x94, 0x00, 0x96,
0x00, 0x98, 0x00, 0x9a, 0x00, 0x9c, 0x00, 0x9e, 0x00, 0xa0,
0x00, 0xa2, 0x00, 0xa4, 0x00, 0xa6, 0x00, 0xa8, 0x00, 0xaa,
0x00, 0xac, 0x00, 0xae, 0x00, 0xb0, 0x00, 0xb2, 0x00, 0xb4,
0x00, 0xb6, 0x00, 0xb8, 0x00, 0xba, 0x00, 0xbc, 0x00, 0xbe,
0x00, 0xc0, 0x00, 0xc2, 0x00, 0xc4, 0x00, 0xc6, 0x00, 0xc8,
0x00, 0xca, 0x00, 0xcc, 0x00, 0xce, 0x00, 0xd0, 0x00, 0xd2,
0x00, 0xd4, 0x00, 0xd6, 0x00, 0xd8, 0x00, 0xda, 0x00, 0xdc,
0x00, 0xde, 0x00, 0xe0, 0x00, 0xe2, 0x00, 0xe4, 0x00, 0xe6,
0x00, 0xe8, 0x00, 0xea, 0x00, 0xec, 0x00, 0xee, 0x00, 0xf0,
0x00, 0xf2, 0x00, 0xf4, 0x00, 0xf6, 0x00, 0xf8, 0x00, 0xfa,
0x00, 0xfc, 0x00, 0xfe, 0x01, 0x01, 0x01, 0x03, 0x01, 0x05,
0x01, 0x07, 0x01, 0x09, 0x01, 0x0b, 0x01, 0x0d, 0x01, 0x0f,
0x01, 0x11, 0x01, 0x13, 0x01, 0x15, 0x01, 0x17, 0x01, 0x19,
0x01, 0x1b, 0x01, 0x1d, 0x01, 0x1f, 0x01, 0x21, 0x01, 0x23,
0x01, 0x25, 0x01, 0x27, 0x01, 0x29, 0x01, 0x2b, 0x01, 0x2d,
0x01, 0x2f, 0x01, 0x31, 0x01, 0x33, 0x01, 0x35, 0x01, 0x37,
0x01, 0x39, 0x01, 0x3b, 0x01, 0x3d, 0x01, 0x3f, 0x01, 0x41,
0x01, 0x43, 0x01, 0x45, 0x01, 0x47, 0x01, 0x49, 0x01, 0x4b,
0x01, 0x4d, 0x01, 0x4f, 0x01, 0x51, 0x01, 0x53, 0x01, 0x55,
0x01, 0x57, 0x01, 0x59, 0x01, 0x5b, 0x01, 0x5d, 0x01, 0x5f,
0x01, 0x61, 0x01, 0x63, 0x01, 0x65, 0x01, 0x67, 0x01, 0x69,
0x01, 0x6b, 0x01, 0x6d, 0x01, 0x6f, 0x01, 0x71, 0x01, 0x73,
0x01, 0x75, 0x01, 0x77, 0x01, 0x79, 0x01, 0x7b, 0x01, 0x7d,
0x01, 0x7f, 0x01, 0x81, 0x01, 0x83, 0x01, 0x85, 0x01, 0x87,
0x01, 0x89, 0x01, 0x8b, 0x01, 0x8d, 0x01, 0x8f, 0x01, 0x91,
0x01, 0x93, 0x01, 0x95, 0x01, 0x97, 0x01, 0x99, 0x01, 0x9b,
0x01, 0x9d, 0x01, 0x9f, 0x01, 0xa1, 0x01, 0xa3, 0x01, 0xa5,
0x01, 0xa7, 0x01, 0xa9, 0x01, 0xab, 0x01, 0xad, 0x01, 0xaf,
0x01, 0xb1, 0x01, 0xb3, 0x01, 0xb5, 0x01, 0xb7, 0x01, 0xb9,
0x01, 0xbb, 0x01, 0xbd, 0x01, 0xbf, 0x01, 0xc1, 0x01, 0xc3,
0x01, 0xc5, 0x01, 0xc7, 0x01, 0xc9, 0x01, 0xcb, 0x01, 0xcd,
0x01, 0xcf, 0x01, 0xd1, 0x01, 0xd3, 0x01, 0xd5, 0x01, 0xd7,
0x01, 0xd9, 0x01, 0xdb, 0x01, 0xdd, 0x01, 0xdf, 0x01, 0xe1,
0x01, 0xe3, 0x01, 0xe5, 0x01, 0xe7, 0x01, 0xe9, 0x01, 0xeb,
0x01, 0xed, 0x01, 0xef, 0x01, 0xf1, 0x01, 0xf3, 0x01, 0xf5,
0x01, 0xf7, 0x01, 0xf9, 0x01, 0xfb, 0x01, 0xfd, 0x02, 0x00,
0x02, 0x02, 0x02, 0x04, 0x02, 0x06, 0x02, 0x08, 0x02, 0x0a,
0x02, 0x0c, 0x02, 0x0e, 0x02, 0x10, 0x02, 0x12, 0x02, 0x14,
0x02, 0x16, 0x02, 0x18, 0x02, 0x1a, 0x02, 0x1c, 0x02, 0x1e,
0x02, 0x20, 0x02, 0x22, 0x02, 0x24, 0x02, 0x26, 0x02, 0x28,
0x02, 0x2a, 0x02, 0x2c, 0x02, 0x2e, 0x02, 0x30, 0x02, 0x32,
0x02, 0x34, 0x02, 0x36, 0x02, 0x38, 0x02, 0x3a, 0x02, 0x3c,
0x02, 0x3e, 0x02, 0x40, 0x02, 0x42, 0x02, 0x44, 0x02, 0x46,
0x02, 0x48, 0x02, 0x4a, 0x02, 0x4c, 0x02, 0x4e, 0x02, 0x50,
0x02, 0x52, 0x02, 0x54, 0x02, 0x56, 0x02, 0x58, 0x02, 0x5a,
0x02, 0x5c, 0x02, 0x5e, 0x02, 0x60, 0x02, 0x62, 0x02, 0x64,
0x02, 0x66, 0x02, 0x68, 0x02, 0x6a, 0x02, 0x6c, 0x02, 0x6e,
0x02, 0x70, 0x02, 0x72, 0x02, 0x74, 0x02, 0x76, 0x02, 0x78,
0x02, 0x7a, 0x02, 0x7c, 0x02, 0x7e, 0x02, 0x80, 0x02, 0x82,
0x02, 0x84, 0x02, 0x86, 0x02, 0x88, 0x02, 0x8a, 0x02, 0x8c,
0x02, 0x8e, 0x02, 0x90, 0x02, 0x92, 0x02, 0x94, 0x02, 0x96,
0x02, 0x98, 0x02, 0x9a, 0x02, 0x9c, 0x02, 0x9e, 0x02, 0xa0,
0x02, 0xa2, 0x02, 0xa4, 0x02, 0xa6, 0x02, 0xa8, 0x02, 0xaa,
0x02, 0xac, 0x02, 0xae, 0x02, 0xb0, 0x02, 0xb2, 0x02, 0xb4,
0x02, 0xb6, 0x02, 0xb8, 0x02, 0xba, 0x02, 0xbc, 0x02, 0xbe,
0x02, 0xc0, 0x02, 0xc2, 0x02, 0xc4, 0x02, 0xc6, 0x02, 0xc8,
0x02, 0xca, 0x02, 0xcc, 0x02, 0xce, 0x02, 0xd0, 0x02, 0xd2,
0x02, 0xd4, 0x02, 0xd6, 0x02, 0xd8, 0x02, 0xda, 0x02, 0xdc,
0x02, 0xde, 0x02, 0xe0, 0x02, 0xe2, 0x02, 0xe4, 0x02, 0xe6,
0x02, 0xe8, 0x02, 0xea, 0x02, 0xec, 0x02, 0xee, 0x02, 0xf0,
0x02, 0xf2, 0x02, 0xf4, 0x02, 0xf6, 0x02, 0xf8, 0x02, 0xfa,
0x02, 0xfc, 0x02, 0xfe, 0x03, 0x01, 0x03, 0x03, 0x03, 0x05,
0x03, 0x07, 0x03, 0x09, 0x03, 0x0b, 0x03, 0x0d, 0x03, 0x0f,
0x03, 0x11, 0x03, 0x13, 0x03, 0x15, 0x03, 0x17, 0x03, 0x19,
0x03, 0x1b, 0x03, 0x1d, 0x03, 0x1f, 0x03, 0x21, 0x03, 0x23,
0x03, 0x25, 0x03, 0x27, 0x03, 0x29, 0x03, 0x2b, 0x03, 0x2d,
0x03, 0x2f, 0x03, 0x31, 0x03, 0x33, 0x03, 0x35, 0x03, 0x37,
0x03, 0x39, 0x03, 0x3b, 0x03, 0x3d, 0x03, 0x3f, 0x03, 0x41,
0x03, 0x43, 0x03, 0x45, 0x03, 0x47, 0x03, 0x49, 0x03, 0x4b,
0x03, 0x4d, 0x03, 0x4f, 0x03, 0x51, 0x03, 0x53, 0x03, 0x55,
0x03, 0x57, 0x03, 0x59, 0x03, 0x5b, 0x03, 0x5d, 0x03, 0x5f,
0x03, 0x61, 0x03, 0x63, 0x03, 0x65, 0x03, 0x67, 0x03, 0x69,
0x03, 0x6b, 0x03, 0x6d, 0x03, 0x6f, 0x03, 0x71, 0x03, 0x73,
0x03, 0x75, 0x03, 0x77, 0x03, 0x79, 0x03, 0x7b, 0x03, 0x7d,
0x03, 0x7f, 0x03, 0x81, 0x03, 0x83, 0x03, 0x85, 0x03, 0x87,
0x03, 0x89, 0x03, 0x8b, 0x03, 0x8d, 0x03, 0x8f, 0x03, 0x91,
0x03, 0x93, 0x03, 0x95, 0x03, 0x97, 0x03, 0x99, 0x03, 0x9b,
0x03, 0x9d, 0x03, 0x9f, 0x03, 0xa1, 0x03, 0xa3, 0x03, 0xa5,
0x03, 0xa7, 0x03, 0xa9, 0x03, 0xab, 0x03, 0xad, 0x03, 0xaf,
0x03, 0xb1, 0x03, 0xb3, 0x03, 0xb5, 0x03, 0xb7, 0x03, 0xb9,
0x03, 0xbb, 0x03, 0xbd, 0x03, 0xbf, 0x03, 0xc1, 0x03, 0xc3,
0x03, 0xc5, 0x03, 0xc7, 0x03, 0xc9, 0x03, 0xcb, 0x03, 0xcd,
0x03, 0xcf, 0x03, 0xd1, 0x03, 0xd3, 0x03, 0xd5, 0x03, 0xd7,
0x03, 0xd9, 0x03, 0xdb, 0x03, 0xdd, 0x03, 0xdf, 0x03, 0xe1,
0x03, 0xe3, 0x03, 0xe5, 0x03, 0xe7, 0x03, 0xe9, 0x03, 0xeb,
0x03, 0xed, 0x03, 0xef, 0x03, 0xf1, 0x03, 0xf3, 0x03, 0xf5,
0x03, 0xf7, 0x03, 0xf9, 0x03, 0xfb, 0x03, 0xfd, 0x04, 0x00,
0x04, 0x02, 0x04, 0x04, 0x04, 0x06, 0x04, 0x08, 0x04, 0x0a,
0x04, 0x0c, 0x04, 0x0e, 0x04, 0x10, 0x04, 0x12, 0x04, 0x14,
0x04, 0x16, 0x04, 0x18, 0x04, 0x1a, 0x04, 0x1c, 0x04, 0x1e,
0x04, 0x20, 0x04, 0x22, 0x04, 0x24, 0x04, 0x26, 0x04, 0x28,
0x04, 0x2a, 0x04, 0x2c, 0x04, 0x2e, 0x04, 0x30, 0x04, 0x32,
0x04, 0x34, 0x04, 0x36, 0x04, 0x38, 0x04, 0x3a, 0x04, 0x3c,
0x04, 0x3e, 0x04, 0x40, 0x04, 0x42, 0x04, 0x44, 0x04, 0x46,
0x04, 0x48, 0x04, 0x4a, 0x04, 0x4c, 0x04, 0x4e, 0x04, 0x50,
0x04, 0x52, 0x04, 0x54, 0x04, 0x56, 0x04, 0x58, 0x04, 0x5a,
0x04, 0x5c, 0x04, 0x5e, 0x04, 0x60, 0x04, 0x62, 0x04, 0x64,
0x04, 0x66, 0x04, 0x68, 0x04, 0x6a, 0x04, 0x6c, 0x04, 0x6e,
0x04, 0x70, 0x04, 0x72, 0x04, 0x74, 0x04, 0x76, 0x04, 0x78,
0x04, 0x7a, 0x04, 0x7c, 0x04, 0x7e, 0x04, 0x80, 0x04, 0x82,
0x04, 0x84, 0x04, 0x86, 0x04, 0x88, 0x04, 0x8a, 0x04, 0x8c,
0x04, 0x8e, 0x04, 0x90, 0x04, 0x92, 0x04, 0x94, 0x04, 0x96,
0x04, 0x98, 0x04, 0x9a, 0x04, 0x9c, 0x04, 0x9e, 0x04, 0xa0,
0x04, 0xa2, 0x04, 0xa4, 0x04, 0xa6, 0x04, 0xa8, 0x04, 0xaa,
0x04, 0xac, 0x04, 0xae, 0x04, 0xb0, 0x04, 0xb2, 0x04, 0xb4,
0x04, 0xb6, 0x04, 0xb8, 0x04, 0xba, 0x04, 0xbc, 0x04, 0xbe,
0x04, 0xc0, 0x04, 0xc2, 0x04, 0xc4, 0x04, 0xc6, 0x04, 0xc8,
0x04, 0xca, 0x04, 0xcc, 0x04, 0xce, 0x04, 0xd0, 0x04, 0xd2,
0x04, 0xd4, 0x04, 0xd6, 0x04, 0xd8, 0x04, 0xda, 0x04, 0xdc,
0x04, 0xde, 0x04, 0xe0, 0x04, 0xe2, 0x04, 0xe4, 0x04, 0xe6,
0x04, 0xe8, 0x04, 0xea, 0x04, 0xec, 0x04, 0xee, 0x04, 0xf0,
0x04, 0xf2, 0x04, 0xf4, 0x04, 0xf6, 0x04, 0xf8, 0x04, 0xfa,
0x04, 0xfc, 0x04, 0xfe, 0x05, 0x01, 0x05, 0x03, 0x05, 0x05,
0x05, 0x07, 0x05, 0x09, 0x05, 0x0b, 0x05, 0x0d, 0x05, 0x0f,
0x05, 0x11, 0x05, 0x13, 0x05, 0x15, 0x05, 0x17, 0x05, 0x19,
0x05, 0x1b, 0x05, 0x1d, 0x05, 0x1f, 0x05, 0x21, 0x05, 0x23,
0x05, 0x25, 0x05, 0x27, 0x05, 0x29, 0x05, 0x2b, 0x05, 0x2d,
0x05, 0x2f, 0x05, 0x31, 0x05, 0x33, 0x05, 0x35, 0x05, 0x37,
0x05, 0x39, 0x05, 0x3b, 0x05, 0x3d, 0x05, 0x3f, 0x05, 0x41,
0x05, 0x43, 0x05, 0x45, 0x05, 0x47, 0x05, 0x49, 0x05, 0x4b,
0x05, 0x4d, 0x05, 0x4f, 0x05, 0x51, 0x05, 0x53, 0x05, 0x55,
0x05, 0x57, 0x05, 0x59, 0x05, 0x5b, 0x05, 0x5d, 0x05, 0x5f,
0x05, 0x61, 0x05, 0x63, 0x05, 0x65, 0x05, 0x67, 0x05, 0x69,
0x05, 0x6b, 0x05, 0x6d, 0x05, 0x6f, 0x05, 0x71, 0x05, 0x73,
0x05, 0x75, 0x05, 0x77, 0x05, 0x79, 0x05, 0x7b, 0x05, 0x7d,
0x05, 0x7f, 0x05, 0x81, 0x05, 0x83, 0x05, 0x85, 0x05, 0x87,
0x05, 0x89, 0x05, 0x8b, 0x05, 0x8d, 0x05, 0x8f, 0x05, 0x91,
0x05, 0x93, 0x05, 0x95, 0x05, 0x97, 0x05, 0x99, 0x05, 0x9b,
0x05, 0x9d, 0x05, 0x9f, 0x05, 0xa1, 0x05, 0xa3, 0x05, 0xa5,
0x05, 0xa7, 0x05, 0xa9, 0x05, 0xab, 0x05, 0xad, 0x05, 0xaf,
0x05, 0xb1, 0x05, 0xb3, 0x05, 0xb5, 0x05, 0xb7, 0x05, 0xb9,
0x05, 0xbb, 0x05, 0xbd, 0x05, 0xbf, 0x05, 0xc1, 0x05, 0xc3,
0x05, 0xc5, 0x05, 0xc7, 0x05, 0xc9, 0x05, 0xcb, 0x05, 0xcd,
0x05, 0xcf, 0x05, 0xd1, 0x05, 0xd3, 0x05, 0xd5, 0x05, 0xd7,
0x05, 0xd9, 0x05, 0xdb, 0x05, 0xdd, 0x05, 0xdf, 0x05, 0xe1,
0x05, 0xe3, 0x05, 0xe5, 0x05, 0xe7, 0x05, 0xe9, 0x05, 0xeb,
0x05, 0xed, 0x05, 0xef, 0x05, 0xf1, 0x05, 0xf3, 0x05, 0xf5,
0x05, 0xf7, 0x05, 0xf9, 0x05, 0xfb, 0x05, 0xfd, 0x06, 0x00,
0x06, 0x02, 0x06, 0x04, 0x06, 0x06, 0x06, 0x08, 0x06, 0x0a,
0x06, 0x0c, 0x06, 0x0e, 0x06, 0x10, 0x06, 0x12, 0x06, 0x14,
0x06, 0x16, 0x06, 0x18, 0x06, 0x1a, 0x06, 0x1c, 0x06, 0x1e,
0x06, 0x20, 0x06, 0x22, 0x06, 0x24, 0x06, 0x26, 0x06, 0x28,
0x06, 0x2a, 0x06, 0x2c, 0x06, 0x2e, 0x06, 0x30, 0x06, 0x32,
0x06, 0x34, 0x06, 0x36, 0x06, 0x38, 0x06, 0x3a, 0x06, 0x3c,
0x06, 0x3e, 0x06, 0x40, 0x06, 0x42, 0x06, 0x44, 0x06, 0x46,
0x06, 0x48, 0x06, 0x4a, 0x06, 0x4c, 0x06, 0x4e, 0x06, 0x50,
0x06, 0x52, 0x06, 0x54, 0x06, 0x56, 0x06, 0x58, 0x06, 0x5a,
0x06, 0x5c, 0x06, 0x5e, 0x06, 0x60, 0x06, 0x62, 0x06, 0x64,
0x06, 0x66, 0x06, 0x68, 0x06, 0x6a, 0x06, 0x6c, 0x06, 0x6e,
0x06, 0x70, 0x06, 0x72, 0x06, 0x74, 0x06, 0x76, 0x06, 0x78
};
@@ -1,25 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#ifndef H_BLE_PERIODIC_ADV_
#define H_BLE_PERIODIC_ADV_
#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;
#ifdef __cplusplus
}
#endif
#endif
@@ -10,6 +10,5 @@ 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_EXAMPLE_EXTENDED_ADV=y
CONFIG_BT_NIMBLE_EXT_ADV=y
CONFIG_BT_NIMBLE_MAX_PERIODIC_SYNCS=1
@@ -10,7 +10,6 @@ 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_EXAMPLE_EXTENDED_ADV=y
CONFIG_BT_NIMBLE_EXT_ADV=y
CONFIG_BT_NIMBLE_MAX_PERIODIC_SYNCS=1
@@ -10,7 +10,6 @@ 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_EXAMPLE_EXTENDED_ADV=y
CONFIG_BT_NIMBLE_EXT_ADV=y
CONFIG_BT_NIMBLE_MAX_PERIODIC_SYNCS=1
CONFIG_BT_NIMBLE_MAX_CONNECTIONS=1
@@ -2,43 +2,46 @@
## Introduction
In this tutorial, the ble_periodic_adv example code for the espressif chipsets with BLE5.0 support is reviewed. This example aims at understanding periodic advertisements and related NimBLE APIs.This code implements the periodic advertisement functionality along with extended advertisement by generating a non-resolvable private address.
In this tutorial, the ble_periodic_adv example code for the Espressif chipsets with BLE 5.0 support is reviewed. This example demonstrates periodic advertising using the NimBLE stack. It configures extended non-connectable advertising on instance 0, sets up periodic advertising parameters, and begins transmitting periodic advertisement data.
## Includes
This example is located in the examples folder of the ESP-IDF under the [ble_periodic_adv/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 <assert.h>
#include <string.h>
#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 "periodic_adv.h"
#include "host/ble_gap.h"
#include "host/ble_hs_adv.h"
#include "patterns.h"
#include "host/util/util.h"
#include "services/gap/ble_svc_gap.h"
#include "modlog/modlog.h"
#include "esp_peripheral.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`, `“periodic_adv.h` which expose the BLE APIs required to implement this example.
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_gap.h"`, `"ble_hs_adv.h"` and `"ble_svc_gap.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.
* `periodic_adv.h`:It includes the code containing forward declarations of 2 structs `ble_hs_cfg` , and `ble_gatt_register_ctxt` based on weather macro `H_BLE_PERIODIC_ADV_` is defined.
* `ble_hs.h`: Defines the functionalities to handle the host event.
* `ble_gap.h`: Defines GAP (Generic Access Profile) related APIs for advertising and scanning.
* `ble_hs_adv.h`: Defines advertisement data field manipulation APIs.
* `ble_svc_gap.h`: Defines the macros for device name, and device appearance and declares the function to set them.
* `modlog/modlog.h`: Provides logging macros used throughout the example.
* `esp_peripheral.h`: Provides the `print_addr` utility for printing BLE addresses.
## Main Entry Point
The programs entry point is the app_main() function:
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) {
@@ -57,17 +60,19 @@ app_main(void)
ble_hs_cfg.reset_cb = periodic_adv_on_reset;
ble_hs_cfg.sync_cb = periodic_adv_on_sync;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
#if CONFIG_BT_NIMBLE_GAP_SERVICE
/* Set the default device name. */
rc = ble_svc_gap_device_name_set("nimble_periodic_adv");
int rc = ble_svc_gap_device_name_set("nimble_periodic_adv");
assert(rc == 0);
#endif
/* XXX Need to have a template for store */
/* XXX Need to have template for store */
ble_store_config_init();
nimble_port_freertos_init(periodic_adv_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.
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) {
@@ -79,60 +84,9 @@ 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:
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);
os_msys_init();
void ble_store_ram_init(void);
/* XXX Need to have a template for store */
ble_store_ram_init();
#endif
/* Initialize the host */
ble_hs_init();
return ESP_OK;
}
```
The host is configured by setting up the callbacks for Stack-reset, Stack-sync, and Storage status
The host is configured by setting up the callbacks for Stack-reset, Stack-sync, and Storage status:
```c
ble_hs_cfg.reset_cb = periodic_adv_on_reset;
@@ -140,102 +94,138 @@ ble_hs_cfg.sync_cb = periodic_adv_on_sync;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
```
The main function calls `ble_svc_gap_device_name_set()` to set the default device name. 'blecent_phy' is passed as the default device name to this function.
The main function calls `ble_svc_gap_device_name_set()` to set the default device name. This call is guarded by `CONFIG_BT_NIMBLE_GAP_SERVICE` to handle cases where the GAP service is optionally disabled:
```c
#if CONFIG_BT_NIMBLE_GAP_SERVICE
rc = ble_svc_gap_device_name_set("nimble_periodic_adv");
```
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();
assert(rc == 0);
#endif
```
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()`.
The main function calls `ble_store_config_init()` to configure the host by setting up the storage callbacks which handle the read, write, and deletion of security material.
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(periodic_adv_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 to handle the default queue, it is easier to create a separate task.
## Generation of non-resolvable private address
## Host Sync Callback - Address Inference
In Bluetooth Low Energy (BLE), a non-resolvable private address is a type of Bluetooth device address that is used for privacy purposes. It is a randomly generated address that changes periodically to prevent long-term tracking of a device. The API call to `ble_hs_id_gen_rnd()` is responsible for generating a non-resolvable private address. NRPA is a 48-bit address that is stored in `addr.val`.
When the NimBLE host and controller are synced, the `periodic_adv_on_sync()` callback is invoked. This function ensures a valid identity address is available and infers the appropriate address type before starting periodic advertising:
```c
#if CONFIG_EXAMPLE_RANDOM_ADDR
static void
periodic_adv_set_addr(void)
periodic_adv_on_sync(void)
{
ble_addr_t addr;
int rc;
uint8_t own_addr_type;
/* generate new non-resolvable private address */
rc = ble_hs_id_gen_rnd(0, &addr);
assert(rc == 0);
/* set generated address */
rc = ble_hs_id_set_rnd(addr.val);
assert(rc == 0);
}
#if CONFIG_EXAMPLE_RANDOM_ADDR
rc = ble_hs_util_ensure_addr(1);
#else
rc = ble_hs_util_ensure_addr(0);
#endif
assert(rc == 0);
rc = ble_hs_id_infer_auto(0, &own_addr_type);
assert(rc == 0);
/* Begin advertising. */
start_periodic_adv(own_addr_type);
}
```
`ble_hs_util_ensure_addr()` ensures the device has a valid identity address. The argument `1` requests a random address, while `0` requests a public address. `ble_hs_id_infer_auto()` determines the best address type to use (public or random) based on what is available on the device. The inferred `own_addr_type` is passed to `start_periodic_adv()` so that the advertising instance uses the correct address type.
## Periodic Advertisement
Periodic advertisement start by creating instances of structures `ble_gap_periodic_adv_params`, `ble_gap_ext_adv_params`, `ble_hs_adv_fields`, and `os_mbuf`. Advertising parameters such as connecting modes, advertising intervals, peer address, advertising-filter policy, etc are defined in these structures for periodic and extended advertisements. `pparams` and `params` instances have parameters for periodic advertisement and extended advertisement respectively.
Bluetooth device address is given by the structure ble_aadr_t which contains the fields for address type and address value.
Periodic advertisement starts by creating instances of structures `ble_gap_periodic_adv_params`, `ble_gap_ext_adv_params`, `ble_hs_adv_fields`, and `os_mbuf`. Advertising parameters such as connecting modes, advertising intervals, own address type, PHYs, etc. are defined in these structures for periodic and extended advertisements.
## Need of Extended Advertisement in Periodic Advertisement
### Need of Extended Advertisement in Periodic Advertisement
Non-connectable and non-scannable advertising events containing synchronization information about a periodic advertising train are necessary for the scanner device to sync with the periodic advertising train. The periodic advertising will utilize the same physical layer (PHY) as the auxiliary packet, which is part of the extended advertisement.
Below is the implementation to start periodic advertisement.
Below is the implementation to start periodic advertisement:
```c
static void
start_periodic_adv(void)
start_periodic_adv(uint8_t own_addr_type)
{
int rc;
struct ble_gap_periodic_adv_params pparams;
struct ble_gap_ext_adv_params params;
struct ble_hs_adv_fields adv_fields;
struct os_mbuf *data;
uint8_t instance = 1;
uint8_t instance = 0;
ble_addr_t addr;
uint8_t addr_type;
#if MYNEWT_VAL(BLE_PERIODIC_ADV_ENH)
struct ble_gap_periodic_adv_start_params eparams;
memset(&eparams, 0, sizeof(eparams));
#endif
/* set random (NRPA) address for instance */
rc = ble_hs_id_gen_rnd(1, &addr);
/* For periodic we use instance with non-connectable advertising */
memset (&params, 0, sizeof(params));
params.own_addr_type = own_addr_type;
params.primary_phy = BLE_HCI_LE_PHY_1M;
params.secondary_phy = BLE_HCI_LE_PHY_2M;
/* Use controller-selected default TX power. */
params.tx_power = 0x7f;
params.sid = 2;
/* configure instance 0 */
rc = ble_gap_ext_adv_configure(instance, &params, NULL, NULL, NULL);
assert (rc == 0);
addr_type = params.own_addr_type == BLE_OWN_ADDR_RANDOM ? BLE_ADDR_RANDOM : BLE_ADDR_PUBLIC;
rc = ble_hs_id_copy_addr(addr_type, addr.val, NULL);
assert(rc == 0);
MODLOG_DFLT(INFO, "Device Address: ");
print_addr(addr.val);
MODLOG_DFLT(INFO, "\n");
/* For periodic we use instance with non-connectable advertising */
memset (&params, 0, sizeof(params));
...
```
/* advertise using random addr */
params.own_addr_type = BLE_OWN_ADDR_RANDOM;
params.primary_phy = BLE_HCI_LE_PHY_1M;
params.secondary_phy = BLE_HCI_LE_PHY_2M;
params.sid = 2;
The function takes `own_addr_type` as a parameter (inferred in `periodic_adv_on_sync`). Key aspects:
/* configure instance 1 */
rc = ble_gap_ext_adv_configure(instance, &params, NULL, NULL, NULL);
assert (rc == 0);
1. **Instance 0** is used for the advertising instance.
2. The address type is set dynamically from the inferred `own_addr_type` rather than being hardcoded. This ensures the example works correctly across all ESP32 variants, including those that only have a random address (e.g., some ESP32-C2, ESP32-H2 configurations).
3. After configuring the instance, the device address is retrieved using `ble_hs_id_copy_addr()` based on the address type.
4. `params.tx_power = 0x7f` tells the controller to use its default TX power.
rc = ble_gap_ext_adv_set_addr(instance, &addr );
assert (rc == 0);
### Parameter Configuration
memset(&adv_fields, 0, sizeof(adv_fields));
adv_fields.name = (const uint8_t *)"Periodic ADV";
adv_fields.name_len = strlen((char *)adv_fields.name);
#### For Extended Advertisement
/* Default to legacy PDUs size, mbuf chain will be increased if needed
*/
data = os_msys_get_pkthdr(BLE_HCI_MAX_ADV_DATA_LEN, 0);
```c
params.own_addr_type = own_addr_type;
params.primary_phy = BLE_HCI_LE_PHY_1M; // Primary advertising PHY is set to 1M
params.secondary_phy = BLE_HCI_LE_PHY_2M; // Secondary advertising PHY is set to 2M
params.tx_power = 0x7f; // Use controller-selected default TX power
params.sid = 2; // Advertising set ID is assigned value 2
```
The periodic advertisement uses a non-connectable advertising mode. `memset (&params, 0, sizeof(params))` initialises params to 0. This also sets `params.connectable` to 0.
#### For Periodic Advertisement
```c
memset(&pparams, 0, sizeof(pparams));
pparams.include_tx_power = 0; // TX power is not included in advertising PDU
pparams.itvl_min = BLE_GAP_PERIODIC_ITVL_MS(120); // Minimum periodic advertising interval of 120ms
pparams.itvl_max = BLE_GAP_PERIODIC_ITVL_MS(240); // Maximum periodic advertising interval of 240ms
```
### Setting Advertisement Data
The extended advertisement data (device name) is set using `ble_hs_adv_set_fields_mbuf()`:
```c
data = os_msys_get_pkthdr(BLE_HS_ADV_MAX_FIELD_SZ, 0);
assert(data);
rc = ble_hs_adv_set_fields_mbuf(&adv_fields, data);
@@ -243,78 +233,54 @@ start_periodic_adv(void)
rc = ble_gap_ext_adv_set_data(instance, data);
assert(rc == 0);
```
/* configure periodic advertising */
memset(&pparams, 0, sizeof(pparams));
pparams.include_tx_power = 0;
pparams.itvl_min = BLE_GAP_ADV_ITVL_MS(120);
pparams.itvl_max = BLE_GAP_ADV_ITVL_MS(240);
rc = ble_gap_periodic_adv_configure(instance, &pparams);
assert(rc == 0);
The periodic advertising data is set separately as raw data:
```c
data = os_msys_get_pkthdr(sizeof(periodic_adv_raw_data), 0);
assert(data);
rc = os_mbuf_append(data, periodic_adv_raw_data, sizeof(periodic_adv_raw_data));
assert(rc == 0);
#if MYNEWT_VAL(BLE_PERIODIC_ADV_ENH)
rc = ble_gap_periodic_adv_set_data(instance, data, NULL);
#else
rc = ble_gap_periodic_adv_set_data(instance, data);
#endif
assert (rc == 0);
```
### Starting Periodic and Extended Advertising
Periodic advertising is started first, then extended advertising. When `BLE_PERIODIC_ADV_ENH` is enabled, the enhanced API is used which accepts additional parameters (e.g., `include_adi`):
```c
/* start periodic advertising */
assert (rc == 0 rc = ble_gap_periodic_adv_start(instance);
);
#if MYNEWT_VAL(BLE_PERIODIC_ADV_ENH)
#if CONFIG_EXAMPLE_PERIODIC_ADV_ENH
eparams.include_adi = 1;
#endif
rc = ble_gap_periodic_adv_start(instance, &eparams);
#else
rc = ble_gap_periodic_adv_start(instance);
#endif
assert (rc == 0);
/* start advertising */
rc = ble_gap_ext_adv_start(instance, 0, 0);
assert (rc == 0);
MODLOG_DFLT(INFO, "instance %u started (periodic)\n", instance);
}
```
The periodic advertisement uses a non-connectable advertising mode. `memset (&params, 0, sizeof(params))` initialises params to 0. This also sets `params.connectable` to 0.
## Parameter Configuration
The below snippets represent the parameter configuration for extended and periodic advertisement.
### For Extended Advertisement
```c
params.own_addr_type = BLE_OWN_ADDR_RANDOM; //Own address type is set to Random
params.primary_phy = BLE_HCI_LE_PHY_1M; // Primary advertising PHY is set to 1M
params.secondary_phy = BLE_HCI_LE_PHY_2M; // Secondary advertising PHY is set to 2M
params.sid = 2; // Advertising set Id is assigned with value 2.
```
### For Periodic Advertisment
```c
memset(&pparams, 0, sizeof(pparams));
pparams.include_tx_power = 0; // Indicates that TX power is not included in advertising PDU
pparams.itvl_min = BLE_GAP_ADV_ITVL_MS(120); // Minimum advertising interval of 240ms
pparams.itvl_max = BLE_GAP_ADV_ITVL_MS(240); //Maximum advertising interval of 480ms
```
Periodic advertisement is started for a particular advertisement instance by calling the API `ble_gap_periodic_adv_start(instance)`. This function takes instance-id as an input parameter. It defines the hci command by initializing the command parameters which are represented in the following lines.
```c
struct ble_hci_le_set_periodic_adv_enable_cp cmd;
cmd.enable = 0x01;
cmd.adv_handle = instance;
```
Extended advertising is invoked for a particular instance using the API call `ble_gap_ext_adv_start(instance, 0, 0)`.Instance-id, duration, and max_events are input parameters for this API call respectively.
Duration represents the time for which the adverteiment will take place. Upon expiration, the advertising procedure ends, and the BLE_GAP_EVENT_ADV_COMPLETE event is reported.0 value is used for no expiration.
max_events Number of advertising events that should be sent before advertising ends and a BLE_GAP_EVENT_ADV_COMPLETE event is reported.0 value is used for no limit.
`ble_gap_ext_adv_start(instance, 0, 0)` starts extended advertising with no duration limit and no maximum event count.
## Conclusion
This Walkthrough covers the code explanation of the BLE_PERIODIC_ADV. The following points are concluded through this walkthrough.
This walkthrough covers the code explanation of the BLE_PERIODIC_ADV example. The following points are concluded:
1. Periodic advertising allows the scanner to sync with the advertiser so the scanner and advertiser would wake up at the same time.
2. Periodic advertisment uses NRPA (Non Resolvable private adress). It is a randomly generated address that changes periodically to prevent long-term tracking of a device.
3. Extended advertising is used to indicate to the scanner that the advertiser is utilizing periodic advertising. Therefore, periodic advertising is started before extended advertising so that the scanner and advertiser can synchronize their actions and operate at the same time.
1. Periodic advertising allows the scanner to sync with the advertiser so the scanner and advertiser wake up at the same time.
2. The example uses `ble_hs_id_infer_auto()` to dynamically determine the correct address type, ensuring compatibility across all supported ESP32 variants.
3. Extended advertising is used to indicate to the scanner that the advertiser is utilizing periodic advertising. Therefore, periodic advertising is started before extended advertising so that the scanner and advertiser can synchronize their actions.
4. When `BLE_PERIODIC_ADV_ENH` is enabled, the enhanced periodic advertising API allows additional features such as ADI (Advertising Data Info) inclusion.
@@ -8,11 +8,11 @@
This example performs passive scan for non-connectable non-scannable extended advertisement, it then establishes the periodic sync with the advertiser and then listens to the periodic advertisements.
It uses ESP32C3's Bluetooth controller and NimBLE stack based BLE host.
It uses Bluetooth controller and NimBLE stack based BLE host.
This example aims at understanding BLE periodic sync establishment and periodic advertisement reports.
To test this demo, use any periodic advertiser with uses extended adv data as "ESP_PERIODIC_ADV".
To test this demo, use the `ble_periodic_adv` example or any periodic advertiser with SID 2.
Note :
@@ -30,23 +30,11 @@ idf.py set-target <chip_name>
### Hardware Required
* A development board with ESP32/ESP32-C3 SoC (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.)
* A development board with a supported SoC (e.g., ESP32-C3, ESP32-C6, ESP32-H2, ESP32-S3, etc.)
* A USB cable for Power supply and programming
See [Development Boards](https://www.espressif.com/en/products/devkits) for more information about it.
### Configure the Project
Open the project configuration menu:
```bash
idf.py menuconfig
```
In the `Example Configuration` menu:
* Change the `Peer Address` option if needed.
### Build and Flash
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
@@ -1,2 +1,3 @@
idf_component_register(SRCS "main.c"
INCLUDE_DIRS ".")
INCLUDE_DIRS "."
PRIV_REQUIRES bt nvs_flash)
@@ -1,14 +1,4 @@
menu "Example Configuration"
config EXAMPLE_EXTENDED_ADV
bool
depends on SOC_BLE_50_SUPPORTED && BT_NIMBLE_50_FEATURE_SUPPORT
default y if SOC_ESP_NIMBLE_CONTROLLER
select BT_NIMBLE_EXT_ADV
prompt "Enable Extended Adv"
help
Use this option to enable extended advertising in the example.
If this option is disabled, ensure config BT_NIMBLE_EXT_ADV is
also disabled from NimBLE stack menuconfig
config EXAMPLE_PERIODIC_ADV_ENH
bool
@@ -1,3 +1,2 @@
dependencies:
nimble_central_utils:
path: ${IDF_PATH}/examples/bluetooth/nimble/common/nimble_central_utils
## IDF Component Manager Manifest File
dependencies: {}
@@ -1,23 +1,28 @@
/*
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <assert.h>
#include <string.h>
#include <stdint.h>
#include <stdio.h>
#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 "periodic_sync.h"
#include "host/ble_gap.h"
#include "host/util/util.h"
#include "services/gap/ble_svc_gap.h"
#include "modlog/modlog.h"
static const char *tag = "NimBLE_BLE_PERIODIC_SYNC";
static int synced = 0;
static int periodic_sync_gap_event(struct ble_gap_event *event, void *arg);
void ble_store_config_init(void);
@@ -26,7 +31,7 @@ static void
periodic_sync_scan(void)
{
uint8_t own_addr_type;
struct ble_gap_disc_params disc_params = {0};
struct ble_gap_ext_disc_params disc_params = {0};
int rc;
/* Figure out address to use while advertising (no privacy for now) */
@@ -36,25 +41,11 @@ periodic_sync_scan(void)
return;
}
/* Tell the controller to filter duplicates; we don't want to process
* repeated advertisements from the same device.
*/
disc_params.filter_duplicates = 0;
/**
* Perform a passive scan. I.e., don't send follow-up scan requests to
* each advertiser.
*/
disc_params.passive = 1;
/* Use defaults for the rest of the parameters. */
disc_params.itvl = 0;
disc_params.window = 0;
disc_params.filter_policy = 0;
disc_params.limited = 0;
rc = ble_gap_disc(own_addr_type, BLE_HS_FOREVER, &disc_params,
periodic_sync_gap_event, NULL);
rc = ble_gap_ext_disc(own_addr_type, 0, 0,
0, 0, 0, &disc_params, &disc_params,
periodic_sync_gap_event, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "Error initiating GAP discovery procedure; rc=%d\n",
rc);
@@ -63,32 +54,31 @@ periodic_sync_scan(void)
void print_periodic_sync_data(struct ble_gap_event *event)
{
MODLOG_DFLT(DEBUG, "status : %d\nperiodic_sync_handle : %d\nsid : %d\n", event->periodic_sync.status, event->periodic_sync.sync_handle, event->periodic_sync.sid);
MODLOG_DFLT(DEBUG, "adv addr : ");
for (int i = 0; i < 6; i++) {
MODLOG_DFLT(DEBUG, "%d ", event->periodic_sync.adv_addr.val[i]);
}
MODLOG_DFLT(DEBUG, "\nadv_phy : %s\n", event->periodic_sync.adv_phy == 1 ? "1m" : (event->periodic_sync.adv_phy == 2 ? "2m" : "coded"));
MODLOG_DFLT(DEBUG, "per_adv_ival : %d\n", event->periodic_sync.per_adv_ival);
MODLOG_DFLT(DEBUG, "adv_clk_accuracy : %d\n", event->periodic_sync.adv_clk_accuracy);
MODLOG_DFLT(INFO, "status : %d\nperiodic_sync_handle : %d\nsid : %d\n",
event->periodic_sync.status, event->periodic_sync.sync_handle, event->periodic_sync.sid);
MODLOG_DFLT(INFO, "adv addr : ");
ESP_LOG_BUFFER_HEX("NimBLE", event->periodic_sync.adv_addr.val, 6);
MODLOG_DFLT(INFO, "adv_phy : %s\n", event->periodic_sync.adv_phy == 1 ? "1m" : (event->periodic_sync.adv_phy == 2 ? "2m" : "coded"));
MODLOG_DFLT(INFO, "per_adv_ival : %d\n", event->periodic_sync.per_adv_ival);
MODLOG_DFLT(INFO, "adv_clk_accuracy : %d\n", event->periodic_sync.adv_clk_accuracy);
}
void print_periodic_adv_data(struct ble_gap_event *event)
{
MODLOG_DFLT(DEBUG, "sync_handle : %d\n", event->periodic_report.sync_handle);
MODLOG_DFLT(DEBUG, "tx_power : %d\n", event->periodic_report.tx_power);
MODLOG_DFLT(DEBUG, "rssi : %d\n", event->periodic_report.rssi);
MODLOG_DFLT(DEBUG, "data_status : %d\n", event->periodic_report.data_status);
MODLOG_DFLT(DEBUG, "data_length : %d\n", event->periodic_report.data_length);
MODLOG_DFLT(DEBUG, "data : ");
for (int i = 0; i < event->periodic_report.data_length; i++) {
MODLOG_DFLT(DEBUG, "%c", ((char *)event->periodic_report.data)[i]);
}
MODLOG_DFLT(DEBUG, "\n");
MODLOG_DFLT(INFO, "sync_handle : %d\n", event->periodic_report.sync_handle);
MODLOG_DFLT(INFO, "tx_power : %d\n", event->periodic_report.tx_power);
MODLOG_DFLT(INFO, "rssi : %d\n", event->periodic_report.rssi);
MODLOG_DFLT(INFO, "data_status : %d\n", event->periodic_report.data_status);
MODLOG_DFLT(INFO, "data_length : %d\n", event->periodic_report.data_length);
MODLOG_DFLT(INFO, "data : ");
ESP_LOG_BUFFER_HEX("NimBLE", event->periodic_report.data, event->periodic_report.data_length);
MODLOG_DFLT(INFO, "\n");
}
void print_periodic_sync_lost_data(struct ble_gap_event *event)
{
MODLOG_DFLT(DEBUG, "sync_handle : %d\n", event->periodic_sync_lost.sync_handle);
MODLOG_DFLT(DEBUG, "reason : %s\n", event->periodic_sync_lost.reason == 13 ? "timeout" : (event->periodic_sync_lost.reason == 14 ? "terminated locally" : "Unknown reason"));
MODLOG_DFLT(INFO, "sync_handle : %d\n", event->periodic_sync_lost.sync_handle);
MODLOG_DFLT(INFO, "reason : %s\n", event->periodic_sync_lost.reason == 13 ? "timeout" : (event->periodic_sync_lost.reason == 14 ? "terminated locally" : "Unknown reason"));
}
/**
* The nimble host executes this callback when a GAP event occurs. The
@@ -107,19 +97,16 @@ void print_periodic_sync_lost_data(struct ble_gap_event *event)
static int
periodic_sync_gap_event(struct ble_gap_event *event, void *arg)
{
(void)arg;
switch (event->type) {
#if CONFIG_EXAMPLE_EXTENDED_ADV
case BLE_GAP_EVENT_EXT_DISC:
case BLE_GAP_EVENT_EXT_DISC: {
/* An advertisement report was received during GAP discovery. */
struct ble_gap_ext_disc_desc *disc = ((struct ble_gap_ext_disc_desc *)(&event->ext_disc));
const struct ble_gap_ext_disc_desc *disc = &event->ext_disc;
if (disc->sid == 2 && synced == 0) {
synced++;
ble_addr_t addr;
uint8_t adv_sid;
struct ble_gap_periodic_sync_params params;
struct ble_gap_periodic_sync_params params = {0};
int rc;
memcpy(&addr, &disc->addr, sizeof(disc->addr));
memcpy(&adv_sid, &disc->sid, sizeof(disc->sid));
synced++;
params.skip = 10;
params.sync_timeout = 1000;
@@ -129,10 +116,11 @@ periodic_sync_gap_event(struct ble_gap_event *event, void *arg)
or the Data-Id is updated by the advertiser */
params.filter_duplicates = 1;
#endif
rc = ble_gap_periodic_adv_sync_create(&addr, adv_sid, &params, periodic_sync_gap_event, NULL);
rc = ble_gap_periodic_adv_sync_create(&disc->addr, disc->sid, &params, periodic_sync_gap_event, NULL);
assert(rc == 0);
}
return 0;
}
case BLE_GAP_EVENT_PERIODIC_REPORT:
MODLOG_DFLT(INFO, "Periodic adv report event: \n");
print_periodic_adv_data(event);
@@ -141,12 +129,19 @@ periodic_sync_gap_event(struct ble_gap_event *event, void *arg)
MODLOG_DFLT(INFO, "Periodic sync lost\n");
print_periodic_sync_lost_data(event);
synced = 0;
/* Restart scanning to re-sync */
periodic_sync_scan();
return 0;
case BLE_GAP_EVENT_PERIODIC_SYNC:
case BLE_GAP_EVENT_PERIODIC_SYNC:
MODLOG_DFLT(INFO, "Periodic sync event : \n");
print_periodic_sync_data(event);
if (event->periodic_sync.status != 0) {
synced = 0;
} else {
/* Cancel scanning since sync is established */
ble_gap_disc_cancel();
}
return 0;
#endif
default:
return 0;
}
@@ -166,7 +161,7 @@ periodic_sync_on_sync(void)
rc = ble_hs_util_ensure_addr(0);
assert(rc == 0);
/* Begin scanning for a peripheral to connect to. */
/* Begin scanning for a periodic advertiser to sync with. */
periodic_sync_scan();
}
@@ -182,7 +177,6 @@ void periodic_sync_host_task(void *param)
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) {
@@ -202,17 +196,9 @@ app_main(void)
ble_hs_cfg.sync_cb = periodic_sync_on_sync;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
/* Initialize data structures to track connected peers. */
#if MYNEWT_VAL(BLE_INCL_SVC_DISCOVERY) || MYNEWT_VAL(BLE_GATT_CACHING_INCLUDE_SERVICES)
rc = peer_init(MYNEWT_VAL(BLE_MAX_CONNECTIONS), 64, 64, 64, 64);
assert(rc == 0);
#else
rc = peer_init(MYNEWT_VAL(BLE_MAX_CONNECTIONS), 64, 64, 64);
assert(rc == 0);
#endif
#if CONFIG_BT_NIMBLE_GAP_SERVICE
/* Set the default device name. */
rc = ble_svc_gap_device_name_set("nimble_periodic_sync");
int rc = ble_svc_gap_device_name_set("nimble_periodic_sync");
assert(rc == 0);
#endif
@@ -1,26 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#ifndef H_BLE_PERIODIC_SYNC_
#define H_BLE_PERIODIC_SYNC_
#include "modlog/modlog.h"
#include "esp_central.h"
#ifdef __cplusplus
extern "C" {
#endif
struct ble_hs_adv_fields;
struct ble_gap_conn_desc;
struct ble_hs_cfg;
union ble_store_value;
union ble_store_key;
#ifdef __cplusplus
}
#endif
#endif
@@ -10,6 +10,5 @@ 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_EXAMPLE_EXTENDED_ADV=y
CONFIG_BT_NIMBLE_EXT_ADV=y
CONFIG_BT_NIMBLE_MAX_PERIODIC_SYNCS=1
@@ -10,7 +10,6 @@ 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_EXAMPLE_EXTENDED_ADV=y
CONFIG_BT_NIMBLE_EXT_ADV=y
CONFIG_BT_NIMBLE_MAX_PERIODIC_SYNCS=1
@@ -10,7 +10,6 @@ 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_EXAMPLE_EXTENDED_ADV=y
CONFIG_BT_NIMBLE_EXT_ADV=y
CONFIG_BT_NIMBLE_MAX_PERIODIC_SYNCS=1
CONFIG_BT_NIMBLE_MAX_CONNECTIONS=1
@@ -2,42 +2,44 @@
## Introduction
In this tutorial, the ble_periodic_sync example code for the espressif chipsets with BLE5.0 support is reviewed. This example aims at understanding BLE periodic sync establishment and periodic advertisement reports.It supports the chips like ESP32-C2,
ESP32-C3, ESP32-C6,ESP32-H2, and ESP32-S3.
In this tutorial, the ble_periodic_sync example code for the Espressif chipsets with BLE 5.0 support is reviewed. This example demonstrates BLE periodic sync establishment and periodic advertisement reports. It performs a passive extended scan for non-connectable non-scannable extended advertisements, establishes periodic sync with the advertiser, and then listens to the periodic advertisement data.
## Includes
This example is located in the examples folder of the ESP-IDF under the [ble_periodic_sync/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 <assert.h>
#include <string.h>
#include <stdint.h>
#include <stdio.h>
#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 "periodic_sync.h"
#include "host/ble_gap.h"
#include "host/util/util.h"
#include "services/gap/ble_svc_gap.h"
#include "modlog/modlog.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`, `“periodic_sync.h` which expose the BLE APIs required to implement this example.
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_gap.h"`, and `"ble_svc_gap.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.
* `periodic_sync.h`:It includes the code containing forward declarations of structures for storing host_advertsing_fileds, host configrations and connection descriptors based on weather macro `H_BLE_PERIODIC_SYNC_` is defined.Also, it includes the unions for storing CCCD, Security values, and Keys for their lookups.
* `ble_hs.h`: Defines the functionalities to handle the host event.
* `ble_gap.h`: Defines GAP related APIs for advertising, scanning, and periodic sync.
* `ble_svc_gap.h`: Defines the macros for device name, and device appearance and declares the function to set them.
* `modlog/modlog.h`: Provides logging macros used throughout the example.
## Main Entry Point
The programs entry point is the app_main() function:
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) {
@@ -57,88 +59,24 @@ app_main(void)
ble_hs_cfg.sync_cb = periodic_sync_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);
#if CONFIG_BT_NIMBLE_GAP_SERVICE
/* Set the default device name. */
rc = ble_svc_gap_device_name_set("nimble_periodic_sync");
int rc = ble_svc_gap_device_name_set("nimble_periodic_sync");
assert(rc == 0);
#endif
/* XXX Need to have template for store */
ble_store_config_init();
nimble_port_freertos_init(periodic_sync_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 );
```
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.
## 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:
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).
```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);
os_msys_init();
void ble_store_ram_init(void);
/* XXX Need to have a template for store */
ble_store_ram_init();
#endif
/* Initialize the host */
ble_hs_init();
return ESP_OK;
}
```
The host is configured by setting up the callbacks for Stack-reset, Stack-sync, and Storage status
The host is configured by setting up the callbacks for Stack-reset, Stack-sync, and Storage status:
```c
ble_hs_cfg.reset_cb = periodic_sync_on_reset;
@@ -146,181 +84,111 @@ ble_hs_cfg.sync_cb = periodic_sync_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. 'blecent_phy' is passed as the default device name to this function.
The device name is set using `ble_svc_gap_device_name_set()`, guarded by `CONFIG_BT_NIMBLE_GAP_SERVICE` for cases where the GAP service may be disabled:
```c
#if CONFIG_BT_NIMBLE_GAP_SERVICE
rc = ble_svc_gap_device_name_set("nimble_periodic_sync");
```
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();
assert(rc == 0);
#endif
```
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()`.
The main function calls `ble_store_config_init()` to configure the host by setting up the storage callbacks which handle the read, write, and deletion of security material.
The main function ends by creating a task where nimble will run using `nimble_port_freertos_init()`:
```c
nimble_port_freertos_init(periodic_sync_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 to handle the default queue, it is easier to create a separate task.
## Periodic Synchronisation scanning
## Host Sync Callback
When the NimBLE host and controller are synced, the `periodic_sync_on_sync()` callback is invoked. It ensures a valid identity address is set and begins scanning:
This example performs a passive scan for non-connectable non-scannable extended advertisements, it then establishes the periodic sync with the advertiser and then listens to the periodic advertisements.
variable `own_addr_type` refers to the address type used by a BLE device to identify itself during communication. Its valid values are :
```c
BLE_OWN_ADDR_PUBLIC
static void
periodic_sync_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);
BLE_OWN_ADDR_RANDOM
BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT
BLE_OWN_ADDR_RPA_RANDOM_DEFAULT
/* Begin scanning for a periodic advertiser to sync with. */
periodic_sync_scan();
}
```
Discovery parameters are defined in the object disc_params which includes particulars of the discovery procedure such as scan interval, scan window, filter_policy, and flags to decide whether to use a limited discovery procedure, passive scanning, and enables duplicate filtering.
## Periodic Synchronisation Scanning
This example performs a passive extended scan for non-connectable non-scannable extended advertisements. It uses `ble_gap_ext_disc()` (extended discovery) rather than the legacy `ble_gap_disc()` API to receive extended advertisement reports that contain periodic advertising synchronization information.
```c
static void
periodic_sync_scan(void)
{
uint8_t own_addr_type;
struct ble_gap_disc_params disc_params;
struct ble_gap_ext_disc_params disc_params = {0};
int rc;
/* Figure out address to use while advertising (no privacy for now) */
/* Figure out address to use while scanning (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;
}
/* Tell the controller to filter duplicates; we don't want to process
* repeated advertisements from the same device.
*/
disc_params.filter_duplicates = 0;
/**
* Perform a passive scan. I.e., don't send follow-up scan requests to
* each advertiser.
*/
disc_params.passive = 1;
/* Use defaults for the rest of the parameters. */
disc_params.itvl = 0;
disc_params.window = 0;
disc_params.filter_policy = 0;
disc_params.limited = 0;
rc = ble_gap_disc(own_addr_type, BLE_HS_FOREVER, &disc_params,
periodic_sync_gap_event, NULL);
rc = ble_gap_ext_disc(own_addr_type, 0, 0,
0, 0, 0, &disc_params, &disc_params,
periodic_sync_gap_event, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "Error initiating GAP discovery procedure; rc=%d\n",
rc);
}
}
```
## Address Generation To establish the connection
```c
/* 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;
}
```
The above function call `ble_hs_id_infer_auto(0,own_addr_type)` figures out the address to use while scanning. Depending on privacy parameters public address or private address can be assigned. Public address types are `BLE_OWN_ADDR_RANDOM`, `BLE_OWN_ADDR_PUBLIC`, and private address types are `BLE_OWN_ADDR_RPA_RANDOM_DEFAULT`, `BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT`.
1st parameter of the function `ble_hs_id_infer_auto` defines the privacy.0 value indicates that privacy is not used.
## Configuration of discovery parameters
`disc_params.filter_duplicates = 0;`
Here we are telling the controller to filter the repeated advertisements from the same device.
`disc_params.passive = 1;`
To perform passive scanning `passive` field is set to 1. Other discovery parameters are set to default as follows.
```c
/* Use defaults for the rest of the parameters. */
disc_params.itvl = 0;
disc_params.window = 0;
disc_params.filter_policy = 0;
disc_params.limited = 0;
```
## Perform Discovery Procedures
By utilizing the ble_gap_disc function and associated callbacks, you can initiate and manage the discovery procedure to scan for nearby BLE devices and gather information about their presence, capabilities, and services.
```c
rc = ble_gap_disc(own_addr_type, BLE_HS_FOREVER, &disc_params,
periodic_sync_gap_event, NULL);
```
Above mentioned function `ble_gap_disc` performs the limited or general discovery procedures.
It received the following parameter.
1. `own_addr_type`: This refers to the address type that the stack should utilize when sending scan requests.
2. `BLE_HS_FOREVER`: This refers to the duration of the discovery procedure. Once the expiration time is reached, the procedure concludes, and a BLE_GAP_EVENT_DISC_COMPLETE event is generated and reported. The duration is measured in milliseconds. If you want the procedure to have no expiration, you can specify BLE_HS_FOREVER. Alternatively, if you wish to use the default expiration time defined by the stack, you can specify 0.
3. `disc_params`: This refers to discovery arguments.
4. `periodic_sync_gap_event`:You can assign a callback function to be associated with the discovery procedure. This callback function will be responsible for handling and processing advertising reports received during the discovery process, as well as any events related to the termination of the discovery procedure.
5. `NULL`: The optional argument to pass to the callback function.
Key aspects:
- `ble_hs_id_infer_auto()` determines the best address type (public or random) to use for scanning.
- `ble_gap_ext_disc_params` is used instead of `ble_gap_disc_params` since we need extended discovery to receive `BLE_GAP_EVENT_EXT_DISC` events.
- `disc_params.passive = 1` sets passive scanning the scanner only listens for advertisements without sending scan requests.
- `ble_gap_ext_disc()` initiates the extended discovery procedure with the configured parameters.
## GAP Events
The nimble host executes this callback when a GAP event occurs. The application associates a GAP event callback with each connection that is established. periodic_sync uses the same callback for all connections.
4 types of gap events are handled in the function`periodic_sync_gap_event`. Events are the following:
The nimble host executes the `periodic_sync_gap_event` callback when a GAP event occurs. Four types of gap events are handled:
1. BLE_GAP_EVENT_EXT_DISC
2. BLE_GAP_EVENT_PERIODIC_REPORT
3. BLE_GAP_EVENT_PERIODIC_SYNC_LOST
4. BLE_GAP_EVENT_PERIODIC_SYNC
1. `BLE_GAP_EVENT_EXT_DISC`
2. `BLE_GAP_EVENT_PERIODIC_REPORT`
3. `BLE_GAP_EVENT_PERIODIC_SYNC_LOST`
4. `BLE_GAP_EVENT_PERIODIC_SYNC`
```c
static int
periodic_sync_gap_event(struct ble_gap_event *event, void *arg)
{
(void)arg;
switch (event->type) {
#if CONFIG_EXAMPLE_EXTENDED_ADV
case BLE_GAP_EVENT_EXT_DISC:
/* An advertisment report was received during GAP discovery. */
struct ble_gap_ext_disc_desc *disc = ((struct ble_gap_ext_disc_desc *)(&event->disc));
case BLE_GAP_EVENT_EXT_DISC: {
/* An advertisement report was received during GAP discovery. */
const struct ble_gap_ext_disc_desc *disc = &event->ext_disc;
if (disc->sid == 2 && synced == 0) {
synced++;
const ble_addr_t addr;
uint8_t adv_sid;
struct ble_gap_periodic_sync_params params;
struct ble_gap_periodic_sync_params params = {0};
int rc;
memcpy((void *)&addr, (void *)&disc->addr, sizeof(disc->addr));
memcpy(&adv_sid, &disc->sid, sizeof(disc->sid));
synced++;
params.skip = 10;
params.sync_timeout = 1000;
rc = ble_gap_periodic_adv_sync_create(&addr, adv_sid, &params, periodic_sync_gap_event, NULL);
#if CONFIG_EXAMPLE_PERIODIC_ADV_ENH
params.filter_duplicates = 1;
#endif
rc = ble_gap_periodic_adv_sync_create(&disc->addr, disc->sid, &params, periodic_sync_gap_event, NULL);
assert(rc == 0);
}
return 0;
}
case BLE_GAP_EVENT_PERIODIC_REPORT:
MODLOG_DFLT(INFO, "Periodic adv report event: \n");
print_periodic_adv_data(event);
@@ -329,12 +197,19 @@ periodic_sync_gap_event(struct ble_gap_event *event, void *arg)
MODLOG_DFLT(INFO, "Periodic sync lost\n");
print_periodic_sync_lost_data(event);
synced = 0;
/* Restart scanning to re-sync */
periodic_sync_scan();
return 0;
case BLE_GAP_EVENT_PERIODIC_SYNC:
case BLE_GAP_EVENT_PERIODIC_SYNC:
MODLOG_DFLT(INFO, "Periodic sync event : \n");
print_periodic_sync_data(event);
if (event->periodic_sync.status != 0) {
synced = 0;
} else {
/* Cancel scanning since sync is established */
ble_gap_disc_cancel();
}
return 0;
#endif
default:
return 0;
}
@@ -343,25 +218,28 @@ periodic_sync_gap_event(struct ble_gap_event *event, void *arg)
### BLE_GAP_EVENT_EXT_DISC
An extended advertising report is received during this event. Once the event is received it is handled by calling the function `ble_gap_periodic_adv_sync_create()`.This method performs the synchronization procedure with periodic advertisers.
An extended advertising report is received during this event. The handler checks if the advertisement's SID (Set ID) matches the expected value (2) and if we haven't already synced. If both conditions are met, it creates a periodic sync using `ble_gap_periodic_adv_sync_create()` with the advertiser's address and SID directly from the extended discovery descriptor. When `CONFIG_EXAMPLE_PERIODIC_ADV_ENH` is enabled, duplicate filtering is turned on so periodic reports are only delivered when the advertising data changes or the Data-Id is updated.
### BLE_GAP_EVENT_PERIODIC_REPORT
Periodic advertisement report is printed in this case.`print_periodic_adv_data()` performs the required printing task. It includes a sync handle, transmit power, data status, data length, and data itself.
Periodic advertisement report data is printed. `print_periodic_adv_data()` displays the sync handle, transmit power, RSSI, data status, data length, and data contents using `ESP_LOG_BUFFER_HEX`.
### BLE_GAP_EVENT_PERIODIC_SYNC_LOST:
### BLE_GAP_EVENT_PERIODIC_SYNC_LOST
Periodic synchronization lost data is printed in this case.`print_periodic_sync_lost_data()` prints the data which includes the sync handle and reason for data loss. here are 2 codes used for data loss reasons.
1. 13: Timeout
2. 14: Terminated locally
Periodic synchronization lost data is printed. `print_periodic_sync_lost_data()` prints the sync handle and reason for sync loss:
- 13: Timeout
- 14: Terminated locally
The `synced` flag is reset to 0 and `periodic_sync_scan()` is called to restart scanning, allowing the device to discover and re-sync with a periodic advertiser.
### BLE_GAP_EVENT_PERIODIC_SYNC
Periodic synchronization data is printed in this. `print_periodic_sync_data()` prints the required data. It includes a periodic sync handle, advertising address, advertising physical channel, periodic advertising interval, and advertiser clock accuracy.
Periodic synchronization establishment data is printed. `print_periodic_sync_data()` prints the status, periodic sync handle, SID, advertising address, advertising PHY, periodic advertising interval, and advertiser clock accuracy. If the sync status is non-zero (failure), the `synced` flag is reset to allow retry. On successful sync establishment (status == 0), `ble_gap_disc_cancel()` is called to stop scanning since a periodic sync has been established and further scanning is unnecessary.
## Conclusion
1. This Walkthrough covers the code explanation for the BLE_PERIODIC_SYNC example.
2. In this example, a passive scan is conducted to detect non-connectable and non-scannable extended advertisements from nearby devices.
3. Once such an advertisement is found, the example establishes a periodic sync with the advertising device. After successfully establishing the periodic sync, the example starts listening to the periodic advertisements transmitted by the advertiser.
4. Also walkthrough includes the periodic synchronization gap events and their handling.
1. This walkthrough covers the code explanation for the BLE_PERIODIC_SYNC example.
2. In this example, an extended passive scan is conducted to detect non-connectable and non-scannable extended advertisements from nearby devices.
3. Once such an advertisement with a matching SID is found, the example establishes a periodic sync with the advertising device. After successfully establishing the periodic sync, the example starts listening to the periodic advertisements transmitted by the advertiser.
4. The example uses `ble_hs_id_infer_auto()` to dynamically determine the correct address type, ensuring compatibility across all supported ESP32 variants.
5. The walkthrough also covers the periodic synchronization gap events and their handling.
@@ -267,12 +267,10 @@ ext_blecent_should_connect(const struct ble_gap_ext_disc_desc *disc)
if (!ad_struct_len || (offset + ad_struct_len + 1 > disc->length_data)) {
break;
}
/* Search if LE PHY UUID is advertised */
if (disc->data[offset] == 0x03 && disc->data[offset + 1] == 0x03) {
if ( disc->data[offset + 2] == 0xAB && disc->data[offset + 3] == 0xF2 ) {
return 1;
}
}
offset += ad_struct_len + 1;
@@ -27,7 +27,7 @@ These `includes` are required for the FreeRTOS and underlying system components
* `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_svc_gap.h`:Defines the macros for device name ,device appearance and declare the function to set them.
* `phy_cent.h`: Defines the macro name `LE_PHY_UUID16` and `LE_PHY_CHR_UUID16`.
@@ -48,19 +48,30 @@ app_main(void)
}
ESP_ERROR_CHECK(ret);
nimble_port_init();
ret = nimble_port_init();
if (ret != ESP_OK) {
MODLOG_DFLT(ERROR, "Failed to init nimble %d \n", ret);
return;
}
/* Configure the host. */
ble_hs_cfg.reset_cb = blecent_on_reset;
ble_hs_cfg.sync_cb = blecent_on_sync;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
/* Initialize data structures to track connected peers. */
#if MYNEWT_VAL(BLE_INCL_SVC_DISCOVERY) || MYNEWT_VAL(BLE_GATT_CACHING_INCLUDE_SERVICES)
rc = peer_init(MYNEWT_VAL(BLE_MAX_CONNECTIONS), 64, 64, 64, 64);
assert(rc == 0);
#else
rc = peer_init(MYNEWT_VAL(BLE_MAX_CONNECTIONS), 64, 64, 64);
assert(rc == 0);
#endif
#if CONFIG_BT_NIMBLE_GAP_SERVICE
/* Set the default device name. */
rc = ble_svc_gap_device_name_set("blecent-phy");
assert(rc == 0);
#endif
/* XXX Need to have template for store */
ble_store_config_init();
@@ -191,10 +202,10 @@ nimble_port_freertos_init(blecent_host_task);
## Intializaion of LE PHY to Default 1M PHY .
1M PHY is the default PHY for BLE devices which enables it to provide a data rate of 1 Mbps. It is used while establishing the connection between devices and maintains backward compatibility with all those devices that don't have BLE5.0 support.`set_default_le_phy_before_conn()` function set default LE PHY before establishing a connection.
1M PHY is the default PHY for BLE devices which enables it to provide a data rate of 1 Mbps. It is used while establishing the connection between devices and maintains backward compatibility with all those devices that don't have BLE5.0 support.`set_default_le_phy()` function set default LE PHY before establishing a connection.
```c
void set_default_le_phy_before_conn(uint8_t tx_phys_mask, uint8_t rx_phys_mask)
void set_default_le_phy(uint8_t tx_phys_mask, uint8_t rx_phys_mask)
{
int rc = ble_gap_set_prefered_default_le_phy(tx_phys_mask, rx_phys_mask);
if (rc == 0)
@@ -207,32 +218,11 @@ void set_default_le_phy_before_conn(uint8_t tx_phys_mask, uint8_t rx_phys_mask)
}
```
## Setting LE PHY to preferred LE PHY.
## Connecting on Different PHYs
2M PHY is introduced in BLE5.0 to increase the symbol rate at the physical layer. It provides a symbol rate of 2 Mega symbols per second where each symbol corresponds to a single bit. This allows the user to double the number of bits sent over the air during a given period, or conversely reduce energy consumption for a given amount of data by having the necessary transmit time.
2M PHY is introduced in BLE5.0 to increase the symbol rate at the physical layer. It provides a symbol rate of 2 Mega symbols per second where each symbol corresponds to a single bit. This allows the user to double the number of bits sent over the air during a given period, or conversely reduce energy consumption for a given amount of data by having the necessary transmit time.
The following lines change the default LE PHY to 2M PHY.
` tx_phys_mask = BLE_HCI_LE_PHY_2M_PREF_MASK`
` rx_phys_mask = BLE_HCI_LE_PHY_2M_PREF_MASK`
```c
void set_prefered_le_phy_after_conn(uint16_t conn_handle)
{
uint8_t tx_phys_mask = 0, rx_phys_mask = 0;
tx_phys_mask = BLE_HCI_LE_PHY_2M_PREF_MASK;
rx_phys_mask = BLE_HCI_LE_PHY_2M_PREF_MASK;
int rc = ble_gap_set_prefered_le_phy(conn_handle, tx_phys_mask, rx_phys_mask,0);
if (rc == 0) {
MODLOG_DFLT(INFO, "Prefered LE PHY set to LE_PHY_2M successfully");
} else {
MODLOG_DFLT(ERROR, "Failed to set prefered LE_PHY_2M");
}
}
```
Instead of changing the PHY after connection establishment, this example uses `ble_gap_ext_connect()` to initiate connections directly on the preferred PHY. After the first connection (on 1M PHY) is disconnected, the central connects directly on 2M PHY, and then on Coded PHY.
## Read Operation
@@ -255,7 +245,7 @@ blecent_read(const struct peer *peer)
}
rc = ble_gattc_read(peer->conn_handle, chr->chr.val_handle,
NULL, NULL);
blecent_on_read, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "Error: Failed to read characteristic; rc=%d\n",
rc);
@@ -271,17 +261,31 @@ blecent_read(const struct peer *peer)
## BLE GAP Connect Event
Once the connection is established `BLE_GAP_EVENT_CONNECT` event occurs. This event is handled by checking the current LE PHY equals 1M PHY and updating it to 2M PHY followed by performing the service discovery. If the connection attempt is failed then central start scanning again using the `blecent_scan` function.
Once the connection is established `BLE_GAP_EVENT_CONNECT` event occurs. This event is handled by logging which PHY the connection was established on, performing the service discovery (if GATT client is enabled), and adding the peer. If the connection attempt failed, the central resumes scanning using the `blecent_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 ");
switch (s_current_phy) {
case BLE_HCI_LE_PHY_1M_PREF_MASK:
MODLOG_DFLT(INFO,"Connection established on 1M Phy");
break;
case BLE_HCI_LE_PHY_2M_PREF_MASK:
case BLE_HCI_LE_PHY_1M_PREF_MASK | BLE_HCI_LE_PHY_2M_PREF_MASK:
MODLOG_DFLT(INFO,"Connection established on 2M Phy");
break;
case BLE_HCI_LE_PHY_CODED_PREF_MASK:
MODLOG_DFLT(INFO,"Connection established on Coded Phy");
break;
}
rc = ble_gap_conn_find(event->connect.conn_handle, &desc);
assert(rc == 0);
assert(rc == 0);
print_conn_desc(&desc);
MODLOG_DFLT(INFO, "\n");
@@ -292,10 +296,7 @@ case BLE_GAP_EVENT_CONNECT:
return 0;
}
if (s_current_phy == BLE_HCI_LE_PHY_1M_PREF_MASK) {
/* Update LE PHY from 1M to 2M */
set_prefered_le_phy_after_conn(event->connect.conn_handle);
}
#if MYNEWT_VAL(BLE_GATTC)
/* Perform service discovery. */
rc = peer_disc_all(event->connect.conn_handle,
blecent_on_disc_complete, NULL);
@@ -303,6 +304,7 @@ case BLE_GAP_EVENT_CONNECT:
MODLOG_DFLT(ERROR, "Failed to discover services; rc=%d\n", rc);
return 0;
}
#endif
} else {
/* Connection attempt failed; resume scanning. */
MODLOG_DFLT(ERROR, "Error: Connection failed; status=%d\n",
@@ -315,8 +317,8 @@ case BLE_GAP_EVENT_CONNECT:
## 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`. As the connection is terminated so the event is handled by setting up the default LE PHY to 1M followed by calling the `blecent_scan` function.
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`. As the connection is terminated, the current PHY is advanced to the next one, and a direct connection is initiated using `ble_gap_ext_connect()` on the preferred PHY.
```c
case BLE_GAP_EVENT_DISCONNECT:
/* Connection terminated. */
@@ -341,8 +343,20 @@ The connection between Central and Peripheral is terminated when the service dis
case BLE_HCI_LE_PHY_CODED_PREF_MASK:
return 0;
}
set_default_le_phy_before_conn(s_current_phy, s_current_phy);
blecent_scan();
vTaskDelay(200);
/* Attempt direct connection on 2M or Coded phy now */
if (s_current_phy == BLE_HCI_LE_PHY_CODED_PREF_MASK) {
MODLOG_DFLT(INFO, " Attempting to initiate connection on Coded PHY \n");
ble_gap_ext_connect(0, &conn_addr, 30000, BLE_HCI_LE_PHY_CODED_PREF_MASK,
NULL, NULL, NULL, blecent_gap_event, NULL);
}
else if (s_current_phy == BLE_HCI_LE_PHY_2M_PREF_MASK) {
MODLOG_DFLT(INFO, " Attempting to initiate connection on 2M PHY \n");
ble_gap_ext_connect(0, &conn_addr, 30000, (BLE_HCI_LE_PHY_1M_PREF_MASK | BLE_HCI_LE_PHY_2M_PREF_MASK),
NULL, NULL, NULL, blecent_gap_event, NULL);
}
return 0;
case BLE_GAP_EVENT_DISC_COMPLETE:
@@ -354,7 +368,7 @@ The connection between Central and Peripheral is terminated when the service dis
## Conclusion
This Walkthrough covers the code explanation of the BLE_PHY_CENTRAL example. The following points are concluded through this walkthrough.
1. As the nimble stack is initialized default LE PHY is set to 1M in the `blecent_on_sync` function.
2. Once the connection is established default LE PHY is updated to 2M PHY and service discovery is performed.
3. Once the Service discovery completes or fails, a connection is terminated.
4. Once the connection is terminated, depending on the value current LE PHY it is updated to the preferred LE PHY.
1. As the nimble stack is initialized, default LE PHY is set to all PHYs (1M, 2M, Coded) in the `blecent_on_sync` function, and scanning begins on 1M PHY.
2. Once the connection is established, the current PHY is logged and service discovery is performed.
3. Once the service discovery completes or fails, a connection is terminated.
4. Once the connection is terminated, a direct connection is initiated on the next PHY (2M, then Coded) using `ble_gap_ext_connect()`.
@@ -58,7 +58,7 @@ I (459) NimBLE: 60:55:f9:f7:3e:23
I (469) NimBLE:
I (469) NimBLE: Default LE PHY set successfully
I (479) NimBLE: GAP procedure initiated: extended advertise; instance=1
I (479) NimBLE: GAP procedure initiated: extended advertise; instance=0
I (479) uart: queue free spaces: 8
I (489) main_task: Returned from app_main()
@@ -89,7 +89,7 @@ I (1779) NimBLE: conn_itvl=40 conn_latency=0 supervision_timeout=256 encrypted=
I (1789) NimBLE:
I (1789) NimBLE: GAP procedure initiated: extended advertise; instance=1
I (1789) NimBLE: GAP procedure initiated: extended advertise; instance=0
I (1809) NimBLE: connection established; status=0
I (1809) NimBLE: handle=1 our_ota_addr_type=0 our_ota_addr=
@@ -118,7 +118,7 @@ I (3039) NimBLE: conn_itvl=40 conn_latency=0 supervision_timeout=256 encrypted=
I (3049) NimBLE:
I (3059) NimBLE: GAP procedure initiated: extended advertise; instance=1
I (3059) NimBLE: GAP procedure initiated: extended advertise; instance=0
I (3079) NimBLE: connection established; status=0
I (3079) NimBLE: handle=1 our_ota_addr_type=0 our_ota_addr=
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -104,7 +104,7 @@ ext_bleprph_advertise(void)
{
struct ble_gap_ext_adv_params params;
struct os_mbuf *data = NULL;
uint8_t instance = 1;
uint8_t instance = 0;
int rc;
/* use defaults for non-set params */
@@ -118,8 +118,8 @@ ext_bleprph_advertise(void)
/*enable connectable advertising for all Phy*/
params.connectable = 1;
/* advertise using random addr */
params.own_addr_type = BLE_OWN_ADDR_PUBLIC;
/* advertise using the inferred address type */
params.own_addr_type = own_addr_type;
/* Set current phy; get mbuf for scan rsp data; fill mbuf with scan rsp data */
switch (s_current_phy) {
@@ -78,8 +78,10 @@ app_main(void)
ble_hs_cfg.sm_their_key_dist = 1;
#endif
#if MYNEWT_VAL(BLE_GATTS)
rc = gatt_svr_init_le_phy();
assert(rc == 0);
#endif
/* Set the default device name. */
rc = ble_svc_gap_device_name_set("bleprph-phy");
@@ -166,8 +168,8 @@ esp_err_t esp_nimble_init(void)
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 = blecent_on_reset;
ble_hs_cfg.sync_cb = blecent_on_sync;
ble_hs_cfg.reset_cb = bleprph_on_reset;
ble_hs_cfg.sync_cb = bleprph_on_sync;
ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
```
@@ -221,10 +223,10 @@ nimble_port_freertos_init(bleprph_host_task);
## Intializaion of LE PHY to Default 1M PHY.
1M PHY is the default PHY for BLE devices which enables it to provide a data rate of 1 Mbps. It is used while establishing the connection between devices and maintains backward compatibility with all those devices that don't have BLE5.0 support.`set_default_le_phy_before_conn()` function set default LE PHY before establishing a connection.
1M PHY is the default PHY for BLE devices which enables it to provide a data rate of 1 Mbps. It is used while establishing the connection between devices and maintains backward compatibility with all those devices that don't have BLE5.0 support.`set_default_le_phy()` function set default LE PHY before establishing a connection.
```c
void set_default_le_phy_before_conn(uint8_t tx_phys_mask, uint8_t rx_phys_mask)
void set_default_le_phy(uint8_t tx_phys_mask, uint8_t rx_phys_mask)
{
int rc = ble_gap_set_prefered_default_le_phy(tx_phys_mask, rx_phys_mask);
if (rc == 0)
@@ -252,15 +254,14 @@ As the phy_cent disconnects the connection link with the peripheral, `BLE_GAP_EV
/* Connection terminated; resume advertising. */
#if CONFIG_EXAMPLE_EXTENDED_ADV
switch (s_current_phy) {
case BLE_HCI_LE_PHY_1M_PREF_MASK:
/* Setting current phy to create a connection on 2M PHY */
/* Setting current phy to create connection on 2M PHY */
s_current_phy = BLE_HCI_LE_PHY_2M_PREF_MASK;
break;
case BLE_HCI_LE_PHY_2M_PREF_MASK:
/* Setting current phy to create a connection on CODED PHY */
/* Setting current phy to create connection on CODED PHY */
s_current_phy = BLE_HCI_LE_PHY_CODED_PREF_MASK;
break;
@@ -270,11 +271,8 @@ As the phy_cent disconnects the connection link with the peripheral, `BLE_GAP_EV
default:
return 0;
}
set_default_le_phy_before_conn(s_current_phy, s_current_phy);
ext_bleprph_advertise();
#else
bleprph_advertise();
#endif
```
@@ -291,19 +289,22 @@ ext_bleprph_advertise(void)
{
struct ble_gap_ext_adv_params params;
struct os_mbuf *data = NULL;
uint8_t instance = 1;
uint8_t instance = 0;
int rc;
/* use defaults for non-set params */
memset (&params, 0, sizeof(params));
/* enable connectable advertising */
params.connectable = 1;
params.scannable = 1;
params.legacy_pdu = 1;
if (s_current_phy == BLE_HCI_LE_PHY_1M_PREF_MASK) {
params.scannable = 1;
params.legacy_pdu = 1;
}
/* advertise using random addr */
params.own_addr_type = BLE_OWN_ADDR_PUBLIC;
/*enable connectable advertising for all Phy*/
params.connectable = 1;
/* advertise using the inferred address type */
params.own_addr_type = own_addr_type;
/* Set current phy; get mbuf for scan rsp data; fill mbuf with scan rsp data */
switch (s_current_phy) {
@@ -311,24 +312,24 @@ ext_bleprph_advertise(void)
params.primary_phy = BLE_HCI_LE_PHY_1M;
params.secondary_phy = BLE_HCI_LE_PHY_1M;
data = ext_get_data(ext_adv_pattern_1M, sizeof(ext_adv_pattern_1M));
params.sid = 0;
break;
case BLE_HCI_LE_PHY_2M_PREF_MASK:
params.primary_phy = BLE_HCI_LE_PHY_1M;
params.secondary_phy = BLE_HCI_LE_PHY_2M;
data = ext_get_data(ext_adv_pattern_2M, sizeof(ext_adv_pattern_2M));
params.sid = 1;
break;
case BLE_HCI_LE_PHY_CODED_PREF_MASK:
params.primary_phy = BLE_HCI_LE_PHY_CODED;
params.secondary_phy = BLE_HCI_LE_PHY_CODED;
data = ext_get_data(ext_adv_pattern_coded, sizeof(ext_adv_pattern_coded));
params.sid = 2;
break;
}
//params.tx_power = 127;
params.sid = 1;
params.itvl_min = BLE_GAP_ADV_FAST_INTERVAL1_MIN;
params.itvl_max = BLE_GAP_ADV_FAST_INTERVAL1_MIN;
@@ -261,10 +261,10 @@ ext_ble_prox_cent_should_connect(const struct ble_gap_ext_disc_desc *disc)
/* The device has to advertise support for Proximity sensor (link loss)
* service (0x1803).
*/
do {
while (offset < disc->length_data) {
ad_struct_len = disc->data[offset];
if (!ad_struct_len) {
if (ad_struct_len == 0 || offset + ad_struct_len + 1 > disc->length_data) {
break;
}
@@ -276,8 +276,7 @@ ext_ble_prox_cent_should_connect(const struct ble_gap_ext_disc_desc *disc)
}
offset += ad_struct_len + 1;
} while ( offset < disc->length_data );
}
return 0;
}
+11 -8
View File
@@ -539,23 +539,24 @@ ext_blecent_should_connect(const struct ble_gap_ext_disc_desc *disc)
/* The device has to advertise support for the Alert Notification
* service (0x1811).
*/
do {
while (offset < disc->length_data) {
ad_struct_len = disc->data[offset];
if (!ad_struct_len) {
if (ad_struct_len == 0 || offset + ad_struct_len + 1 > disc->length_data) {
break;
}
/* Search if ANS UUID is advertised */
if (disc->data[offset] == 0x03 && disc->data[offset + 1] == 0x03) {
if ( disc->data[offset + 2] == 0x18 && disc->data[offset + 3] == 0x11 ) {
return 1;
/* Search if ANS UUID (0x1811) is advertised */
if (ad_struct_len >= 3 && (disc->data[offset + 1] == 0x02 || disc->data[offset + 1] == 0x03)) {
for (int i = 2; i + 1 <= ad_struct_len; i += 2) {
if (disc->data[offset + i] == 0x18 && disc->data[offset + i + 1] == 0x11) {
return 1;
}
}
}
offset += ad_struct_len + 1;
} while ( offset < disc->length_data );
}
return 0;
}
@@ -1110,6 +1111,8 @@ app_main(void)
#if NIMBLE_BLE_CONNECT
#if MYNEWT_VAL(STATIC_PASSKEY)
/* WARNING: Hardcoded passkey for demonstration only.
* In production, generate a random passkey per pairing. */
ble_sm_configure_static_passkey(456789, true);
#endif
@@ -370,6 +370,8 @@ bleprph_gap_event(struct ble_gap_event *event, void *arg)
if (event->passkey.params.action == BLE_SM_IOACT_DISP) {
pkey.action = event->passkey.params.action;
/* WARNING: Hardcoded passkey for demonstration only.
* In production, generate a random passkey per pairing. */
pkey.passkey = 123456; // This is the passkey to be entered on peer
ESP_LOGI(tag, "Enter passkey %" PRIu32 "on the peer side", pkey.passkey);
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
@@ -600,6 +602,8 @@ app_main(void)
#endif
#if MYNEWT_VAL(STATIC_PASSKEY) && NIMBLE_BLE_CONNECT
/* WARNING: Hardcoded passkey for demonstration only.
* In production, generate a random passkey per pairing. */
ble_sm_configure_static_passkey(456789, true);
#endif
@@ -1,16 +1,11 @@
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.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 "bleprph.h"
#include "services/ans/ble_svc_ans.h"
/*** Maximum number of characteristics with the notify flag ***/
@@ -4,14 +4,12 @@
* SPDX-License-Identifier: Apache-2.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 "bleprph.h"
#include "uart_driver.h"
@@ -345,6 +343,8 @@ bleprph_gap_event(struct ble_gap_event *event, void *arg)
if (event->passkey.params.action == BLE_SM_IOACT_DISP) {
pkey.action = event->passkey.params.action;
/* WARNING: Hardcoded passkey for demonstration only.
* In production, generate a random passkey per pairing. */
pkey.passkey = 123456; // This is the passkey to be entered on peer
ESP_LOGI(tag, "Enter passkey %" PRIu32 "on the peer side", pkey.passkey);
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
@@ -1,15 +1,10 @@
/*
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2026 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 "esp_central.h"
/**
* Utility function to log an array of bytes.
@@ -4,8 +4,6 @@
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <assert.h>
#include <string.h>
#include "host/ble_hs.h"
#include "esp_central.h"
@@ -427,6 +427,8 @@ bleprph_gap_event(struct ble_gap_event *event, void *arg)
if (event->passkey.params.action == BLE_SM_IOACT_DISP) {
pkey.action = event->passkey.params.action;
/* WARNING: Hardcoded passkey for demonstration only.
* In production, generate a random passkey per pairing. */
pkey.passkey = 123456; // This is the passkey to be entered on peer
ESP_LOGI(tag, "Enter passkey %" PRIu32 "on the peer side", pkey.passkey);
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
@@ -1,16 +1,37 @@
# Throughput demo Examples
# Throughput Demo Examples
There are two different example folders inside this `throughput_app`, `bleprph_throughput` and `blecent_throughput`. As the names suggest, both of them play role of `peripheral` and `central` respectively. These example demonstrate application throughput for NimBLE on ESP32. Two ESP32 boards are needed to run this demo. The `blecent_throughput` example has CLI support to select GATT operation from READ/WRITE/NOTIFY. It can also accept connection parameters at start of program, more details can be found in respective READMEs.
There are two example folders inside this `throughput_app`: `bleprph_throughput` (peripheral) and `blecent_throughput` (central). These examples demonstrate BLE GATT throughput measurement using NimBLE on ESP32. Two ESP32 boards are needed to run this demo. The `blecent_throughput` example has CLI support to select GATT operation from READ/WRITE/NOTIFY and configure connection parameters at runtime. More details can be found in respective READMEs.
## Using the examples
## Using the Examples
First build and flash two ESP32 boards with `bleprph_throughput` and `blecent_throughput` examples. The central automatically scans and connects to peripheral based on peripheral name string (`nimble_prph`). In the next step, user may choose to configure connection parameters (`MTU`, `connection interval`, `latency`, `supervision timeout`, `connection event length`). In the next step, user needs to specify throughput test name (`read`, `write` or `notify`) and test time in seconds. Below are
sample numbers of different throughput test runs for 60 seconds (MTU = 512, conn itvl = 7.5msec, conn event length = 7.5msec)
Build and flash two ESP32 boards with `bleprph_throughput` and `blecent_throughput` examples. The central automatically scans and connects to the peripheral based on device name (`nimble_prph`). After connection, the user may optionally configure connection parameters (`MTU`, `connection interval`, `latency`, `supervision timeout`, `connection event length`). Then the user specifies the throughput test type (`read`, `write` or `notify`) and test duration in seconds.
|GATT method | Measurement time | Application Throughput|
Below are sample throughput numbers for a 60-second test run (MTU = 512, conn itvl = 7.5ms, DLE = 251 bytes, 1M PHY):
|GATT Method | Measurement Time | Application Throughput|
|--- | --- | ---|
|NOTIFY | 60 seconds | ~340Kbps|
|READ | 60 seconds | ~200kbps|
|WRITE | 60 seconds | ~500kbps|
|NOTIFY | 60 seconds | ~340 Kbps|
|READ | 60 seconds | ~200 Kbps|
|WRITE | 60 seconds | ~500 Kbps|
The notify output is seen on `bleprph_throughput` console and read/write throughput are seen on `blecent_throughput` console.
The notify throughput output is displayed on the `bleprph_throughput` console, while read/write throughput results are shown on the `blecent_throughput` console.
## Throughput Optimization
The following parameters have the most significant impact on throughput:
1. **Connection Interval**: The default is 7.5ms (minimum allowed by BLE spec). Shorter intervals mean more connection events per second and higher throughput.
2. **Connection Event Length**: Controls how long a single connection event can last. The default maximum CE length is 15ms, allowing the controller to extend events and transmit more PDUs per event when possible.
3. **Notification Pipelining**: The peripheral queues multiple notifications simultaneously (pipeline depth of 15) rather than waiting for each one to complete before sending the next. This allows the controller to pack multiple PDUs into each connection event.
4. **Payload Size**: Payload sizes are set to the maximum ATT capacity for each operation (509 bytes for notify/write, 510 bytes for read) to maximize data per PDU.
5. **Data Length Extension (DLE)**: LL packet length is set to 251 bytes to use the maximum Link Layer PDU size.
6. **MTU**: Set to 512 bytes to allow large ATT payloads and reduce protocol overhead.
7. **MSYS Buffer Count**: Both peripheral and central are configured with 50 MSYS blocks (`CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=50`) to provide sufficient buffer space for high-throughput operations.
8. **PHY**: On BLE 5.0 supported chipsets, 2M PHY can be selected to double the air data rate. Use Extended Advertising mode and specify PHY in the throughput CLI command.
@@ -7,19 +7,20 @@
`blecent_throughput` demonstrates client side implementation required for NimBLE throughput example. It connects to `bleprph_throughput` based on name string `nimble_prph`. It has interactive CLI support to start READ/WRITE/NOTIFY GATT operation for user specified time.
It performs read operation on peripheral's `THRPT_LONG_CHR_READ_WRITE` characteristic, write operation on `THRPT_CHR_READ_WRITE` and subscribes to `THRPT_CHR_NOTIFY` characteristic for notifications. If user does not specify any throughput test method for 30 seconds (`BLE_RX_TIMEOUT`) then the program starts with default READ operations for 60 seconds.
It performs read operations on the peripheral's `THRPT_LONG_CHR_READ_WRITE` characteristic (510 bytes), write operations on `THRPT_CHR_READ_WRITE` (509-byte payload), and subscribes to `THRPT_CHR_NOTIFY` for notifications. If the user does not specify a throughput test method within 30 seconds (`BLE_RX_TIMEOUT`), the program defaults to READ operations for 60 seconds.
`blecent_throughput` uses the `NimBLE` as BLE host.
### Procedure to use this demo example
### Procedure to Use This Demo Example
* `idf.py menuconfig` and configure the parameters as needed (connection related parameters in example parameters).
* `idf.py menuconfig` and configure the parameters as needed (connection related parameters in Example Configuration).
* The default connection interval is set to 7.5ms (6 units) for maximum throughput. This can be changed via menuconfig or at runtime.
* To test throughput on 1M PHY, make sure to disable the Example Configuration -> Enable Extended Adv flag in menuconfig on both sides.
* To test on 2M PHY or Coded PHY (S2/S8), you must enable the Example Configuration -> Enable Extended Adv flag on both sides.
* `bleprph_throughput` example needs to be run along with this client side example.
* After connection link is established between these two devices, user is given a window of `YES_NO_PARAM` (5 seconds) to customize connection parameters. If user has configured parameters from menuconfig, this step can be skipped by either waiting it out or entering `Insert no`.
* User needs to enter `Insert yes` to customize connection parameters. Enter `MTU` and other connection parameters as directed on console instructions e.g. `MTU 512` for MTU and `conn 6 120 0 500 0 0` for connection parameters in sequence of `min conn_itvl`, `max conn_itvl`, `latency`, `supervision timeout`, `min conn_evt_len` and `max_conn_evt_len`.
* User will be now presented with throughput test related console prints, this suggests application is now ready to be run throughput test for user defined time. The prints may appear like below
* After the connection link is established between the two devices, the user is given a window of `YES_NO_PARAM` (5 seconds) to customize connection parameters. If the user has configured parameters from menuconfig, this step can be skipped by either waiting it out or entering `Insert No`.
* Enter `Insert Yes` to customize connection parameters. Enter `MTU` and other connection parameters as directed on console instructions, e.g. `MTU 512` for MTU and `conn 6 6 0 500 12 24` for connection parameters in sequence of `min conn_itvl`, `max conn_itvl`, `latency`, `supervision timeout`, `min conn_evt_len` and `max_conn_evt_len`.
* The user will be presented with throughput test related console prints. The prints may appear like below
```
====================================================================================
@@ -41,8 +42,8 @@ It performs read operation on peripheral's `THRPT_LONG_CHR_READ_WRITE` character
```
* If user fail to enter any values for next 30 seconds, the app falls to default behavior of READ for 60 seconds mode and 1M phy channel.
* Read and write throughput numbers will be presented in `blecent_throughput` console output. For notification `bleprph_throughput` console shall be referred, as the peripheral is the one who is sending notifications. Below is the sample output of the app:
* If the user fails to enter any values within 30 seconds, the app falls to the default behavior of READ for 60 seconds on 1M PHY.
* Read and write throughput numbers are displayed on the `blecent_throughput` console. For notification throughput, refer to the `bleprph_throughput` console, as the peripheral is the one sending notifications. Below is sample output:
```
Type 'help' to get the list of commands.
@@ -136,9 +137,11 @@ idf.py -p PORT flash monitor
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example scope
## Example Scope
This demo example tries to demonstrate stable implementation of GATT operations like read/write and notify. The READ and WRITE GATT operations require ACK from peer, so this central app makes sure that next GATT operation is performed after completion of previous one. In case of notification (`bleprph_throughput` sends) operation there is no waiting for ACK, however one needs to mind `os_mbufs` getting full, so one may need to allocate higher number of mbufs through menuconfig.
This demo example demonstrates stable GATT read/write and notify operations at high throughput. The READ and WRITE GATT operations require an ACK from the peer, so this central app ensures that the next GATT operation is performed after completion of the previous one. For write operations, `ble_gattc_write_no_rsp_flat` is used (Write Without Response) which does not require an ATT-level ACK, allowing higher write throughput. If the mbuf pool is exhausted (`BLE_HS_ENOMEM`), the app yields briefly and retries.
For notifications (`bleprph_throughput` sends), the peripheral uses pipelined notification sending with a configurable pipeline depth, allowing the controller to pack multiple PDUs per connection event. Both sides are configured with `CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=50` to provide sufficient buffer space for high-throughput operations.
## Example output
@@ -8,15 +8,17 @@ menu "Example Configuration"
config EXAMPLE_CONN_ITVL_MIN
int "Minimum connection itvl"
default 104
default 6
help
Set the minimum connection interval in 1.25msec units.
Default 6 (7.5ms) for maximum throughput.
config EXAMPLE_CONN_ITVL_MAX
int "Maximum connection itvl"
default 104
default 6
help
Set the maximum connection interval in 1.25msec units.
Default 6 (7.5ms) for maximum throughput.
config EXAMPLE_CONN_LATENCY
int "Connection latency"
@@ -35,12 +37,14 @@ menu "Example Configuration"
default 12
help
Set the minimum connection event length in 0.625msec units.
Default 12 (7.5ms) matches the minimum connection interval.
config EXAMPLE_CONN_CE_LEN_MAX
int "Maximum connection event length"
default 12
default 24
help
Set the maximum connection event length in 0.625msec units.
Default 24 (15ms) allows controller to extend events when possible.
config EXAMPLE_EXTENDED_ADV
bool
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -49,8 +49,8 @@
#define WRITE_THROUGHPUT 2
#define NOTIFY_THROUGHPUT 3
#define READ_THROUGHPUT_PAYLOAD 500
#define WRITE_THROUGHPUT_PAYLOAD 495
#define READ_THROUGHPUT_PAYLOAD 510 /* MTU(512) - ATT read rsp header(1) - 1 (avoid Read Blob) */
#define WRITE_THROUGHPUT_PAYLOAD 509 /* MTU(512) - ATT write cmd header(3) */
#define LL_PACKET_TIME 2120
#define LL_PACKET_LENGTH 251
static const char *tag = "blecent_throughput";
@@ -59,7 +59,7 @@ static SemaphoreHandle_t xSemaphore;
static int mbuf_len_total;
static int failure_count;
static TaskHandle_t throughput_task_handle = NULL;
static int conn_params_def[] = {40, 40, 0, 500, 80, 80};
static int conn_params_def[] = {6, 6, 0, 500, 12, 24};
/* test_data accepts test_name and test_time from CLI */
static int test_data[6] = {1, 600, 0, 0, 0, 0};
static int mtu_def = 512;
@@ -13,5 +13,6 @@ CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=512
CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=20
CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=255
CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=50
CONFIG_BT_NIMBLE_LOG_LEVEL=4
CONFIG_BT_NIMBLE_LOG_LEVEL_NONE=y
@@ -5,7 +5,7 @@
(See the README.md file in the upper level 'examples' directory for more information about examples.)
`bleprph_throughput` demonstrates server side implementation required for NimBLE throughput example. It has characteristics supporting READ, WRITE and NOTIFY (`PTS_LONG_CHR_READ_WRITE`,`PTS_CHR_READ_WRITE`,`PTS_CHR_NOTIFY`). The data of 500 Bytes (`READ_THROUGHPUT_PAYLOAD`) and 400 Bytes (`WRITE_THROUGHPUT_PAYLOAD`) is transferred for throughput GATT read and write operations respectively.
`bleprph_throughput` demonstrates server side implementation required for NimBLE throughput example. It has characteristics supporting READ, WRITE and NOTIFY (`THRPT_LONG_CHR_READ_WRITE`, `THRPT_CHR_READ_WRITE`, `THRPT_CHR_NOTIFY`). The read characteristic holds 510 bytes (`READ_THROUGHPUT_PAYLOAD`) and the write characteristic accepts up to 509 bytes (`WRITE_THROUGHPUT_PAYLOAD`). Notifications are sent with a 509-byte payload (`NOTIFY_THROUGHPUT_PAYLOAD`). These sizes are chosen to maximize ATT PDU utilization with the default MTU of 512.
`bleprph_throughput` uses the `nimble` component as BLE host.
@@ -61,7 +61,9 @@ I (83943) bleprph_throughput: Notification test completed for stipulated time o
> Here, bps is bits per second; count is number of Notifications successfully sent.
## Example scope
## Example Scope
This demo example along with `blecent_throughput` tries to demonstrate stable implementation of GATT operations like read/write and notify. For `bleprph_throughput` app, notifications are sent almost continuously for stipulated period of time. The almost part is because we use counting semaphore (~100) to mimic continuous notifications. Here one needs to understand that notifications are sent in `os_mbufs` packets and there can always be chance of them getting full because of continuous operation, so one may need to allocate higher number of mbufs through menuconfig, whenever there is `os_mbuf` memory exhaustion, app provides delay so NimBLE host stack can breathe and free `mbuf chains`.
This demo example along with `blecent_throughput` demonstrates stable GATT read/write and notify operations at high throughput. For notifications, the peripheral uses a pipelined approach: a counting semaphore (max 100) allows multiple notifications to be queued simultaneously (pipeline depth of 15). This enables the BLE controller to pack multiple PDUs into each connection event for maximum throughput.
Notifications are sent in `os_mbufs` packets. The example is configured with `CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=50` to provide sufficient buffer space. If mbuf exhaustion occurs during continuous transfer, the app yields briefly to allow the NimBLE host stack to free completed `mbuf chains` before retrying.
@@ -33,8 +33,8 @@
#define THRPT_CHR_NOTIFY 0x000a
#define THRPT_LONG_CHR_READ_WRITE 0x000b
#define READ_THROUGHPUT_PAYLOAD 500
#define WRITE_THROUGHPUT_PAYLOAD 500
#define READ_THROUGHPUT_PAYLOAD 510 /* MTU(512) - ATT read rsp header(1) - 1 (avoid Read Blob) */
#define WRITE_THROUGHPUT_PAYLOAD 509 /* MTU(512) - ATT write cmd header(3) */
static const char *tag = "bleprph_throughput";
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -30,8 +30,9 @@ static uint8_t s_current_phy;
static const char *device_name = "nimble_prph";
#define NOTIFY_THROUGHPUT_PAYLOAD 495
#define MIN_REQUIRED_MBUF 2 /* Assuming payload of 500Bytes and each mbuf can take 292Bytes. */
#define NOTIFY_THROUGHPUT_PAYLOAD 509 /* MTU(512) - ATT notify header(3) */
#define MIN_REQUIRED_MBUF 2 /* Assuming payload of 500Bytes and each mbuf can take 292Bytes. */
#define NOTIFY_PIPELINE_DEPTH 15 /* Number of notifications to keep in flight for throughput */
#define PREFERRED_MTU_VALUE 512
#define LL_PACKET_TIME 2120
#define LL_PACKET_LENGTH 251
@@ -276,8 +277,8 @@ notify_task(void *arg)
do {
om = ble_hs_mbuf_from_flat(payload, sizeof(payload));
if (om == NULL) {
/* Memory not available for mbuf */
vTaskDelay(100 / portTICK_PERIOD_MS);
/* Memory not available for mbuf, yield briefly */
vTaskDelay(1);
}
} while (om == NULL);
@@ -286,18 +287,14 @@ notify_task(void *arg)
ESP_LOGE(tag, "Error while sending notification; rc = %d", rc);
notify_count -= 1;
xSemaphoreGive(notify_sem);
/* Most probably error is because we ran out of mbufs (rc = 6),
* increase the mbuf count/size from menuconfig. Though
* inserting delay is not good solution let us keep it
* simple for time being so that the mbufs get freed up
* (?), of course assumption is we ran out of mbufs */
vTaskDelay(10 / portTICK_PERIOD_MS);
/* Yield to let mbufs free up */
vTaskDelay(1);
}
} else {
ESP_LOGE(tag, "Not enough OS_MBUFs available; reduce notify count ");
xSemaphoreGive(notify_sem);
notify_count -= 1;
vTaskDelay(10 / portTICK_PERIOD_MS);
/* Yield briefly to let mbufs free up */
vTaskDelay(1);
}
end_time = esp_timer_get_time();
@@ -395,7 +392,14 @@ gatts_gap_event(struct ble_gap_event *event, void *arg)
ESP_LOGI(tag, "notify test time = %d", *(int *)arg);
notify_test_time = *((int *)arg);
}
xSemaphoreGive(notify_sem);
if (notify_state) {
/* Prime the notification pipeline to allow multiple in-flight
* notifications. This enables the controller to fill connection
* events with back-to-back PDUs for maximum throughput. */
for (int i = 0; i < NOTIFY_PIPELINE_DEPTH; i++) {
xSemaphoreGive(notify_sem);
}
}
} else if (event->subscribe.attr_handle != notify_handle) {
notify_state = event->subscribe.cur_notify;
}