use crate::datatypes::{ByteString, Value, MAX_STRING_LEN};
use crate::lss::LssAddress;
use crate::object_dictionary::{Address, Entry, ObjectDictionary};
use crate::Result;
#[derive(Debug, Clone, Copy)]
pub struct StandardObjects {
pub device_type: u32,
pub identity: LssAddress,
pub producer_heartbeat_ms: u16,
pub error_history_len: u8,
pub device_name: Option<ByteString>,
}
impl StandardObjects {
pub const fn new(device_type: u32, identity: LssAddress) -> Self {
Self {
device_type,
identity,
producer_heartbeat_ms: 0,
error_history_len: 0,
device_name: None,
}
}
pub fn with_heartbeat(mut self, ms: u16) -> Self {
self.producer_heartbeat_ms = ms;
self
}
pub fn with_error_history(mut self, entries: u8) -> Self {
self.error_history_len = entries;
self
}
pub fn with_device_name(mut self, name: &str) -> Self {
let end = name.len().min(MAX_STRING_LEN);
self.device_name = ByteString::from_bytes(&name.as_bytes()[..end]).ok();
self
}
pub fn insert_into<const N: usize>(&self, od: &mut ObjectDictionary<N>) -> Result<()> {
od.insert(
Address::new(0x1000, 0),
Entry::constant(Value::Unsigned32(self.device_type)),
)?;
od.insert(Address::new(0x1001, 0), Entry::ro(Value::Unsigned8(0)))?;
od.insert(
Address::new(0x1017, 0),
Entry::rw(Value::Unsigned16(self.producer_heartbeat_ms)),
)?;
od.insert(
Address::new(0x1018, 0),
Entry::constant(Value::Unsigned8(4)),
)?;
od.insert(
Address::new(0x1018, 1),
Entry::constant(Value::Unsigned32(self.identity.vendor_id)),
)?;
od.insert(
Address::new(0x1018, 2),
Entry::constant(Value::Unsigned32(self.identity.product_code)),
)?;
od.insert(
Address::new(0x1018, 3),
Entry::constant(Value::Unsigned32(self.identity.revision_number)),
)?;
od.insert(
Address::new(0x1018, 4),
Entry::constant(Value::Unsigned32(self.identity.serial_number)),
)?;
if let Some(name) = self.device_name {
od.insert(
Address::new(0x1008, 0),
Entry::constant(Value::VisibleString(name)),
)?;
}
if self.error_history_len > 0 {
od.insert(Address::new(0x1003, 0), Entry::rw(Value::Unsigned8(0)))?;
for sub in 1..=self.error_history_len {
od.insert(Address::new(0x1003, sub), Entry::ro(Value::Unsigned32(0)))?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Error;
fn identity() -> LssAddress {
LssAddress {
vendor_id: 0x0000_012E,
product_code: 0x4001_0002,
revision_number: 0x0000_0003,
serial_number: 0x00C0_FFEE,
}
}
#[test]
fn inserts_the_mandatory_baseline() {
let mut od = ObjectDictionary::<16>::new();
StandardObjects::new(0x0004_0192, identity())
.with_heartbeat(1000)
.insert_into(&mut od)
.unwrap();
assert_eq!(
od.read(Address::new(0x1000, 0)).unwrap(),
Value::Unsigned32(0x0004_0192)
);
assert_eq!(
od.read(Address::new(0x1001, 0)).unwrap(),
Value::Unsigned8(0)
);
assert_eq!(
od.read(Address::new(0x1017, 0)).unwrap(),
Value::Unsigned16(1000)
);
}
#[test]
fn identity_record_matches_the_lss_address() {
let mut od = ObjectDictionary::<16>::new();
let id = identity();
StandardObjects::new(0, id).insert_into(&mut od).unwrap();
assert_eq!(
od.read(Address::new(0x1018, 0)).unwrap(),
Value::Unsigned8(4) );
assert_eq!(
od.read(Address::new(0x1018, 1)).unwrap(),
Value::Unsigned32(id.vendor_id)
);
assert_eq!(
od.read(Address::new(0x1018, 2)).unwrap(),
Value::Unsigned32(id.product_code)
);
assert_eq!(
od.read(Address::new(0x1018, 3)).unwrap(),
Value::Unsigned32(id.revision_number)
);
assert_eq!(
od.read(Address::new(0x1018, 4)).unwrap(),
Value::Unsigned32(id.serial_number)
);
}
#[test]
fn error_history_is_optional_and_sized() {
let mut od = ObjectDictionary::<16>::new();
StandardObjects::new(0, identity())
.insert_into(&mut od)
.unwrap();
assert!(od.entry(Address::new(0x1003, 0)).is_none());
let mut od = ObjectDictionary::<16>::new();
StandardObjects::new(0, identity())
.with_error_history(3)
.insert_into(&mut od)
.unwrap();
assert_eq!(
od.read(Address::new(0x1003, 0)).unwrap(),
Value::Unsigned8(0)
);
assert!(od.entry(Address::new(0x1003, 3)).is_some());
assert!(od.entry(Address::new(0x1003, 4)).is_none());
}
#[test]
fn node_built_from_standard_objects_mirrors_emergency() {
use crate::emcy::{error_code, ErrorRegister};
use crate::node::Node;
use crate::types::NodeId;
let mut od = ObjectDictionary::<16>::new();
StandardObjects::new(0x0004_0192, identity())
.with_heartbeat(1000)
.with_error_history(4)
.insert_into(&mut od)
.unwrap();
let mut node = Node::new(NodeId::new(0x10).unwrap(), od);
node.raise_emergency(
error_code::VOLTAGE,
ErrorRegister(ErrorRegister::VOLTAGE),
[0; 5],
);
assert_eq!(
node.od().read(Address::new(0x1001, 0)).unwrap(),
Value::Unsigned8(ErrorRegister::GENERIC | ErrorRegister::VOLTAGE)
);
assert_eq!(
node.od().read(Address::new(0x1003, 0)).unwrap(),
Value::Unsigned8(1)
);
assert_eq!(
node.od().read(Address::new(0x1003, 1)).unwrap(),
Value::Unsigned32(u32::from(error_code::VOLTAGE))
);
}
#[test]
fn device_name_is_optional_visible_string() {
let mut od = ObjectDictionary::<16>::new();
StandardObjects::new(0, identity())
.insert_into(&mut od)
.unwrap();
assert!(od.entry(Address::new(0x1008, 0)).is_none());
let mut od = ObjectDictionary::<16>::new();
StandardObjects::new(0, identity())
.with_device_name("canopen-rs node")
.insert_into(&mut od)
.unwrap();
match od.read(Address::new(0x1008, 0)).unwrap() {
Value::VisibleString(bs) => assert_eq!(bs.as_str(), Some("canopen-rs node")),
other => panic!("expected a VISIBLE_STRING, got {other:?}"),
}
}
#[test]
fn reports_dictionary_full() {
let mut od = ObjectDictionary::<4>::new();
assert_eq!(
StandardObjects::new(0, identity()).insert_into(&mut od),
Err(Error::DictionaryFull)
);
}
}