embedded-hal icon indicating copy to clipboard operation
embedded-hal copied to clipboard

Asynchronous embedded-hal traits roadmap

Open eldruin opened this issue 2 years ago • 8 comments

We have started a project to develop a asynchronous versions of the embedded-hal traits, at the moment championed by @Dirbaio. This will be developed as a separate crate called embedded-hal-async until it is ready for integration inside a module in embedded-hal. This can be found in the subfolder embedded-hal-async

Roadmap:

  • [ ] Add asynchronous traits
    • #348
    • #347
    • #349
    • #344
    • #346
    • [ ] CAN
  • [x] Publish first version to crates.io: #379
  • [ ] Use traits across the community.
    • [ ] In HAL implementations. e.g. in embassy
    • [ ] In device drivers
  • [ ] Transition to async fn in traits (expected breaking change inside embedded-hal-async).
  • [ ] GATs are available in Rust stable. (Tracking issue)
  • [ ] async fn in traits are available in Rust stable (Tracking issue)
  • [ ] Merge the asynchronous traits into a module inside embedded-hal once these are deemed stable.

Asynchronous execution holds great potential for embedded. Please join us!

eldruin avatar Feb 10 '22 08:02 eldruin

Would it be worth cutting a 0.1.0-alpha (or just 0.1.0) release with the existing I2C, SPI etc async traits, while Serial is worked out?

Given the higher complexity I also expect CAN might take a while longer to figure out than the others, so at least getting the basic traits onto crates.io would let people start using them without git dependencies.

adamgreig avatar Apr 12 '22 18:04 adamgreig

Could we have an alpha 1 release of this? I have a pending PR for embassy that depends on the new SPI transaction API in master. Cheers!

kalkyl avatar May 23 '22 07:05 kalkyl

I've started trying to port some of my code over to embedded-hal-async, and I'm wondering if you have any guidance for how device driver crates should be ported. It's not a great story if we tell everyone they have to maintain two copies of every function, where the ~only difference is async and .await sprinkled in one of them.

I'm wondering if there might be a procedural macro solution that would allow a single device driver to be compiled in both blocking and async modes. Has anyone explored that? Or is it more trouble than it's worth and we expect crates to quickly move over to the new traits? (Presumably that's blocked on the necessary features hitting stable.)

quentinmit avatar Jul 09 '22 06:07 quentinmit

there's a blocking to "fake async" adapter that you can use to get an async impl out of any HAL with only a blocking impl. It's "fake async" because it will block and never "async yield", but for many use cases that's fine (spi/i2c register reads/writes are fast).

This allows maintaining just one version of the driver (async) but still using it with HALs that don't support async.

You still need an "executor", but since there's no yielding/waking going on a very dumb one like loop { fut.poll() } is enough (roughly same idea as nb::block!()), so it should still be very lightweight.

Dirbaio avatar Jul 09 '22 15:07 Dirbaio

I saw that adapter, but I feel like you wouldn't want to do "async only" until both these traits are stable and they can be compiled with stable Rust, and probably an fut::block!() macro that mirrors nb:block!() exists for people who are not using async.

quentinmit avatar Jul 09 '22 16:07 quentinmit

I'm wondering if there might be a procedural macro solution that would allow a single device driver to be compiled in both blocking and async modes. Has anyone explored that?

i've spent way more time than i should have trying to achieve this with traits with no real success (though radio is kinda neat in that our higher-level blocking traits automagically become async)... definitely interested in whether we can do useful things with proc macros without too much magic (tm).

Or is it more trouble than it's worth and we expect crates to quickly move over to the new traits?

i don't think we can expect folks to all move to async (or vice versa), so the story for compatibility and combining the two seems fairly important!

ryankurte avatar Jul 10 '22 01:07 ryankurte

I'm wondering if there might be a procedural macro solution that would allow a single device driver to be compiled in both blocking and async modes. Has anyone explored that?

i've spent way more time than i should have trying to achieve this with traits with no real success (though radio is kinda neat in that our higher-level blocking traits automagically become async)... definitely interested in whether we can do useful things with proc macros without too much magic (tm).

I started writing something like this and then I discovered the maybe-async-cfg crate already does pretty much exactly what I was thinking of.

I was able to very easily adapt my async device driver into something that compiles both sync and async versions side-by-side. I don't quite love the user ergonomics (it generates types named FooSync and FooAsync to keep them separate; I think it would be better if they could just be generated in two different modules, so the user would only have to change a use statement to swap between them, but then the problem is that async is a reserved identifier and use device::r#async::Device is not a great UX either.)

@ryankurte Maybe take a look at that crate and see what you think?

quentinmit avatar Jul 18 '22 06:07 quentinmit

I was able to very easily adapt my async device driver into something that compiles both sync and async versions side-by-side. I don't quite love the user ergonomics (it generates types named FooSync and FooAsync to keep them separate; I think it would be better if they could just be generated in two different modules, so the user would only have to change a use statement to swap between them, but then the problem is that async is a reserved identifier and use device::r#async::Device is not a great UX either.)

I think the accepted naming convention for async modules is asynch :)

kalkyl avatar Jul 18 '22 07:07 kalkyl

Hello everyone, I'm joining this conversation to see the progress of the issue. It seems that there are still a few things missing (to my understanding) in order to release a stable version of the async traits, however, there is one thing that can be done additionally:

Digital Pin (input, output, etc.) in the async hal should be added, until the traits get merged inside the embedded-hal, until then we still have to rely on embedded-hal for the async versions when dealing with digital pins.

elpiel avatar Jun 12 '23 21:06 elpiel

Discussion point from https://github.com/rust-embedded/embedded-hal/issues/177:

Should embedded-hal-async be merged back into embedded-hal, now that async fn in traits are stable? And if so, how do we best do that?

madsmtm avatar Nov 21 '23 23:11 madsmtm

Another thing: Is embedded-hal-nb still useful, or should it be deprecated now that we can do async things in stable?

madsmtm avatar Nov 21 '23 23:11 madsmtm

my personal opionion is we should deprecate it (i.e never release embedded-hal-nb 1.0), but it's up to the HAL team as a whole to decide... It was discussed a while back and the conclusion was "don't deprecate, or at least not yet". Perhaps now that stable AFIT is here it's worth a second discussion?

Dirbaio avatar Nov 25 '23 01:11 Dirbaio

For what it's worth, I never encountered a library that actually implements the embedded-hal-nb traits in the wild. Most of the dependent packages are HALs, based on what's published at least.

jessebraham avatar Nov 25 '23 10:11 jessebraham

embedded-hal-async 1.0.0 has now been released. Thank you so much to everyone that was worked and helped through all this time to make this a reality. 🎉

eldruin avatar Jan 09 '24 22:01 eldruin