12. Dynamic Advertising Data

We just explored how we can create the advertised BD name at runtime. Advertising data can contain multiple segments of various other data types, and some of those can carry dynamic application data - meaning data that changes over time. The dynamic data could be sensor data or other data that it makes sense to advertise. The advantage of advertising sensor data is that we can transmit the data without the requirement to enter into a BLE connection. By advertising our data, we can make it available to an infinite number of central devices, such as smartphones, simultaneously.

Typically, dynamic advertising data is provided in one of the following advertising data types:
  • GAP_AD_TYPE_SERVICE_16_BIT_DATA, 16bit UUID service data which is used when the data is associated with a Bluetooth® SIG adopted profile

  • GAP_AD_TYPE_SERVICE_128_BIT_DATA, 128bit UUDI service data which is used for custom service data and is available without restrictions

  • GAP_AD_TYPE_MANU_SPECIFIC_DATA, Manufacturer specific data which requires a 16bit Company identifier registered with the Bluetooth® SIG

We will use the unrestricted 128bit UUID service advertising data type in the following. The methods demonstrated will work equally well with any of the other types as long as you adhere to their specific restrictions.

The 128bit UUID service data segment consists of 4 parts:
  1. The one octet length of the segment excluding the length field itself

  2. The advertising data type, GAP_AD_TYPE_SERVICE_128_BIT_DATA

  3. The 128bit (16 octets) universally unique identifier or UUID

  4. The value of the data that we want to advertise.

In the previous part of the tutorial, we demonstrated how to set the BD name at runtime. In order to simplify this part of the tutorial, we will revert back to default advertising, and let the SDK construct the full advertising package.

  • In user_callback_config.h, change the following declaration (highlighted portion)

    static const struct default_app_operations user_default_app_operations = {
    -   .default_operation_adv = user_advertise_operation,
    +  .default_operation_adv  = default_advertise_operation,
    };
    

Hint

After we revert back to default advertising, the BD name will also revert back to the smiley with shades emoji.

We will, as mentioned previously, use the GAP_AD_TYPE_SERVICE_128_BIT_DATA to advertise dynamic data. Our data will be a simple counter of one octet that we will increment using a timer. We will just randomly generate a UUID for the data.

We can now calculate the length field as follows:
  • 1 octet data type (GAP_AD_TYPE_SERVICE_128_BIT_DATA is defined in gap.h as 0x21)

  • 16 octets UUID

    Note

    Here: 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11

  • 1 byte of data (We will start with a value of 0x00)

The length field must therefore be 18 octets or 0x12 for those of us who are addicted to hexadecimal notation.

Formatted as a string, the data will look like this:

\x12\x21\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x00

  1. In user_config.h, insert this string into the USER_ADVERTISE_DATA definition:

    #define USER_ADVERTISE_DATA "\x18\x21\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x00"
    
  2. We will need to keep our counter value retained during sleep. In user_empty_periphral_template.c, right below the include statements, add the following:

    uint8_t my_counter __SECTION_ZERO("retention_mem_area0") = 0; // @RETENTION MEMORY
    

    We will have to initialize our counter. To do this we will re-route the .app_on_init callback to user space.

  3. In user_callback_config, change the callback from default_app_on_init to user_app_on_init:

    static const struct arch_main_loop_callbacks user_app_main_loop_callbacks = {
    - .app_on_init            = default_app_on_init,
    + .app_on_init            = user_app_on_init,
    
  4. Now, add a prototype to this function at the bottom of user_empty_peripheral_template.h:

    void user_app_on_init(void);
    
  5. This gives us a function that we can use for initialization of our global variable as well as starting our data update timer. At the bottom of user_empty_peripheral_template.c, add the following code:

    void user_app_on_init()
    {
       my_counter = 0;
       app_easy_timer(100, update_adv_data); // One second one-shot timer
       // After we have initialized our variable, we call the default handler
       default_app_on_init();
    }
    
  6. We can then implement our timer handler just above this function:

    void update_adv_data()
    {
       my_counter++;
    
       // Copy the counter value into the advertising data (ignore the scan response data)
       uint8_t adv_data[USER_ADVERTISE_DATA_LEN];
       memcpy(&adv_data, update_adv_data, USER_ADVERTISE_DATA_LEN);
    
       // Load the counter value into the last octet of the advertising data
       adv_data[USER_ADVERTISE_DATA_LEN -1] = my_counter;
    
       // Update the advertising data
       app_easy_gap_update_adv_data(adv_data, USER_ADVERTISE_DATA_LEN, NULL, NULL);
    
       // Restart the timer
       app_easy_timer(100, update_adv_data); // One second one-shot timer
    }