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

no_std and use in peripherals #15

Open
omelia-iliffe opened this issue Aug 30, 2024 · 10 comments
Open

no_std and use in peripherals #15

omelia-iliffe opened this issue Aug 30, 2024 · 10 comments

Comments

@omelia-iliffe
Copy link
Contributor

omelia-iliffe commented Aug 30, 2024

Hello!

I am looking at developing for a no_std environmet and I would like to use this library.
There is good flexibility with the ReadBuffer and WriteBuffer generics but its locked into using SerialPort. I know this has given greater control than just using the std::io::Read/Write traits. Would you be open to considering making the serial_port field of the bus an interface trait to allow other interface implementations?

Other barriers to no_std are the use of

  • Duration and Instant in the timeout,
  • std::io::Error,
  • Vec in various return types,
  • std::borrow::Borrow in bulk_write, can be easily switch to core::borrow::Borrow

My use case is actually developing a device/peripheral which uses the dynamixel protocol for receiving communication. As in it receives Instruction packets, parses them and responses with a StatusPacket. Similar to how the Dynamixel2Arduino Slave mode works, if you are familar with that. https://github.com/ROBOTIS-GIT/Dynamixel2Arduino/blob/master/examples/advanced/slave/slave.ino
This could exist as a seperate crate, or if you are interested it could be added to this crate. It would add methods to received and parse Instruction Packets and methods to return a response packet. I began planning it only to realise it would reuse many of the same elements already developed for this crate (ie the instruction_id consts, the find_header method and general architecture)

Let me know what you think :) I have time to impl most of this

@de-vri-es
Copy link
Member

de-vri-es commented Aug 30, 2024

Hey!

Yeah, I'm definitely open to moving the serial port choice to a trait and adding the device side of the protocol.

Duration exists in core, but Instant does not. If we need to, we could have a top-level Platform/System trait that has a few associated types for things like what SerialPort to use and what type to use for Instant and Duration. The std::io::Error can be part of the trait that the serial port needs to implement. Then you would have Bus<ReadBuf, WriteBuf, Platform/System> (I don't want to tie the choice of buffer to the platform/system).

The only downside I see here is that the errors returned by Bus will become generic over the error type of the serial port. This tends to be a little annoying, but we might be able to provide some type aliases for the default platform/system to help with that if necessary (feel free to skip this, there is high bike-shedding potential here).

One thing I'm still thinking about: We should probably implement the client side and the device side in different structs. I don't see a use-case for mixing them (there can be only one node on the network sending instructions), and the Bus API is already quite large. Maybe Device and Bus? Or Device and Client (and keep Bus as deprecated alias)?

Of course, if we split the types it might make sense to add a private low level type that implements the basic message exchanges features, so the high level types only need to implement high level instructions on top of the low level interface.

@omelia-iliffe
Copy link
Contributor Author

Awesome!
I think I will start with making the serial port generic and go from there. :)

@de-vri-es
Copy link
Member

de-vri-es commented Dec 30, 2024

I want to reduce the number of generic parameters of Bus/Client/Device to reduce the mental overhead of using these types.

The most obvious candidate is the read and write buffers.

So I was thinking of replacing the generic parameters with something like this:

#[cfg(feature = "alloc")]
use core::ops::DerefMut;

/// Read or write buffer used to temporarily store messages going over a bus.
pub struct Buffer<'a> {
	inner: BufferInner<'a>,
}

enum BufferInner<'a> {
	BorrowedSlice(&'a mut [u8]),
	#[cfg(feature = "alloc")]
	Vec(alloc::vec::Vec<u8>),
	#[cfg(feature = "alloc")]
	Dyn(Box<dyn core::ops::DerefMut<Target = [u8]>>),
}

However, this removes the possibility to use an owned fixed-size array as read/write buffer, which might be annoying for embedded use. Adding a fixed size array as variant is not really feasible, as it would increase the size of the enum unconditionally.

Basically, with this design, only a borrowed slice remains as possibility without alloc, which infects the type with a lifetime. It can be 'static though.

In practice I think this is not a big limitation if you create only one (or a fixed amount of) Client/Device objects, which seems reasonable to assume for embedded application.

For use cases where a dynamic amount of Client/Device objects is created, I would usually also expect alloc to be available. And then you can use an owned Vec as buffer again.

What do you think, @omelia-iliffe?

@Flova
Copy link

Flova commented Feb 6, 2025

What's the state of this? We are also very interested in migrating our devices/slaves from the Dynamixel2Arduino to a rust based implementation.

@omelia-iliffe
Copy link
Contributor Author

omelia-iliffe commented Feb 6, 2025

Hi Flova,

Whilst we haven't finalized a crates.io release that includes no_std support it is implemented and ready to be tested using the main branch of the repo.

What platform are you targeting?
I have made an example for rp2040 can to be found here: https://github.com/omelia-iliffe/dynamixel2-rs/tree/no_std_example/examples%2Frp2040

They may not be 100% up to date but should offer a good starting point.

It's definitely more involved than the Dynamixel2Arduino approach but will be a lot more powerful, covering hopefully the entirety of Protocol 2 😀

@Flova
Copy link

Flova commented Feb 6, 2025

Thanks for the speedy answer. We are targeting the esp32. We use it to build dynamixel bus compatible IMUs, LEDs, Buttons and Load cells for our humanoid robots (https://robocup.informatik.uni-hamburg.de/wp-content/uploads/2024/12/IMG_5228-scaled.jpg). So we are very much interested in a slave/device crate for the dynamixel protocol.

Thanks for the example. I had a quick look, but was not sure if this was for slave device. Or is it agnostic?

Sounds promising.

@de-vri-es
Copy link
Member

de-vri-es commented Feb 7, 2025

@omelia-iliffe recently also added support for the device side of the protocol, along with enabling embedded use 🥳

The main blocker for releasing a new version for now is my time: I want to do a large complete review of the API before releasing the next major version.

And I want to review not only the new API's, but also the old ones. For example, we also recently started rewriting the interface for functions that receive replies from multiple motors, to stop using callbacks and use iterator-like objects instead.

All of these are breaking changes, so I want to take some time to make sure we don't need another breaking change soon. However, I'm also in the middle of a large home renovation, so my time is very limited :(

But please, if you can, try out the API from the main branch and maybe even #20. Any real world use of the interface before releasing it would be very useful.

@omelia-iliffe
Copy link
Contributor Author

Wow awesome use case.

Thanks for the example. I had a quick look, but was not sure if this was for slave device. Or is it agnostic?

Whoops sorry. In my head it was a device (slave) but it's not. I'll put together a quick device example for you sometime soon! And you could take a look at the integration tests in the repo. That creates a mock communication between a client and a device for testing, but the parsing of the StatusPacket on the device side will look similar.

@Flova
Copy link

Flova commented Feb 7, 2025

That would be great. Thanks for your quick response and support!

@omelia-iliffe
Copy link
Contributor Author

Hi, I updated the rp2040 example to include a device. It now has code for both clients and devices. Hopefully its able to get you started.
One thing you'll need to do for the esp32 is implement the SerialPort trait with the peripherals specific to the esp32. Let me know if you get stuck or run into issues. We are still very much working on these features so feedback would be really appreciated :)

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

3 participants