Skip to main content

zencan_common/
messages.rs

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