Zephyr, NRFConnect 2.3, and BLE reading/writing

Can someone please, for the love of all that is holy, point me towards some guidance on how to read values of custom characteristics set by another device?

I cannot find this ANYWHERE.

Parents
  • Hi Amanda,

    I'm sorry to say this really doesn't help. 

    Again, I'm trying to read characteristics belonging to a service setup and advertised by another device. Since it's my device, my service, and my characteristics I know all the UUIDs.

    What I'm not seeing is a way to read these characteristics. 

    Please bear in mind, I don't want to read them on my mobile phone - I want to read them from another device.

    Are you able to point me to the appropriate sections of those pages, or direct me to a more appropriate resource.

    Thanks,
    S.

  • Hi,

    scath said:
    I have multiple characteristics in the service: some need to be read from, some need to be written to, some need a subscription.

    Then, this https://github.com/nrfconnect/sdk-nrf/blob/750b0d51e152534fe274ba66c7cf29b22c45c34a/subsys/bluetooth/services/hrs_client.c would be suitable for your usages which show how to read, write and subscribe characteristics in the Heart Rate Service

    You can take a look at the Generic Attribute Profile (GATT) documentation. 

    -Amanda H.

  • Thanks for the speedy reply, Amanda. I'll take a look asap!

    Cheers!

  • Hi Amanda,

    I appreciate the effort, and I've been able to work out some of what's happening here, but I have to say that the documentation is absolutely awful.

    Having reviewed the code you suggested, I find exactly 4 short comments across several hundred lines of code. It vaguely lays out a strategy for setting up the BLE framework, but has no guidance on actually implementing the code.

    For example: Lines 185:218 of `hrs_client.c` define `bt_hrs_client_measurement_subscribe()`... but there's no demonstration or discussion on where this actually gets called. That's confusing, since this method requires a custom callback as an argument. It would be very helpful to see where that method gets called, and the custom callback declared and set up for actual execution.

    Can I ask you for some guidance on setting this up, and/or where I can find discussion on the intended execution path for the multithreaded BLE system? Though I admittedly may not have seen it, I'm not finding information on this.

    Many thanks,

    S.

  • Hi, 

    Sorry, I should mention that the hrs_client.c is used in the Central Heart Rate Monitor with Coded PHY. bt_hrs_client_measurement_subscribe() is called at https://github.com/nrfconnect/sdk-nrf/blob/v2.3.0/samples/bluetooth/central_hr_coded/src/main.c#L78 

    Not sure whether I understand the requirement correctly or not. https://academy.nordicsemi.com/lessons/lesson-7-multithreaded-applications/ explains the Multithreaded applications.

    -Amanda H.

  • Apologies, Amanda - I solved this and then had to rush to solve another problem.

    If I recall correctly, I was attempting to discover services and characteristics; this was challenging at the time, and I suspect the documentation wasn't really that hot.

    That said, if anyone else is looking for help here's what I found:

    There are many ways you can connect to a device that offers services over BLE.

    1) You can use a premade discover manager (NCS and/or Zephyr have one as far as I know). This can be very, very slow and drain your battery if you're disconnecting and reconnecting often. If you don't know what services are available, this may be your best option.
     
    2) You can can quickly "discover" services and characteristics for which you already have the UUIDs. With NCS/Zephyr, you can do this by calling bt_gatt_discover() with a set of discovery parameters you populate yourself. Using this method, you would ensure that you set a callback function that iteratively discovers any subsequent services using the UUIDs you have in hand once a connection is made and the first service is discovered.

    Though more involved than the discovery manager, it can be much faster.

    For more info, I would consult the hr_central example in the zephyr github repo.

    3) You can hard set the handles for all the services you want to use if you have foreknowledge of what they'll be. This only works if you have complete control of both sides of the connection as far as I understand, but it's extremely fast since you can effectively skip all discovery and just call read/write/notify/etc at will.

    In that case, you would want to create a set of read/write/subscribe params somewhere on your device (I would suggest the stack rather than heap, though both work fine), manually set them up, and use them to interact with your connected device after connections have been established.

    Something like

    static struct bt_gatt_read_params my_bt_gatt_read_params[2];
    static struct bt_gatt_write_params my_bt_gatt_write_params[2];
    static struct bt_gatt_subscribe_params my_bt_gatt_subscribe_params;
    
    
    uint8_t my_gatt_write_cb(struct bt_conn *conn, uint8_t err, struct bt_gatt_write_params *params)
    {
        LOG_INF("We tried to send data over ble.");
        if (err){
            LOG_ERR("Error While Sending Data: err = %d\n", err);
        }
        
    }
    
    //... similar for reads, notifies, etc...
    my_bt_gatt_write_params[0].func = my_gatt_write_cb;
    my_bt_gatt_write_params[0].handle = <handle of char here>;
    my_bt_gatt_write_params[0].data = <data goes here>;
    my_bt_gatt_write_params[0].length = <length of data goes here>;
    
    
    // once connected, with a proper connection handle - in this case 'my_conn'...
    
    int err = bt_gatt_write(my_conn, my_bt_gatt_write_params[0]);
    if(err) {
        LOG_ERR("Error in bt_gatt_write: %d", err);
    };



    That said, I would love clearer documentation here - this was a bit of a bear to figure out unaided, but ended up being very simple to implement once understood.


    Cheers.

Reply
  • Apologies, Amanda - I solved this and then had to rush to solve another problem.

    If I recall correctly, I was attempting to discover services and characteristics; this was challenging at the time, and I suspect the documentation wasn't really that hot.

    That said, if anyone else is looking for help here's what I found:

    There are many ways you can connect to a device that offers services over BLE.

    1) You can use a premade discover manager (NCS and/or Zephyr have one as far as I know). This can be very, very slow and drain your battery if you're disconnecting and reconnecting often. If you don't know what services are available, this may be your best option.
     
    2) You can can quickly "discover" services and characteristics for which you already have the UUIDs. With NCS/Zephyr, you can do this by calling bt_gatt_discover() with a set of discovery parameters you populate yourself. Using this method, you would ensure that you set a callback function that iteratively discovers any subsequent services using the UUIDs you have in hand once a connection is made and the first service is discovered.

    Though more involved than the discovery manager, it can be much faster.

    For more info, I would consult the hr_central example in the zephyr github repo.

    3) You can hard set the handles for all the services you want to use if you have foreknowledge of what they'll be. This only works if you have complete control of both sides of the connection as far as I understand, but it's extremely fast since you can effectively skip all discovery and just call read/write/notify/etc at will.

    In that case, you would want to create a set of read/write/subscribe params somewhere on your device (I would suggest the stack rather than heap, though both work fine), manually set them up, and use them to interact with your connected device after connections have been established.

    Something like

    static struct bt_gatt_read_params my_bt_gatt_read_params[2];
    static struct bt_gatt_write_params my_bt_gatt_write_params[2];
    static struct bt_gatt_subscribe_params my_bt_gatt_subscribe_params;
    
    
    uint8_t my_gatt_write_cb(struct bt_conn *conn, uint8_t err, struct bt_gatt_write_params *params)
    {
        LOG_INF("We tried to send data over ble.");
        if (err){
            LOG_ERR("Error While Sending Data: err = %d\n", err);
        }
        
    }
    
    //... similar for reads, notifies, etc...
    my_bt_gatt_write_params[0].func = my_gatt_write_cb;
    my_bt_gatt_write_params[0].handle = <handle of char here>;
    my_bt_gatt_write_params[0].data = <data goes here>;
    my_bt_gatt_write_params[0].length = <length of data goes here>;
    
    
    // once connected, with a proper connection handle - in this case 'my_conn'...
    
    int err = bt_gatt_write(my_conn, my_bt_gatt_write_params[0]);
    if(err) {
        LOG_ERR("Error in bt_gatt_write: %d", err);
    };



    That said, I would love clearer documentation here - this was a bit of a bear to figure out unaided, but ended up being very simple to implement once understood.


    Cheers.

Children
No Data
Related