use crate::error::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CommunicationObject {
Nmt,
Sync,
Emergency,
Time,
Tpdo1,
Rpdo1,
Tpdo2,
Rpdo2,
Tpdo3,
Rpdo3,
Tpdo4,
Rpdo4,
Tsdo,
Rsdo,
Heartbeat,
}
impl CommunicationObject {
pub fn function_code(self) -> u16 {
match self {
CommunicationObject::Nmt => 0x000,
CommunicationObject::Sync => 0x080,
CommunicationObject::Emergency => 0x080,
CommunicationObject::Time => 0x100,
CommunicationObject::Tpdo1 => 0x180,
CommunicationObject::Rpdo1 => 0x200,
CommunicationObject::Tpdo2 => 0x280,
CommunicationObject::Rpdo2 => 0x300,
CommunicationObject::Tpdo3 => 0x380,
CommunicationObject::Rpdo3 => 0x400,
CommunicationObject::Tpdo4 => 0x480,
CommunicationObject::Rpdo4 => 0x500,
CommunicationObject::Tsdo => 0x580,
CommunicationObject::Rsdo => 0x600,
CommunicationObject::Heartbeat => 0x700,
}
}
pub fn cob_id(self, node_id: u8) -> Result<u16> {
let base = self.function_code();
match self {
CommunicationObject::Nmt
| CommunicationObject::Sync
| CommunicationObject::Time => Ok(base),
_ => {
if node_id == 0 || node_id > 0x7F {
return Err(Error::InvalidNodeId(node_id));
}
Ok(base | node_id as u16)
}
}
}
}
pub fn parse_cob_id(cob_id: u16) -> Option<(CommunicationObject, u8)> {
if cob_id == 0x000 {
return Some((CommunicationObject::Nmt, 0));
}
if cob_id == 0x080 {
return Some((CommunicationObject::Sync, 0));
}
if cob_id == 0x100 {
return Some((CommunicationObject::Time, 0));
}
let function = cob_id & 0x780;
let nid = (cob_id & 0x7F) as u8;
if nid == 0 || nid > 0x7F {
return None;
}
let obj = match function {
0x080 => CommunicationObject::Emergency,
0x180 => CommunicationObject::Tpdo1,
0x200 => CommunicationObject::Rpdo1,
0x280 => CommunicationObject::Tpdo2,
0x300 => CommunicationObject::Rpdo2,
0x380 => CommunicationObject::Tpdo3,
0x400 => CommunicationObject::Rpdo3,
0x480 => CommunicationObject::Tpdo4,
0x500 => CommunicationObject::Rpdo4,
0x580 => CommunicationObject::Tsdo,
0x600 => CommunicationObject::Rsdo,
0x700 => CommunicationObject::Heartbeat,
_ => return None,
};
Some((obj, nid))
}
pub fn is_reserved_standard_cob_id(cob_id: u16) -> bool {
matches!(
cob_id,
0x000 | 0x080 | 0x100 ) || (0x081..=0x0FF).contains(&cob_id) || (0x181..=0x1FF).contains(&cob_id) || (0x201..=0x27F).contains(&cob_id) || (0x281..=0x2FF).contains(&cob_id) || (0x301..=0x37F).contains(&cob_id) || (0x381..=0x3FF).contains(&cob_id) || (0x401..=0x47F).contains(&cob_id) || (0x481..=0x4FF).contains(&cob_id) || (0x501..=0x57F).contains(&cob_id) || (0x581..=0x5FF).contains(&cob_id) || (0x601..=0x67F).contains(&cob_id) || (0x701..=0x77F).contains(&cob_id) || (0x680..=0x6FF).contains(&cob_id) || (0x780..=0x7FF).contains(&cob_id) }
pub fn is_reserved_extended_cob_id(cob_id: u32) -> bool {
matches!(cob_id, 0x09 | 0xA9 | 0xAA) || (0xFE00..=0xFEFF).contains(&cob_id)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cob_id_tpdo1_node10() {
assert_eq!(CommunicationObject::Tpdo1.cob_id(0x10).unwrap(), 0x190);
}
#[test]
fn cob_id_heartbeat_node1() {
assert_eq!(CommunicationObject::Heartbeat.cob_id(0x01).unwrap(), 0x701);
}
#[test]
fn cob_id_rsdo_node127() {
assert_eq!(CommunicationObject::Rsdo.cob_id(0x7F).unwrap(), 0x67F);
}
#[test]
fn cob_id_nmt_ignores_node() {
assert_eq!(CommunicationObject::Nmt.cob_id(0).unwrap(), 0x000);
}
#[test]
fn cob_id_rejects_zero_node() {
assert!(matches!(
CommunicationObject::Tpdo1.cob_id(0),
Err(Error::InvalidNodeId(0))
));
}
#[test]
fn cob_id_rejects_node_over_127() {
assert!(matches!(
CommunicationObject::Tpdo1.cob_id(128),
Err(Error::InvalidNodeId(128))
));
}
#[test]
fn parse_heartbeat() {
assert_eq!(parse_cob_id(0x710), Some((CommunicationObject::Heartbeat, 0x10)));
}
#[test]
fn parse_tpdo1() {
assert_eq!(parse_cob_id(0x190), Some((CommunicationObject::Tpdo1, 0x10)));
}
#[test]
fn parse_sync() {
assert_eq!(parse_cob_id(0x080), Some((CommunicationObject::Sync, 0)));
}
#[test]
fn parse_emergency() {
assert_eq!(parse_cob_id(0x081), Some((CommunicationObject::Emergency, 1)));
}
#[test]
fn parse_invalid() {
assert_eq!(parse_cob_id(0x150), None); }
#[test]
fn reserved_standard_covers_all_canopen_objects() {
assert!(is_reserved_standard_cob_id(0x701));
assert!(is_reserved_standard_cob_id(0x77F));
assert!(is_reserved_standard_cob_id(0x181));
assert!(is_reserved_standard_cob_id(0x1FF));
assert!(is_reserved_standard_cob_id(0x581));
assert!(is_reserved_standard_cob_id(0x67F));
assert!(is_reserved_standard_cob_id(0x000));
assert!(is_reserved_standard_cob_id(0x080));
assert!(is_reserved_standard_cob_id(0x100));
assert!(is_reserved_standard_cob_id(0x7E5));
}
#[test]
fn reserved_standard_allows_safe_ids() {
assert!(!is_reserved_standard_cob_id(0x101));
assert!(!is_reserved_standard_cob_id(0x17F));
assert!(!is_reserved_standard_cob_id(0x180));
assert!(!is_reserved_standard_cob_id(0x200));
assert!(!is_reserved_standard_cob_id(0x280));
assert!(!is_reserved_standard_cob_id(0x300));
assert!(!is_reserved_standard_cob_id(0x380));
assert!(!is_reserved_standard_cob_id(0x400));
assert!(!is_reserved_standard_cob_id(0x480));
assert!(!is_reserved_standard_cob_id(0x500));
assert!(!is_reserved_standard_cob_id(0x580));
assert!(!is_reserved_standard_cob_id(0x600));
assert!(!is_reserved_standard_cob_id(0x700));
}
#[test]
fn reserved_standard_includes_lss_range() {
assert!(is_reserved_standard_cob_id(0x680));
assert!(is_reserved_standard_cob_id(0x6FF));
assert!(is_reserved_standard_cob_id(0x780));
assert!(is_reserved_standard_cob_id(0x7FF));
}
#[test]
fn reserved_extended_known() {
assert!(is_reserved_extended_cob_id(0x09));
assert!(is_reserved_extended_cob_id(0xA9));
assert!(is_reserved_extended_cob_id(0xAA));
assert!(is_reserved_extended_cob_id(0xFE00));
assert!(is_reserved_extended_cob_id(0xFEFF));
assert!(!is_reserved_extended_cob_id(0xAB));
assert!(!is_reserved_extended_cob_id(0xFDFF));
}
}