BLE event handlers, why so many! Plz explain structure!

Afternoon folks,

I'm new to nordic, previously a TI developer. I'm still getting my head around the SDK. I've unfortunately been left with some poorly organised, very-un-annotated firmware to try and decipher. I am currently trying to get my head around the system for handling BLE events:

I'm new to BLE terminology and subsequently i have tried to find best terms to express my understanding, please bare with me:

The code i have. Runs 3 event handlers every time a device is connected (for example). In no particular order, these are: a generic "ble_evt_handler", the register observer for this is set up during BLE stack initialisation. The application also runs a "ble_cus" handler, the register observer for this is set up during a separate initialisation function, before the main superloop. This second handler, contains a function that sets another event, triggering a third event handler "ble_cus_evt_handler" this handler is seemingly associated with the individual service during the initialisation of the service. 

I have seen a similar structure implemented on a few examples, but these examples seemed to split events across the handlers, not pile all events blindly through all 3 handlers. 

1: What is the difference between all of the "smaller", more specific handlers, such as "gatt_evt_handler", "hrs_evt_handler" etc. and the seemingly more global "ble_evt_handler"?

2: Why do these "smaller" handlers need to contain functions that trigger a secondary handler, can the first handler not just service the event itself?

3: Do i really need 3 handlers for a simple event?

4: I come from a background of "simplicity leads to functioning reliable code" - If i were to rationalise this, knowing the application will likely not have a great number of different events to process, is there some way i can do without one or two of these handlers? 

Many thanks,

Sean Howson

-nRf52840 DK, SDK17.0.2, S140, segger embedded studio, windows 10.

  • Hi Sean

    It sounds a bit like the project you are looking at is based on the ble_app_uart project, and they replaced the ble_nus module (Nordic UART Service) with their own service called ble_cus. 

    1: What is the difference between all of the "smaller", more specific handlers, such as "gatt_evt_handler", "hrs_evt_handler" etc. and the seemingly more global "ble_evt_handler"?

    The main difference is that the ble_evt_handler will receive every single BLE related event from the Bluetooth stack (SoftDevice), and as such can receive a very wide number of different events. These are sorted into groups based on what part of the stack they originate from, such as the common BLE events, GAP , GATT client and GATT server events. 

    The gatt_evt_handler and hrs_evt_handler are associated with specific SDK modules, and will only forward event related to their domain. For example the gatt module will only forward events related to data length or MTU exchange, while the hrs module will only forward events related to the heart rate service. 

    Both of these modules will have their own BLE event handler (similar to ble_evt_handler), but will only filter out events that they care about, and trigger the gatt_evt_handler or hrs_evt_handler when there is something happening that the application might need to respond to. 

    2: Why do these "smaller" handlers need to contain functions that trigger a secondary handler, can the first handler not just service the event itself?

    Typically triggering a secondary handler is done to pass events up one layer in the application (from the gatt module to the application layer for instance). These modules will be self contained as much as it is reasonable, but certain events or state changes need to be forwarded to the caller of the module, which would be the application main file in most examples. 

    I agree it would be odd to have several handler forwards happening within one file or module, I don't think this is happening in any of the standard modules or examples, but I have seen cases where the ble_evt_handler would do some pre-filtering of events, and then forward the events to various other handlers based on the event type (you could have a ble_gap_evt_handler for only handling BLE_GAP events for instance). 

    3: Do i really need 3 handlers for a simple event?

    In the same file or module, no, but in the case of BLE events it is common to have a large number of modules that all register for BLE events, even if they all just care about a small subset of the events available. 

    4: I come from a background of "simplicity leads to functioning reliable code" - If i were to rationalise this, knowing the application will likely not have a great number of different events to process, is there some way i can do without one or two of these handlers? 

    It is a bit hard to answer this question without having a look at the implementation. Would it be possible for you to share the related code snippets, if not the whole application?

    I can make the case private, in case you don't want to share any code in a public ticket. 

    Best regards
    Torbjørn

  • It sounds a bit like the project you are looking at is based on the ble_app_uart project, and they replaced the ble_nus module (Nordic UART Service) with their own service called ble_cus.

    I think the project may be based on this tutorial by Nordic:

    https://github.com/NordicPlayground/nRF5x-custom-ble-service-tutorial

    It uses the name "cus" for the custom service, and also does the event handling in the way described.

  • Hi Guys,

    Thank you both for your replies and thanks for answering all my questions.

    After posting i did find the app_uart project and actually found some annotations in the code i have that had clearly been copied and pasted from the uart project.

    Thanks for breaking down the different types of handler.

    So to add clarity to my previous question:

    3: Do i really need 3 handlers for a simple event?

    What i should have said was:

    Do i really need to run a handler, constaining an enormous switch statment, the each of the cases for which, runs a function which converts the event to a custom event type (yet still identical in it's raw value) and then redirects the execution, to the next handler.... the next handler also containing an equally large and identical switch statement, the cases for this statement ACTUALLY handle the event. ALSO the event is handled completely separately by the ble_event_handler.

    For now, i have removed most of the gubbins from the handler registered as an observer and simply passed the event to the secondary handler (without any form of sorting/processing)

    I now understand the origins of the re-direct and the way each handler is aimed at a specific subgroup of events (illustrated by their names i suppose).

    I can understand why there would be several of the "smaller" handlers running for different categories of event. I just can't see a reason to process, for example, the "on connect" event, in both the "big" ble_evt_handler AND the "small" ble_gap_evt_handler, seems to lack organisation/clarity?

    General opinion question here:

    Some of my issues are currently stemming from the multiple file types present in the project. A number of the main handler files have been written in Cpp,  with a "be careful not to make more than one instance of each class" - notice issued against each of them. For me this set off red flags.

    The previous author came from a non industrial background and used cpp because it was the language that made most sense to him, but the more i read through the code, the more it seems to be cramming a round peg into a square hole. All sorts of things have been modified, to try to accomadate the cpp code.

    Is this normal practice within the Nordic world? It's certainly not been normal for me in previous roles.

    Thanks,

    Sean

    P.S. for now i think you've clarified enough for me that i will avoid posting any code, but i appreciate the offer.

  • Hi Sean

    Thanks to Markku for the clarification. I thought "cus" sounded familiar, but when I didn't find it in the SDK I stopped searching for it. 

    SeanHowsonTB said:
    Do i really need to run a handler, constaining an enormous switch statment, the each of the cases for which, runs a function which converts the event to a custom event type (yet still identical in it's raw value) and then redirects the execution, to the next handler.... the next handler also containing an equally large and identical switch statement, the cases for this statement ACTUALLY handle the event. ALSO the event is handled completely separately by the ble_event_handler.

    No, running the switch statement twice in a nested fashion makes little sense to me, and to my knowledge this is not done by any of the standard examples. The normal approach is for the top level handler to do the filtering, and cast the event to the specific event type (which as you indicate is not a conversion per se, just a type cast), and then forward the event to a more specific handler that is only used for a single type of event, or a small subset of events. 

    The code snippet below is from the ble_nus.c implementation, which does it in this way for events relevant to the NUS service: 

    void ble_nus_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context)
    {
        if ((p_context == NULL) || (p_ble_evt == NULL))
        {
            return;
        }
    
        ble_nus_t * p_nus = (ble_nus_t *)p_context;
    
        switch (p_ble_evt->header.evt_id)
        {
            case BLE_GAP_EVT_CONNECTED:
                on_connect(p_nus, p_ble_evt);
                break;
    
            case BLE_GATTS_EVT_WRITE:
                on_write(p_nus, p_ble_evt);
                break;
    
            case BLE_GATTS_EVT_HVN_TX_COMPLETE:
                on_hvx_tx_complete(p_nus, p_ble_evt);
                break;
    
            default:
                // No implementation needed.
                break;
        }
    }
    

    In this case the casting of the event is handled by the on_write(), on_connect() and on_hvx_tx_complete() functions, but since the event type is checked by the top level handler it is not checked again in the event specific handler. 

    SeanHowsonTB said:
    I just can't see a reason to process, for example, the "on connect" event, in both the "big" ble_evt_handler AND the "small" ble_gap_evt_handler, seems to lack organisation/clarity?

    I agree. Within one file or module each event should only be processed once, but you could have multiple files that all have to process the connected event, which is normal. 

    SeanHowsonTB said:
    Is this normal practice within the Nordic world? It's certainly not been normal for me in previous roles.

    Using C++ is very rare for Nordic projects, but it happens from time to time. It is definitely not something I recommend unless you have previous experience using C++ in embedded systems, and have a good reason for doing so. 

    Many of the drivers and modules in the SDK are instance based, which would make them well suited to be re-implemented in an object oriented language like C++, but all the time the entire SDK is written in C it is quite a bit of work to adapt it to C++. 

    Also, many of the modules in the SDK won't build in C++ because they use syntax structures that are only supported in C (those do exist unforunately), and in this case the best workaround is to only call these libraries from C files, or rewriting the library files to be C++ compliant, which I would strongly discourage. 

    Best regards
    Torbjørn

  • Torbjørn,

    Thanks for the detailed reply.

    To be honest, i was second guessing myself a little with the whole, multi-languge stuff.

    I think my current aim is to gain a deeper understanding of the current code, and produce a working build for our upcoming prototyes, but eventually my aim will be to get rid of the C++

    Using C++ is very rare for Nordic projects,

    ...Thats all you had to say!

    I'm really not sure why it's been done, besides the preference of the previous developer. But it seems to be causing numerous problems.

    I'm still digesting the technical aspects to your reply, but in the meantime, one question has cropped up that i can't seem to find an immediate clear answer to:

    We have talked about the "ble_evt_handler" being a "global/big" handler for all events and the other handlers "hrs_evt_handler", for example, only responding to events relevant to them.

    This custom handler, which i'm assuming has been made up of butchered code, seems to respond to a great number of events, where might i look for a "list of things that trigger it", in plain english, i'm looking for a list of events from various modules that say "when one of these events happens, trigger the registered observer". Any clues as to where such lists are typically declared for other similar handlers (hrs handler, bas handler, for example)?

    Thanks again for all the help. Always appreciated!

    Sean

Related