Hi Pavel,
Thank you for the thorough response! I'm very excited to hear that both you and other members of the uavcan community have taken interest in Rust. I have cc'd the mailing list as you suggested. Anyone reading this email is, of course, welcome to respond to it.
I'm personally convinced that through giving memory safety guarantees (statically) Rust has a lot to offer for mission-critical embedded software. Rust is also a new language with a somewhat different set of features than traditional embedded languages. I also have high hopes that these features together with cargo (with
crates.io integration) will make code reuse a lot less painful for embedded systems. I'm also certain that it will be the best for the community to have all the upstream uavcan repositories organized in one place.
What I'm not certain about is exactly how to structure the uavcan rust project to make it the best it can be. What I'm currently doing is testing out how different ways feels, while finding out what the Rust language has to offer and what features that are about to be implemented. When I've gained some more information and experience I feel like it would be natural to present to the community how I believe the different crates making up the full uavcan implementation should be structured. After improvements have been made and potential issues have been addressed, it would be great if this resulted in the transfer of ownership into the Uavcan team. We should still be able to break the structure (if needed), but not be a high-frequency thing (as it is for the moment).
The current version at
crates.io can serialize/parse single frame messages, I hope to have the first usable (being able to parse/serialize any uavcan message) function ready in the next few weeks. Then I will do some lite documentation and write a small post on how to use it. After that, I will need some time to feel out how the current implementation is working and improve upon it (hopefully the community will do some testing as well). This is about when I believe the time is right to try to "finalize" the structure of the implementation. Let's aim to have defined the crate structure and transferred to the uavcan team by the end of September (unless some big changes are required)?
The rest of the email will be me presenting what I'm currently thinking, I encourage everyone to find potential flaws in the reasoning.
uavcan-core (does not require rust std lib): Everything you really need to use uavcan in rust.
uavcan: Implementation of extensions that requires rust std lib (heap allocation etc). I'm not certain about to which extent this will be used but I'm currently thinking that If uavcan-core defines a MessageBuilder trait, it could be implemented as a StaticMessageBuilder (where the space for incoming can frames would be pre allocated) in uavcan-core, while uavcan could have a HeapAllocatingMessageBuilder.
These crates would only care about stuff relevant to uavcan, and would not contain any drivers. A separate crate would be made for every arch port. Some crates could be:
uavcan-stm32: This crate should import uavcan-core and some can driver crate for stm32 and bind them together.
uavcan-socketcan: This crate should import uavcan-core and socketcan and bind them together
To clarify:
- No assumptions whether std is available or not in the port should be made.
- Everything about the interface should be specified in uavcan-core. This means that the use of the implementation will be uniform over all architectures.
- Even though the ports exist in different crates they may very well exist in the same git repo
The uavcan team can then decide on which implementation is regarded as first class and maintain these. It should also be relatively easy for others to port to second class achritectures. Personally I'm going to atleast port to the S32K serie and socketcan. The S32K drivers need some love first, so it's probably a couple of months down the road before I'm able to release anything useful for the general public. The S32K will also be my secondary testing platform after socketcan.
There are three things I found difficult in libuavcan, and wish to make easier for this implementation. I've also written a short text on how it might be solved. (Disclaimers: I've not used libuavcan much, correct me if I'm wrong).
Building for the correct target
There should be a clear cut between the transport layer and the link layer. This equates into, the implementation should be simple to use without any port. The uavcan core should pump out can frames from uavcan frames regardless whether there exists a can controller or not. If it's simple to use without any port, it will hopefully be simple to port as well. Deciding on which port to use is simply done by selecting the correct cargo crate.
Only using parts of the features
For a very simple node it might be overkill to use the complete scheduler implementation from libuavcan, if you only wanted to send node status + esc status and only want to receive esc setpoints it should not require several KB with ram. I want this library to be helpful even on the small 8bits.
Using the uavcan structures
I believe that it should be a clear cut between dsdl and uavcan. Not because it's super desirable to use uavcan without dsdl (spoilers: it's not), but rather because of the intuitiveness of how to manipulate the data structures if they're basically rust data structures. I would like to have the uavcan types and structs as first class Rust types.
What I currently see as a desirable way to translate from the dsdl structs into Rust is:
- Since I wish to be able to send any Rust struct over Uavcan (and don't write crazy amounts of code) to be able to derive the traits needed for serializing/parsing the struct. This is easy in Rust.
- Create a frame from an ID, a path and an Uavcan struct. This is easy with a macro but not as aesthetic as deriving a trait (but not bad at all).
- Use a dsdl compiler to compile from dsdl format to rust struct. Since all code generation is done by the derivable traits this will only require to generate the structs (no implementations)
- I would originally like to calculate the data type signatures when deriving the struct trait, but I think it might end up a lot more practical to do this with the dsdl compiler.
With this, compiling the dsdl is highly independent of the uavcan implementation. Adding rust generation to the python compiler is a tempting way to go. But implementing the compiler in rust also has some advantages. The big advantage of using the python implementation is of course that it will most likely save some time. The most important advantages for reimplementing in rust is:
- Removing external dependencies (this will ease the use of the library)
- Better integration with cargo. We could have a setting in Cargo.toml field where the repo/path of the dsdl was specified that defaulted to UAVCAN/dsdl. (this will ease the use of the library, especially with custom dsdl definitions)
- Rust have great libraries for generating rust code syn/quote
- Rust also have a library, nom, (that I don't really know but seem great) for constructing parsers.
I'm not in a rush figuring out the details around the dsdl compilation. The important things at this point are that we will need one in a couple of months. It will probably only need to generate structs (and probably signatures). The "code generating" will probably be done by deriving traits and using macros.
Making the rust library a complete implementation of the full uavcan stack (and it's hopefully future extension to CAN-FD) is definitely one of the goals of the project. I will definitely keep in mind to not exclude redundant interfaces in my design suggestions. I would love to have some help doing this. If this is a particularly difficult thing to make fit in the implementation it would maybe be smart creating an issue tracking this feature so we can discuss what we need from the rest of the implementation to make it happen?
All Best,
Kjetil