This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

ble_advertising_advdata_update

Hi All. 

I am trying to use the "ble_advertising_advdata_update" function, but my compiler is giving me the following error. Which class do I need to add?

undefined reference to `ble_advertising_advdata_update


I am using SDK nRF5_SDK_15.0.0_a53641a. Advertising works ok after initialisation, but I am trying to update the manufacturer data whilst advertising.

Thanks

void advertising_update(void)
{
    ret_code_t err_code;
    uint8_t testData[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x11, 0x22};

    ble_gap_adv_data_t adv_data2 = {
        .adv_data.p_data = testData,
        .adv_data.len = 0x08,
        .scan_rsp_data.p_data = testData,
        .scan_rsp_data.len = 0//sizeof(raw_scan_rsp_data_buffer2)
    };

    err_code = ble_advertising_advdata_update(&m_advertising, &adv_data2, true);
    APP_ERROR_CHECK(err_code);
}

Parents
  • While the original poster has likely moved on, I am posting for others who come here with a similar issue. 

    My team also wanted to update the advertising data of a device dynamically. Stripping away any details that are beyond the scope of the question, our use case was a sensor which would advertise 1 when sensing an event and advertise 0 when not sensing an event. 

    We ran into the same problems using the SDK and API to dynamically update the advertising data as others here and in other forum posts. 

    We did find a working and efficient solution, however, and it is as follows:

    Note the ble_gap_adv_data_t struct which holds the advertising data. When initialized it would look something like:

    static uint8_t m_adv_handle = BLE_GAP_ADV_SET_HANDLE_NOT_SET;                   /**< Advertising handle used to identify an advertising set. */
    static uint8_t m_enc_advdata[BLE_GAP_ADV_SET_DATA_SIZE_MAX];                    /**< Buffer for storing an encoded advertising set. */
    static uint8_t m_enc_scan_response_data[BLE_GAP_ADV_SET_DATA_SIZE_MAX];         /**< Buffer for storing an encoded scan data. */
    
    /**@brief Struct that contains pointers to the encoded advertising data. */
    static ble_gap_adv_data_t m_adv_data =
    {
        .adv_data =
        {
            .p_data = m_enc_advdata,
            .len    = BLE_GAP_ADV_SET_DATA_SIZE_MAX
        },
        .scan_rsp_data =
        {
            .p_data = m_enc_scan_response_data,
            .len    = BLE_GAP_ADV_SET_DATA_SIZE_MAX
    
        }
    };

    As the OP (Original Poster) noted, the .adv_data.p_data data field is the right place to be looking. So let's follow .adv_data.p_data through the code and see what happens...

    First, let's look at the relevant bits of advertising_init()

    static void advertising_init(void)
    {
        .
        .
        .
    
        // Build and set advertising data.
        ble_advdata_t advdata;
        memset(&advdata, 0, sizeof(advdata));
        
        uint8_t data[]                      = {0xDE, 0xAD, 0xBE, 0xEF};
        manuf_data.company_identifier       = 0x0059;
        manuf_data.data.p_data              = data;
        manuf_data.data.size                = sizeof(data);
        advdata.p_manuf_specific_data       = &manuf_data;
    
        advdata.name_type          = BLE_ADVDATA_FULL_NAME;
        advdata.include_appearance = true;
        advdata.flags              = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
    
        err_code = ble_advdata_encode(&advdata, m_adv_data.adv_data.p_data, &m_adv_data.adv_data.len);
        APP_ERROR_CHECK(err_code);
        
        NRF_LOG_INFO("m_adv_data.adv_data.p_data address %p", m_adv_data.adv_data.p_data);
        
        // Build and set scan response data.
        .
        .
        .
    
        // Build and set advertising parameters.
        ble_gap_adv_params_t adv_params;
        .
        .
        .
    
        err_code = sd_ble_gap_adv_set_configure(&m_adv_handle, &m_adv_data, &adv_params);
        APP_ERROR_CHECK(err_code);
    }

    Of important note here is that, to keep with Bluetooth standards, the ble_advdata_encode() function is used. This is because, from Source:

    "...the term advertising data refers to the 0..31 byte long payload that is available for application use. (In Bluetooth Core specification this field is referred to as AdvData).

    Advertising data consists of one or more Advertising Data (AD) elements. Each element is formatted as follows:

    • 1st byte: length of the element (excluding the length byte itself)
    • 2nd byte: AD type – specifies what data is included in the element
    • AD data – one or more bytes - the meaning is defined by AD type"

    Which is why our dynamic advertising data is in the manufacturer-specific data field.

    So we don't want to simply override the entire .adv_data.p_data as the OP does or we will not be in compliance with Bluetooth standards (unless you take care to format your payload yourself). 

    The last call in the advertising_init() snippet was:

    err_code = sd_ble_gap_adv_set_configure(&m_adv_handle, &m_adv_data, &adv_params);

    which is a Soft Device call so all we can look at is the declaration of that function which can be found in ble_gap.h:

    SVCALL(SD_BLE_GAP_ADV_SET_CONFIGURE, uint32_t, sd_ble_gap_adv_set_configure(uint8_t *p_adv_handle, ble_gap_adv_data_t const *p_adv_data, ble_gap_adv_params_t const *p_adv_params));

    ...noting that SVCALL is a macro defined in nrf_svc.h and that this whole thing really does turn into a function definition. So why did we come all this way to look at a messy function declaration? We came so we could see the parameter ble_gap_adv_data_t const *p_adv_data, and in particular note that the pointer it is looking for is a constant. Therefore, while we can't know what sd_ble_gap_adv_set_configure(...) is doing since the soft device is proprietary, we can know that it expects the pointer to the advertising data to never change, which suggests the soft device will keep looking in the same location for advertising data (testing have verified this). 

    And that brings us to our solution, because we defined m_adv_data, so we can directly write to its address in memory. Writing to it is easy, we just use memcpy() or something like:

    m_adv_data.adv_data.p_data[your_offset] = 0x01;

    The only thing you need to do is determine your_offset, which is the where in p_data we want to put our dynamically updated payload, as it is going to be different depending on what fields you have in your advertising payload. Remember, we can't just write anywhere in p_data because the format is standardized, and only certain fields are meant to convey arbitrary data.

    And now you may realize why 

    NRF_LOG_INFO("m_adv_data.adv_data.p_data address %p", m_adv_data.adv_data.p_data);

    was in the advertising_init() function, and why the manuf_data.data.p_data was set to 0xDEADBEEF... it was so we could run the debugger to find the offset we need to write directly to the manufacturer-specific data section of m_adv_data.adv_data.p_data.

    Now, if we ran the debugger and look at the logs we would see the address of m_adv_data.adv_data.p_data printed. Then, we would go to the view->debug->memory option in Segger Embedded Studio and go to that address and look for 0xDEADBEEF. You count the bytes from the address of m_adv_data.adv_data.p_data to the first D in 0xDEADBEEF to get your offset. 

    As for my team's use case, we can now dynamically update the advertising data by writing 

    m_adv_data.adv_data.p_data[11] = 0x01;

    when the sensor picks up an event and 

    m_adv_data.adv_data.p_data[11] = 0x00;

    when it does not. This is a simple solution that bypasses using the advertising update functions of the SDK, and avoids other complex solutions like double buffering. It is just one line to change the advertising data dynamically. 

    Just be mindful, as since you are writing directly to memory nothing is error checking what you are writing. You need to make sure that you don't write more characters than your field allows, that you update the offset if you change the number of fields in the advertising payload, and that you don't write anything which breaks the Bluetooth standard (such as writing over length fields, etc).

    Hope that helps.

Reply
  • While the original poster has likely moved on, I am posting for others who come here with a similar issue. 

    My team also wanted to update the advertising data of a device dynamically. Stripping away any details that are beyond the scope of the question, our use case was a sensor which would advertise 1 when sensing an event and advertise 0 when not sensing an event. 

    We ran into the same problems using the SDK and API to dynamically update the advertising data as others here and in other forum posts. 

    We did find a working and efficient solution, however, and it is as follows:

    Note the ble_gap_adv_data_t struct which holds the advertising data. When initialized it would look something like:

    static uint8_t m_adv_handle = BLE_GAP_ADV_SET_HANDLE_NOT_SET;                   /**< Advertising handle used to identify an advertising set. */
    static uint8_t m_enc_advdata[BLE_GAP_ADV_SET_DATA_SIZE_MAX];                    /**< Buffer for storing an encoded advertising set. */
    static uint8_t m_enc_scan_response_data[BLE_GAP_ADV_SET_DATA_SIZE_MAX];         /**< Buffer for storing an encoded scan data. */
    
    /**@brief Struct that contains pointers to the encoded advertising data. */
    static ble_gap_adv_data_t m_adv_data =
    {
        .adv_data =
        {
            .p_data = m_enc_advdata,
            .len    = BLE_GAP_ADV_SET_DATA_SIZE_MAX
        },
        .scan_rsp_data =
        {
            .p_data = m_enc_scan_response_data,
            .len    = BLE_GAP_ADV_SET_DATA_SIZE_MAX
    
        }
    };

    As the OP (Original Poster) noted, the .adv_data.p_data data field is the right place to be looking. So let's follow .adv_data.p_data through the code and see what happens...

    First, let's look at the relevant bits of advertising_init()

    static void advertising_init(void)
    {
        .
        .
        .
    
        // Build and set advertising data.
        ble_advdata_t advdata;
        memset(&advdata, 0, sizeof(advdata));
        
        uint8_t data[]                      = {0xDE, 0xAD, 0xBE, 0xEF};
        manuf_data.company_identifier       = 0x0059;
        manuf_data.data.p_data              = data;
        manuf_data.data.size                = sizeof(data);
        advdata.p_manuf_specific_data       = &manuf_data;
    
        advdata.name_type          = BLE_ADVDATA_FULL_NAME;
        advdata.include_appearance = true;
        advdata.flags              = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
    
        err_code = ble_advdata_encode(&advdata, m_adv_data.adv_data.p_data, &m_adv_data.adv_data.len);
        APP_ERROR_CHECK(err_code);
        
        NRF_LOG_INFO("m_adv_data.adv_data.p_data address %p", m_adv_data.adv_data.p_data);
        
        // Build and set scan response data.
        .
        .
        .
    
        // Build and set advertising parameters.
        ble_gap_adv_params_t adv_params;
        .
        .
        .
    
        err_code = sd_ble_gap_adv_set_configure(&m_adv_handle, &m_adv_data, &adv_params);
        APP_ERROR_CHECK(err_code);
    }

    Of important note here is that, to keep with Bluetooth standards, the ble_advdata_encode() function is used. This is because, from Source:

    "...the term advertising data refers to the 0..31 byte long payload that is available for application use. (In Bluetooth Core specification this field is referred to as AdvData).

    Advertising data consists of one or more Advertising Data (AD) elements. Each element is formatted as follows:

    • 1st byte: length of the element (excluding the length byte itself)
    • 2nd byte: AD type – specifies what data is included in the element
    • AD data – one or more bytes - the meaning is defined by AD type"

    Which is why our dynamic advertising data is in the manufacturer-specific data field.

    So we don't want to simply override the entire .adv_data.p_data as the OP does or we will not be in compliance with Bluetooth standards (unless you take care to format your payload yourself). 

    The last call in the advertising_init() snippet was:

    err_code = sd_ble_gap_adv_set_configure(&m_adv_handle, &m_adv_data, &adv_params);

    which is a Soft Device call so all we can look at is the declaration of that function which can be found in ble_gap.h:

    SVCALL(SD_BLE_GAP_ADV_SET_CONFIGURE, uint32_t, sd_ble_gap_adv_set_configure(uint8_t *p_adv_handle, ble_gap_adv_data_t const *p_adv_data, ble_gap_adv_params_t const *p_adv_params));

    ...noting that SVCALL is a macro defined in nrf_svc.h and that this whole thing really does turn into a function definition. So why did we come all this way to look at a messy function declaration? We came so we could see the parameter ble_gap_adv_data_t const *p_adv_data, and in particular note that the pointer it is looking for is a constant. Therefore, while we can't know what sd_ble_gap_adv_set_configure(...) is doing since the soft device is proprietary, we can know that it expects the pointer to the advertising data to never change, which suggests the soft device will keep looking in the same location for advertising data (testing have verified this). 

    And that brings us to our solution, because we defined m_adv_data, so we can directly write to its address in memory. Writing to it is easy, we just use memcpy() or something like:

    m_adv_data.adv_data.p_data[your_offset] = 0x01;

    The only thing you need to do is determine your_offset, which is the where in p_data we want to put our dynamically updated payload, as it is going to be different depending on what fields you have in your advertising payload. Remember, we can't just write anywhere in p_data because the format is standardized, and only certain fields are meant to convey arbitrary data.

    And now you may realize why 

    NRF_LOG_INFO("m_adv_data.adv_data.p_data address %p", m_adv_data.adv_data.p_data);

    was in the advertising_init() function, and why the manuf_data.data.p_data was set to 0xDEADBEEF... it was so we could run the debugger to find the offset we need to write directly to the manufacturer-specific data section of m_adv_data.adv_data.p_data.

    Now, if we ran the debugger and look at the logs we would see the address of m_adv_data.adv_data.p_data printed. Then, we would go to the view->debug->memory option in Segger Embedded Studio and go to that address and look for 0xDEADBEEF. You count the bytes from the address of m_adv_data.adv_data.p_data to the first D in 0xDEADBEEF to get your offset. 

    As for my team's use case, we can now dynamically update the advertising data by writing 

    m_adv_data.adv_data.p_data[11] = 0x01;

    when the sensor picks up an event and 

    m_adv_data.adv_data.p_data[11] = 0x00;

    when it does not. This is a simple solution that bypasses using the advertising update functions of the SDK, and avoids other complex solutions like double buffering. It is just one line to change the advertising data dynamically. 

    Just be mindful, as since you are writing directly to memory nothing is error checking what you are writing. You need to make sure that you don't write more characters than your field allows, that you update the offset if you change the number of fields in the advertising payload, and that you don't write anything which breaks the Bluetooth standard (such as writing over length fields, etc).

    Hope that helps.

Children
  • , your answer was the solution for me. Very simple, and does the job perfectly!

    I'm using SDK 17.0.2 (S112) and while some variables and names have changed, following the same steps brought me to the right pointer and bytes I have to change dynamically.

    The only restriction is that you have to keep your data length unchanged, but that's hardly an issue for most applications, in my view.

    I'll have this running in prototypes in a few days, and I'll post any issues here if they appear.

    Thanks.

Related