Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unify callback / iterator approaches #163

Open
petekubiak opened this issue Nov 13, 2024 · 7 comments
Open

Unify callback / iterator approaches #163

petekubiak opened this issue Nov 13, 2024 · 7 comments
Milestone

Comments

@petekubiak
Copy link
Collaborator

Currently there are two mechanisms for interacting with gatt events in TrouBLE:

  • server.next() iterator/stream approach
  • attribute callbacks

Each of these methods currently have different strengths and weaknesses to one another:

Iterator/stream Callback
Synchronicity ✔️ Async ❌ Only sync
Interaction ❌ Occurs after event - cannot interact ✔️ Occurs during event - can interact
Access ✔️ Embedded in task context - full access to local variables ❌ Accessed through fn pointer - access only through what is passed in / statically available

@lulf has suggested a unification of these two mechanisms to provide the benefits of both: (from #151 (comment)):

I think some kind of async iterator API is more powerful than the callbacks and more natural to async, so if we can get some way to poll characteristics for events or the like, maybe that could be an alternative.

@petekubiak
Copy link
Collaborator Author

One possibility might be to modify the server.next() function signature to accept a closure, rather than returning a GattEvent. The closure could have a signature something like | Connection, GattEvent, Attribute, &[u8]| -> Result<(), Error>. You could then use match statements on this information to create a sort of dispatch table to provide handlers for the relevant interactions on the relevant attribute. Where applicable the Result type could be used as an accept/reject mechanism, as is currently implemented for the on_write callback.
The return type of server.next() could then be another Result type indicating the outcome of the event for handling by the application layer.

@alexmoon
Copy link

So to handle interaction in an event based model, the event should contain a continuation object with methods to allow the host to complete the handling of the event. I talked about this some in #133 (comment) (the fourth option listed in that comment). At that point the iterator/stream API is a strict superset of the callback API.

@jamessizeland
Copy link
Collaborator

just to make a record of the conversation we had about improving WriteEvent, that might be relevant to this work.

#170 (comment)

@lulf lulf added this to the 0.1 milestone Nov 20, 2024
@lulf
Copy link
Member

lulf commented Nov 20, 2024

I think having the iterator/stream with continuation object would be nice for a 0.1 (feel free to disagree!)

@jamessizeland
Copy link
Collaborator

What do you mean by continuation object?

@lulf
Copy link
Member

lulf commented Nov 21, 2024

@jamessizeland An API like this:

impl Connection<'_> {
    async fn next(&mut self) -> ConnectionEvent<'_> {}
}

enum ConnectionEvent<'a> {
   Gatt(GattEvent<'a>),
}

enum GattEvent<'a> {
   Read(ReadEvent)
   Write(WriteEvent<'a>)
}

struct WriteEvent<'a> {
   data: &'a [u8]
}

impl<'a> WriteEvent<'a> {
      // Process the event in the table
      fn process(self, table: ...) -> Reply<'a>;
    
      // Provide your own response
      fn respond(self, res: AttRes<'a>) -> Reply<'a>;
}

impl Reply<'a> {
     async fn send(self);
}

And the code using it:

match conn.next().await {
    ConnectionEvent::Gatt(GattEvent::Write(event)) => {
          // Peek at data before it is processed by table
          ....
          // Optional: Process data in table/GATT server
          let reply = event.process(&table);

          // Alternative if not wanting to use the table:
          // let reply = event.respond(...);

          // Send response back
          reply.send().await;
    }
}

Probably lots of inconsistencies in the above, but the point is that one can iterate async over the events, and decide how to handle it (either store/process it in server), or deal with it separately.

@jamessizeland
Copy link
Collaborator

Aha I see! I've got some time today to look at write events, I was going to see if I could create an enum within the macro that gives the name of the written characteristic rather than just the handle Id.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants