use crate::types::NodeId;
use crate::{Error, Result};
pub const EMCY_COB_BASE: u16 = 0x080;
pub const fn emcy_cob_id(node: NodeId) -> u16 {
EMCY_COB_BASE + node.raw() as u16
}
pub type EmcyPayload = [u8; 8];
pub mod error_code {
pub const ERROR_RESET: u16 = 0x0000;
pub const GENERIC: u16 = 0x1000;
pub const CURRENT: u16 = 0x2000;
pub const VOLTAGE: u16 = 0x3000;
pub const TEMPERATURE: u16 = 0x4000;
pub const DEVICE_HARDWARE: u16 = 0x5000;
pub const DEVICE_SOFTWARE: u16 = 0x6000;
pub const ADDITIONAL_MODULES: u16 = 0x7000;
pub const MONITORING: u16 = 0x8000;
pub const COMMUNICATION: u16 = 0x8100;
pub const EXTERNAL: u16 = 0x9000;
pub const ADDITIONAL_FUNCTIONS: u16 = 0xF000;
pub const DEVICE_SPECIFIC: u16 = 0xFF00;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct ErrorRegister(pub u8);
impl ErrorRegister {
pub const GENERIC: u8 = 0x01;
pub const CURRENT: u8 = 0x02;
pub const VOLTAGE: u8 = 0x04;
pub const TEMPERATURE: u8 = 0x08;
pub const COMMUNICATION: u8 = 0x10;
pub const DEVICE_PROFILE: u8 = 0x20;
pub const MANUFACTURER: u8 = 0x80;
pub const NONE: ErrorRegister = ErrorRegister(0);
pub const fn contains(self, bits: u8) -> bool {
self.0 & bits == bits
}
pub const fn has_error(self) -> bool {
self.0 != 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EmergencyMessage {
pub error_code: u16,
pub error_register: ErrorRegister,
pub vendor_specific: [u8; 5],
}
impl EmergencyMessage {
pub const fn new(
error_code: u16,
error_register: ErrorRegister,
vendor_specific: [u8; 5],
) -> Self {
Self {
error_code,
error_register,
vendor_specific,
}
}
pub const fn error_reset() -> Self {
Self {
error_code: error_code::ERROR_RESET,
error_register: ErrorRegister::NONE,
vendor_specific: [0; 5],
}
}
pub const fn is_error_reset(&self) -> bool {
self.error_code == error_code::ERROR_RESET
}
pub fn encode(&self) -> EmcyPayload {
let mut p = [0u8; 8];
p[0..2].copy_from_slice(&self.error_code.to_le_bytes());
p[2] = self.error_register.0;
p[3..8].copy_from_slice(&self.vendor_specific);
p
}
pub fn decode(data: &[u8]) -> Result<Self> {
if data.len() != 8 {
return Err(Error::BadLength);
}
let mut vendor_specific = [0u8; 5];
vendor_specific.copy_from_slice(&data[3..8]);
Ok(Self {
error_code: u16::from_le_bytes([data[0], data[1]]),
error_register: ErrorRegister(data[2]),
vendor_specific,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cob_id_follows_convention() {
assert_eq!(emcy_cob_id(NodeId::new(0x05).unwrap()), 0x085);
}
#[test]
fn encode_matches_known_frame() {
let msg = EmergencyMessage::new(
0x3210,
ErrorRegister(ErrorRegister::GENERIC | ErrorRegister::VOLTAGE),
[0xAA, 0xBB, 0xCC, 0xDD, 0xEE],
);
assert_eq!(
msg.encode(),
[0x10, 0x32, 0x05, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE]
);
}
#[test]
fn decode_matches_known_frame() {
let msg =
EmergencyMessage::decode(&[0x10, 0x32, 0x05, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE]).unwrap();
assert_eq!(msg.error_code, 0x3210);
assert!(msg.error_register.contains(ErrorRegister::VOLTAGE));
assert!(msg.error_register.has_error());
assert_eq!(msg.vendor_specific, [0xAA, 0xBB, 0xCC, 0xDD, 0xEE]);
}
#[test]
fn encode_decode_roundtrip() {
let msg = EmergencyMessage::new(
error_code::COMMUNICATION,
ErrorRegister(ErrorRegister::GENERIC | ErrorRegister::COMMUNICATION),
[1, 2, 3, 4, 5],
);
assert_eq!(EmergencyMessage::decode(&msg.encode()).unwrap(), msg);
}
#[test]
fn error_reset_is_recognised() {
let reset = EmergencyMessage::error_reset();
assert!(reset.is_error_reset());
assert!(!reset.error_register.has_error());
assert_eq!(reset.encode(), [0; 8]);
}
#[test]
fn decode_rejects_wrong_length() {
assert_eq!(EmergencyMessage::decode(&[0; 7]), Err(Error::BadLength));
}
}