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

Automatically start notification upon connection event (manually write cccd?) - Short tutorial on notifications

Hi,

I want to implement a Peripheral (GATT Server) that automatically starts notification once a Central connects. No bonding is provided in our system (connection is between arbitrary devices).

I have read several times that notifications must be initiated by the GATT Client. But when a bonding exists between the devices, the former configuration (e.g. notification) is restored upon connection. It sounds like the device manager is able to restore the notification state (cccd) at the moment a connection is established.

Can this be done manually / in my own code, e.g. by writing to cccd? I couldn't find any SD interface function that enables notifications. Manually writing to cccd results in error code 15 (NRF_ERROR_FORBIDDEN).

My code looks something like this...

static void on_connect(ble_myservice_t* p_service, ble_evt_t* p_event)
{
    uint32_t result;
    ble_gatts_value_t value;
    uint16_t data;

    p_service->conn_handle = p_event->evt.gap_evt.conn_handle;

    // Seems we need this, otherwise sd_ble_gatts_hvx() fails
    result = sd_ble_gatts_sys_attr_set(p_service->conn_handle, NULL, 0, 0);

    // Just for robustness, check for valid cccd handle
    if (p_service->mydata_handles.cccd_handle)
    {
        data = 1;  // 1 = Enable notifications
        value.len = 2;
        value.offset = 0;
        value.p_value = (uint8_t*)(&data);
        result = sd_ble_gatts_value_set(p_service->conn_handle, p_service->mydata_handles.cccd_handle, &value);
        // Here we get error 15 (NRF_ERROR_FORBIDDEN)
    }
}

Any suggestions? Or is the only way to write to cccd from the Central / GATT Client each time (which will cause additional latency that I would like to avoid)?

Update: I tried to update cccd after connection establishment by directly using the handle (which fails). After some research, it seems like ´sd_ble_gatts_sys_attr_set()´is used for setting cccd values. But I can nowhere find any specification about the buffer content that is handed over to ´sd_ble_gatts_sys_attr_set()´... shouldn't this be in the documentation at infocenter.nordicsemi.com?

Parents
  • Oookay, after spending a lot of time to get my communication flow work I would like to share my current knowledge about notifications with everyone.

    How to enable notifications in the GATT Server upon connection establishment:

    Use sd_ble_gatts_sys_attr_set() when the connection is established (BLE_GAP_EVT_CONNECTED) to initialize the related cccd values. The cccd cannot be written using the sd_ble_gatts_value_set() function like I assumend before!

    As I couldn't find any information about the syntax of the buffer handed over to sd_ble_gatts_sys_attr_set(), I will give a short summary here. The buffer contains a list of 6 byte (interpreted as 3x uint16_t) long entries which contain cccd initialization data:

    uint16_t handle    // use the cccd handle contained in the handles structure when initalizing the characteristic.
    uint16_t length    // lentgh of the attribute. Set this value to 2 as cccd attributes are uint16_t.
    uint16_t data      // the content of the attribute. Set this value to 1 to enable notifications. Set it to 2 to enable indications (not tested).
    

    At the end of the list, a CRC16 value is added. You may use the following webpage to calculate the CRC (please use the CRC-CCITT (0xFFFF) value): CRC calculator - please remember to add the CRC bytes with LSB first.

    The following example enables notifications for the characteristic whose cccd handle is 16:

    static void on_connect(ble_cbs_t* p_service, ble_evt_t* p_event)
    {
        uint16_t buffer[4];
        conn_handle = p_event->evt.gap_evt.conn_handle;    // save connection handle to global variable
        buffer[0] = 16;      // cccd handle
        buffer[1] = 2;       // cccd attribute size = 2
        buffer[2] = 1;       // 1 = enable notifications
        buffer[3] = 0xCACD;  // CRC-CCITT (0xFFFF)
        sd_ble_gatts_sys_attr_set(conn_handle, (uint8_t*)(buffer), 8, 0);    // initialize cccd attribute
    }
    

    That's it for the GATT Server side. If something doesn't work, you may enable notifications with Master Control Panel and read the system attribute buffer using sd_ble_gatts_sys_attr_get() in the disconnection event handler (in fact this is what I did to find out how this mechanism works). Also remember to configure the characteristic for notifications (set corresponding flag) when initializing your service in the SoftDevice.

    How to receive notifications in the GATT Client:

    All we have to do is handle the BLE_GATTC_EVT_HVX event. The notification data is contained in the event parameter structure.

    First I was confused why the data is not written to my characteristic variables that I use for manual read operations. Then I realized that characteristic values are only stored in the GATT Server and not in the GATT Client (the user is responsible for saving the data to local variables, if necessary).

    When I tried to invole sd_ble_gattc_hv_confirm() in my notification event handler, I received NRF_ERROR_INVALID_STATE as return code. I think this is because notifications do not use handshaking, so no confirmation packet can be sent. But it should be necessary to use this function when working with indications (that use handshaking).

    Future things to find out:

    How to initiate notifications from a nRF GATT Client the official way. I know that the GATT Client is supposed to write the cccd attribute to 1, but do not know yet how to find out the cccs handle and which SoftDevice interface function shall be used to set the cccd to 1. I have read that some devices perform a complete discovery during which the cccd handle is obtained. Although I consider this procedure pretty inefficient (especially if I already know the UUID of the characteristic that I want to observe).

    I hope this short tutorial may help some of you to get notifications running faster.

  • The cccd handle value in my case was 14. So it seems we need to calculate the CRC value in the application itself as it may vary.  CRC value can be calculated using crc16_compute( ) API. This API is provided by Nordic. 

    buffer[0] = p_nus->tx_handles.cccd_handle;                  // cccd handle
    
    buffer[1] = 2;                                              // cccd attribute size = 2
    
    buffer[2] = 1;                                              // 1 = enable notifications
    
    buffer[3] = crc16_compute((uint8_t const *)buffer,  6, NULL); // calculate CRC-CCITT (0xFFFF);

    This will also take care of any change in the cccd handle value

Reply
  • The cccd handle value in my case was 14. So it seems we need to calculate the CRC value in the application itself as it may vary.  CRC value can be calculated using crc16_compute( ) API. This API is provided by Nordic. 

    buffer[0] = p_nus->tx_handles.cccd_handle;                  // cccd handle
    
    buffer[1] = 2;                                              // cccd attribute size = 2
    
    buffer[2] = 1;                                              // 1 = enable notifications
    
    buffer[3] = crc16_compute((uint8_t const *)buffer,  6, NULL); // calculate CRC-CCITT (0xFFFF);

    This will also take care of any change in the cccd handle value

Children
  • Hi, 

    I try to enable notification of CCCD to send data over Bluetooth from my device to my phone. So in the function on_connect, I enable notifications with this code

        uint16_t buffer[4];
        buffer[0] = p_nus->rx_handles.cccd_handle;                     //cccd handle
        buffer[1] = 2;                                                 //cccd attribute size = 2
        buffer[2] = 1;                                                  //1 = enable notifications
        buffer[3] = crc16_compute((uint8_t const*)buffer, 6, NULL);    //CR-CCITT 
        sd_ble_gatts_sys_attr_set(p_ble_evt->evt.gap_evt.conn_handle, (uint8_t*)(buffer), 8, 0);   //initialize cccd attribute

    but I still have the same mistake: NRF_ERROR_INVALID_STATE which means notification disabled. So why there are no enabled?

Related