Skip to main content

canopen_rs/
emcy.rs

1//! Emergency object (EMCY) — device error signalling (CiA 301 §7.2.7).
2//!
3//! A node transmits an EMCY frame when an internal error appears or clears. The
4//! default COB-ID is `0x080 + node` ([`emcy_cob_id`]) — the same base as SYNC,
5//! but SYNC is broadcast at exactly `0x080` while EMCY node ids are `1..=127`,
6//! so they never collide.
7//!
8//! An EMCY frame is always eight bytes:
9//!
10//! | bytes | field |
11//! |-------|-------|
12//! | 0..2  | emergency error code (little-endian) |
13//! | 2     | error register (a copy of object `0x1001`) |
14//! | 3..8  | manufacturer-specific error field |
15//!
16//! A frame with error code `0x0000` signals *error reset / no error*.
17//!
18//! ```
19//! use canopen_rs::emcy::{error_code, EmergencyMessage, ErrorRegister};
20//!
21//! // An overvoltage emergency: voltage error code, with the generic and
22//! // voltage bits set in the error register.
23//! let msg = EmergencyMessage::new(
24//!     error_code::VOLTAGE,
25//!     ErrorRegister(ErrorRegister::GENERIC | ErrorRegister::VOLTAGE),
26//!     [0; 5],
27//! );
28//! let frame = msg.encode();
29//! assert_eq!(&frame[..3], &[0x00, 0x30, 0x05]); // code 0x3000 (LE), register 0x05
30//! assert_eq!(EmergencyMessage::decode(&frame).unwrap(), msg);
31//! ```
32
33use crate::types::NodeId;
34use crate::{Error, Result};
35
36/// COB-ID base for the EMCY channel: `0x080 + node`.
37pub const EMCY_COB_BASE: u16 = 0x080;
38
39/// The COB-ID of a node's EMCY producer channel.
40pub const fn emcy_cob_id(node: NodeId) -> u16 {
41    EMCY_COB_BASE + node.raw() as u16
42}
43
44/// The 8-byte EMCY payload carried in a CAN frame's data field.
45pub type EmcyPayload = [u8; 8];
46
47/// Well-known emergency error-code *classes* — the high byte of the 16-bit
48/// code (CiA 301 §7.2.7.1, Table 22). The low byte refines the class.
49pub mod error_code {
50    /// No error / error reset.
51    pub const ERROR_RESET: u16 = 0x0000;
52    /// Generic error.
53    pub const GENERIC: u16 = 0x1000;
54    /// Current.
55    pub const CURRENT: u16 = 0x2000;
56    /// Voltage.
57    pub const VOLTAGE: u16 = 0x3000;
58    /// Temperature.
59    pub const TEMPERATURE: u16 = 0x4000;
60    /// Device hardware.
61    pub const DEVICE_HARDWARE: u16 = 0x5000;
62    /// Device software.
63    pub const DEVICE_SOFTWARE: u16 = 0x6000;
64    /// Additional modules.
65    pub const ADDITIONAL_MODULES: u16 = 0x7000;
66    /// Monitoring.
67    pub const MONITORING: u16 = 0x8000;
68    /// Communication (subset of monitoring).
69    pub const COMMUNICATION: u16 = 0x8100;
70    /// External error.
71    pub const EXTERNAL: u16 = 0x9000;
72    /// Additional functions.
73    pub const ADDITIONAL_FUNCTIONS: u16 = 0xF000;
74    /// Device-specific.
75    pub const DEVICE_SPECIFIC: u16 = 0xFF00;
76}
77
78/// The error register (object `0x1001`): a bitfield summarising which error
79/// classes are currently active on the node (CiA 301 §7.5.2.2).
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
81pub struct ErrorRegister(pub u8);
82
83impl ErrorRegister {
84    /// Bit 0 — a generic error is present. Set whenever any other bit is.
85    pub const GENERIC: u8 = 0x01;
86    /// Bit 1 — current.
87    pub const CURRENT: u8 = 0x02;
88    /// Bit 2 — voltage.
89    pub const VOLTAGE: u8 = 0x04;
90    /// Bit 3 — temperature.
91    pub const TEMPERATURE: u8 = 0x08;
92    /// Bit 4 — communication (overrun, error state).
93    pub const COMMUNICATION: u8 = 0x10;
94    /// Bit 5 — device-profile-specific.
95    pub const DEVICE_PROFILE: u8 = 0x20;
96    /// Bit 7 — manufacturer-specific. (Bit 6 is reserved, always 0.)
97    pub const MANUFACTURER: u8 = 0x80;
98
99    /// An error register with no bits set (no active error).
100    pub const NONE: ErrorRegister = ErrorRegister(0);
101
102    /// Whether all of `bits` are set.
103    pub const fn contains(self, bits: u8) -> bool {
104        self.0 & bits == bits
105    }
106
107    /// Whether any error is signalled (any bit set).
108    pub const fn has_error(self) -> bool {
109        self.0 != 0
110    }
111}
112
113/// An emergency message.
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub struct EmergencyMessage {
116    /// The 16-bit emergency error code (see [`error_code`]).
117    pub error_code: u16,
118    /// The error register (object `0x1001`) at the time of the event.
119    pub error_register: ErrorRegister,
120    /// Five bytes of manufacturer-specific error information.
121    pub vendor_specific: [u8; 5],
122}
123
124impl EmergencyMessage {
125    /// A new emergency message.
126    pub const fn new(
127        error_code: u16,
128        error_register: ErrorRegister,
129        vendor_specific: [u8; 5],
130    ) -> Self {
131        Self {
132            error_code,
133            error_register,
134            vendor_specific,
135        }
136    }
137
138    /// The *error reset / no error* message (code `0x0000`, register clear).
139    pub const fn error_reset() -> Self {
140        Self {
141            error_code: error_code::ERROR_RESET,
142            error_register: ErrorRegister::NONE,
143            vendor_specific: [0; 5],
144        }
145    }
146
147    /// Whether this message signals *error reset / no error*.
148    pub const fn is_error_reset(&self) -> bool {
149        self.error_code == error_code::ERROR_RESET
150    }
151
152    /// Encode this message into its 8-byte payload.
153    pub fn encode(&self) -> EmcyPayload {
154        let mut p = [0u8; 8];
155        p[0..2].copy_from_slice(&self.error_code.to_le_bytes());
156        p[2] = self.error_register.0;
157        p[3..8].copy_from_slice(&self.vendor_specific);
158        p
159    }
160
161    /// Decode an EMCY frame's data field.
162    ///
163    /// Returns [`Error::BadLength`] unless `data` is exactly eight bytes.
164    pub fn decode(data: &[u8]) -> Result<Self> {
165        if data.len() != 8 {
166            return Err(Error::BadLength);
167        }
168        let mut vendor_specific = [0u8; 5];
169        vendor_specific.copy_from_slice(&data[3..8]);
170        Ok(Self {
171            error_code: u16::from_le_bytes([data[0], data[1]]),
172            error_register: ErrorRegister(data[2]),
173            vendor_specific,
174        })
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181
182    #[test]
183    fn cob_id_follows_convention() {
184        assert_eq!(emcy_cob_id(NodeId::new(0x05).unwrap()), 0x085);
185    }
186
187    // Known-good frame: overvoltage (code 0x3210) with the generic+voltage
188    // register bits set (0x05) and vendor bytes 0xAA..0xEE. Code is
189    // little-endian in bytes 0..2.
190    #[test]
191    fn encode_matches_known_frame() {
192        let msg = EmergencyMessage::new(
193            0x3210,
194            ErrorRegister(ErrorRegister::GENERIC | ErrorRegister::VOLTAGE),
195            [0xAA, 0xBB, 0xCC, 0xDD, 0xEE],
196        );
197        assert_eq!(
198            msg.encode(),
199            [0x10, 0x32, 0x05, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE]
200        );
201    }
202
203    #[test]
204    fn decode_matches_known_frame() {
205        let msg =
206            EmergencyMessage::decode(&[0x10, 0x32, 0x05, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE]).unwrap();
207        assert_eq!(msg.error_code, 0x3210);
208        assert!(msg.error_register.contains(ErrorRegister::VOLTAGE));
209        assert!(msg.error_register.has_error());
210        assert_eq!(msg.vendor_specific, [0xAA, 0xBB, 0xCC, 0xDD, 0xEE]);
211    }
212
213    #[test]
214    fn encode_decode_roundtrip() {
215        let msg = EmergencyMessage::new(
216            error_code::COMMUNICATION,
217            ErrorRegister(ErrorRegister::GENERIC | ErrorRegister::COMMUNICATION),
218            [1, 2, 3, 4, 5],
219        );
220        assert_eq!(EmergencyMessage::decode(&msg.encode()).unwrap(), msg);
221    }
222
223    #[test]
224    fn error_reset_is_recognised() {
225        let reset = EmergencyMessage::error_reset();
226        assert!(reset.is_error_reset());
227        assert!(!reset.error_register.has_error());
228        assert_eq!(reset.encode(), [0; 8]);
229    }
230
231    #[test]
232    fn decode_rejects_wrong_length() {
233        assert_eq!(EmergencyMessage::decode(&[0; 7]), Err(Error::BadLength));
234    }
235}