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    count: Option<u8>,
297}
298
299impl SyncObject {
300    /// Create a new SyncObjectd
301    pub fn new(count: Option<u8>) -> Self {
302        Self { count }
303    }
304}
305
306impl From<SyncObject> for CanMessage {
307    fn from(value: SyncObject) -> Self {
308        if let Some(count) = value.count {
309            CanMessage::new(SYNC_ID, &[count])
310        } else {
311            CanMessage::new(SYNC_ID, &[])
312        }
313    }
314}
315
316impl From<CanMessage> for SyncObject {
317    fn from(msg: CanMessage) -> Self {
318        if msg.id() == SYNC_ID {
319            let count = if msg.data().is_empty() {
320                None
321            } else {
322                Some(msg.data()[0])
323            };
324            Self { count }
325        } else {
326            panic!("Invalid message ID for SyncObject");
327        }
328    }
329}
330
331impl TryFrom<CanMessage> for ZencanMessage {
332    type Error = MessageError;
333
334    fn try_from(msg: CanMessage) -> Result<Self, Self::Error> {
335        let cob_id = msg.id();
336        if cob_id == NMT_CMD_ID {
337            Ok(ZencanMessage::NmtCommand(msg.try_into()?))
338        } else if cob_id.raw() & !0x7f == HEARTBEAT_ID as u32 {
339            let node = (cob_id.raw() & 0x7f) as u8;
340            let toggle = (msg.data[0] & (1 << 7)) != 0;
341            let state: NmtState = (msg.data[0] & 0x7f)
342                .try_into()
343                .map_err(|e: InvalidNmtStateError| MessageError::InvalidNmtState { value: e.0 })?;
344            Ok(ZencanMessage::Heartbeat(Heartbeat {
345                node,
346                toggle,
347                state,
348            }))
349        } else if cob_id.raw() & 0xff80 == 0x580 {
350            // SDO response
351            let resp: SdoResponse = msg
352                .try_into()
353                .map_err(|_| MessageError::MalformedMsg { cob_id })?;
354            Ok(ZencanMessage::SdoResponse(resp))
355        } else if cob_id.raw() >= 0x580 && cob_id.raw() <= 0x580 + 256 {
356            // SDO request
357            let req: SdoRequest = msg
358                .data()
359                .try_into()
360                .map_err(|_| MessageError::MalformedMsg { cob_id })?;
361            Ok(ZencanMessage::SdoRequest(req))
362        } else if cob_id == SYNC_ID {
363            Ok(ZencanMessage::Sync(msg.into()))
364        } else if cob_id == LSS_REQ_ID {
365            let req: LssRequest = msg
366                .data()
367                .try_into()
368                .map_err(|_| MessageError::MalformedMsg { cob_id })?;
369            Ok(ZencanMessage::LssRequest(req))
370        } else if cob_id == LSS_RESP_ID {
371            let resp: LssResponse = msg
372                .data()
373                .try_into()
374                .map_err(|_| MessageError::MalformedMsg { cob_id })?;
375            Ok(ZencanMessage::LssResponse(resp))
376        } else {
377            Err(MessageError::UnrecognizedId { cob_id })
378        }
379    }
380}
381
382/// An enum representing all of the standard messages
383#[derive(Clone, Copy, Debug)]
384#[cfg_attr(feature = "defmt", derive(defmt::Format))]
385#[allow(missing_docs)]
386pub enum ZencanMessage {
387    NmtCommand(NmtCommand),
388    Sync(SyncObject),
389    Heartbeat(Heartbeat),
390    SdoRequest(SdoRequest),
391    SdoResponse(SdoResponse),
392    LssRequest(LssRequest),
393    LssResponse(LssResponse),
394}
395
396/// An error for problems converting CanMessages to zencan types
397#[derive(Debug, Clone, Copy, PartialEq, Snafu)]
398pub enum MessageError {
399    /// Not enough bytes were present in the message
400    MessageTooShort,
401    /// The message was malformed in some way
402    MalformedMsg {
403        /// The COB ID of the malformed message
404        cob_id: CanId,
405    },
406    /// The message ID was not the expected value
407    #[snafu(display("Unexpected message ID found: {cob_id:?}, expected: {expected:?}"))]
408    UnexpectedId {
409        /// Received ID
410        cob_id: CanId,
411        /// Expected ID
412        expected: CanId,
413    },
414    /// A field in the message contained an unallowed value for that field
415    InvalidField,
416    /// The COB ID of the message does not correspond to an expected ZencanMessag
417    ///
418    /// This isn't particular surprising, many messages on the bus will not (e.g. PDOs)
419    UnrecognizedId {
420        /// The unrecognized COB
421        cob_id: CanId,
422    },
423    /// The NMT state integer in the message is not a valid NMT state
424    InvalidNmtState {
425        /// The invalid byte
426        value: u8,
427    },
428    /// An invalid LSS command specifier was found in the message
429    #[snafu(display("Unexpected LSS command: {value}"))]
430    UnexpectedLssCommand {
431        /// The invalid byte
432        value: u8,
433    },
434}