Hi Chris,

On Fri, Sep 7, 2018 at 8:00 PM Christopher Collins <ch...@runtime.io> wrote:

> Hello all,
>
> TL;DR: Proposal for registration of GAP event listeners in the NimBLE
> host.  Currently, GAP event callbacks are specified by the code which
> creates a connection.  This proposal allows code to listen for GAP
> events without creating a connection.
>
> The NimBLE host allows the application to associate callbacks with GAP
> events.  Callbacks are configured *per connection*.  When the
> application initiates a connection, it specifies a single callback to
> be executed for all GAP events involving the connection.  Similarly,
> when the application starts advertising, it specifies the callback to
> use with the connection that may result.
>
> This design provides some degree of modularity.  If a package
> independently creates a connection, it can handle all the relevant GAP
> events without involving other packages in the system.  However, there
> is a type of modularity that this does not provide: *per event*
> callbacks.  If a package doesn't want to initiate any GAP events, but
> it wants to be notified every time a connection is established (for
> example), there is no way to achieve this.  The problem here is that
> there is only a single callback associated with each connection.  What
> we really need is a list of callbacks that all get called whenever a
> GAP event occurs.
>
> My proposal is to add the following to the NimBLE host API:
>
>     struct ble_gap_listener {
>         /*** Public. */
>         ble_gap_event_fn *fn;
>         void *arg;
>
>         /*** Internal. */
>         STAILQ_ENTRY(ble_gap_listener) next;
>     };
>
>     /**
>      * @brief Registers a BLE GAP listener.
>      *
>      * When a GAP event occurs, callbacks are executed in the following
>      * order:
>      * 1. Connection-specific GAP event callback.
>      * 2. Each registered listener, in the order they were registered.
>      *
>      * @param listener                 The listener to register.
>      */
>     void ble_gap_register(struct ble_gap_listener *listener);
>
>     /**
>      * @brief Unregisters a BLE GAP listener.
>      *
>      * @param listener                 The listener to unregister.
>      *
>      * @return                      0 on success;
>      *                              BLE_HS_ENOENT if the listener is
>      *                                  not registered.
>      */
>     int ble_gap_unregister(struct ble_gap_listener *listener);
>
> Initially, I thought this functionality could be achieved with a new
> package implemented on top of the host (call it `blemux`).
> Unfortunately, I think there are some issues that make such an approach
> unwieldy for the user.  I imagined such a package would be used as
> follows:
>
>     1. The `blemux` package exposes a GAP callback (call it
>        `blemux_gap_event`).
>     2. Elsewhere in the sytem, for each GAP function call, the caller
>        specifies `blemux_gap_event` as the callback.
>     3. Each package that is interested in GAP events registers one or
>        more listeners via `blemux_register()`.
>     4. When a GAP event occurs, `blemux_gap_event` is executed.  This
>        callback executes every registered listener.
>
> The problem is that packages have no guarantee that the app is using
> blemux.  A package can depend on `blemux` and can register listeners,
> but it really only works if every other BLE-aware package in the system
> also uses `blemux`.  If any package doesn't use `blemux`, then the GAP
> procedures that it initiates won't be reported to the `blemux`
> listeners.  A secondary problem is that this just feels like a clumsy
> API.  It is confusing and error prone to need to specify
> `blemux_gap_event` as the GAP event callback.
>
> So, I think this functionality should be implemented directly in the
> host.
>

Sounds exactly like something I wrote some time ago, so I can only agree
with above :-)
Typical scenario here (and what I was doing) is that external package, like
service, wants to be notified about connection or characteristic
subscription state. Currently we do this by exposing extra API from package
which app needs to call from GAP callbacks in order to everything work
properly. It works, but it's really clunky. Hooks provided by host are imo
good way to handle this.

I did not finish my service so forgot to send a PR with NimBLE changes, but
here they are for reference - I can create PR if they look ok.
https://github.com/andrzej-kaczmarek/apache-mynewt-nimble/commit/6cc902821e6d65fbd5f72fa89e7219915afca38d

The API is pretty much the same as proposed. The difference is that
listener struct is opaque and fields are initialized when listener is
registered.
Notified events are the ones which are notifications, i.e. do not require
interaction from app (so e.g. pairing event does not call listeners since
it does not make sense to do so).


> Thoughts?
>
> Thanks,
> Chris
>

Best,
Andrzej

Reply via email to