How to read a characteristic - just read, not get a notification. As a client

After a lot of help, I've managed to get a central_uart with custom UUIDs going.

After stripping out as much unwanted code and callback functions as possible, moving UART and BLE code  into their own files, getting rid of all mallocs and unnecessary queues, without all that noise, I think I now finally just about understand how all the stuff knits together and basically understand  how you, as a client, get data off a server. At least, if you subscribe to a characteristic.

In this particular instance, I need to read  one more two byte characteristic, but not subscribe to get notification of change. That characteristic will be set to a value before I try to connect to the remote unit and it will not change after that. So, how can I, as a client, simply read the value in a characteristic on a server?

I've tried searching for this and the closest I can find is this:

How to correctly read from a Characteristic

But it makes no sense to me. It says call:

sd_ble_gattc_read

But that doesn't, as  far as I can see actually read the data that's in the characteristic. It returns a  32-bit value indicating success of failure (which really should be an enum IMHO), but not data. So what do you call to get the data out of the characteristic on the server?


Also, for that function call in the API details on line it says:

offset    Offset into the attribute value to be read.

What does that even mean?

Am assuming conn_handle and handle are found when you do the gatt_discover.

As with all these things with this ecosystem it would be really useful to have very basic, clearly written examples. Maybe there is one somewhere, but I can't find it.

Thanks.

Parents
  • Hello,

    You can perform a GATT read using the bt_gatt_read() function. You can find several examples of this API being used by searching the through SDK tree in VS code:

    For example, here in the hrs_client.c file: https://github.com/nrfconnect/sdk-nrf/blob/67c885fb8d2d73067c77e0d105d8a88a8fbfca6c/subsys/bluetooth/services/hrs_client.c#L270 

    Best regards,

    Vidar

  • Hi, thanks for that.

    Like all this code I just find it impossible to follow.

    Before we get to that though, how do I get this SDK tree? I can't find anything like that in my VSC.

    So, let's go look at that function:

    int bt_hrs_client_sensor_location_read(struct bt_hrs_client *hrs_c,
                          bt_hrs_client_read_sensor_location_cb read_cb)

    I think this isn't actually reading, it's just setting up yet another callback function to be called once the read has been done?

    We're passing a pointer to a struct of bt_hrs_client which in my case I think is the  bt_custom_client that you came up with for me.

    I really struggle here. This is defined in hrs_cleint.h as :

    typedef void (*bt_hrs_client_read_sensor_location_cb)(struct bt_hrs_client *hrs_c,
                                  enum bt_hrs_client_sensor_location location,
                                  int err);

    Why is this callback function  necessary? Later on there is this:

    params->func = on_hrs_sensor_location_read;

    Which I am guessing is saying that once the characteristic has been read on  the remote node, then call the function:

    on_hrs_sensor_location_read

    Where you can get the data in the characteristic?

    I can't find where:

    struct bt_hrs_client_body_sensor_location  is filled. I'm guessing that in this:

    struct bt_hrs_client_body_sensor_location {
        /** Value handle. */
        uint16_t handle;

        /** Read parameters. */
        struct bt_gatt_read_params read_params;

        /** Read complete callback. */
        bt_hrs_client_read_sensor_location_cb read_cb;
    };

    handle is what was discovered in the gatt discovery for that particular UUID?

    Ideally I'd get rid of that callback.

    So then we check for NULL pointer I think with:

    (!hrs_c || !read_cb) which should read

    if (NULL == hrs_c) || (NULL == read_cb) ?

    Then there's this:

    if (!bt_hrs_client_has_sensor_location(hrs_c))

    Which I don't understand at all

    Then:

    if (atomic_test_and_set_bit(&hrs_c->state, HRS_SENSOR_LOCATION_READ_IN_PROGRES))

    I've seen this in a few places. I'm guessing this is doing the equivalent of this?

    Also, HRS_SENSOR_LOCATION_READ_IN_PROGRES is defined in hrs_client.h along with some other things:

    #define HRS_MEASUREMENT_NOTIFY_ENABLED BIT(0)
    #define HRS_MEASUREMENT_READ_IN_PROGRES BIT(1)
    #define HRS_SENSOR_LOCATION_READ_IN_PROGRES BIT(2)
    #define HRS_CONTROL_POINT_WRITE_PENDING BIT(3)

    But where does this come form, how are these assigned and how
    do they correspond to my custom_client setup please?



    ---------------------------------------------------------------

    bool ret_val;
    uint32_t value;
    ret_val = true;
    value = hrs_c->state

    hrs_c->state |= (1 << HRS_SENSOR_LOCATION_READ_IN_PROGRES);
    if (0 == (value & (1 << HRS_SENSOR_LOCATION_READ_IN_PROGRES)))
         ret_val = false;
    return ret_val;

    ---------------------------------------------------------------

    I'm guessing it's looking to see if a read's already in progress and if not, kicking one off.

    Then we come to assigning this first callback:

    hrs_c->sensor_location_char.read_cb = read_cb;

    Which I don't get.

    And I start to lose the trail with this even more:

    So there's a struct of type bt_gatt_read_params:

    I can see that in gatt.h but don't understand what some of the values do.

    There's yet another callback and I could just about understand func being needed to be called when the read was done, is that right?

    But there's this offset again which I have no idea about.
    Again the handle which I guess comes from gatt discovery but what is  handle_count for?


    And then I have a lack of knowledge of what calls bt_hrs_client_sensor_location_read, what should be passed to it and what the callback function(s) look like for when it's actually read.

    I can't find anything that calls bt_hrs_client_sensor_location_read and maybe that comes from not knowing how to get at this SDK tree.

    Am thinking that actually I don't really have a semi decent grip on how all this knits together yet.

    Thanks

  • ...

    Should I ignore your previous comment, or are you referring to an edit you made?

  • Should I ignore your previous comment, or are you referring to an edit you made

    Smiley I still need the previous, long post answering please, the second one, I altered because I realised what had happened and didn't need it answering!

  • DiBosco said:
    I still need the previous, long post answering please, the second one, I altered because I realised what had happened and didn't need it answering!

    Thanks, I just had to make sure Slight smile

    DiBosco said:
    Before we get to that though, how do I get this SDK tree? I can't find anything like that in my VSC.

    You can press Ctrl+Shift+f to open the search window and search across all files. Make sure the SDK folder is included in your VS Code workspace and not only your project for this to work.

    DiBosco said:
    int bt_hrs_client_sensor_location_read(struct bt_hrs_client *hrs_c,
                          bt_hrs_client_read_sensor_location_cb read_cb)

    I think this isn't actually reading, it's just setting up yet another callback function to be called once the read has been done?

    Like with the nus_client.c implementation, the heart rate client  also provides callbacks that can be registered by an application to receive data and events from the service. This part is not relevant in your case, what is relevant to look at is what parameters are passed to bt_gatt_read() and how the attribute handle for this characteristic is found through service discovery. The other parts of the code is mostly specific to interfacing with the standard Heart Rate service.

    bt_gatt_read_params struct that needs to be passed to bt_gatt_read():

  • I'm still not getting this I'm afraid:


    My code:

    /** @brief CUSTOM Client structure. */
    struct bt_custom_client
    {
    struct bt_conn *conn; // Connection object.
    atomic_t state; // Internal state.
    /** Handles on the connected peer device that are needed
    * to interact with the device.
    */
    struct bt_custom_client_handles handles;

    /** GATT subscribe parameters for CUSTOM TX Characteristic. */
    struct bt_gatt_subscribe_params tx_notif_params;

    /** GATT write parameters for CUSTOM RX Characteristic. */
    struct bt_gatt_subscribe_params rx_notif_params;
    struct bt_gatt_write_params tx_write_params;

    // GATT discovery
    struct bt_gatt_subscribe_params association_parameters;

    };
    The heart rate demo:

    /**@brief Heart Rate Service Client instance structure.
    * This structure contains status information for the client.
    */
    struct bt_hrs_client
    {
    /** Connection object. */
    struct bt_conn *conn;

    /** Heart Rate Measurement characteristic. */
    struct bt_hrs_client_hr_meas measurement_char;

    /** Sensor Body Location characteristic. */
    struct bt_hrs_client_body_sensor_location sensor_location_char;

    /** Heart Rate Control Point characteristic. */
    struct bt_hrs_client_control_point cp_char;

    /** Internal state. */
    atomic_t state;
    };

    In hrd_client.c

    params = &hrs_c->sensor_location_char.read_params;
     
    So the demo has this sensor_location_char which is of type

    struct bt_hrs_client_body_sensor_location

    Whereas my code has structs

    bt_gatt_subscribe_params
    bt_gatt_write_params

    These looko like system structs as opposed to the heart
    rate which look like custom ones

    The one I want to read is:

    struct bt_gatt_subscribe_params association_parameters;

    But I've realised that bt_gatt_subscribe_params
    is almost definitely wrong. Also it's surely not:

    bt_gatt_write_params

    So am I needing to create a custom struct like this:

    struct bt_custom_association_struct
    {
    /** Value handle. */
    uint16_t handle;

    /** Read parameters. */
    struct bt_gatt_read_params read_params;

    /** Read complete callback. */
    bt_hrs_client_read_sensor_location_cb read_cb;
    };

    But with a different callback. Maybe this:

    typedef uint8_t (*on_association_value_read)(struct bt_conn *conn, uint8_t err,
    struct bt_gatt_read_params *params,
    const void *data, uint16_t length);

    I don't understand what

    bt_gatt_subscribe_params
    bt_gatt_write_params

    Are for and why they are different to the
    heart rate one which seems to have all custom structs

    I'm not at all sure I've asked a good question here.




  • I also don't get why when I put things in an inline code block on this forum it looks so wrong.

Reply Children
  • You can use the "insert" option in the editor to insert a code snippet. This provides better code formatting.

    These looko like system structs as opposed to the heart
    rate which look like custom ones

    The bt_hrs_client_body_sensor_location struct defined by the hrs client contains elements relevant to the body sensor location characteristic for this implementation. This includes the bt_gatt_read_params struct and a callback function which is registered by the application to receive read responses.

    Are you able to share your current project here or in a private ticket so I can have a look? If so, please zip the project directory and upload the zip file.

  • OK, I have got it going. Out of desperation I made a custom struct up with only a handle and

    struct bt_gatt_read_params read_params;

    And that now works; I can read the value. There's still bits
    I don't understand, but if it works I shouldn't worry too much
    about it.

    I'm not too far off having the whole thing
    working now I think. (Famous last words!)

    Many thanks again for the patience and the help.

Related