use heapless::Deque;
pub const EMCY_COB_ID_BASE: u16 = 0x80;
pub const ERR_REG_GENERIC: u8 = 0x01;
pub const ERR_REG_CURRENT: u8 = 0x02;
pub const ERR_REG_VOLTAGE: u8 = 0x04;
pub const ERR_REG_TEMPERATURE: u8 = 0x08;
pub const ERR_REG_COMMUNICATION: u8 = 0x10;
pub const ERR_REG_PROFILE: u8 = 0x20;
pub const ERR_REG_MANUFACTURER: u8 = 0x80;
pub const EMCY_NO_ERROR: u16 = 0x0000;
pub const EMCY_GENERIC: u16 = 0x1000;
pub const EMCY_CURRENT_INPUT: u16 = 0x2100;
pub const EMCY_VOLTAGE_MAINS: u16 = 0x3100;
pub const EMCY_TEMP_AMBIENT: u16 = 0x4100;
pub const EMCY_COM_CAN_OVERRUN: u16 = 0x8110;
pub const EMCY_COM_BUS_OFF: u16 = 0x8140;
pub const EMCY_PROTO_PDO_LEN: u16 = 0x8200;
pub const EMCY_EXTERNAL: u16 = 0x9000;
pub const EMCY_MANUFACTURER: u16 = 0xFF00;
#[derive(Debug, Clone, Copy)]
pub struct EmergencyEvent {
pub error_code: u16,
pub error_reg: u8,
pub data: [u8; 5],
pub sequence: u32,
}
impl EmergencyEvent {
pub fn new(error_code: u16, error_reg: u8, data: [u8; 5], sequence: u32) -> Self {
Self {
error_code,
error_reg,
data,
sequence,
}
}
pub fn to_bytes(&self) -> [u8; 8] {
let ec = self.error_code.to_le_bytes();
[
ec[0],
ec[1],
self.error_reg,
self.data[0],
self.data[1],
self.data[2],
self.data[3],
self.data[4],
]
}
pub fn from_bytes(b: &[u8; 8], sequence: u32) -> Self {
Self {
error_code: u16::from_le_bytes([b[0], b[1]]),
error_reg: b[2],
data: [b[3], b[4], b[5], b[6], b[7]],
sequence,
}
}
pub fn is_error_reset(&self) -> bool {
self.error_code == EMCY_NO_ERROR
}
}
pub struct EmergencyProducer {
cob_id: u16,
error_reg: u8,
history: Deque<EmergencyEvent, 8>,
total_emitted: u32,
sequence: u32,
active_error: bool,
}
impl EmergencyProducer {
pub fn new(node_id: u8) -> Self {
Self {
cob_id: EMCY_COB_ID_BASE + node_id as u16,
error_reg: 0,
history: Deque::new(),
total_emitted: 0,
sequence: 0,
active_error: false,
}
}
pub fn cob_id(&self) -> u16 {
self.cob_id
}
pub fn error_reg(&self) -> u8 {
self.error_reg
}
pub fn has_active_error(&self) -> bool {
self.active_error
}
pub fn total_emitted(&self) -> u32 {
self.total_emitted
}
pub fn history_len(&self) -> usize {
self.history.len()
}
pub fn emit_error(&mut self, code: u16, error_reg: u8, data: [u8; 5]) -> [u8; 8] {
self.error_reg |= error_reg;
self.active_error = code != EMCY_NO_ERROR;
self.sequence += 1;
let event = EmergencyEvent::new(code, self.error_reg, data, self.sequence);
let frame = event.to_bytes();
if self.history.is_full() {
self.history.pop_front();
}
let _ = self.history.push_back(event);
self.total_emitted += 1;
frame
}
pub fn reset_error(&mut self, error_reg_bits: u8) -> [u8; 8] {
self.error_reg &= !error_reg_bits;
if self.error_reg == 0 {
self.active_error = false;
}
self.emit_error(EMCY_NO_ERROR, 0, [0u8; 5])
}
pub fn last_event(&self) -> Option<&EmergencyEvent> {
self.history.back()
}
pub fn event_at(&self, idx: usize) -> Option<&EmergencyEvent> {
self.history.iter().nth(idx)
}
pub fn clear_history(&mut self) {
while self.history.pop_front().is_some() {}
}
}
#[derive(Debug, Clone)]
pub struct EmergencyConsumer {
cob_id: u16,
last_event: Option<EmergencyEvent>,
total_received: u32,
sequence: u32,
}
impl EmergencyConsumer {
pub fn new(cob_id: u16) -> Self {
Self {
cob_id,
last_event: None,
total_received: 0,
sequence: 0,
}
}
pub fn on_emcy(&mut self, frame: &[u8; 8]) {
self.sequence += 1;
self.total_received += 1;
self.last_event = Some(EmergencyEvent::from_bytes(frame, self.sequence));
}
pub fn last_event(&self) -> Option<&EmergencyEvent> {
self.last_event.as_ref()
}
pub fn total_received(&self) -> u32 {
self.total_received
}
pub fn cob_id(&self) -> u16 {
self.cob_id
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_emit_error() {
let mut prod = EmergencyProducer::new(1);
let frame = prod.emit_error(
EMCY_COM_BUS_OFF,
ERR_REG_COMMUNICATION,
[0xDE, 0xAD, 0, 0, 0],
);
let code = u16::from_le_bytes([frame[0], frame[1]]);
assert_eq!(code, EMCY_COM_BUS_OFF);
assert_eq!(frame[2], ERR_REG_COMMUNICATION);
assert_eq!(frame[3], 0xDE);
assert!(prod.has_active_error());
assert_eq!(prod.history_len(), 1);
}
#[test]
fn test_history_ring_buffer_overflow() {
let mut prod = EmergencyProducer::new(2);
for i in 0..10u8 {
prod.emit_error(EMCY_GENERIC, ERR_REG_GENERIC, [i, 0, 0, 0, 0]);
}
assert_eq!(prod.history_len(), 8);
assert_eq!(prod.last_event().unwrap().data[0], 9);
}
#[test]
fn test_reset_error() {
let mut prod = EmergencyProducer::new(1);
prod.emit_error(EMCY_GENERIC, ERR_REG_GENERIC | ERR_REG_VOLTAGE, [0; 5]);
assert!(prod.has_active_error());
prod.reset_error(ERR_REG_GENERIC | ERR_REG_VOLTAGE);
assert!(!prod.has_active_error());
assert_eq!(prod.error_reg(), 0);
}
#[test]
fn test_emergency_consumer() {
let mut consumer = EmergencyConsumer::new(0x81);
let frame = [0x00u8, 0x10, 0x01, 0xAA, 0, 0, 0, 0]; consumer.on_emcy(&frame);
assert_eq!(consumer.total_received(), 1);
let ev = consumer.last_event().unwrap();
assert_eq!(ev.error_code, EMCY_GENERIC);
assert_eq!(ev.error_reg, ERR_REG_GENERIC);
assert_eq!(ev.data[0], 0xAA);
}
#[test]
fn test_frame_roundtrip() {
let mut prod = EmergencyProducer::new(5);
let frame = prod.emit_error(EMCY_TEMP_AMBIENT, ERR_REG_TEMPERATURE, [1, 2, 3, 4, 5]);
let mut consumer = EmergencyConsumer::new(0x85);
consumer.on_emcy(&frame);
let ev = consumer.last_event().unwrap();
assert_eq!(ev.error_code, EMCY_TEMP_AMBIENT);
assert_eq!(ev.error_reg, ERR_REG_TEMPERATURE);
assert_eq!(ev.data, [1, 2, 3, 4, 5]);
}
#[test]
fn test_error_register_accumulation() {
let mut prod = EmergencyProducer::new(1);
prod.emit_error(EMCY_GENERIC, ERR_REG_GENERIC, [0; 5]);
prod.emit_error(EMCY_VOLTAGE_MAINS, ERR_REG_VOLTAGE, [0; 5]);
assert_eq!(prod.error_reg(), ERR_REG_GENERIC | ERR_REG_VOLTAGE);
}
#[test]
fn test_event_serialization() {
let ev = EmergencyEvent::new(EMCY_COM_BUS_OFF, ERR_REG_COMMUNICATION, [1, 2, 3, 4, 5], 1);
let bytes = ev.to_bytes();
let parsed = EmergencyEvent::from_bytes(&bytes, 1);
assert_eq!(parsed.error_code, EMCY_COM_BUS_OFF);
assert_eq!(parsed.error_reg, ERR_REG_COMMUNICATION);
assert_eq!(parsed.data, [1, 2, 3, 4, 5]);
}
}