The Rust library uavcan.rs is looking for a new maintainer

Hi @pavel.kirienko, thank you for the heads up! I’ve started by translating canard to Rust. On doing so, I desired to use uX, a lib that kjetil once wrote to cover custom width integer types. However, uX is in a bad shape, as it does not allow for Try{From,Into} conversions (e.g. trying to cast an i17 to an i9). So, currently I’m working on an alternative. The process is much more tedious than thought due to

I hope that I will be able to finish this work on the custom width integers in the next weeks, but up till now the semester is also calling for my time. I have some good news though: It looks like I’m going to be able to connect the Rust implementation of UAVCAN with my research at university. If this works out, I’m going to be able to work close to full time on it for a couple of months.

Do you have any sweet idea about what to do with the custom width integers?

1 Like

Maybe consider using the standard-width integers only, as we do in C++/C? I realize that this approach loses the expressivity of DSDL somewhat, though.

Ok, let’s play this through. If we use only the standard width types:

  • What do we do if at least one out-of-bounds bit is set on a message to be transmitted?
    • Do we silently ignore it (for example using a bitmask with &&)?
    • Do we return an error?
    • Do we panic?
  • If transmitted (after serialization), is a u11 always exactly eleven bits wide (thus resulting in arbitrary bit length messages), will each field be padded to the next byte, or will the whole message be padded to a one byte alignment? (I shortly looked into the spec and did not find it, if you just name a sub chapter that would be lovely)

This is covered in section 3.4.2.2 Cast mode.

Section 3.7.3 Primitive types.

1 Like

Hi! Out of curiosity: Is the new rust library being developed in the open? Where can I find it?

We are fleshing out a common CAN interface at: https://github.com/rust-embedded/embedded-hal/pull/212.
Any feedback to make it suitable for UAVCAN is welcome. This would allow UAVCAN rust to work on many platforms out of the box.

Hi Timo. We have our requirements neatly organized in this little write-up:

Even though it talks about RTOS, the requirements apply regardless of the model of the underlying platform. They are based on reasonable assumptions and are battle-proven in many practical applications from drones to satellites. Please let me know if your team needs additional input.

The official repo is here: https://github.com/UAVCAN/uavcan.rs, and it is currently stagnating because see above. Maybe @wucke13 has some updates, maybe not.

@tkr Nope, it’s not in the open yet.
@pavel.kirienko Do you think that it’s a good strategy that I’ll publish my ongoing work on my personal github account, and that we move it to the uavcan namespace once it starts becoming usable? If not, what do you suggest?

Whatever works for you, works for us.

1 Like

Well then, @tkr :
https://github.com/wucke13/uavcan.rs

I know, I know, it’s not much.

1 Like

Thank you @wucke13 for publishing! Following any further development.

@pavel.kirienko
Timestamping (for both RX and TX messages) and message deadlines is something we still have to look into. Thank you for the pointers.
Are there any specific requirements for receive filters?

No. I think all APIs I ever encountered were more or less adequate in this regard, which is not surprising considering that the feature is simple. Perhaps you should ensure that it is always possible to query the number of filters available because it’s vital for automatic acceptance filter configuration (more info about that in the Specification and in PyUAVCAN).

1 Like

I’m sorry to announce that the project for which I intended to implement the new uavcan-rs was cancelled. Therefore this was moved way down in my priorities list. I’m still happy to be part of the progress but judging from my current situation, I do not think that I will contribute significant work in the foreseeable future.

I’ve been playing around with an implementation here: https://github.com/davidlenfesty/uavcan.rs.

It’s starting to get to a place where I’m mostly happy with the API. Some kinks to work out with broadcasting messages, but I’ll be spending some time over my christmas break on this as well.

I took something close to the libcanard approach, where the message serialization and hardware drivers are completely ignored, you’re required to implement that in your own project, with the major difference that I’m splitting out the transport-specific implementation, and the session management.

The current (and probable) solution for abstracting over generic transports is a Transport trait that I can simply declare to handle all of the transport specific operations, like processing an incoming frame into the internal representation. This will allow the user to specify the transport a node operates on simply by declaring it at instantiation, e.g. let node = Node<uavcan::transports::Can>::new(); There’s some room as well for an implementation of placing messages from one transport onto another, but there are a lot of questions to answer before I can even start to think of an interface.

Session management is handled similarly, with the major difference being that the trait to implement will be public to any user, meaning they can implement their own system to fit their memory model. One of my annoyances with the current libcanard implementation is that it’s a bit heavy for a small node where all subscriptions and input buffers could be statically allocated, and it’s extra effort to set up and less performant on something with an OS than if we just used native OS types, so I plan to implement three managers in the library, one pure-static implementation, one similar to libcanard’s model, and one based entirely on std data structures that is flexible and easy to use. This specifically lets it serve both the no_std and std users quite well.

I can see wrapper libraries that automatically handle transmission/reception of messages being quite easy to implement, however those would be transport (and system) dependent so they should live outside of the main crate.

Again all the serialization would be handled externally to the main crate, and generating the structures at least would be done by nunavut. I’m currently leaning towards handling the serialization business logic with Rust macros, but that’s a discussion for much later.

The current state is:

  • I’m only working on the simplest possible std implementation for now - it’s the easiest to debug, and also fits my most immediate use case.
  • Full receive path with generic transports and session managers implemented (“functional” but not verified to be protocol-compliant)
  • I need to figure out the API for transmitting messages, specifically how that fits into generic transports and no_std implementations.
  • at one point I had a “functional” (but not verified) implementation: https://github.com/davidlenfesty/uavcan.rs/commit/7a1385a17bf1acd9c2f542c3e44745251c459cce
  • lots and lots of general code cleanup to do

Hope this is at least interesting :slight_smile: I am open to comments and suggestions.

3 Likes

Nice.

Are you aware of Canadensis? I stumbled upon it a few weeks ago completely by accident. I don’t know the author; Scott tried to get in touch via GitHub but unsuccessfully. Maybe you could extract some useful ideas out of it or avoid duplicated effort where possible (it is Apache/MIT licensed as usual).

Do you have UAVCAN/UDP on the roadmap before v1.0? Are you planning to support redundant transports? Having only CAN it is impossible to verify the validity of your abstract transport model for other transports. Maybe PyUAVCAN would be a better inspiration here than libcanard because it is designed to support all transports.

A fully static implementation might be wasteful and/or complex to set up correctly.

See, if the size of your transfer payload buffer (i.e., extent of the data type) is b and the network may contain up to n nodes, you need to statically allocate b \times (n-1) of memory to account for the worst case where every node emits a multi-frame transfer simultaneously (i.e., there are n-1 incomplete transfers at the same time). Heap allocation allows the user to downsize the memory requirement by making a reasonable assumption about the likelihood of such an outcome in a given application and because the heap is inherently able to dynamically redistribute memory according to the current demand. The static allocation would require much more attention from the developer to size the memory resources correctly, and hence it is more error-prone and has the potential to undermine the reliability of the system.

The documentation of O1Heap makes a reference to Herter’s dissertation where this idea is summarized as follows:

While often yielding better structured source code, another, more important advantage of dynamic memory allocation is to alleviate the programmer from finding ways to cope with a small amount of memory. I.e., alleviating him or her from assigning memory addresses such that objects not allocated contemporaneously can share memory locations to reduce overall memory consumption.

I have not heard of canadensis :slight_smile:. Definitely worth an in-depth look, although we seem to be using a completely different architecture there’s some bits in there I already see might be good to copy from. An interesting bit where we “differ” is they use seperate Publisher/Subscriber types where I bundle it into a node. But the only thing coupling the two parts together in my implementation is the node ID and Transport, which are both cheap to have copies of (the transport is just type system magic so no extra cost there).

Re: UAVCAN/UDP, yes supporting both UDP and CAN would be the bare minimum before I declared it v1.0. Hopefully I could also fit in UAVCAN/Serial but I haven’t looked into the state of that recently. Redundant transports are in my plans as well. Full UAVCAN v1 support is my requirement for releasing a 1.0.

Re: fully-static session management. I agree with your points. Just specifically some of my node implementations are simple enough that subscriptions never change, all of the messages I care about fit in <= 2 frames, and I can guarantee that I can process these messages faster than I can pull them off the bus, so the implementation memory requirements alone for any dynamic allocation would dwarf any “extra” memory usage from the static buffers I would need. Maybe that implementation doesn’t belong in the library, which is a discussion to be had (later, I won’t be implementing it for a while anyways). Mostly the point of that section is that I wanted a way for the library user to implement their own session management if the provided one isn’t ideal for their needs - without modifying the base library.

I just started looking into my old attempt and then found this new posting in the thread. @david.lenfesty it’s excellent news that you got this far already with your implementation. I would be happy gently participate in the design, if desired?

I’m especially interested in a std based async adapter on top of your work - but that’s something for the far future :slight_smile: . Regarding the serialization, packed_struct could serve as good starter. I think generating structs annotated with the packed_struct macros from nunavut will yield reasonable results fast. In the long run, packed_struct could end up a bit fat for no_std though.

Edit:

I just noticed that @david.lenfesty code in github does not have an issue tracker. I would prefer discussing issues over there though. Is there any value in not using a dev branch on UAVCAN/uavcan.rs for the development? Than we would not have to do any magic to migrate issues if we feel at some point this could become the new UAVCAN Rust implementation.

Absolutely! The more the merrier.

Honestly I don’t think an async adapter is out of the question for right now, it would help prove out the design idea and probably fix a few oversights.

For packed_struct yeah I think that would be great to start out with. My eventual hope was to provide something like it, but tailored for UAVCAN (as I don’t believe packed_struct can capture all the semantics), and just generate structs through nunavut, like you suggested, although I hadn’t been looking for existing crates that did this.

As for the extra branch, I see no problem with moving to a dev branch on the official repo, I mostly forked because I didn’t have any write access to the repo, but I see @pavel.kirienko added me to the organization potentially for this purpose. If that was the purpose I’ll set up the branch and maybe add a few issues to summarize where I am with it later on tonight.

Yes, this is the intended purpose. Please feel free to move your work back into the upstream repo.

I moved everything to a branch on the main repo and started an issue to track overall progress here.

1 Like

We are on our way! I’ve verified that I can communicate with a pyuavcan node - both single- and multi-frame transfers.

Lot’s of optimization/cleanup to do before I consider a release but the core functionality is working.

(currently all the test code is in the examples/basic folder, that’ll get cleaned up and documented properly eventually but for now I’m just using basic testing).

Most immediate on my list is making the core of it properly no_std (there’s still a sneaky Vec in the Transfer type that needs to get removed. Mostly just a lifetime puzzle to solve), then after that I’ll clean up the std SessionManager.

2 Likes