Using BLE "discoverDescriptors" returns error BLE_ERROR_INVALID_STATE

I have my BLE app working fine for the peripheral device.

I am now attempting to create a BLE app to act as the central client device, which will enable the NOTIFY CCCD etc.

I have based my app on the MBED LED Blinker example and now stuck in the middle of the code conversion.

The problem is that I am having a hard time getting my “discoverDesciptors” function to work. It returns an error saying BLE_ERROR_INVALID_STATE.

/* mbed Microcontroller Library

 * Copyright (c) 2006-2015 ARM Limited

 *

 * Licensed under the Apache License, Version 2.0 (the "License");

 * you may not use this file except in compliance with the License.

 * You may obtain a copy of the License at

 *

 *     http://www.apache.org/licenses/LICENSE-2.0

 *

 * Unless required by applicable law or agreed to in writing, software

 * distributed under the License is distributed on an "AS IS" BASIS,

 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

 * See the License for the specific language governing permissions and

 * limitations under the License.

 */

#include <events/mbed_events.h>

#include <mbed.h>

#include "ble/BLE.h"

#include "ble/DiscoveredCharacteristic.h"

#include "ble/DiscoveredService.h"

#include "ble/Gap.h"

#include "ble/gap/AdvertisingDataParser.h"

#include "pretty_printer.h"

const static char PEER_NAME[] = "ButtonLED";

const static uint16_t PEER_UUID = 0x1815;

const static uint16_t PEER_CHAR_UUID = 0x2A56;

static EventQueue event_queue(/* event count */ 10 * EVENTS_EVENT_SIZE);

static DiscoveredCharacteristic led_characteristic[3];

static uint8_t cIndex = 0;

static bool trigger_led_characteristic = false;

typedef CharacteristicDescriptorDiscovery::DiscoveryCallbackParams_t  DiscoveryCallbackParams_t;

typedef CharacteristicDescriptorDiscovery::TerminationCallbackParams_t TerminationCallbackParams_t;

void update_led_characteristic(void) {

    if (!BLE::Instance().gattClient().isServiceDiscoveryActive()) {

        //printf("Button state is: %u\r\n", led_characteristic[0].read());

        //printf("Button colour is: %u\r\n", led_characteristic[1].read());

    }

}

// This gets triggered once connected

void service_discovery(const DiscoveredService *service) {

    if (service->getUUID().shortOrLong() == UUID::UUID_TYPE_SHORT) {

        printf("Service UUID-%x attrs[%u - %u]\r\n", service->getUUID().getShortUUID(), service->getStartHandle(), service->getEndHandle());

    } else {

        printf("S UUID-");

        const uint8_t *longUUIDBytes = service->getUUID().getBaseUUID();

        for (unsigned i = 0; i < UUID::LENGTH_OF_LONG_UUID; i++) {

            printf("%02x", longUUIDBytes[i]);

        }

        printf(" attrs[%u %u]\r\n", service->getStartHandle(), service->getEndHandle());

    }

}

/**

* Handle the discovery of the characteristic descriptors.

*

* If the descriptor found is a CCCD then stop the discovery. Once the

* process has ended subscribe to server initiated events by writing the

* value of the CCCD.

*/

void when_descriptor_discovered(const DiscoveryCallbackParams_t* event)

{

    printf("   - Descriptor discovered at %u", event->descriptor.getAttributeHandle());

    if (event->descriptor.getUUID() == BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG) {

        BLE::Instance().gattClient().terminateCharacteristicDescriptorDiscovery(event->characteristic);

        printf("   - CCCD found\r\n");

    }

}

/**

* If a CCCD has been found subscribe to server initiated events by writing

* its value.

*/

void when_descriptor_discovery_ends(const TerminationCallbackParams_t *event) {

    printf("   - when_descriptor_discovery_ends\r\n");

}

// This gets triggered once connected

void characteristic_discovery(const DiscoveredCharacteristic *characteristicP) {

    printf("  Char UUID-%x valueHandle[%u] declHandle[%u] connHandle[%u]\r\n", 

        characteristicP->getUUID().getShortUUID(),

        characteristicP->getValueHandle(), 

        characteristicP->getDeclHandle(),

        characteristicP->getConnectionHandle());

    if (characteristicP->getProperties().broadcast()) printf("  Char has Broadcast property\r\n");

    if (characteristicP->getProperties().indicate()) printf("  Char has Indicate property\r\n");

    if (characteristicP->getProperties().notify()) {

        printf("  Char has Notify property\r\n");

        printf("   - Initiating descriptor discovery of %u.\r\n", characteristicP->getValueHandle());

        ble_error_t error = characteristicP->discoverDescriptors(&when_descriptor_discovered, &when_descriptor_discovery_ends);

        if (error) print_error(error, "Error caused by discoverDescriptors");

    }

    if (characteristicP->getProperties().read()) printf("  Char has Read property\r\n");

    if (characteristicP->getProperties().write()) printf("  Char has Write property\r\n");

    if (characteristicP->getProperties().writeWoResp()) printf("  Char has Write NoResp property\r\n");

    if (cIndex == sizeof(led_characteristic)/sizeof(led_characteristic[0])-1) trigger_led_characteristic = true;

    if (cIndex < sizeof(led_characteristic)/sizeof(led_characteristic[0])) cIndex++;

}

void discovery_termination(ble::connection_handle_t connectionHandle) {

    printf("Terminated Discovery for handle %u\r\n", connectionHandle);

    // Here we need to register for notification events.

    

    if (trigger_led_characteristic) {

        trigger_led_characteristic = false;

        event_queue.call(update_led_characteristic);

    }

}

void when_DataRead(const GattReadCallbackParams *response) {

    if (response->handle == led_characteristic[0].getValueHandle()) {

        printf("handle %u, offset %u, len %u\r\n", response->handle, response->offset, response->len);

        for (unsigned index = 0; index < response->len; index++) {

           printf("%c[%02x]", response->data[index], response->data[index]);

        }

       printf("\r\n");

        // --------------------uint8_t toggledValue = response->data[0] ^ 0x1;

        // --------------------led_characteristic.write(1, &toggledValue);

    }

    else if (response->handle == led_characteristic[1].getValueHandle()) {

        printf("handle %u, offset %u, len %u\r\n", response->handle, response->offset, response->len);

        for (unsigned index = 0; index < response->len; index++) {

            printf("%c[%02x]", response->data[index], response->data[index]);

        }

       printf("\r\n");

        // --------------------uint8_t toggledValue = response->data[0] ^ 0x1;

        // --------------------led_characteristic.write(1, &toggledValue);

    }

}

void when_written(const GattWriteCallbackParams *response) {

    printf("Something was written using handle %u\r\n",response->handle);

    /*

    if (response->handle == led_characteristic[0].getValueHandle()) {

        printf("Button state is: %u\r\n", led_characteristic[0].read());

    }

    else if (response->handle == led_characteristic[1].getValueHandle()) {

        printf("Button colour is: %u\r\n", led_characteristic[1].read());

    }

     */

}

void trigger_hvx(const GattHVXCallbackParams *response) {

    if (response->handle == led_characteristic[0].getConnectionHandle()) {

        printf("Button state is: %u\r\n", led_characteristic[0].read());

    }

    else if (response->handle == led_characteristic[1].getConnectionHandle()) {

        printf("Button colour is: %u\r\n", led_characteristic[1].read());

    }

}

class LEDBlinkerDemo : ble::Gap::EventHandler {

public:

    LEDBlinkerDemo(BLE &ble, events::EventQueue &event_queue) :

        _ble(ble),

        _event_queue(event_queue),

        _red_led(LED1, 1),

        _green_led(LED2, 1),

        _blue_led(LED3, 1),

        _is_connecting(false),

        _is_connected(false) {

            

         }

    ~LEDBlinkerDemo() { }

    void start() {

        _ble.gap().setEventHandler(this);

        _ble.init(this, &LEDBlinkerDemo::on_init_complete);

        _event_queue.call_every(500ms, this, &LEDBlinkerDemo::blink);

        _event_queue.dispatch_forever();

    }

    

private:

    /** Callback triggered when the ble initialization process has finished */

    void on_init_complete(BLE::InitializationCompleteCallbackContext *params) {

        if (params->error != BLE_ERROR_NONE) {

           printf("Ble initialization failed.");

            return;

        }

        

        _ble.gattClient().onDataRead(when_DataRead);

        _ble.gattClient().onDataWritten(when_written);

        _ble.gattClient().onHVX(trigger_hvx);

                

        print_mac_address();

        ble::ScanParameters scan_params;

        _ble.gap().setScanParameters(scan_params);

        _ble.gap().startScan();

    }

    void blink() {

        if (!_is_connected) _red_led = !_red_led;

    }

    /* Event handler */

    void onDisconnectionComplete(const ble::DisconnectionCompleteEvent&) {

        printf("Disconnected. Start scanning...\r\n");

        _ble.gap().startScan();

        _is_connecting = false;

        _is_connected = false;

    }

    void onConnectionComplete(const ble::ConnectionCompleteEvent& event) {

        if (event.getOwnRole() == ble::connection_role_t::CENTRAL) {

            _ble.gattClient().onServiceDiscoveryTermination(discovery_termination);

            _ble.gattClient().launchServiceDiscovery(

                event.getConnectionHandle(),

                service_discovery,

                characteristic_discovery,

                PEER_UUID,

                PEER_CHAR_UUID

            );

            printf("Now Connected...\r\n");

            _is_connected = true;

            if (cIndex == sizeof(led_characteristic)/sizeof(led_characteristic[0])) {

                printf("Enabling Notify property\r\n");

                //_ble.gattClient().discoverCharacteristicDescriptors();

            }

        } else {

            _ble.gap().startScan();

        }

        _is_connecting = false;

    }

    void onAdvertisingReport(const ble::AdvertisingReportEvent &event) {

        /* don't bother with analysing scan result if we're already connecting */

        if (_is_connecting) {

            return;

        }

        ble::AdvertisingDataParser adv_data(event.getPayload());

        /* parse the advertising payload, looking for a discoverable device */

        while (adv_data.hasNext()) {

            ble::AdvertisingDataParser::element_t field = adv_data.next();

            if (field.type == ble::adv_data_type_t::INCOMPLETE_LIST_16BIT_SERVICE_IDS) {

                printf("INCOMPLETE_16BIT_SERVIDS FOUND...\r\n");

            }

            else if (field.type == ble::adv_data_type_t::COMPLETE_LIST_16BIT_SERVICE_IDS) {

                printf("COMPLETE_16BIT_SERVIDS FOUND...\r\n");

                if ((field.value.size() > 1) && (field.value.size() < 256)) {

                    for (uint8_t xx = 0; xx < field.value.size(); xx +=2) {

                        printf(" - Service UUID: 0x%02X%02X ", field.value[xx+1], field.value[xx]);

                    }

                    printf("\r\n");

                }

            }

            else if (field.type == ble::adv_data_type_t::INCOMPLETE_LIST_128BIT_SERVICE_IDS) {

                printf("INCOMPLETE_128BIT_SERVIDS FOUND...\r\n");

            }

            else if (field.type == ble::adv_data_type_t::COMPLETE_LIST_128BIT_SERVICE_IDS) {

                printf("COMPLETE_128BIT_SERVIDS FOUND...\r\n");

            }

            /* connect to a discoverable device */

            if (field.type == ble::adv_data_type_t::COMPLETE_LOCAL_NAME &&

                field.value.size() == strlen(PEER_NAME) &&

                (memcmp(field.value.data(), PEER_NAME, field.value.size()) == 0)) {

                printf("This MAC Adr matches local name: ");

                print_address(event.getPeerAddress().data());

                printf(" rssi: %d, scan response: %u, connectable: %u\r\n",

                       event.getRssi(), event.getType().scan_response(), event.getType().connectable());

                ble_error_t error = _ble.gap().stopScan();

                if (error) {

                    print_error(error, "Error caused by Gap::stopScan");

                    return;

                }

                const ble::ConnectionParameters connection_params;

                error = _ble.gap().connect(

                    event.getPeerAddressType(),

                    event.getPeerAddress(),

                    connection_params

                );

                if (error) {

                    _ble.gap().startScan();

                    return;

                }

                /* we may have already scan events waiting

                 * to be processed so we need to remember

                 * that we are already connecting and ignore them */

                _is_connecting = true;

                // Set up the LED's

                _red_led = 0;

                _green_led = 1;

                _blue_led = 1;

                return;

            }

        }

    }

private:

    BLE &_ble;

    

    events::EventQueue &_event_queue;

    DigitalOut _red_led;

    DigitalOut _green_led;

    DigitalOut _blue_led;

    bool _is_connecting;

    bool _is_connected;

};

/** Schedule processing of events from the BLE middleware in the event queue. */

void schedule_ble_events(BLE::OnEventsToProcessCallbackContext *context) {

    event_queue.call(Callback<void()>(&context->ble, &BLE::processEvents));

}

int main()

{

    BLE &ble = BLE::Instance();

    ble.onEventsToProcess(schedule_ble_events);

    LEDBlinkerDemo demo(ble, event_queue);

    demo.start();

    return 0;

}

Although frustrating at times, sometimes no response is the best medicine.

I finally discovered the issue.

ble_error_t error = characteristicP->discoverDescriptors(&when_descriptor_discovered, &when_descriptor_discovery_ends);

This can only be called when this callback has been triggered:

_ble.gattClient().onServiceDiscoveryTermination(discovery_termination);

That means you need to copy and store the characteristics of choice that were discovered in memory until when they are later needed (an unfortunate use of resource…) .

1 Like

It appears that the above “solution” only works if you have one characteristic which has a NOTIFY or INDICATE property. When you have two characteristics this method does not work.

So what is the point of having a function (discoverDescriptors) which uses callbacks! if they do not work properly.

There has to be a better way of discovering descriptors for all characteristics because this is not working… I now simply get the BLE_ERROR_INVALID_STATE on the 2nd characteristic with Notify/Indicate property.

For what I can work out, when you use
discoverDescriptors(&when_descriptor_discovered, &when_descriptor_discovery_ends);

In the onServiceDiscoveryTermination callback function, it does not return back to this function after the when_descriptor_discovery_ends completes.

I have no idea what happens here. Does it return back to onConnectionComplete. The problem here is that this is all event driven and there is not many event types other than “getOwnRole” and “getStatus”.

So at a loss and putting this down to a BLE API bug.

Does anyone know what causes the BLE_ERROR_INVALID_STATE error?

I cannot figure out why my second characteristic is throwing up this descriptor discovery error.

I even placed a check prior to the 2nd characteristic initiating the descriptor discovery process where I used

gattClient().isCharacteristicDescriptorDiscoveryActive

and this returns negative. So it’s telling me that the previous Descriptor Discovery process is complete.

So what is causing the error. Something is not right…

Indeed, something was not right!

You cannot process the next descriptor discovery until you process the CCCD write event.

As such you need to include the onDataWritten callback function

.gattClient().onDataWritten( -- your data written function -- );

Then and only then (as in within your data written function) do you proceed to the next characteristic. Without that it fails.

Well, there you go… that was a rather messy learning process.

So after all that I now understand why the GattClient() example is the way it is…

Hopefully this forum post helps others in the same boat.