use core::fmt::Write as _;
use canopen_rs::{AccessType, Value};
use crate::eds::Eds;
pub fn generate_object_dictionary(eds: &Eds, fn_name: &str) -> String {
let n = eds.objects.len();
let mut out = String::new();
let _ = writeln!(
out,
"/// Object dictionary generated from an EDS by canopen-host."
);
let _ = writeln!(
out,
"pub fn {fn_name}() -> canopen_rs::ObjectDictionary<{n}> {{"
);
let _ = writeln!(out, " let mut od = canopen_rs::ObjectDictionary::new();");
for obj in &eds.objects {
let _ = writeln!(
out,
" od.insert(canopen_rs::Address::new({:#06x}, {:#04x}), canopen_rs::Entry {{ value: {}, access: canopen_rs::AccessType::{} }}).unwrap();",
obj.address.index,
obj.address.subindex,
value_literal(&obj.default_value),
access_variant(obj.access),
);
}
let _ = writeln!(out, " od");
let _ = writeln!(out, "}}");
out
}
fn access_variant(access: AccessType) -> &'static str {
match access {
AccessType::Ro => "Ro",
AccessType::Wo => "Wo",
AccessType::Rw => "Rw",
AccessType::Const => "Const",
}
}
fn value_literal(value: &Value) -> String {
match value {
Value::Boolean(b) => format!("canopen_rs::Value::Boolean({b})"),
Value::Unsigned8(x) => format!("canopen_rs::Value::Unsigned8({x:#04x})"),
Value::Unsigned16(x) => format!("canopen_rs::Value::Unsigned16({x:#06x})"),
Value::Unsigned32(x) => format!("canopen_rs::Value::Unsigned32({x:#010x})"),
Value::Unsigned64(x) => format!("canopen_rs::Value::Unsigned64({x:#018x})"),
Value::Integer8(x) => format!("canopen_rs::Value::Integer8({x})"),
Value::Integer16(x) => format!("canopen_rs::Value::Integer16({x})"),
Value::Integer32(x) => format!("canopen_rs::Value::Integer32({x})"),
Value::Integer64(x) => format!("canopen_rs::Value::Integer64({x})"),
Value::Real32(x) => format!("canopen_rs::Value::Real32({x:?}f32)"),
Value::Real64(x) => format!("canopen_rs::Value::Real64({x:?}f64)"),
Value::VisibleString(s) => string_literal("VisibleString", s.as_bytes()),
Value::OctetString(s) => string_literal("OctetString", s.as_bytes()),
Value::Domain(s) => string_literal("Domain", s.as_bytes()),
_ => panic!("codegen: unsupported value variant {value:?}"),
}
}
fn string_literal(variant: &str, bytes: &[u8]) -> String {
let mut list = String::new();
for (i, b) in bytes.iter().enumerate() {
if i > 0 {
list.push_str(", ");
}
let _ = write!(list, "{b:#04x}");
}
format!("canopen_rs::Value::{variant}(canopen_rs::ByteString::from_bytes(&[{list}]).unwrap())")
}
#[cfg(test)]
mod tests {
use super::*;
use canopen_rs::{Address, ObjectDictionary};
const SAMPLE: &str = "\
[1000]
ParameterName=Device type
DataType=0x0007
AccessType=ro
DefaultValue=0x00040192
[1008]
ParameterName=Device name
DataType=0x0009
AccessType=const
DefaultValue=Widget
[1017]
ParameterName=Producer heartbeat time
DataType=0x0006
AccessType=rw
DefaultValue=1000
";
#[allow(clippy::needless_return)]
fn generated_sample() -> ObjectDictionary<3> {
let mut od = ObjectDictionary::new();
od.insert(
Address::new(0x1000, 0x00),
canopen_rs::Entry {
value: canopen_rs::Value::Unsigned32(0x00040192),
access: AccessType::Ro,
},
)
.unwrap();
od.insert(
Address::new(0x1008, 0x00),
canopen_rs::Entry {
value: canopen_rs::Value::VisibleString(
canopen_rs::ByteString::from_bytes(&[0x57, 0x69, 0x64, 0x67, 0x65, 0x74])
.unwrap(),
),
access: AccessType::Const,
},
)
.unwrap();
od.insert(
Address::new(0x1017, 0x00),
canopen_rs::Entry {
value: canopen_rs::Value::Unsigned16(0x03e8),
access: AccessType::Rw,
},
)
.unwrap();
od
}
#[test]
fn generated_shape_builds_a_correct_od() {
let od = generated_sample();
assert_eq!(
od.read(Address::new(0x1000, 0)).unwrap(),
Value::Unsigned32(0x0004_0192)
);
assert_eq!(
od.read(Address::new(0x1017, 0)).unwrap(),
Value::Unsigned16(1000)
);
match od.read(Address::new(0x1008, 0)).unwrap() {
Value::VisibleString(bs) => assert_eq!(bs.as_str(), Some("Widget")),
other => panic!("expected VISIBLE_STRING, got {other:?}"),
}
}
#[test]
fn generator_emits_the_expected_source() {
let eds = Eds::parse(SAMPLE).unwrap();
let src = generate_object_dictionary(&eds, "device_od");
assert!(src.contains("pub fn device_od() -> canopen_rs::ObjectDictionary<3> {"));
assert!(src.contains(
"od.insert(canopen_rs::Address::new(0x1000, 0x00), canopen_rs::Entry { value: canopen_rs::Value::Unsigned32(0x00040192), access: canopen_rs::AccessType::Ro }).unwrap();"
));
assert!(src.contains(
"od.insert(canopen_rs::Address::new(0x1017, 0x00), canopen_rs::Entry { value: canopen_rs::Value::Unsigned16(0x03e8), access: canopen_rs::AccessType::Rw }).unwrap();"
));
assert!(src.contains(
"canopen_rs::Value::VisibleString(canopen_rs::ByteString::from_bytes(&[0x57, 0x69, 0x64, 0x67, 0x65, 0x74]).unwrap())"
));
}
}