-
Notifications
You must be signed in to change notification settings - Fork 200
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
[WIP] CAN Interface take 2 #77
Conversation
@kjetilkjeka I'd need to check that in detail but on a first glance it looks good to me. I'm not sure I do understand the purpose (and quite different signature) of |
Can is designed with RT systems in mind. This means that there is some priority of frames involved. This means that implementing a Can driver is not straight forward, the naive implementation will most likely include a potential unbounded priority inversion. The idea is that
|
I only see three minor issues with those methods:
If they're "special" I'd prefer having them separately in an extra- or super-trait. |
@kjetilkjeka Looks good to me. I'd like to hear from a user (e.g. @dunmatt @tib888) whether that could work for them. |
I fixed my own concern by letting Instead of having a second weird transmit function i included the I'll leave it for a day or two to let other users express concerns, after that I will make a test implementation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall I'm not convinced, I think the associated types are taking it a bit far just to avoid having an "id type" field broken out.
src/can.rs
Outdated
type Id: Id + From<Self> + TryInto<Self>; | ||
} | ||
|
||
/// A Can Extended (28-bit) ID |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor nit: Should say "29-bit"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks :)
#[cfg(feature = "unproven")] | ||
pub trait Frame { | ||
/// The Id type of this Frame | ||
type Id: Id; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm dubious of the value of having Frame type and the Id type be associated. It's very common for both Id types to be present on the bus at the same time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not a problem as Id is defined as "A type that can either be BaseId
or ExtendedId
"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm surely not understanding something then (please forgive me, I'm still pretty new to the language). For a bus that contains messages of both Id types, how many Frame types would you expect to come out of the driver?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just one Frame type and one ID type. Some examples of how the Frame type could look (only one of them, is actually required):
pub enum Frame1 {
BaseIdDataFrame(_),
BaseIdRemoteFrame(_),
ExtendedIdDataFrame(_),
ExtendedIdRemoteFrame(_),
}
pub struct Frame2 {
remote: bool,
extended_id: bool,
data: [u8; 8],
}
and the Id type could look like (only one of them, is actually required):
extern crate ux;
use ux::{u11, u29};
pub enum Id1 {
Base(u11),
Extended(u29),
}
pub struct Id2 {
extended: bool,
id: u32,
}
When implementing the trait each representation of a Frame
would have to choose a representation of a nId
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So you would also have
impl BaseId for Id2 {...}
impl ExtendedId for Id2 {...}
? That is to say, you'd have Id2 be both Base and Extended?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, this will not work. I will need to remove the BaseId and ExtendedId and add approriate methods for distinguishing between ids.
src/can.rs
Outdated
#[cfg(feature = "unproven")] | ||
pub trait Interface { | ||
/// The Can Frame this Interface operates on | ||
type Frame: Frame; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't this associated type mean that an interface only works with one frame type? If so, and the frame type is associated with the Id type, you'd need two different interfaces for the same hardware in the event of a bus that contains both id types. That doesn't strike me as a good design.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Examples of Frame implementations follows:
pub enum Frame1 {
BaseIdDataFrame(_),
BaseIdRemoteFrame(_),
ExtendedIdDataFrame(_),
ExtendedIdRemoteFrame(_),
}
pub struct Frame2 {
remote: bool,
extended_id: bool,
data: [u8; 8],
}
So the same interface can be used for extended id and base id
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In Frame2, what would be the type of the id?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably something close to:
pub struct Id2 {
extended: bool,
id: u32,
}
fn id(&self) -> Self::Id; | ||
|
||
/// Returns `Some(Data)` if data frame. | ||
/// Returns `None` if remote frame. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like a non-obvious way to encode remote frames. If I asked a random programmer "What would you guess is the difference between a CAN frame with None for the data, and a CAN frame with Some(empty_slice) as the data?" I'm skeptical they'd guess "Whether or not its a remote frame!"
Or to voice my concern from a different angle, Option means "may not exist", where as RTR means "I would like someone to do a thing", and those aren't well connected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. How can we change this to the better?
Returning a len 0 slice for a remote frame is worse i believe. At the same time we will need to support frame encodings where it's not statically known if they are remote or data frames (from the discussion in the last PR)
Would it be sufficient to introduce is_remote_frame(&self) -> bool
and is_data_frame(&self) -> bool
as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is a len 0 slice worse? On the wire the length field is indeed zero and zero bytes come before the checksum. It seems like a great modeling of what is actually happening on the bus. It seems like zero length slice is what Occam's Razor would call for
I'm not sure what you mean wrt not knowing if a frame is remote or data, it's a required bit, it will always be present and always be either 0 or 1...? What's a situation where you might not know?
But yes, is_remote_frame(&self) -> bool
seems like a fine alternative to using Option here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is a len 0 slice worse? On the wire the length field is indeed zero and zero bytes come before the checksum. It seems like a great modeling of what is actually happening on the bus. It seems like zero length slice is what Occam's Razor would call for
I feel like there is a conceptual difference between "this data frame didn't have any data" and "this frame was not a data frame". And more importantly, using the API must be easy. An interface where you first have to check if it's a data frame before reading the data is easier to mess up than an interface where you will not be able to get to the data unless it is a data frame.
I'm not sure what you mean wrt not knowing if a frame is remote or data, it's a required bit, it will always be present and always be either 0 or 1...? What's a situation where you might not know?
It's possible to use different types for Remote/data frames and know the difference between these types statically. Or use the same type for both and distinguish between them dynamically (using a variable). The Trait
will need to support both.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you'll find it challenging to come up with an API that is natural for both static and dynamic typing. Static typing would have it that the id types are different in some way, and dynamic would have it that everything from either id type is on both.
But I hear what you're saying about the conceptual difference. And you made a good point in the other PR, namely, that the compiler should enforce that RTR frames have no data. I would say, though, that this conceptual difference is a bit idealistic. Obviously it's best if invalid configurations can't be represented, but what if invalid configurations are what other devices on the bus are sending? Should the HAL prevent communication with such devices (that are, generally, not within the user's control, eg the ECUs in a car)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Due to the nature of this crate is essential that it works for all reasonable representations of the can frame.
I would say, though, that this conceptual difference is a bit idealistic.
Sure, but I would also argue that it's an idiomatic use of Rusts typesystem.
Obviously it's best if invalid configurations can't be represented, but what if invalid configurations are what other devices on the bus are sending? Should the HAL prevent communication with such devices (that are, generally, not within the user's control, eg the ECUs in a car)?
The DLC field in a remote frame doesn't mean "how long is the data field for this message" it means "How much data do i want to receive". Therefore the CAN controller would never be able to assemble such frame and this would never be an issue. But even if it were, I would almost always go for the fail fast and loudly approach and not accept a malformed Frame.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
enum /* (I don't know what this should be named)) */ {
Remote,
Data(&[u8])
}
This would be an idiomatic solution that clearly represents what this frame is.
It's essential that it works for all reasonable uses of CAN, it's not clear
that it needs to be representation agnostic, another way to support all
reasonable uses of the hardware is to specify a representation that will
always work. This is, for example, how the ChibiOS HAL works.
I get that that's how dlc works, but since this is a HAL we can't assume
the hardware will filter bad frames. I agree with you that failing fast
and loud is usually the right call in these cases, but I'd argue that
failing in that way is the job of the driver rather than the HAL, that the
HAL should not contain any policy decisions. There are legitimate uses for
drivers that don't fail in that case (eg, can pen testing, and interacting
with poorly implemented devices (both of which I do at work))
…On Apr 21, 2018 2:01 PM, "Kjetil Kjeka" ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In src/can.rs
<#77 (comment)>:
> +pub trait ExtendedId: Sized {
+ /// A generic ID type that encapsulate this type
+ type Id: Id + From<Self> + TryInto<Self>;
+}
+
+/// A Can Frame
+#[cfg(feature = "unproven")]
+pub trait Frame {
+ /// The Id type of this Frame
+ type Id: Id;
+
+ /// Returns the Can-ID
+ fn id(&self) -> Self::Id;
+
+ /// Returns `Some(Data)` if data frame.
+ /// Returns `None` if remote frame.
Due to the nature of this crate is essential that it works for all
reasonable representations of the can frame.
I would say, though, that this conceptual difference is a bit idealistic.
Sure, but I would also argue that it's an idiomatic use of Rusts
typesystem.
Obviously it's best if invalid configurations can't be represented, but
what if invalid configurations are what other devices on the bus are
sending? Should the HAL prevent communication with such devices (that are,
generally, not within the user's control, eg the ECUs in a car)?
The DLC field in a remote frame doesn't mean "how long is the data field
for this message" it means "How much data do i want to receive". Therefore
the CAN controller would never be able to assemble such frame and this
would never be an issue. But even if it were, I would almost always go for
the fail fast and loudly approach and not accept a malformed Frame.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#77 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ABDfy6bzvlcf82m0LawPeVqYmMzRch8zks5tq3P9gaJpZM4TcOxW>
.
|
@dunmatt I've addressed your concerns about the difficulties of use due to associated types in the latest commit. Remember that things like
I think the policy from the maintainers of this hal has been that as long as not every implementation contains something, we should leave it out. Parsing malformed frames is not something every controller will do and we should therefore avoid supporting it (at all) in this trait. If this is something you rely on you should probably use some functions from the driver instead from the hal. I guess the hal must contain some policy decision if it is to behave approximately the same for all drivers, for instance return |
That seems to suggest that you believe that the drivers are providers of the HAL rather than consumers of the HAL. I assert it's the other way around, that the reason to have a HAL in the first place is to be consumed by drivers. My whole objection is that drivers such as I need cannot be written to consume the HAL you're proposing (which is fine, you need not cater to me, I'm an unusual case and I'm perfectly happy scratching this itch for myself). |
The device drivers are consumers of the HAL while HW drivers are "producers". The HW drivers will also implement things that are not included in the HAL, and is possible to call directly into from the device drivers. Is it the assumption of no data in remote frames that doesn't work for you? Perhaps you can give a concrete example of when this happens so it's easier for me to understand the need? |
Ah, I see what you're saying, what you're calling HW drivers I had been calling HAL implementations, but you're right that they need not be the same. That said, the functionality I'm talking about is send and receive, if I have to bypass the HAL in order to send and receive there is really no point depending on the HAL. Sure, my exact use case is this: the control and security of autonomous cars. There are computers from a half dozen or so different vendors on the bus, of which some we have executive control, some we have a little control, and the remainder are black boxes (literally, in one case). Moreover for business reasons we don't want to marry ourselves to our current vehicle platform, so there's a level of uncertainty even in terms of the devices on the network (ie I can't assume that all devices I'll ever care about are standards compliant, but I can assume that if one isn't I won't be able to fix it). So given that I'm trying to control and secure black boxes, how would I do it? Setting aside control, in security applications you always assume malicious input, so my software needs to be able to detect (receive) malicious input and respond to it. The assumption that only valid data happens on the CAN bus is one problem I have. Another is that my design intuitions skew more towards KISS than "make it impossible to fuck up" (but you're right that the latter is more idiomatic). Another is that enforcing policies isn't the job of an abstraction layer. |
In such network (with a lot of different can controllers) wouldn't at least some of them be standards compliant and inject Error frames? Wouldn't the node sending malformed messages go into bus-off state pretty quick? Have you encountered any concrete devices that behaves this way?
Putting a black box node on a cars can network sounds to me like installing software you don't know what do on your servers. With CAN my solution would be:
|
Wouldn't the node sending malformed messages go into bus-off state pretty
quick? Have you encountered any concrete devices that behaves this way?
Not if they're malicious they wouldn't, and I'm building one, respectively.
Putting a black box node on a cars can network sounds to me like
installing software you don't know what do on your servers.
Quite right, it's a wonder anyone deploys Window or Linux servers without
first auditing the whole thing for themselves. By which I mean, the black
boxes come with the cars when we buy them, and for the sake of getting
stuff done we do have to trust someone sometime... Trust but verify.
Believe me, if we could get the auto makers to give us their source we'd do
it.
…On Apr 22, 2018 11:58 AM, "Kjetil Kjeka" ***@***.***> wrote:
Sure, my exact use case is this: the control and security of autonomous
cars. There are computers from a half dozen or so different vendors on the
bus, of which some we have executive control, some we have a little
control, and the remainder are black boxes (literally, in one case).
Moreover for business reasons we don't want to marry ourselves to our
current vehicle platform, so there's a level of uncertainty even in terms
of the devices on the network (ie I can't assume that all devices I'll ever
care about are standards compliant, but I can assume that if one isn't I
won't be able to fix it).
In such network (with a lot of different can controllers) wouldn't at
least some of them be standards compliant and inject Error frames? Wouldn't
the node sending malformed messages go into bus-off state pretty quick?
Have you encountered any concrete devices that behaves this way?
So given that I'm trying to control and secure black boxes, how would I do
it? Setting aside control, in security applications you always assume
malicious input, so my software needs to be able to detect (receive)
malicious input and respond to it.
Putting a black box node on a cars can network sounds to me like
installing software you don't know what do on your servers.
With CAN my solution would be:
1. Only allow standard compliant devices on the bus. CAN is quite
robust in itself.
2. Keep malicious nodes physically away from the bus. Require strict
specifications of every node.
3. Be vary of anything that can proxy stuff onto your can bus.
4. Use a redundant interface for mission critical nodes, and only keep
mission critical nodes on the second CAN-bus. This way if a malicious node
messes up your primary can bus you can still provide some fail safes.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#77 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ABDfy1D93u-pNhSXTW8-CiBXJ5Nq0sNGks5trKiwgaJpZM4TcOxW>
.
|
@dunmatt Indeed the assumption for the HAL is to make it easy to write well-behaved applications across different hardware. If that means that special applications like pen testing are not possible then it is what it is. My question to you would be why you want to use an HAL anyway? For such uses you'd typically want to use both the most capable hardware with the lowest level access possible. A HAL will never give you that kind of access... Even the existence of a HAL implementation doesn't necessarily mean that you have to use it, you can still directly access the hardware instead. |
Actually I might have missed your point, you were saying that a node I
might want to interact with should go to bus off. I agree that it should,
but I don't have enough faith in the quality of automotive software in
general to assume that even the bus off part of the spec will be correctly
implemented. Even if I had proof that our current cars are standards
compliant I'd be wary of committing to being incompatible with non
standards compliant devices
…On Apr 22, 2018 12:28 PM, "M@" ***@***.***> wrote:
> Wouldn't the node sending malformed messages go into bus-off state
pretty quick? Have you encountered any concrete devices that behaves this
way?
Not if they're malicious they wouldn't, and I'm building one, respectively.
> Putting a black box node on a cars can network sounds to me like
installing software you don't know what do on your servers.
Quite right, it's a wonder anyone deploys Window or Linux servers without
first auditing the whole thing for themselves. By which I mean, the black
boxes come with the cars when we buy them, and for the sake of getting
stuff done we do have to trust someone sometime... Trust but verify.
Believe me, if we could get the auto makers to give us their source we'd do
it.
On Apr 22, 2018 11:58 AM, "Kjetil Kjeka" ***@***.***> wrote:
> Sure, my exact use case is this: the control and security of autonomous
> cars. There are computers from a half dozen or so different vendors on the
> bus, of which some we have executive control, some we have a little
> control, and the remainder are black boxes (literally, in one case).
> Moreover for business reasons we don't want to marry ourselves to our
> current vehicle platform, so there's a level of uncertainty even in terms
> of the devices on the network (ie I can't assume that all devices I'll ever
> care about are standards compliant, but I can assume that if one isn't I
> won't be able to fix it).
>
> In such network (with a lot of different can controllers) wouldn't at
> least some of them be standards compliant and inject Error frames? Wouldn't
> the node sending malformed messages go into bus-off state pretty quick?
> Have you encountered any concrete devices that behaves this way?
>
> So given that I'm trying to control and secure black boxes, how would I
> do it? Setting aside control, in security applications you always assume
> malicious input, so my software needs to be able to detect (receive)
> malicious input and respond to it.
>
> Putting a black box node on a cars can network sounds to me like
> installing software you don't know what do on your servers.
>
> With CAN my solution would be:
>
> 1. Only allow standard compliant devices on the bus. CAN is quite
> robust in itself.
> 2. Keep malicious nodes physically away from the bus. Require strict
> specifications of every node.
> 3. Be vary of anything that can proxy stuff onto your can bus.
> 4. Use a redundant interface for mission critical nodes, and only
> keep mission critical nodes on the second CAN-bus. This way if a malicious
> node messes up your primary can bus you can still provide some fail safes.
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <#77 (comment)>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/ABDfy1D93u-pNhSXTW8-CiBXJ5Nq0sNGks5trKiwgaJpZM4TcOxW>
> .
>
|
@daniel, I completely agree, the original question was if this would work
for my use case, and the answer is: no and that's fine. I really don't
mind scratching my own itch.
As for why I'd want a hal, it's so that if later I find the can peripheral
too limiting I can swap in a bit banged implementation without rewriting
everything
…On Apr 22, 2018 12:38 PM, "Daniel Egger" ***@***.***> wrote:
@dunmatt <https://github.com/dunmatt> Indeed the assumption for the HAL
is to make it easy to write well-behaved applications across different
hardware. If that means that special applications like pen testing are not
possible then it is what it is.
My question to you would be why you want to use an HAL anyway? For such
uses you'd typically want to use both the most capable hardware with the
lowest level access possible. A HAL will never give you that kind of
access... Even the existence of a HAL implementation doesn't necessarily
mean that you have to use it, you can still directly access the hardware
instead.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#77 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ABDfywp0krMyugG_XZQa1PKRAHh_-vAIks5trLH2gaJpZM4TcOxW>
.
|
Unfortunately nowadays I do not have much time to deal with this, but please find here a few things popped in my mind reading the proposed code: I see that filtering is not addressed here (yet?), but from my experience the RTR should be part of the ID, since frame filtering involves the RTR bit also. I may be wrong, but please consider this. I think this below will not work if you really want a non blocking implementation for more than one transmit mailbox, because it can not trace the state/progress of the transfer: You may have a function, which puts a frame in a transmit mailbox and returns a handle to the chosen mailbox, like this: Then you will have to pull that handle to competition with an other function. Furthermore this start_transmit(...) could have separate id and data parameters (as I written above) because you may reuse the same ID may times and change the data only. Wrapping these two always into a frame is just boilerplate... But again, maybe I'm wrong. Also, the Interface trait should be splitted into RxInterface and TxInterface because those are almost independent. One part of the application may need only the ownership of the receiver and an other only the transmitter... stm32f103 for example have two independent receive fifos (you can configure the filters where to put the received frames) and three independent mailboxes (but there is central a register which points to the empty or lowest priority tx mailbox). I have a working, but still work in progress CAN implementation (not implements this HAL yet) here: |
Yes filtering is core part of the can specification and will need to be added. From the CAN specification (ISO-11898-1:2003, don't have access to the 2015 review, please mail me if you have):
And the definition of identifier is:
I interpret this as a can controller is not required to filter on RTR to be compliant to specifications.
I think you're correct, I will rethink this. |
- removed weird transmit function - Allow one function to receive all kinds of can frames
Rebased onto japaric/master. I made a split between Receiver and Transmitter after @tib888 recommendation. This only makes the interface more flexible and i don't think there are many disadvantages to doing so. The problem of following the progress of the transfer is a hard one. I don't think it's a good idea to hand out Mailbox handles for two reasons:
I instead provided a method to see if there are any pending transmissions matching an ID. I think this will be sufficient in most (all) cases. Perhaps anyone can think of a case where it is not? (@tib888 ) |
I've been busy with a lot of other things so I'm afraid not. I expect to be able to use some time on this within the next couple of months, as I'm going to need a CAN interface for one of this microcontrollers myself by this time. A couple of things that has made this not straight forward to implement is the following:
I've been investigating these two things recently, and will hopefully be able to suggest some imporvements soon. |
I've recently taken up an interest in this. What issues need to be resolved to get this moving forward? I am willing to commit time to this. |
Great! First of all you should look at the traits I've proposed and see if they make sense to you or whether you think anything should be done in a different way. If you think my current proposal make sense, you should attempt
|
One thing I'll point out here that, IMO, seems a bit inefficient: Since |
First I will mention some things I think the API should attempt to avoid:
The current traits are not designed specifically to work with
I agree that using a concrete type will be more ergonomic than the Id trait from this PR. Although it's idiomatic to this HAL to use minimal traits for a lot of things, also using this approach for ID is perhaps to overdo it and we should definitively discuss the advantages of making this a concrete type instead. I'm not certain to what degree the bit format of the u32 is standardized over different hardware. If this is different, using a fixed layout u32 would only allow optimizing for one platform. This would, in turn, make the static dispatch trait approach more efficient. If we want to go with a type instead of a trait, we should at least use a newtype of #[derive(Debug, PartialEq, Clone, Copy, Hash /* etc */)]
pub struct CanId(u32);
impl From<CanId> for u32 { /* ... */ }
impl CanId {
const ID_MASK: u32 = 0x7ff;
const EXTENDED_MASK: u32 = 0x1fffffff;
const EFF_MASK: u32 = (1<<31);
pub fn try_from_id(u16) -> Result<Self, _> { /* ... */ }
pub fn try_from_extended(u32) -> Result<Self, _> { /* ... */ }
pub fn try_from_bits(u32) -> Result<Self, _> { /* ... */ }
pub fn is_extended(self) -> bool { /* ... */ }
pub fn id(self) -> u32 { /* ... */ }
} The problem with this is of course that A different alternative would be to use a bit more friendly and simpler trait. This will allow for using optimized bit layouts for all hardware, while perhaps being a bit less cumbersome to work with than the trait from the PR. Let me suggest something like the following: pub trait CanId : Into<u32> {
/// The mask of the ID when a standard frame is converted into an `u32`
const ID_MASK: u32;
/// The mask of the ID when an extended frame is converted into an `u32`
const EXTENDED_MASK: u32;
/// The mask of the extended frame flag when converted into an `u32`
const EFF_MASK: u32;
pub fn try_from_bits(u32) -> Result<Self, _>;
pub fn try_from_id(u16) -> Result<Self, _> {
/* Default implementation based on `try_from_bits` and consts */
}
pub fn try_from_extended(u32) -> Result<Self, _> {
/* Default implementation based on `try_from_bits` and consts */
}
pub fn is_extended(self) -> {
/* Default implementation based on `Into<u32>` and consts */
}
pub fn id(self) -> u32 {
/* Default implementation based on `Into<u32>` and consts */
}
} The approach for creating a new platform would be to create a To gain more insight, we should look whether bit patterns of registers used in CAN hardware is standardized. I suggest we at least compare the following relevant platforms:
|
A possible solution if the CAN ID types aren't compatible could be to have HAL implementors specify CanId <-> u32 conversion methods. EDIT: I just read again and saw you mentioned that. I'll go look at those implementations and get back to you. |
For the record, at least the ST implementation makes a signal that can be sent to an MCP2515 which translates it to the CAN differential signal. |
Oops, I misunderstood what the MCP2515/17 are. Ignore my comments about ST and the MCP. |
I've taken a look at the SAM and STM32 implementations, and so far, I'm already seeing differences in ID encoding, but they're along the lines of a mask and a bit shift from what I'm seeing. Nothing too fancy. Perhaps just add a bit shift const to the CanId trait? |
@kjetilkjeka I think an idiomatic way to solve the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've come up with a few changes. Let me know what you all think.
diff --git a/src/can.rs b/src/can.rs
index 47ed7ba..7c070fd 100644
--- a/src/can.rs
+++ b/src/can.rs
@@ -4,31 +4,44 @@ use nb;
/// A type that can either be `BaseId` or `ExtendedId`
#[cfg(feature = "unproven")]
-pub trait Id {
- /// The (11-bit) BaseId variant.
- type BaseId;
+pub enum Id {
+ Base(u32),
+ Extended(u32)
+}
+
+#[cfg(feature = "unproven")]
+#[derive(Debug)]
+pub struct IdError;
- /// The (29-bit) ExtendedId variant.
- type ExtendedId;
+#[cfg(feature = "unproven")]
+impl core::error::Error for IdError {}
- /// Returns `Some(base_id)` if this Can-ID is 11-bit.
- /// Returns `None` if this Can-ID is 29-bit.
- fn base_id(&self) -> Option<Self::BaseId>;
+#[cfg(feature = "unproven")]
+impl core::convert::TryFrom<u32> for Id {
+ type Error = IdError;
+ fn try_from(from: u32) -> Result<Self, Self::Error> {
+ if (!0x7FF | from) == 0 {
+ Id::Base(from)
+ } else if (!0x1FFFFFFF | from) == 0 {
+ Id::Extended(from)
+ } else {
+ IdError {}
+ }
+ }
+}
- /// Returns `Some(extended_id)` if this Can-ID is 29-bit.
- /// Returns `None` if this Can-ID is 11-bit.
- fn extended_id(&self) -> Option<Self::ExtendedId>;
+#[cfg(feature = "unproven")]
+pub enum FrameContents<'a> {
+ DataFrame(&'a [u8]),
+ RemoteFrame
}
/// A type that will either accept or filter a `Frame`.
/// The filtering is done solely on the `ID` of the `Frame`.
#[cfg(feature = "unproven")]
pub trait Filter {
- /// The Id type this filter works on
- type Id: Id;
-
/// Constructs a filter that only accepts `Frame`s with the provided identifier.
- fn from_id(id: Self::Id) -> Self;
+ fn from_id(id: Id) -> Self;
/// Constructs a filter that will accept any `Frame`.
fn accept_all() -> Self;
@@ -51,31 +64,12 @@ pub trait Filter {
/// A Can Frame
#[cfg(feature = "unproven")]
pub trait Frame {
- /// The Id type of this Frame
- type Id: Id;
-
- /// Returns true if this `Frame` is a remote frame
- fn is_remote_frame(&self) -> bool;
-
- /// Returns true if this `Frame` is a data frame
- fn is_data_frame(&self) -> bool;
-
- /// Returns true if this `Frame` is a extended id frame
- fn is_base_id_frame(&self) -> bool {
- self.id().base_id().is_some()
- }
-
- /// Returns true if this `Frame` is a extended id frame
- fn is_extended_id_frame(&self) -> bool {
- self.id().extended_id().is_some()
- }
-
/// Returns the Can-ID
- fn id(&self) -> Self::Id;
+ fn id(&self) -> Id;
- /// Returns `Some(Data)` if data frame.
- /// Returns `None` if remote frame.
- fn data(&self) -> Option<&[u8]>;
+ /// Returns `DataFrame(Data)` if data frame.
+ /// Returns `RemoteFrame` if remote frame.
+ fn data(&self) -> FrameContents;
}
/// A Can-FD Frame
@@ -83,35 +77,16 @@ pub trait Frame {
/// A "ordinary" Can-Frame must also be representable by this type.
#[cfg(feature = "unproven")]
pub trait FdFrame {
- /// The Id type of this Frame
- type Id: Id;
-
/// Returns true if this frame would/has be(en) transmitted as a Can-Fd frame.
/// Returns false if this frame would/has be(en) transmitted as a "ordinary" Can frame.
fn is_fd_frame(&self) -> bool;
- /// Returns true if this `Frame` is a remote frame
- fn is_remote_frame(&self) -> bool;
-
- /// Returns true if this `Frame` is a data frame
- fn is_data_frame(&self) -> bool;
-
- /// Returns true if this `Frame` is a extended id frame
- fn is_base_id_frame(&self) -> bool {
- self.id().base_id().is_some()
- }
-
- /// Returns true if this `Frame` is a extended id frame
- fn is_extended_id_frame(&self) -> bool {
- self.id().extended_id().is_some()
- }
-
/// Returns the Can-ID
- fn id(&self) -> Self::Id;
+ fn id(&self) -> Id;
- /// Returns `Some(Data)` if data frame.
- /// Returns `None` if remote frame.
- fn data(&self) -> Option<&[u8]>;
+ /// Returns `DataFrame(Data)` if data frame.
+ /// Returns `RemoteFrame` if remote frame.
+ fn data(&self) -> FrameContents;
}
@@ -120,17 +95,14 @@ pub trait FdFrame {
/// May be a `Transmitter`, `Receiver` or both.
#[cfg(feature = "unproven")]
pub trait Interface {
- /// The Id type that works with this `Interface`
- type Id: Id;
-
/// The Can Frame this Interface operates on
- type Frame: Frame<Id = Self::Id>;
+ type Frame: Frame;
/// The Interface Error type
type Error;
/// The Filter type used in this `Interface`
- type Filter: Filter<Id = Self::Id>;
+ type Filter: Filter;
}
/// A CAN interface that is able to transmit frames.
@jdemilledt I don't think your changes related to ID will work. When converting from By making the ID an enum instead of a trait, we're also missing the opportunity of abstracting over the native bit layout of the CAN controller. This will be slightly less efficient in both memory and CPU, although this might be fine I think the trait I previously proposed might solve this in a good way. I also don't like the removal of the small default implemented convenience functions. I think they are quite valuable in practice. And they are implemented by default, meaning no overhead for the implementer. Returning the |
Those are some good points. I don't necessarily agree about making a trait instead of a fully implemented struct since CAN IDs are the same across platforms except the underlying implementation's bit requirements, which could be implemented in the |
I'm talking about the functions like |
I see. I removed them because I thought they were redundant, but if you want to keep those, that's fine. |
@kjetilkjeka I think you may update your pr to current version, some changes is good for me, some need clarification. |
Any updates on bringing can bus to embedded-hal? |
|
||
/// A type that can either be `BaseId` or `ExtendedId` | ||
#[cfg(feature = "unproven")] | ||
pub trait Id { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really get the value of not specifying concrete types (u16 and u32) for base and extended id.
How about adding a
fn id(&self) -> u32;
possibly even with a blanket implementation based on the base_id
and extended_id
fns, but I think right now there is no way to guarantee that not both are not None
so this might need to return Result<u32, Error>
which is kind of ugly.
314: Controller Area Network (CAN) Take 4 r=eldruin a=timokroeger Updated to the latest HAL changes: * Removed `try_` prefix * Moved non-blocking implementation to `nb` module * Removed default `blocking` implementaions ## Usage Example [stm32-fwupdate](https://github.com/timokroeger/pcan-basic-rs/blob/eh-take-4/pcan-basic/examples/stm32-fwupdate.rs) ## Implementations Updated for this PR: * [pcan-basic](https://github.com/timokroeger/pcan-basic-rs/blob/eh-take-4/pcan-basic/src/lib.rs) on top of an existing software API * [bxcan](https://github.com/timokroeger/bxcan/blob/eh-take-4/src/lib.rs#L460) Based on the very similar predecessor traits `embedded-can` v0.3 ([diff v0.3 -> this PR](https://github.com/timokroeger/embedded-can/compare/eh-take-4)) * [candev](https://github.com/reneherrero/candev) implementing the traits for linux SocketCAN * [socketcan-isotc](https://github.com/marcelbuesing/socketcan-isotp) ## Previous Discussion * #212 * #77 * #21 * #53 Co-authored-by: Timo Kröger <timokroeger93@gmail.com>
Solved in #314 |
77: Adapt to embedded-hal-1.0.0-alpha.7 r=posborne a=eldruin For the iterator methods I just collected the results. I have not looked into really using an iterating interface but merging this would be a first step and if there is interest we can look into that later on. Fixes rust-embedded#76 Co-authored-by: Diego Barrios Romero <eldruin@gmail.com>
#21 and #53 has been quiet for a while, the natural step was to try again based on the feedback given in that discussion. I've not implemented the Traits yet, as i wanted to get some quick feedback before i bothered.
@japaric @therealprof is this something close to what you're comfortable merging?
@dunmatt @tib888 t can this work for you?
The thing i dislike the most with the current state of this interface is that there is no function to return both CanFd and Can frames. This might be a source of a few different bugs.