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    /// A block-transfer CRC did not match the received data.
44    CrcMismatch,
45}
46
47impl fmt::Display for Error {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        let msg = match self {
50            Error::BadLength => "frame or buffer length invalid for operation",
51            Error::Overflow => "value does not fit the target CANopen data type",
52            Error::ObjectNotFound => "no object at the requested index/subindex",
53            Error::InvalidNodeId => "node id out of range (must be 1..=127)",
54            Error::ReadOnly => "object is not writable",
55            Error::WriteOnly => "object is not readable",
56            Error::TypeMismatch => "value data type does not match the object",
57            Error::DictionaryFull => "object dictionary capacity exhausted",
58            Error::UnsupportedTransfer => "value cannot be carried by this transfer type",
59            Error::UnexpectedCommand => "unexpected SDO command byte in response",
60            Error::UnknownState => "unrecognised NMT state in heartbeat frame",
61            Error::PdoTooLong => "mapped PDO objects exceed eight bytes",
62            Error::MappingFull => "PDO mapping capacity exhausted",
63            Error::InvalidSyncCounter => "SYNC counter overflow must be 0 or 2..=240",
64            Error::ToggleMismatch => "SDO segment toggle bit did not alternate",
65            Error::CrcMismatch => "block-transfer CRC did not match the data",
66        };
67        f.write_str(msg)
68    }
69}
70
71#[cfg(feature = "std")]
72extern crate std;
73#[cfg(feature = "std")]
74impl std::error::Error for Error {}
75
76/// A CANopen node identifier.
77///
78/// Valid *device* node ids are `1..=127`. The value `0` is reserved for
79/// broadcast in NMT and LSS; use [`NodeId::BROADCAST`] for it and
80/// [`NodeId::new`] for a checked device id.
81#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
82pub struct NodeId(u8);
83
84impl NodeId {
85    /// The broadcast node id (`0`), used by NMT and LSS.
86    pub const BROADCAST: NodeId = NodeId(0);
87
88    /// Create a checked device node id in `1..=127`.
89    ///
90    /// Returns [`Error::InvalidNodeId`] for `0` or any value above `127`.
91    pub fn new(id: u8) -> Result<Self> {
92        if (1..=127).contains(&id) {
93            Ok(NodeId(id))
94        } else {
95            Err(Error::InvalidNodeId)
96        }
97    }
98
99    /// The raw node id byte.
100    pub const fn raw(self) -> u8 {
101        self.0
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn node_id_accepts_valid_range() {
111        assert_eq!(NodeId::new(1).unwrap().raw(), 1);
112        assert_eq!(NodeId::new(127).unwrap().raw(), 127);
113    }
114
115    #[test]
116    fn node_id_rejects_out_of_range() {
117        assert_eq!(NodeId::new(0), Err(Error::InvalidNodeId));
118        assert_eq!(NodeId::new(128), Err(Error::InvalidNodeId));
119    }
120
121    #[test]
122    fn broadcast_is_zero() {
123        assert_eq!(NodeId::BROADCAST.raw(), 0);
124    }
125}