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

I give up; how do I do a service changed?

The documentation is VERY difficult to search. A search only goes over the chapter. I get different results depending upon which one I am in .... aarrg! And there are SOOO many.

So I want to do something very basic; 

On my peripheral I want to expose the service changed characteristic. That appears to be done by default because I see it on my central. But now I try to enable indications from the central and it fails. Well of course it does! I need to handle the BLE_GATTS_EVT_WRITE event!

So I do and come across a MAJOR difficulty. I need to have the handle of the Service Changed Characteristic. But there is no way to get it; or at least I cannot find a way to get it.

So how can I get the handle of the service changed characteristic so I can deal with it in the BLE_GATTS_EVT_WRITE event?

Parents
  • Hi Brian, 

    We have two examples in the SDK : ble_app_gatts_c and ble_app_gatts. Have you tried to test them ? 

    They are not exactly the same as you wanted (server on peripheral and client on central) however, you can use them as the referral. 

    Basically, on the server, you need to enable service changed, and send the indication. This is already handled by gatt_cache_manager.c and you just need to call pm_local_database_has_changed() to send the indication. ( it's inside bsp_event_handler() in ble_app_gatts)

    On the client, you need to do service discovery and write to the CCCD to enable indication. This is already handled by nrf_ble_gatts.c and the ble_db_discovery.c modules. Please have a look in gatts_evt_handler in main.c in ble_app_gatts_c. 

  • Thanks for the response. I understand that a client can support services; its just in practice (out in the field) I have never seen it ... except perhaps for a CTS.

    In any case, I am using the pc-ble-driver so I must use sd_* calls myself. Even so, it looks like it should be easy. Basically, as far as I can tell the GATT service is already set up and the service changed characteristic exists. Now I have an Android application that will attempt to enable indications on the service changed characteristic if it finds one. It works fine for peripheral devices out in the field.

    I also have no sd_* APIs in the pc-ble-driver that allow me to get the handle of the GATT services, characteristics, or descriptors. Looked for answers on that, too. I did see some post which suggests the handle is 0x0B but that may even be another platform, so I don't know if that is even right. Found no documentation on it though.

    So that's where I am at. What config step am I missing? Where can I find documentation on how to do this?

    UPDATE: It appears the handle I am using 0x0B for the Service Changed characteristic is wrong.

    The sd_ble_gatts_service_changed() method returns the error incorrect handle. But I cannot get the handle.

  • Then I better start from 0 and not what I get from the 'get' method. I am still suspicious that it has something to do with the fact I cannot restore the system attributes because I do not get signaled the ..._SYS_ATTR_MISSING event

    Since my \examples\connectivity\ble_connectivity does not have the code you reference above, I am kind of stuck with trial and error from zero ... if it even works. 

    I definitely will not hard code, as I am implementing a peripheral, running it, disconnecting, and then implementing another, one after the other. So far it all works except for the service changed and the BLE_GATTS_EVT_SYS_ATTR_MISSING event. However, my central always re-enables indications and notifications because of the misbehaving devices that do not do bonding correctly.

    Doing the 'cycle' starting with 0 I no longer get the invalid handle, instead I get 

    BLE_ERROR_GATTS_SYS_ATTR_MISSING

  • Have you enabled indication from the central side ? Please use nRFConnect to test first, before using Android. 

  • Yes I have (I did use Android and it works with other devices). If I knew the handle value of the service changed characteristic I should be able to catch it in my peripheral code ... unless Nordic doesn't pass that one up to the application (like it does other notification/indication attempts).

    I trapped all attempts by collector to enable characteristics and I see this 

    Client Enabling CCCD of characeteristic handle 13 with value 2 at time 1953

    However, on a reconnect when I attempt to do a service changed indication I get this:

    Started advertising at time 0
    Connected at time 234, connection handle 0x0001
    Failed invoking service changed indication. Invalid handle 0
    Failed invoking service changed indication. Invalid handle 1
    Failed invoking service changed indication. Invalid handle 2
    Failed invoking service changed indication. Invalid handle 3
    Failed invoking service changed indication. Invalid handle 4
    Failed invoking service changed indication. Invalid handle 5
    Failed invoking service changed indication. Invalid handle 6
    Failed invoking service changed indication. Invalid handle 7
    Failed invoking service changed indication. Invalid handle 8
    Failed invoking service changed indication. Invalid handle 9
    Failed invoking service changed indication. Invalid handle 10
    Failed invoking service changed indication. Invalid handle 11
    Failed invoking service changed indication. Invalid handle 12
    Failed invoking service changed indication. Invalid handle 13
    Failed invoking service changed indication. Service attributes missing
    Result of sending encryption params 0
    Security settings reported at time 843:
    security level: 2
    security mode: 1
    key length: 16
    Disconnected at time 1078

    Of course the Android central is going to fail now as it will try to write to handles based upon the old service tables as it never re-did service discovery to find out that there is no DIS anymore and the HeartRate service values have changed.

  • I did use Android and it works with other devices => no this doesn't guarantee that the phone has written to CCCD before your call. 

    It's much easier to test when you have full control of the central device. Please use the nRFConnect on PC to continue. 

    To get the system attribute you call sd_ble_gatts_sys_attr_get(). And when the bonded connection is reestablished, you call sd_ble_gatts_sys_attr_set() with the exact same data.

    You don't need to know which handle the service changed handle is. 

    We have code to find the service changed cccd handle value in service_changed_cccd() in gatt_cache_manager.c . But it only used to check if the service changed actually exist or not. 

    Please have  a look inside service_changed_send_in_evt() to see how we handle the error codes when calling sd_ble_gatts_service_changed()

  • Just a note on this web app: The 'reply', verify answer, etc buttons/options often don't show up until I do many screen refreshes, especially on the last messages. If you have a 'reply' from an older message but not the last message, hitting the reply there makes the buttons appear on the last message. Nothing do to with this thread but it makes participation annoying and sometimes 'come back later to try'.

    Back to the issue:

    Turns out Android auto-handles service changed because I handled it too and I wrote a trap in my Nordic peripheral to catch all descriptor writes and I was writing the service changed CCCD twice (and the heart rate measurement once).

    I have this code to catch the descriptor-writing attempts by the Android

    case BLE_GATTS_EVT_WRITE:
        if (p_ble_evt->evt.gatts_evt.params.write.handle ==
                m_heart_rate_measurement_handle.cccd_handle)
        {
            uint8_t write_data = p_ble_evt->evt.gatts_evt.params.write.data[0];
            m_send_notifications = (write_data == BLE_GATT_HVX_NOTIFICATION);
            cccdSet = m_send_notifications;
    		printf("Enabling CCCD with %d at time %llu\n", write_data, (GetTickCount64() - elapsedTimeStart));
        }
        else
        {
            uint8_t write_data = p_ble_evt->evt.gatts_evt.params.write.data[0];
                printf("Client Enabling CCCD of characeteristic handle %d with value %d at time %llu\n",
                    p_ble_evt->evt.gatts_evt.params.write.handle, write_data, (GetTickCount64() - elapsedTimeStart));
        }
        break;

    and I see the Android write all the descriptors available to write.

    But the Android enables the service changed on the first connection. Its the second connection where the attempt to send the service changed indication fails. So I believe this has to be some kind of configuration issue as it is very clear the Android is enabling the service changed. So

    Either I am not saving and  restoring the CCCDs correctly between the first run and the second run, or I am trying to invoke the service changed at the wrong time on the reconnect or I have to wait until the Android enables the service changed again (which it won't do since it has already done it ... unless I do it separately. The latter can't be the case since that violates the spec. So am I saving/restoring wrong?

    When the first run disconnects, I invoke the following to get the CCCD data

    sd_ble_gatts_sys_attr_get(m_adapter, p_ble_evt->evt.gap_evt.conn_handle, NULL, &saveDataLength, BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS);
    if(saveDataLength > 0)
    { 
        saveDataBuffer = calloc(1, saveDataLength * sizeof(uint8_t));
        err_code = sd_ble_gatts_sys_attr_get(m_adapter, p_ble_evt->evt.gap_evt.conn_handle, saveDataBuffer, &saveDataLength, BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS);
        if (err_code != NRF_SUCCESS)
        {
            printf("Failed getting persistent sys attr info. Error code: 0x%02X\n", err_code);
            fflush(stdout);
        }
    }

    And on program close I save the 'saveDataBuffer' to a file.

    On restart of the application I read 'saveDataBuffer' from the file and in the connection event callback I invoke this code

    if (saveDataBuffer != NULL)
    {
        err_code = sd_ble_gatts_sys_attr_set(m_adapter, p_ble_evt->evt.gap_evt.conn_handle, saveDataBuffer,
            saveDataLength, BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS);
        if (err_code != NRF_SUCCESS)
        {
            printf("Failed updating persistent sys attr info. Error code: 0x%02X\n", err_code);
            fflush(stdout);
        }
    }

    I have looked at what is in saveDataBuffer both going out and coming in and its only 8 bytes in length so as far as I can tell its the service changed CCCD info.

    At this point I call the service changed right after I restore the CCCDs; thus in the connection event. I waited until after encryption but I got the same result of service attributes missing.

Reply
  • Just a note on this web app: The 'reply', verify answer, etc buttons/options often don't show up until I do many screen refreshes, especially on the last messages. If you have a 'reply' from an older message but not the last message, hitting the reply there makes the buttons appear on the last message. Nothing do to with this thread but it makes participation annoying and sometimes 'come back later to try'.

    Back to the issue:

    Turns out Android auto-handles service changed because I handled it too and I wrote a trap in my Nordic peripheral to catch all descriptor writes and I was writing the service changed CCCD twice (and the heart rate measurement once).

    I have this code to catch the descriptor-writing attempts by the Android

    case BLE_GATTS_EVT_WRITE:
        if (p_ble_evt->evt.gatts_evt.params.write.handle ==
                m_heart_rate_measurement_handle.cccd_handle)
        {
            uint8_t write_data = p_ble_evt->evt.gatts_evt.params.write.data[0];
            m_send_notifications = (write_data == BLE_GATT_HVX_NOTIFICATION);
            cccdSet = m_send_notifications;
    		printf("Enabling CCCD with %d at time %llu\n", write_data, (GetTickCount64() - elapsedTimeStart));
        }
        else
        {
            uint8_t write_data = p_ble_evt->evt.gatts_evt.params.write.data[0];
                printf("Client Enabling CCCD of characeteristic handle %d with value %d at time %llu\n",
                    p_ble_evt->evt.gatts_evt.params.write.handle, write_data, (GetTickCount64() - elapsedTimeStart));
        }
        break;

    and I see the Android write all the descriptors available to write.

    But the Android enables the service changed on the first connection. Its the second connection where the attempt to send the service changed indication fails. So I believe this has to be some kind of configuration issue as it is very clear the Android is enabling the service changed. So

    Either I am not saving and  restoring the CCCDs correctly between the first run and the second run, or I am trying to invoke the service changed at the wrong time on the reconnect or I have to wait until the Android enables the service changed again (which it won't do since it has already done it ... unless I do it separately. The latter can't be the case since that violates the spec. So am I saving/restoring wrong?

    When the first run disconnects, I invoke the following to get the CCCD data

    sd_ble_gatts_sys_attr_get(m_adapter, p_ble_evt->evt.gap_evt.conn_handle, NULL, &saveDataLength, BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS);
    if(saveDataLength > 0)
    { 
        saveDataBuffer = calloc(1, saveDataLength * sizeof(uint8_t));
        err_code = sd_ble_gatts_sys_attr_get(m_adapter, p_ble_evt->evt.gap_evt.conn_handle, saveDataBuffer, &saveDataLength, BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS);
        if (err_code != NRF_SUCCESS)
        {
            printf("Failed getting persistent sys attr info. Error code: 0x%02X\n", err_code);
            fflush(stdout);
        }
    }

    And on program close I save the 'saveDataBuffer' to a file.

    On restart of the application I read 'saveDataBuffer' from the file and in the connection event callback I invoke this code

    if (saveDataBuffer != NULL)
    {
        err_code = sd_ble_gatts_sys_attr_set(m_adapter, p_ble_evt->evt.gap_evt.conn_handle, saveDataBuffer,
            saveDataLength, BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS);
        if (err_code != NRF_SUCCESS)
        {
            printf("Failed updating persistent sys attr info. Error code: 0x%02X\n", err_code);
            fflush(stdout);
        }
    }

    I have looked at what is in saveDataBuffer both going out and coming in and its only 8 bytes in length so as far as I can tell its the service changed CCCD info.

    At this point I call the service changed right after I restore the CCCDs; thus in the connection event. I waited until after encryption but I got the same result of service attributes missing.

Children
No Data
Related