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

Determining if DFU occurred

After a DFU, when the new firmware is booted a service changed indication is not being sent so iOS only sees the DFU service. Should the softdevice be sending the service changed indication automatically? Here it says pm_local_database_has_changed() should be called, but how should I determine if a DFU has just been performed?

Parents
  • Hi,

    Good question. This is not handled by any example so it is up to you how to do it. This could be done in several ways, but one possible way (off the top of my head):

    • The firmware version is stored in the bootoader settings page, and can therefor be read from the application.
    • Use FDS (or other method) to store persistently in flash which firmware version is used when a bond is made to a peer device.
    • When connecting to a peer, search for a FDS record of that peer indicating the firmware version.
      • If the firmware version matches the current version, do nothing.
      • If the firmware version does not match the current version, indicate service changed and update the FDS record to have the value of the current firmware version.
  • So I've implemented this, calling pm_local_database_has_changed() on boot when the application version has changed. This bit works correctly. This function stores flags for each bonded device to track if service changed has been sent. On connection, this indication is sent immediately – before the service changed characteristic has had indications enabled. The call to sd_ble_gatts_service_changed returns NRF_ERROR_INVALID_STATE. However, BLE_GATTS_EVT_SC_CONFIRM still occurs, so the service changed indication is stored as sent and is not sent again. I think this is a bug?

  • After further investigation,

    If NRF_ERROR_INVALID_STATE occurs, on line 364 of gatt_cache_manager.c, the state of the service changed CCCD is determined. However, if the client has not subscribed, then it falls through to NRF_ERROR_NOT_SUPPORTED, which cancels the indication for this peer. I am not sure if this is correct, but perhaps it is as perhaps the invalid state error does not occur if the client is not subscribed.

    In my case, indications are enabled at this point. However, the service_changed_cccd function does not work correctly. It uses sd_ble_gatts_value_get which returns the value of an attribute handle, not its UUID. I don't think there is a way to get the UUID of an attribute. However, the purpose of the function can be achieved with the following code. After changing to this, indications work.

    static ret_code_t service_changed_cccd(uint16_t conn_handle, const uint16_t * p_cccd)
    {
        uint16_t   end_handle;
        ret_code_t err_code = sd_ble_gatts_initial_user_handle_get(&end_handle);
        ASSERT(err_code == NRF_SUCCESS);
    
        for (uint16_t handle = 1; handle < end_handle; handle++)
        {
            uint16_t uuid;
            ble_gatts_value_t value = {.p_value = (uint8_t *)&uuid, .len = 2, .offset = 3};
            err_code = sd_ble_gatts_value_get(conn_handle, handle, &value);
            if(err_code == NRF_ERROR_INVALID_PARAM)
            {
                // the offset was not valid for this attribute
                continue;
            }
            else if (err_code != NRF_SUCCESS)
            {
                return err_code;
            }
            else if (uuid == BLE_UUID_GATT_CHARACTERISTIC_SERVICE_CHANGED)
            {
                 ble_gatts_value_t cccd_val = {.p_value = (uint8_t *)p_cccd, .len = 2, .offset = 0};
                return sd_ble_gatts_value_get(conn_handle, handle+2, &cccd_val);
            }
        }
        return NRF_ERROR_NOT_FOUND;
    }

Reply
  • After further investigation,

    If NRF_ERROR_INVALID_STATE occurs, on line 364 of gatt_cache_manager.c, the state of the service changed CCCD is determined. However, if the client has not subscribed, then it falls through to NRF_ERROR_NOT_SUPPORTED, which cancels the indication for this peer. I am not sure if this is correct, but perhaps it is as perhaps the invalid state error does not occur if the client is not subscribed.

    In my case, indications are enabled at this point. However, the service_changed_cccd function does not work correctly. It uses sd_ble_gatts_value_get which returns the value of an attribute handle, not its UUID. I don't think there is a way to get the UUID of an attribute. However, the purpose of the function can be achieved with the following code. After changing to this, indications work.

    static ret_code_t service_changed_cccd(uint16_t conn_handle, const uint16_t * p_cccd)
    {
        uint16_t   end_handle;
        ret_code_t err_code = sd_ble_gatts_initial_user_handle_get(&end_handle);
        ASSERT(err_code == NRF_SUCCESS);
    
        for (uint16_t handle = 1; handle < end_handle; handle++)
        {
            uint16_t uuid;
            ble_gatts_value_t value = {.p_value = (uint8_t *)&uuid, .len = 2, .offset = 3};
            err_code = sd_ble_gatts_value_get(conn_handle, handle, &value);
            if(err_code == NRF_ERROR_INVALID_PARAM)
            {
                // the offset was not valid for this attribute
                continue;
            }
            else if (err_code != NRF_SUCCESS)
            {
                return err_code;
            }
            else if (uuid == BLE_UUID_GATT_CHARACTERISTIC_SERVICE_CHANGED)
            {
                 ble_gatts_value_t cccd_val = {.p_value = (uint8_t *)p_cccd, .len = 2, .offset = 0};
                return sd_ble_gatts_value_get(conn_handle, handle+2, &cccd_val);
            }
        }
        return NRF_ERROR_NOT_FOUND;
    }

Children
  • Hi,

    Thank you for letting us know. Which SDK version have you used for the change, SDK 15.3?

  • Here is some improved code which uses the UUIDs of the attributes instead of assuming the location of the CCCD relative to the characteristic declaration:

    static ret_code_t service_changed_cccd(uint16_t conn_handle, const uint16_t * p_cccd)
    {
        uint16_t   end_handle;
        bool sc_found = false;
        ret_code_t err_code = sd_ble_gatts_initial_user_handle_get(&end_handle);
        ASSERT(err_code == NRF_SUCCESS);
    
        for (uint16_t handle = 1; handle < end_handle; handle++)
        {
            ble_uuid_t uuid;
            err_code = sd_ble_gatts_attr_get(handle, &uuid, NULL);
            VERIFY_SUCCESS(err_code);
            if(uuid.type != BLE_UUID_TYPE_BLE)
            {
                continue;
            }
            if(uuid.uuid == BLE_UUID_CHARACTERISTIC)
            {
                // characteristic declaration specification from Bluetooth 4.0 Vol 3 3.3.1
                static const uint8_t decl_val_size = 5;
                NRF_LOG_DEBUG("Found char at handle %d", handle);
                // get characteristic declaration
                uint8_t decl_val[decl_val_size];
                ble_gatts_value_t characteristic_decl = {.p_value = decl_val, .len = decl_val_size, .offset = 0};
                err_code = sd_ble_gatts_value_get(conn_handle, handle, &characteristic_decl);
                VERIFY_SUCCESS(err_code);
                if(characteristic_decl.len != decl_val_size)
                {
                    continue;
                }
                uint16_t char_uuid = *((uint16_t *)&decl_val[3]);
                sc_found = char_uuid == BLE_UUID_GATT_CHARACTERISTIC_SERVICE_CHANGED;
                // skip past characteristic value handle
                handle = *((uint16_t *)&decl_val[1]);
                NRF_LOG_DEBUG("Skipping past val handle to %d (%s)", handle+1, sc_found ? "sc found" : "sc not found");
            }
            else if(sc_found && (uuid.uuid == BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG))
            {
                NRF_LOG_DEBUG("Found cccd at handle %d", handle);
                ble_gatts_value_t cccd_val = {.p_value = (uint8_t *)p_cccd, .len = 2, .offset = 0};
                return sd_ble_gatts_value_get(conn_handle, handle, &cccd_val);
            }
        }
        NRF_LOG_DEBUG("Did not find service changed CCCD");
        return NRF_ERROR_NOT_FOUND;
    }

Related