How to update advertising data dynamically using BLE Advertising library

Introduction

The question of how to update advertising data dynamically using the BLE Advertising library has been raised many times by our forum-goers, especially after the release of nRF5 SDK v15.2.0, in which the ble_advertising_advdata_update function was added. The usage of this function changed with the release of nRF5 SDK v16.0.0, and this blog post aims to shed light on this change, and make it clear how to use this function with SDK versions from both before and after the change.

Intention and revision

Prior to the release of nRF5 SDK v15.2.0 the primary way to change advertising data at runtime was to first stop the advertising, configure with new data, and then restart the advertising. This is a sure-fire but tedious way of changing the advertising data. The intention behind the introduction of the ble_advertising_advdata_update function was that the user would no longer have to stop-configure-start the advertising every time the advertising data should change, but rather just provide the new data and be done with it. While the initial release of the function in nRF5 SDK v15.2.0 achieved this, it was not specified thoroughly in the API Reference that the advertising data buffer provided to the ble_advertising_advdata_update function needed to be a new buffer - i.e it would not succeed if the user kept passing the the same data buffer, even though its contents had been updated. This meant that the user would have to do double buffering manually, and alternate between which advertising data buffer that was currently in use, and which could be updated.
This was a point of confusion for many users, which led to the changed implementation in nRF5 SDK v16.0.0.
In nRF5 SDK v16.0.0 the ble_advertising_advdata_update function was augmented to take care of the double-buffering issue.

This change can be seen in the following snippets from the functions source code.

Initial ble_advertising_advdata_update function as part of nRF5 SDK v15.2.0 and v15.3.0:
ret_code_t ble_advertising_advdata_update(ble_advertising_t  * const p_advertising,
                                          ble_gap_adv_data_t * const p_new_advdata_buf,
                                          bool                       permanent)
{
    if (permanent)
    {
        memcpy(&p_advertising->adv_data, p_new_advdata_buf, sizeof(p_advertising->adv_data));
        p_advertising->p_adv_data = &p_advertising->adv_data;
    }
    else
    {
        p_advertising->p_adv_data = p_new_advdata_buf;
    }

    return sd_ble_gap_adv_set_configure(&p_advertising->adv_handle,
                                        p_advertising->p_adv_data,
                                        NULL);
}


Revised ble_advertising_advdata_update function as part of nRF5 SDK v16.0.0 and v17.0.2:
ret_code_t ble_advertising_advdata_update(ble_advertising_t   * const p_advertising,
                                          ble_advdata_t const * const p_advdata,
                                          ble_advdata_t const * const p_srdata)
{
    VERIFY_PARAM_NOT_NULL(p_advertising);
    if (p_advertising->initialized == false)
    {
        return NRF_ERROR_INVALID_STATE;
    }

    if ((p_advdata == NULL) && (p_srdata == NULL))
    {
        return NRF_ERROR_NULL;
    }

    ble_gap_adv_data_t new_adv_data;
    memset(&new_adv_data, 0, sizeof(new_adv_data));

    if (p_advdata != NULL)
    {
        new_adv_data.adv_data.p_data =
            (p_advertising->p_adv_data->adv_data.p_data != p_advertising->enc_advdata[0]) ?
             p_advertising->enc_advdata[0] : p_advertising->enc_advdata[1];
        new_adv_data.adv_data.len = adv_set_data_size_max_get(p_advertising);

        ret_code_t ret = ble_advdata_encode(p_advdata,
                                            new_adv_data.adv_data.p_data,
                                            &new_adv_data.adv_data.len);
        VERIFY_SUCCESS(ret);
    }

    if (p_srdata != NULL)
    {
        new_adv_data.scan_rsp_data.p_data =
            (p_advertising->p_adv_data->scan_rsp_data.p_data != p_advertising->enc_scan_rsp_data[0]) ?
             p_advertising->enc_scan_rsp_data[0] : p_advertising->enc_scan_rsp_data[1];
        new_adv_data.scan_rsp_data.len = adv_set_data_size_max_get(p_advertising);

        ret_code_t ret = ble_advdata_encode(p_srdata,
                                            new_adv_data.scan_rsp_data.p_data,
                                            &new_adv_data.scan_rsp_data.len);
        VERIFY_SUCCESS(ret);
    }

    memcpy(&p_advertising->adv_data, &new_adv_data, sizeof(p_advertising->adv_data));
    p_advertising->p_adv_data = &p_advertising->adv_data;

    return sd_ble_gap_adv_set_configure(&p_advertising->adv_handle,
                                        p_advertising->p_adv_data,
                                        NULL);
}

Notice how the updated function takes care of the double-buffering issue of its predecessor by always populating a new data buffer before encoding and configuring the data.

Super short summary

nRF5 SDK v15.2.0 and v15.3.0:

Create two advertising data buffers, and alternate between which is passed to ble_advertising_advdata_update and which is updated.
To continuously update the advertising data follow the steps below:

1. Create two advertising data buffers
2. Fill the first data buffer with contents, and use it to configure and start advertising.
3. Update the second advertising data buffer with new advertising data.
4. Pass the newly updated second advertising data buffer to the ble_advertising_advdata_update function.
5. Update the first advertising data buffer with new advertising data.
6. Pass the newly updated first advertising data buffer to the ble_advertising_advdata_update function.
7. Repeat steps 3 - 6.

nRF5 SDK v16.0.0 and v17.0.2:

1. Create a single advertising data buffer.
2. Fill the data buffer with contents, and use it to configure and start advertising.
3. Update the advertising data buffer with the new advertising data.
4. Pass the updated advertising data buffer to the ble_advertising_advdata_update function.
5. Repeat steps 3 - 4.


Simple demonstration

Lastly, a simple project is attached to the end of the blogpost that demonstrates how the advertising data can be changed dynamically. The demonstration is made for a nRF52840 DK for use with the S140 SoftDevice and nRF5 SDK v.17.0.2. The included project is based on the BLE peripheral template example from the nRF5 SDK v17.0.2, and is modified to change its advertising data every 5th second. The device advertises as ‘Nordic_template’ and starts out with the default advertising payload from the example, before changing its advertising payload to contain a single letter in its manufacturer specific data field. I will leave the creative part of populating this manufacturer specific data with something cool to you!

Try it out yourself to see how it works, and please do not hesitate to open a ticket on DevZone if you should encounter any issues or questions! :)

ble_app_update_advertising_data.zip
  • Finally I have managed to build the solution with correct paths to all logging functions. But same as the defualt template app it ends in the "NRF_BREAKPOINT_COND" right after starting up. Please would you be so kind and try to help a little. The immediate failure of the code is same also for the template ble app.

  • Hello Johndos,

    Please confirm that you placed the project in the SDK_ROOT/examples/ble_peripheral directory, and not the SDK_ROOT/examples/ directory.
    The example uses relative paths, so it will need to be located at the correct place in the SDK for it to successfully find its required files and modules.

    Best regards,
    Karl

  • Dear friends, I downloaded fresh SDK 17.0.2 and copy your linked example to the examples. I am using SES, I am not able to compile the project. Other examples worked just fine. Your linked example have problems with paths to logging things and even I tried to fix it it started to have problem to found files like sdk_common.h and I am not able to fix. Would be possible to fix the linked example so it will work just OK like your other examples among downloaded SDK. Or any other advice will be much helpful for me.

  • Hello AllenS,

    I am glad to hear that you found the example and blogpost useful in your development! :)

    To answer your questions in order:

    1. The payload of the advertising packets being updated will only ever contain a single letter of the 6 defined ones. The first time it changes it will advertise 'N' for 5 seconds, the next 5 seconds 'O', following that 'R', and so on. So, there are 6 possible payloads defined, each of which is only 1 character big. This was done so that you would see the payload change every 5 seconds.

    2. The static keyword lets the variable be persistent between function calls, so it is not cleared when the function is exited. This way, the variable will keep its value between function calls, and thus iterate to the top index before being reset to zero again. You might previously have seen the static keyword used on global variables - the behavior here is a little different, and makes the variable inaccessible from other files.

    Best regards,
    Karl

  • The defines I am referring to:

    #define MANUF_PAYLOAD_1 0x4E // N
    #define MANUF_PAYLOAD_2 0x4F // O
    #define MANUF_PAYLOAD_3 0x52 // R
    #define MANUF_PAYLOAD_4 0x44 // D
    #define MANUF_PAYLOAD_5 0x49 // I
    #define MANUF_PAYLOAD_6 0x43 // C

    #define PAYLOAD_SIZE 1
    #define TOP_INDEX 5

    static uint8_t manufacturing_data_payload_list [] =
    {
    MANUF_PAYLOAD_1,
    MANUF_PAYLOAD_2,
    MANUF_PAYLOAD_3,
    MANUF_PAYLOAD_4,
    MANUF_PAYLOAD_5,
    MANUF_PAYLOAD_6
    };

    For clarification purposes, I will included the function I am referring to:

    static void adv_data_update_timer_handler(void * p_context)
    {
    ret_code_t err_code;
    ble_advdata_manuf_data_t manuf_data;

    new_advdata.p_manuf_specific_data = &manuf_data;

    static uint8_t payload_index = 0;

    NRF_LOG_INFO("Updating advertising data!");

    manuf_data.company_identifier = APP_COMPANY_IDENTIFIER;

    manuf_data.data.p_data = manufacturing_data_payload_list + payload_index;
    manuf_data.data.size = PAYLOAD_SIZE;

    err_code = ble_advertising_advdata_update(&m_advertising, &new_advdata, NULL);
    APP_ERROR_CHECK(err_code);

    if(payload_index == TOP_INDEX)
    {
    payload_index = 0;
    }
    else
    {
    payload_index++;
    }

    NRF_LOG_INFO("Advertising data updated!");

    }