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?

  • 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.

  • where do I find service_changed_send_in_evt()?

    As stated, my \examples\connectivity\ble_connectivity does nto have the code you referenced above

  • Hi Brian, 

    I'm sorry for the bad user experience. You can click on the Reply button right under the question. 

    The service_changed_send_in_evt() you can find in gatt_cache_manager.c. The file is used in the ble_app_gatts. 
    From what you described, it seems to be correct to me. Not sure why the function still complaining. 

    But I strongly suggest to try testing the ble_app_gatts and ble_app_gatts_c and then compare to the behavior of your application. Try comparing the difference in the code on the working examples with yours. I think you are pretty close. 

  • UI experience: The problem is the reply button doesnt' EXIST! I have to go through loops an hoops to get it to appear (refreshes and reloads). When it finally appears, all is rosy.

    That aside, I do not have the tools to build the HEX files needed to test the example in the NRF SDK. The other problem is that I could not find the file you referenced in the SDK (at least in my copy). So I can't even look at it. I found something similar in another file and that is why I did the 'loop' over ever-increasing handles.

    That 'loop-guess' part still stinks even if it DID work; not knowing what the handle of the service changed characteristic is, providing no means to get it, yet requiring it in the call to invoke the service changed indication.

    Clearly there needs to be an sd_* call that gets the handle needed in the sd_ call that invokes the service changed. Doesn't that make sense? Otherwise DONT demand the handle and have the soft device find it. It clearly knows it!

    In the end the problem is that softdevice thinks it doesn't have the system attributes. What that error means is still unclear to me because a get clearly shows they are there!

    If it was TRULY the case that Android had not enabled indications I would expect the error

    NRF_ERROR_INVALID_STATE 

    not 

    BLE_ERROR_GATTS_SYS_ATTR_MISSING

    At least that's what the documentation says.

Related