Skip to main content

canopen_rs/
types.rs

1//! Foundational CANopen types shared across the stack.
2
3use core::fmt;
4
5/// Result alias for fallible `canopen-rs` operations.
6pub type Result<T> = core::result::Result<T, Error>;
7
8/// Errors surfaced by the `canopen-rs` core.
9#[derive(Debug, Clone, PartialEq, Eq)]
10#[non_exhaustive]
11pub enum Error {
12    /// A frame or buffer was too short or too long for the operation.
13    BadLength,
14    /// A value did not fit the target CANopen data type.
15    Overflow,
16    /// No object exists at the requested (index, subindex).
17    ObjectNotFound,
18    /// A node id outside the valid `1..=127` range was supplied.
19    InvalidNodeId,
20    /// Attempt to write an object that is not writable.
21    ReadOnly,
22    /// Attempt to read an object that is not readable.
23    WriteOnly,
24    /// A value's data type did not match the object's data type.
25    TypeMismatch,
26    /// The object dictionary's fixed capacity is exhausted.
27    DictionaryFull,
28    /// The value cannot be carried by the chosen transfer (e.g. a value
29    /// larger than four bytes attempted over expedited SDO).
30    UnsupportedTransfer,
31    /// A received frame's command byte was not the expected response.
32    UnexpectedCommand,
33    /// A heartbeat frame carried an unrecognised NMT state value.
34    UnknownState,
35    /// A PDO's mapped objects total more than eight bytes (64 bits).
36    PdoTooLong,
37    /// A PDO mapping's fixed capacity is exhausted.
38    MappingFull,
39    /// A SYNC counter overflow value outside `0` or `2..=240`.
40    InvalidSyncCounter,
41    /// An SDO segment's toggle bit did not alternate as required.
42    ToggleMismatch,
43}
44
45impl fmt::Display for Error {
46    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47        let msg = match self {
48            Error::BadLength => "frame or buffer length invalid for operation",
49            Error::Overflow => "value does not fit the target CANopen data type",
50            Error::ObjectNotFound => "no object at the requested index/subindex",
51            Error::InvalidNodeId => "node id out of range (must be 1..=127)",
52            Error::ReadOnly => "object is not writable",
53            Error::WriteOnly => "object is not readable",
54            Error::TypeMismatch => "value data type does not match the object",
55            Error::DictionaryFull => "object dictionary capacity exhausted",
56            Error::UnsupportedTransfer => "value cannot be carried by this transfer type",
57            Error::UnexpectedCommand => "unexpected SDO command byte in response",
58            Error::UnknownState => "unrecognised NMT state in heartbeat frame",
59            Error::PdoTooLong => "mapped PDO objects exceed eight bytes",
60            Error::MappingFull => "PDO mapping capacity exhausted",
61            Error::InvalidSyncCounter => "SYNC counter overflow must be 0 or 2..=240",
62            Error::ToggleMismatch => "SDO segment toggle bit did not alternate",
63        };
64        f.write_str(msg)
65    }
66}
67
68#[cfg(feature = "std")]
69extern crate std;
70#[cfg(feature = "std")]
71impl std::error::Error for Error {}
72
73/// A CANopen node identifier.
74///
75/// Valid *device* node ids are `1..=127`. The value `0` is reserved for
76/// broadcast in NMT and LSS; use [`NodeId::BROADCAST`] for it and
77/// [`NodeId::new`] for a checked device id.
78#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
79pub struct NodeId(u8);
80
81impl NodeId {
82    /// The broadcast node id (`0`), used by NMT and LSS.
83    pub const BROADCAST: NodeId = NodeId(0);
84
85    /// Create a checked device node id in `1..=127`.
86    ///
87    /// Returns [`Error::InvalidNodeId`] for `0` or any value above `127`.
88    pub fn new(id: u8) -> Result<Self> {
89        if (1..=127).contains(&id) {
90            Ok(NodeId(id))
91        } else {
92            Err(Error::InvalidNodeId)
93        }
94    }
95
96    /// The raw node id byte.
97    pub const fn raw(self) -> u8 {
98        self.0
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn node_id_accepts_valid_range() {
108        assert_eq!(NodeId::new(1).unwrap().raw(), 1);
109        assert_eq!(NodeId::new(127).unwrap().raw(), 127);
110    }
111
112    #[test]
113    fn node_id_rejects_out_of_range() {
114        assert_eq!(NodeId::new(0), Err(Error::InvalidNodeId));
115        assert_eq!(NodeId::new(128), Err(Error::InvalidNodeId));
116    }
117
118    #[test]
119    fn broadcast_is_zero() {
120        assert_eq!(NodeId::BROADCAST.raw(), 0);
121    }
122}