zencan_common/
messages.rs

1//! Message definitions
2
3use snafu::Snafu;
4
5use crate::{
6    lss::{LssRequest, LssResponse},
7    sdo::{SdoRequest, SdoResponse},
8};
9
10/// Yet another CanId enum
11///
12/// TODO: Consider if this should use the CanId from embedded_can?
13#[derive(Copy, Clone, Debug, PartialEq, Eq)]
14pub enum CanId {
15    /// An extended 28-bit identifier
16    Extended(u32),
17    /// A std 11-bit identifier
18    Std(u16),
19}
20
21impl core::fmt::Display for CanId {
22    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
23        match self {
24            CanId::Extended(id) => write!(f, "Extended(0x{id:x})"),
25            CanId::Std(id) => write!(f, "Std(0x{id:x})"),
26        }
27    }
28}
29
30impl CanId {
31    /// Create a new extended ID
32    pub const fn extended(id: u32) -> CanId {
33        CanId::Extended(id)
34    }
35
36    /// Create a new standard ID
37    pub const fn std(id: u16) -> CanId {
38        CanId::Std(id)
39    }
40
41    /// Get the raw ID as a u32
42    pub const fn raw(&self) -> u32 {
43        match self {
44            CanId::Extended(id) => *id,
45            CanId::Std(id) => *id as u32,
46        }
47    }
48
49    /// Returns true if this ID is an extended ID
50    pub const fn is_extended(&self) -> bool {
51        match self {
52            CanId::Extended(_) => true,
53            CanId::Std(_) => false,
54        }
55    }
56}
57
58const MAX_DATA_LENGTH: usize = 8;
59
60/// A struct to contain a CanMessage
61#[derive(Clone, Copy, Debug, PartialEq, Eq)]
62pub struct CanMessage {
63    /// The data payload of the message
64    ///
65    /// Note, some bytes may be unused. Check dlc.
66    pub data: [u8; MAX_DATA_LENGTH],
67    /// The length of the data payload
68    pub dlc: u8,
69    /// Indicates this message is a remote transmission request
70    pub rtr: bool,
71    /// The id of this message
72    pub id: CanId,
73}
74
75impl Default for CanMessage {
76    fn default() -> Self {
77        Self {
78            data: [0; MAX_DATA_LENGTH],
79            dlc: 0,
80            id: CanId::Std(0),
81            rtr: false,
82        }
83    }
84}
85
86impl CanMessage {
87    /// Create a new CAN message
88    pub fn new(id: CanId, data: &[u8]) -> Self {
89        let dlc = data.len() as u8;
90        if dlc > MAX_DATA_LENGTH as u8 {
91            panic!(
92                "Data length exceeds maximum size of {} bytes",
93                MAX_DATA_LENGTH
94            );
95        }
96        let mut buf = [0u8; MAX_DATA_LENGTH];
97        buf[0..dlc as usize].copy_from_slice(data);
98        let rtr = false;
99
100        Self {
101            id,
102            dlc,
103            data: buf,
104            rtr,
105        }
106    }
107
108    /// Create a new RTR message
109    ///
110    /// RTR messages have no data payload
111    pub fn new_rtr(id: CanId) -> Self {
112        Self {
113            id,
114            rtr: true,
115            ..Default::default()
116        }
117    }
118
119    /// Get the id of the message
120    pub fn id(&self) -> CanId {
121        self.id
122    }
123
124    /// Get a slice containing the data payload
125    pub fn data(&self) -> &[u8] {
126        &self.data[0..self.dlc as usize]
127    }
128
129    /// Returns true if this message is a remote transmission request
130    pub fn is_rtr(&self) -> bool {
131        self.rtr
132    }
133}
134
135/// The error codes which can be delivered in a CAN frame
136///
137/// These are set by a receiver when it detects an error in a received frame, and received globally
138/// by all nodes on the bus
139#[derive(Clone, Copy, Debug, Snafu)]
140#[repr(u8)]
141pub enum CanError {
142    /// The transmitter detected a different value on the bus than the value is was transmitting at
143    /// a point in the message after the arbitration process (sending of the ID)
144    Bit = 1,
145    /// A receiver detected a sequence of 6 bits of the same level, indicating a failure in bit
146    /// stuffing
147    Stuff = 2,
148    /// A reveiver detected a malformed can frame (e.g. the SOF bit was not dominant, etc)
149    Form = 3,
150    /// The transmitter did not detect an ACK from any receivers
151    Ack = 4,
152    /// A receiver detected a mismatch in CRC value for the message
153    Crc = 5,
154    /// There are other bit patterns possible for the error field, but they have no defined meaning
155    Other,
156}
157
158impl CanError {
159    /// Create a CanError from the on-bus error code
160    pub fn from_raw(raw: u8) -> Self {
161        match raw {
162            1 => Self::Bit,
163            2 => Self::Stuff,
164            3 => Self::Form,
165            4 => Self::Ack,
166            5 => Self::Crc,
167            _ => Self::Other,
168        }
169    }
170}
171
172/// The NMT state transition command specifier
173#[derive(Copy, Clone, Debug, PartialEq)]
174#[cfg_attr(feature = "defmt", derive(defmt::Format))]
175#[repr(u8)]
176pub enum NmtCommandSpecifier {
177    /// Indicates device should transition to the Operation state
178    Start = 1,
179    /// Indicates device should transition to the Stopped state
180    Stop = 2,
181    /// Indicates device should transition to the PreOperational state
182    EnterPreOp = 128,
183    /// Indicates device should perform an application reset
184    ResetApp = 129,
185    /// Indicates device should perform a communications reset
186    ResetComm = 130,
187}
188
189impl NmtCommandSpecifier {
190    /// Create an NmtCommandCmd from the byte value transmitted in the message
191    pub fn from_byte(b: u8) -> Result<Self, MessageError> {
192        match b {
193            1 => Ok(Self::Start),
194            2 => Ok(Self::Stop),
195            128 => Ok(Self::EnterPreOp),
196            129 => Ok(Self::ResetApp),
197            130 => Ok(Self::ResetComm),
198            _ => Err(MessageError::InvalidField),
199        }
200    }
201}
202
203/// The COB ID used for sending NMT commands
204pub const NMT_CMD_ID: CanId = CanId::Std(0);
205/// The COB ID used for sending SYNC commands
206pub const SYNC_ID: CanId = CanId::Std(0x80);
207/// The COB ID used for LSS slave responses
208pub const LSS_RESP_ID: CanId = CanId::Std(0x7E4);
209/// The COB ID used for LSS master requests
210pub const LSS_REQ_ID: CanId = CanId::Std(0x7E5);
211/// The COB ID used for heartbeat messages
212pub const HEARTBEAT_ID: u16 = 0x700;
213/// The default base ID for sending SDO requests (server node ID is added)
214pub const SDO_REQ_BASE: u16 = 0x600;
215/// The default base ID for sending SDO responses (server node ID is added)
216pub const SDO_RESP_BASE: u16 = 0x580;
217
218/// An NmtCommand message
219#[derive(Clone, Copy, Debug)]
220#[cfg_attr(feature = "defmt", derive(defmt::Format))]
221pub struct NmtCommand {
222    /// Specifies the type of command
223    pub cs: NmtCommandSpecifier,
224    /// Indicates the node it applies to. A node of 0 indicates a broadcast command to all nodes.
225    pub node: u8,
226}
227
228impl TryFrom<CanMessage> for NmtCommand {
229    type Error = MessageError;
230
231    fn try_from(msg: CanMessage) -> Result<Self, Self::Error> {
232        let payload = msg.data();
233        if msg.id() != NMT_CMD_ID {
234            Err(MessageError::UnexpectedId {
235                cob_id: msg.id(),
236                expected: NMT_CMD_ID,
237            })
238        } else if payload.len() >= 2 {
239            let cmd = NmtCommandSpecifier::from_byte(payload[0])?;
240            let node = payload[1];
241            Ok(NmtCommand { cs: cmd, node })
242        } else {
243            Err(MessageError::MessageTooShort)
244        }
245    }
246}
247
248impl From<NmtCommand> for CanMessage {
249    fn from(cmd: NmtCommand) -> Self {
250        let mut msg = CanMessage {
251            id: NMT_CMD_ID,
252            dlc: 2,
253            ..Default::default()
254        };
255        msg.data[0] = cmd.cs as u8;
256        msg.data[1] = cmd.node;
257        msg
258    }
259}
260
261/// Possible NMT states for a node
262#[derive(Copy, Clone, Debug, PartialEq)]
263#[cfg_attr(feature = "defmt", derive(defmt::Format))]
264#[repr(u8)]
265pub enum NmtState {
266    /// Bootup
267    ///
268    /// A node never remains in this state, as all nodes should transition automatically into PreOperational
269    Bootup = 0,
270    /// Node has been stopped
271    Stopped = 4,
272    /// Normal operational state
273    Operational = 5,
274    /// Node is awaiting command to enter operation
275    PreOperational = 127,
276}
277
278impl core::fmt::Display for NmtState {
279    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
280        match self {
281            NmtState::Bootup => write!(f, "Bootup"),
282            NmtState::Stopped => write!(f, "Stopped"),
283            NmtState::Operational => write!(f, "Operational"),
284            NmtState::PreOperational => write!(f, "PreOperational"),
285        }
286    }
287}
288
289#[derive(Clone, Copy, Debug)]
290/// An error for [`NmtState::try_from()`]
291pub struct InvalidNmtStateError(u8);
292
293impl TryFrom<u8> for NmtState {
294    type Error = InvalidNmtStateError;
295
296    /// Attempt to convert a u8 to an NmtState enum
297    ///
298    /// Fails with BadNmtStateError if value is not a valid state
299    fn try_from(value: u8) -> Result<Self, Self::Error> {
300        use NmtState::*;
301        match value {
302            x if x == Bootup as u8 => Ok(Bootup),
303            x if x == Stopped as u8 => Ok(Stopped),
304            x if x == Operational as u8 => Ok(Operational),
305            x if x == PreOperational as u8 => Ok(PreOperational),
306            _ => Err(InvalidNmtStateError(value)),
307        }
308    }
309}
310
311/// A Heartbeat message
312#[derive(Clone, Copy, Debug)]
313#[cfg_attr(feature = "defmt", derive(defmt::Format))]
314pub struct Heartbeat {
315    /// The ID of the node transmitting the heartbeat
316    pub node: u8,
317    /// A toggle value which is flipped on every heartbeat
318    pub toggle: bool,
319    /// The current NMT state of the node
320    pub state: NmtState,
321}
322
323impl From<Heartbeat> for CanMessage {
324    fn from(value: Heartbeat) -> Self {
325        let mut msg = CanMessage {
326            id: CanId::Std(HEARTBEAT_ID | value.node as u16),
327            dlc: 1,
328            ..Default::default()
329        };
330        msg.data[0] = value.state as u8;
331        if value.toggle {
332            msg.data[0] |= 1 << 7;
333        }
334        msg
335    }
336}
337/// Represents a SYNC object/message
338///
339/// A single CAN node can serve as the SYNC provider, sending a periodic sync object to all other
340/// nodes. The one byte count value starts at 1, and increments. On overflow, it should be reset to
341/// 1.
342#[derive(Clone, Copy, Debug)]
343#[cfg_attr(feature = "defmt", derive(defmt::Format))]
344pub struct SyncObject {
345    count: u8,
346}
347
348impl SyncObject {
349    /// Create a new SyncObjectd
350    pub fn new(count: u8) -> Self {
351        Self { count }
352    }
353}
354
355impl Default for SyncObject {
356    fn default() -> Self {
357        Self { count: 1 }
358    }
359}
360
361impl From<SyncObject> for CanMessage {
362    fn from(value: SyncObject) -> Self {
363        CanMessage::new(SYNC_ID, &[value.count])
364    }
365}
366
367impl From<CanMessage> for SyncObject {
368    fn from(msg: CanMessage) -> Self {
369        if msg.id() == SYNC_ID {
370            let count = msg.data()[0];
371            Self { count }
372        } else {
373            panic!("Invalid message ID for SyncObject");
374        }
375    }
376}
377
378impl TryFrom<CanMessage> for ZencanMessage {
379    type Error = MessageError;
380
381    fn try_from(msg: CanMessage) -> Result<Self, Self::Error> {
382        let cob_id = msg.id();
383        if cob_id == NMT_CMD_ID {
384            Ok(ZencanMessage::NmtCommand(msg.try_into()?))
385        } else if cob_id.raw() & !0x7f == HEARTBEAT_ID as u32 {
386            let node = (cob_id.raw() & 0x7f) as u8;
387            let toggle = (msg.data[0] & (1 << 7)) != 0;
388            let state: NmtState = (msg.data[0] & 0x7f)
389                .try_into()
390                .map_err(|e: InvalidNmtStateError| MessageError::InvalidNmtState { value: e.0 })?;
391            Ok(ZencanMessage::Heartbeat(Heartbeat {
392                node,
393                toggle,
394                state,
395            }))
396        } else if cob_id.raw() & 0xff80 == 0x580 {
397            // SDO response
398            let resp: SdoResponse = msg
399                .try_into()
400                .map_err(|_| MessageError::MalformedMsg { cob_id })?;
401            Ok(ZencanMessage::SdoResponse(resp))
402        } else if cob_id.raw() >= 0x580 && cob_id.raw() <= 0x580 + 256 {
403            // SDO request
404            let req: SdoRequest = msg
405                .data()
406                .try_into()
407                .map_err(|_| MessageError::MalformedMsg { cob_id })?;
408            Ok(ZencanMessage::SdoRequest(req))
409        } else if cob_id == SYNC_ID {
410            Ok(ZencanMessage::Sync(msg.into()))
411        } else if cob_id == LSS_REQ_ID {
412            let req: LssRequest = msg
413                .data()
414                .try_into()
415                .map_err(|_| MessageError::MalformedMsg { cob_id })?;
416            Ok(ZencanMessage::LssRequest(req))
417        } else if cob_id == LSS_RESP_ID {
418            let resp: LssResponse = msg
419                .data()
420                .try_into()
421                .map_err(|_| MessageError::MalformedMsg { cob_id })?;
422            Ok(ZencanMessage::LssResponse(resp))
423        } else {
424            Err(MessageError::UnrecognizedId { cob_id })
425        }
426    }
427}
428
429/// An enum representing all of the standard messages
430#[derive(Clone, Copy, Debug)]
431#[cfg_attr(feature = "defmt", derive(defmt::Format))]
432#[allow(missing_docs)]
433pub enum ZencanMessage {
434    NmtCommand(NmtCommand),
435    Sync(SyncObject),
436    Heartbeat(Heartbeat),
437    SdoRequest(SdoRequest),
438    SdoResponse(SdoResponse),
439    LssRequest(LssRequest),
440    LssResponse(LssResponse),
441}
442
443/// An error for problems converting CanMessages to zencan types
444#[derive(Debug, Clone, Copy, PartialEq, Snafu)]
445pub enum MessageError {
446    /// Not enough bytes were present in the message
447    MessageTooShort,
448    /// The message was malformed in some way
449    MalformedMsg {
450        /// The COB ID of the malformed message
451        cob_id: CanId,
452    },
453    /// The message ID was not the expected value
454    #[snafu(display("Unexpected message ID found: {cob_id:?}, expected: {expected:?}"))]
455    UnexpectedId {
456        /// Received ID
457        cob_id: CanId,
458        /// Expected ID
459        expected: CanId,
460    },
461    /// A field in the message contained an unallowed value for that field
462    InvalidField,
463    /// The COB ID of the message does not correspond to an expected ZencanMessag
464    ///
465    /// This isn't particular surprising, many messages on the bus will not (e.g. PDOs)
466    UnrecognizedId {
467        /// The unrecognized COB
468        cob_id: CanId,
469    },
470    /// The NMT state integer in the message is not a valid NMT state
471    InvalidNmtState {
472        /// The invalid byte
473        value: u8,
474    },
475    /// An invalid LSS command specifier was found in the message
476    #[snafu(display("Unexpected LSS command: {value}"))]
477    UnexpectedLssCommand {
478        /// The invalid byte
479        value: u8,
480    },
481}