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

How to know when database discovery is finished?

I am using the database discovery module in my app (my code is based on the "HRS client" that is one of the SDK examples). I am able to discover multiple services (both custom and adopted) from the remote device just fine, but this part is unclear: how do I know when the entire database discovery procedure has been finished? 

To be a bit more specific, I first register the services that I am interested in by calling ble_db_discovery_evt_register() one or many times. Then I kick off the service discovery by calling ble_db_discovery_start(). 

After starting, I will get a series of events, type being BLE_DB_DISCOVERY_COMPLETE, BLE_DB_DISCOVERY_SRV_NOT_FOUND etc.. So far so good.

Now the question is: how do I know that the discovery state machine has gone through all the services in the list? I could not find any event to indicate this. I need to know when the discovery is complete so that I can continue with other tasks in my application (e.g. read some of the characteristics in the remote database).

I could of course count the number of events triggered by the discovery module and compare it against the number of services I have registered, but that sounds a bit clumsy. There must be some more elegant way to do this?

Any pointers appreciated.

Parents
  • Hy Tyler, 

     the BLE_DB_DISCOVERY_COMPLETE will be generated when discovery of one service is completed. 

    /**@brief DB Discovery event type. */
    typedef enum
    {
        BLE_DB_DISCOVERY_COMPLETE,      /**< Event indicating that the discovery of one service is complete. */
        BLE_DB_DISCOVERY_ERROR,         /**< Event indicating that an internal error has occurred in the DB Discovery module. This could typically be because of the SoftDevice API returning an error code during the DB discover.*/
        BLE_DB_DISCOVERY_SRV_NOT_FOUND, /**< Event indicating that the service was not found at the peer.*/
        BLE_DB_DISCOVERY_AVAILABLE      /**< Event indicating that the DB discovery instance is available.*/
    } ble_db_discovery_evt_type_t;
    

    Hence, you will have to keep of which services that have been discovered, but in our examples I feel that we have shown how to do this. 

    If we take a look at the ble_app_uart_c example we see that the database discovery module is intialized, i.e. setting the db_disc_handler callback for discovery events in main(). 

    Then in ble_nus_init() we see that we register the NUS service UUID with the database discovery module using 

    ble_db_discovery_evt_register(&uart_uuid);

    i.e. db_disc_handler() will be called if we find a service with a UUID matching uart_uuid. Now in db_disc_handler() we call ble_nus_c_on_db_disc_evt()

    static void db_disc_handler(ble_db_discovery_evt_t * p_evt)
    {
        ble_nus_c_on_db_disc_evt(&m_ble_nus_c, p_evt);
    }
    
    void ble_nus_c_on_db_disc_evt(ble_nus_c_t * p_ble_nus_c, ble_db_discovery_evt_t * p_evt)
    {
        ble_nus_c_evt_t nus_c_evt;
        memset(&nus_c_evt,0,sizeof(ble_nus_c_evt_t));
    
        ble_gatt_db_char_t * p_chars = p_evt->params.discovered_db.charateristics;
    
        // Check if the NUS was discovered.
        if (    (p_evt->evt_type == BLE_DB_DISCOVERY_COMPLETE)
            &&  (p_evt->params.discovered_db.srv_uuid.uuid == BLE_UUID_NUS_SERVICE)
            &&  (p_evt->params.discovered_db.srv_uuid.type == p_ble_nus_c->uuid_type))
        {
            for (uint32_t i = 0; i < p_evt->params.discovered_db.char_count; i++)
            {
                switch (p_chars[i].characteristic.uuid.uuid)
                {
                    case BLE_UUID_NUS_RX_CHARACTERISTIC:
                        nus_c_evt.handles.nus_rx_handle = p_chars[i].characteristic.handle_value;
                        break;
    
                    case BLE_UUID_NUS_TX_CHARACTERISTIC:
                        nus_c_evt.handles.nus_tx_handle = p_chars[i].characteristic.handle_value;
                        nus_c_evt.handles.nus_tx_cccd_handle = p_chars[i].cccd_handle;
                        break;
    
                    default:
                        break;
                }
            }
            if (p_ble_nus_c->evt_handler != NULL)
            {
                nus_c_evt.conn_handle = p_evt->conn_handle;
                nus_c_evt.evt_type    = BLE_NUS_C_EVT_DISCOVERY_COMPLETE;
                p_ble_nus_c->evt_handler(p_ble_nus_c, &nus_c_evt);
            }
        }
    }

    Now this function populates the handle values for all the discovered characteristics and places them in a ble_nus_c_evt_t struct. Then the event_type of the ble_nus_c_evt_t  struct is set to  BLE_NUS_C_EVT_DISCOVERY_COMPLETE and the ble_nus_c_evt_handler is called.  

    uint32_t ble_nus_c_handles_assign(ble_nus_c_t               * p_ble_nus,
                                      uint16_t                    conn_handle,
                                      ble_nus_c_handles_t const * p_peer_handles)
    {
        VERIFY_PARAM_NOT_NULL(p_ble_nus);
    
        p_ble_nus->conn_handle = conn_handle;
        if (p_peer_handles != NULL)
        {
            p_ble_nus->handles.nus_tx_cccd_handle = p_peer_handles->nus_tx_cccd_handle;
            p_ble_nus->handles.nus_tx_handle      = p_peer_handles->nus_tx_handle;
            p_ble_nus->handles.nus_rx_handle      = p_peer_handles->nus_rx_handle;
        }
        return NRF_SUCCESS;
    }
    
    /**@snippet [Handling events from the ble_nus_c module] */
    static void ble_nus_c_evt_handler(ble_nus_c_t * p_ble_nus_c, ble_nus_c_evt_t const * p_ble_nus_evt)
    {
        ret_code_t err_code;
    
        switch (p_ble_nus_evt->evt_type)
        {
            case BLE_NUS_C_EVT_DISCOVERY_COMPLETE:
                NRF_LOG_INFO("Discovery complete.");
                err_code = ble_nus_c_handles_assign(p_ble_nus_c, p_ble_nus_evt->conn_handle, &p_ble_nus_evt->handles);
                APP_ERROR_CHECK(err_code);
                
                err_code = ble_nus_c_tx_notif_enable(p_ble_nus_c);
                APP_ERROR_CHECK(err_code);
                NRF_LOG_INFO("Connected to device with Nordic UART Service.");
                break;
        .
        .
        .
        }
    }

    This function calls ble_nus_c_handles_assign which assigns all the handles to the global ble_nus_c_t struct declared by the BLE_NUS_C_DEF(m_ble_nus_c) macro in main.c

    // Forward declaration of the ble_nus_t type.
    typedef struct ble_nus_c_s ble_nus_c_t;
    
    /**@brief NUS Client structure. */
    struct ble_nus_c_s
    {
        uint8_t                 uuid_type;      /**< UUID type. */
        uint16_t                conn_handle;    /**< Handle of the current connection. Set with @ref ble_nus_c_handles_assign when connected. */
        ble_nus_c_handles_t     handles;        /**< Handles on the connected peer device needed to interact with it. */
        ble_nus_c_evt_handler_t evt_handler;    /**< Application event handler to be called when there is an event related to the NUS. */
    };

    All calls that involve reading/writing to these characteristics should use the handles stored in this global struct. 

    So the TLDR version is that you should not allow the nRF to perform any operations on characteristics for a given service until the central module for the said service, e.g. ble_nus_c, has issued an event indicating that the service has been discovered, e.g. BLE_NUS_C_EVT_DISCOVERY_COMPLETE.

    I hope that made sense. If not, just ask me to clarify in a comment. 

    Best regards

    Bjørn

  • Hi Bjørn,

    thanks for the answer. It is clear to me how the discovery of service and the associated handles works for one individual service. However, my question is related to the case where my app is discovering multiple services.

    Here is the problem that I am facing right now: my app is trying to discover the Device Information service and also another vendor specific service with 128-bit UUID. Therefore I am getting two BLE_DB_DISCOVERY_COMPLETE events.

    Upon getting the first BLE_DB_DISCOVERY_COMPLETE (for the Device Information service), my code checks the handles and then tries to read the Manufacturer Name. The call to sd_ble_gattc_read() fails with code NRF_ERROR_BUSY. I assume this is because the service discovery is still going on (to find the other service my app is interested in).

    The question is: when do I know that it is safe to read the characteristics in the Device Information service? I could add some fixed delay in my app, but that does not seem like a real solution.

    The service discovery module has clearly been designed so that you can configure it to search for multiple services. I think it is missing one more event that would indicate that the module has completed its task.

    As you said, BLE_DB_DISCOVERY_COMPLETE indicates that discovery of one service is complete. The problem is that I don't know how many of these events are still coming in and which of those is the last one. 

  • I was reading through the service discovery module code (ble_db_discovery.c) and noticed that this is the event that should be generated when there are no more services to be found:

    BLE_DB_DISCOVERY_AVAILABLE

    Here's the relevant code snippet, around line #300:

    else
        {
            // No more service discovery is needed.
            p_db_discovery->discovery_in_progress  = false;
            m_pending_user_evts[0].evt.evt_type    = BLE_DB_DISCOVERY_AVAILABLE;
            m_pending_user_evts[0].evt.conn_handle = conn_handle;
        }

    I don't see this event generated at all. I have a case statement that handles the events and there is only two events BLE_DB_DISCOVERY_COMPLETE (as expected), but the BLE_DB_DISCOVERY_AVAILABLE is not there. Is there a bug in the service discovery module? 

    I am using SDK 15.3.0.

  • HI Tyler, 

    As far as I understand you should get the same number of events from the DB discovery module as the number of UUIDs you registered, see the MSC at the bottom of this page: nRF5 SDK v15.3.0: Database Discovery Module.

    So I think you should be able to increment a counter in db_disc_handler() or in the individual ble_xxx_c_on_db_disc_evt handlers() that are called from db_disc_handler(). Once the counter reaches two(i.e. DIS and your custom service) to you know that DB discovery is complete and you should be able to read the Manuf.name.

    Another approach could be to retry the sd_ble_gattc_read() call to read the Manuf.name characteristic when you get the NRF_ERROR_BUSY return code. Either in a while loop or starting an application timer that calls sd_ble_gattc_read() in its callback. The timeout can be very short and if you get NRF_ERROR_BUSY, then you restart the timer. This way you're not busy-waiting in an interrupt context. 

    TylerD said:
    The service discovery module has clearly been designed so that you can configure it to search for multiple services. I think it is missing one more event that would indicate that the module has completed its task.

     I will provide this feedback to the SDK team. 

    Best regards

    Bjørn

  • After reading through the code, the event seems to be there (BLE_DB_DISCOVERY_AVAILABLE ) but for some reason it is not sent to my app. To me it seems like a bug in the service discovery module code. 

  • It seems that this is a known issue with the DB discovery module

    https://devzone.nordicsemi.com/f/nordic-q-a/20846/getting-service-count-from-database-discovery-module

    It has been reported internally and I have bumped the issue as well. 

    UPDATE:

    This will be fixed in nRF5x SDK v16.0.0. Service count will be valid and also BLE_DB_DISCOVERY_AVAILABLE will be triggered. Will see if I can get hold of the fixed module code. 

    Best regards

    Bjørn

  • Thanks for confirming. The original issue seems to be reported over 2 years ago so I'm not holding my breath... EDIT: just saw your update, so I'll give it a try when the SDK v16 is available.

Reply Children
No Data
Related