# BLE Periodic Advertisement with Response Advertiser Example Walkthrough

## Introduction

This tutorial examines the BLE Periodic Advertisement with Responses (PAwR) example code for ESP32 chipsets with BLE 5.0+ support. The code demonstrates how to implement PAwR functionality using NimBLE APIs, which enables bidirectional communication between advertiser and scanner devices in a power-efficient manner.

## Includes

This example is located in the examples folder of the ESP-IDF under the [ble_pawr_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 */
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
```
These includes provide:

- ESP32 logging functionality (esp_log.h)

- Non-volatile storage (nvs_flash.h)

- NimBLE stack porting and FreeRTOS integration

- BLE host stack functionality

## Main Entry Point

The program’s entry point is the app_main() function:
```c
void app_main(void)
{
    esp_err_t ret;

    /* Initialize NVS — it is used to store PHY calibration data */
    ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    ret = nimble_port_init();
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to init nimble %d ", ret);
        return;
    }

    /* Initialize the NimBLE host configuration. */
    ble_hs_cfg.reset_cb = on_reset;
    ble_hs_cfg.sync_cb = on_sync;

    nimble_port_freertos_init(pawr_host_task);
}
```
The main function starts by initializing the non-volatile storage library. This library allows us to save the key-value pairs in flash memory.`nvs_flash_init()` stores the PHY calibration data. In a Bluetooth Low Energy (BLE) device, cryptographic keys used for encryption and authentication are often stored in Non-Volatile Storage (NVS).BLE stores the peer keys, CCCD keys, peer records, etc on NVS. By storing these keys in NVS, the BLE device can quickly retrieve them when needed, without the need for time-consuming key generations.
```c
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
    ESP_ERROR_CHECK(nvs_flash_erase());
    ret = nvs_flash_init();
}
ESP_ERROR_CHECK( ret );
```

## BT Controller and Stack Initialization

The main function calls `nimble_port_init()` to initialize BT Controller and nimble stack. This function initializes the BT controller by first creating its configuration structure named `esp_bt_controller_config_t` with default settings generated by the `BT_CONTROLLER_INIT_CONFIG_DEFAULT()` macro. It implements the Host Controller Interface (HCI) on the controller side, the Link Layer (LL), and the Physical Layer (PHY). The BT Controller is invisible to the user applications and deals with the lower layers of the BLE stack. The controller configuration includes setting the BT controller stack size, priority, and HCI baud rate. With the settings created, the BT controller is initialized and enabled with the `esp_bt_controller_init()` and `esp_bt_controller_enable()` functions:

```c
esp_bt_controller_config_t config_opts = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&config_opts);
```
Next, the controller is enabled in BLE Mode.

```c
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
```
The controller should be enabled in `ESP_BT_MODE_BLE` if you want to use the BLE mode.

There are four Bluetooth modes supported:

1. `ESP_BT_MODE_IDLE`: Bluetooth not running
2. `ESP_BT_MODE_BLE`: BLE mode
3. `ESP_BT_MODE_CLASSIC_BT`: BT Classic mode
4. `ESP_BT_MODE_BTDM`: Dual mode (BLE + BT Classic)

After the initialization of the BT controller, the nimble stack, which includes the common definitions and APIs for BLE, is initialized by using `esp_nimble_init()`:

```c
esp_err_t esp_nimble_init(void)
{

#if !SOC_ESP_NIMBLE_CONTROLLER
    /* Initialize the function pointers for OS porting */
    npl_freertos_funcs_init();

    npl_freertos_mempool_init();

    if(esp_nimble_hci_init() != ESP_OK) {
        ESP_LOGE(NIMBLE_PORT_LOG_TAG, "hci inits failed\n");
        return ESP_FAIL;
    }

    /* Initialize default event queue */
    ble_npl_eventq_init(&g_eventq_dflt);

    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;
}
```
## PAwR Configuration
The example defines several PAwR parameters

```c
#define BLE_PAWR_EVENT_INTERVAL               (600)
#define BLE_PAWR_NUM_SUBEVTS                  (10)
#define BLE_PAWR_SUB_INTERVAL                 (44)  /*!< Interval between subevents (N * 1.25 ms) */
#define BLE_PAWR_RSP_SLOT_DELAY               (20)  /*!< The first response slot delay (N * 1.25 ms) */
#define BLE_PAWR_RSP_SLOT_SPACING             (32)  /*!< Time between response slots (N * 0.125 ms) */
#define BLE_PAWR_NUM_RSP_SLOTS                (5)   /*!< Number of subevent response slots */
#define BLE_PAWR_SUB_DATA_LEN                 (20)
```

These parameters control:

- The interval between periodic advertising events

- Number of subevents per periodic interval

- Timing of response slots

- Data length for subevent payloads


## Periodic Advertising Configuration
```c
memset(&pparams, 0, sizeof(pparams));
pparams.include_tx_power = 0;
pparams.itvl_min = BLE_GAP_PERIODIC_ITVL_MS(3000);
pparams.itvl_max = BLE_GAP_PERIODIC_ITVL_MS(3000);
pparams.num_subevents = BLE_PAWR_NUM_SUBEVTS;
pparams.subevent_interval = BLE_PAWR_SUB_INTERVAL;
pparams.response_slot_delay = BLE_PAWR_RSP_SLOT_DELAY;
pparams.response_slot_spacing = BLE_PAWR_RSP_SLOT_SPACING;
pparams.num_response_slots = BLE_PAWR_NUM_RSP_SLOTS;

rc = ble_gap_periodic_adv_configure(instance, &pparams);
assert(rc == 0);
```
## Key PAwR Parameters:

- num_subevents: Number of subevents per periodic interval (10)

- subevent_interval: Time between subevents (44 × 1.25ms = 55ms)

- response_slot_delay: First response slot delay (20 × 1.25ms = 25ms)

- response_slot_spacing: Time between slots (32 × 0.125ms = 4ms)

- num_response_slots: Number of response slots per subevent (5)

## PAwR Advertisement

The start_periodic_adv() function configures and starts PAwR:
```c
static void
start_periodic_adv(void)
{
    int rc;
    uint8_t addr[6];
    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 = 0;

#if MYNEWT_VAL(BLE_PERIODIC_ADV_ENH)
    struct ble_gap_periodic_adv_enable_params eparams;
    memset(&eparams, 0, sizeof(eparams));
#endif

    /* Get the local public address. */
    rc = ble_hs_id_copy_addr(BLE_ADDR_PUBLIC, addr, NULL);
    assert (rc == 0);

    ESP_LOGI(TAG, "Device Address %02x:%02x:%02x:%02x:%02x:%02x", addr[5], addr[4], addr[3],
             addr[2], addr[1], addr[0]);

    /* For periodic we use instance with non-connectable advertising */
    memset (&params, 0, sizeof(params));
    params.own_addr_type = BLE_OWN_ADDR_PUBLIC;
    params.primary_phy = BLE_HCI_LE_PHY_CODED;
    params.secondary_phy = BLE_HCI_LE_PHY_1M;
    params.sid = 0;
    params.itvl_min = BLE_PAWR_EVENT_INTERVAL;
    params.itvl_max = BLE_PAWR_EVENT_INTERVAL;

    rc = ble_gap_ext_adv_configure(instance, &params, NULL, gap_event_cb, NULL);
    assert (rc == 0);

    memset(&adv_fields, 0, sizeof(adv_fields));
    adv_fields.name = (const uint8_t *)"Nimble_PAwR";
    adv_fields.name_len = strlen((char *)adv_fields.name);

    /* mbuf chain will be increased if needed */
    data = os_msys_get_pkthdr(BLE_HCI_MAX_ADV_DATA_LEN, 0);
    assert(data);

    rc = ble_hs_adv_set_fields_mbuf(&adv_fields, data);
    assert(rc == 0);

    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_PERIODIC_ITVL_MS(3000);
    pparams.itvl_max = BLE_GAP_PERIODIC_ITVL_MS(3000);
    /* Configure the parameters of PAwR. */
    pparams.num_subevents           = BLE_PAWR_NUM_SUBEVTS;
    pparams.subevent_interval       = BLE_PAWR_SUB_INTERVAL;
    pparams.response_slot_delay     = BLE_PAWR_RSP_SLOT_DELAY;
    pparams.response_slot_spacing   = BLE_PAWR_RSP_SLOT_SPACING;
    pparams.num_response_slots      = BLE_PAWR_NUM_RSP_SLOTS;

    rc = ble_gap_periodic_adv_configure(instance, &pparams);
    assert(rc == 0);

    /* start periodic advertising */
#if MYNEWT_VAL(BLE_PERIODIC_ADV_ENH)
    eparams.include_adi = 1;
    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);

    ESP_LOGI(TAG, "instance %u started (periodic)\n", instance);
}
```
Key steps:

- Configure extended advertising parameters

- Set up periodic advertising with subevent and response slot parameters

- Start both periodic and extended advertising


## 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.


## GAP Event Callback
The gap_event_cb function handles PAwR events:

```c
static int gap_event_cb(struct ble_gap_event *event, void *arg)
{
    switch (event->type) {
    case BLE_GAP_EVENT_PER_SUBEV_DATA_REQ:
        /* Handle subevent data request */
        break;

    case BLE_GAP_EVENT_PER_SUBEV_RESP:
        /* Handle scanner responses */
        break;
    }
    return 0;
}
```
It processes two main events:

- BLE_GAP_EVENT_PER_SUBEV_DATA_REQ: Triggered when data needs to be prepared for subevents

- BLE_GAP_EVENT_PER_SUBEV_RESP: Triggered when responses are received from scanners

## Host Task

The pawr_host_task runs the NimBLE stack:
```c
void pawr_host_task(void *param)
{
    ESP_LOGI(TAG, "BLE Host Task Started");
    nimble_port_run();
    nimble_port_freertos_deinit();
}
```

## Conclusion

This PAwR example demonstrates:

1. Configuration of periodic advertising with subevents and response slots

2. Bidirectional communication between advertiser and scanners

3. Efficient power usage through scheduled communication windows

4. Use of extended advertising to announce PAwR capabilities

The implementation shows how to:

- Set up PAwR parameters (intervals, slots, etc.)

- Handle data requests for subevents

- Process responses from scanners

- Manage the advertising lifecycle

PAwR is particularly useful for applications requiring periodic, bidirectional communication with multiple devices while maintaining low power consumption.