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

Handling multiple Characteristics (client side)

BLE_GATTC_EVT_HVX 

Hi,

I've implemented a BLE client that is able to:

  • connect to another BLE device
  • discover its services and characteristics
  • enable notifications for a specific characteristic
  • write to a specific characteristic

My aim is to enable notifications on multiple characteristics, and to be able to know from which characteristic the BLE_GATTC_EVT_HVX is generated, so that I can process the upcoming data in different ways.

This is what I'm doing upon discovery event, where I'm looking for a specific characteristic UUID within my own service to enable its notifications:

my_service_client_evt_t my_evt;

void my_service_on_db_disc_evt(my_service_client_t * p_my_service_client, ble_db_discovery_evt_t const * p_evt)
{    
    // Check if My Service was discovered.
    if (p_evt->evt_type == BLE_DB_DISCOVERY_COMPLETE &&
        p_evt->params.discovered_db.srv_uuid.uuid == MY_SERVICE_UUID &&
        p_evt->params.discovered_db.srv_uuid.type == p_my_service_client->uuid_type)
    {
        my_evt.evt_type    = MY_SERVICE_CLIENT_EVT_DISCOVERY_COMPLETE;
        my_evt.conn_handle = p_evt->conn_handle;
        
        for (uint32_t i = 0; i < p_evt->params.discovered_db.char_count; i++)
        {
            const ble_gatt_db_char_t * p_char = &(p_evt->params.discovered_db.charateristics[i]);

            switch (p_char->characteristic.uuid.uuid)
            {
                case CUSTOM_VALUE_CHAR_UUID:  // The discovered UUID matches the UUID I'm looking for

                    // Select this CVC                    
                    my_evt.peer_db.my_handle = p_char->characteristic.handle_value;
                    
                    // Enable notifications for this CVC
                    cccd_configure(my_evt.conn_handle,p_char->cccd_handle, true);
                   
                    break;
                
                default:
                    break;
            }
        }
        
        //If the instance has been assigned prior to db_discovery, assign the db_handles
        if (p_my_service_client->conn_handle != BLE_CONN_HANDLE_INVALID)
        {
            if (p_my_service_client->peer_my_service_db.my_handle == BLE_GATT_HANDLE_INVALID)
            {
                p_my_service_client->peer_my_service_db = my_evt.peer_db;
            }
        }

        p_my_service_client->evt_handler(p_my_service_client, &my_evt);
    }
}

And this is what I'm doing to process the notifications from such characteristic:

// This handler is called upon BLE_GATTC_EVT_HVX event
static void on_hvx(ble_evt_t const * p_ble_evt)
{
    uint8_t   cvc_length    = p_ble_evt->evt.gattc_evt.params.hvx.len;
    uint8_t * cvc_content   = (uint8_t *)p_ble_evt->evt.gattc_evt.params.hvx.data;
    
    // Doing something with cvc_content
}

Now, I was thinking I could:

  • enable notifications for different characteristics upon different calls to ble_db_discovery_start()
  • retrieve the UUID of the characteristic I'm getting the notification from by looking into p_ble_evt parameters (but I could not find such information)

Therefore I'm asking you, can I achieve this? How can I know to which characteristic the p_ble_evt is related?



Thank you very much and best regards

  • Hi Lorenzo 

    Have you had a look at some of the client examples in the SDK?

    They show how we have decided to handle this situation in the SDK modules. 

    Essentially every attribute (be it a service, characteristic value or characteristic descriptor) will be assigned a unique 16-bit handle during the service discovery process, and it is up to you to take note of this handle and store it somewhere so you know what each handle is associated with. 

    Whenever you get the BLE_GATTC_EVT_HVX event later on it will include the 16-bit handle, and then it is up to you to 'check your notes' and figure out what this handle belongs to. 

    To use the ble_app_uart_c example for reference, it has a single service called NUS, which defines the following structs to keep track of different info related to the service:

    /**@brief Handles on the connected peer device needed to interact with it. */
    typedef struct
    {
        uint16_t nus_tx_handle;      /**< Handle of the NUS TX characteristic, as provided by a discovery. */
        uint16_t nus_tx_cccd_handle; /**< Handle of the CCCD of the NUS TX characteristic, as provided by a discovery. */
        uint16_t nus_rx_handle;      /**< Handle of the NUS RX characteristic, as provided by a discovery. */
    } ble_nus_c_handles_t;
    
    /**@brief Structure containing the NUS event data received from the peer. */
    typedef struct
    {
        ble_nus_c_evt_type_t evt_type;
        uint16_t             conn_handle;
        uint16_t             max_data_len;
        uint8_t            * p_data;
        uint16_t             data_len;
        ble_nus_c_handles_t  handles;     /**< Handles on which the Nordic UART service characteristics were discovered on the peer device. This is filled if the evt_type is @ref BLE_NUS_C_EVT_DISCOVERY_COMPLETE.*/
    } ble_nus_c_evt_t;

    In particular, note the ble_nus_c_handles_t struct which contains handles for the TX characteristic value, the TX characteristic CCCD, and the RX characteristic value. This allows the NUS client code to know whenever the server writes to one of these attributes. 

    In the ble_nus_c_on_db_disc_evt(..) function in ble_nus_c.c you can see how these handles are assigned during the service discovery process, as long as this function is called in the db_disc_handler(..) function in main.c:

    static void db_disc_handler(ble_db_discovery_evt_t * p_evt)
    {
        ble_nus_c_on_db_disc_evt(&m_ble_nus_c, p_evt);
    }

    The NUS service only has one characteristic that supports notifications, but the only difference is that you need to extend the handles my_service_c_handles_t struct with enough handles for all the characteristics, and extend the my_service_on_db_disc_evt function to handle the various UUID's for these characteristics. 

    Best regards
    Torbjørn 

  • Hi Torbjørn,

    it is already much more clear to me now. Could you just confirm things go as follows?

    • Within db_disc_handler(..) I should call different db_disc_evt(..) functions for each "expected" service
    • Within a specific db_disc_evt(..) function, I should parse the discovered characteristics and properly store the needed handles within a certain my_service_evt_t structure
    • Such my_service_evt_t is then passed to my_service_evt_handler(..) that triggers the call to my_service_handles_assign where the handles are actually stored in my very own "clipboard".
    • I can discriminate data belonging to different characteristics by checking their handle against the ones I have in my "clipboard" whenever on_hvx(..) gets triggered
    • I can write data to a specific characteristic by specifying its handle (from my "clipboard") in the .handle element of the write_params structure that I'm passing to sd_ble_gattc_write(..)

    Thank you very much for the very clear answer!

  • Hi Lorenzo 

    Exactly, your steps sum it up nicely. 

    When creating your own service I recommend copying one of the existing ones (like ble_nus.c), and basing your own service off of this. Then you don't risk missing any of these steps. 

    You just need to rename the various functions and structs (replace "nus" with your own service name), change the UUID, and then change the characteristic properties and number of characteristics as needed. 

    Best regards
    Torbjørn

Related