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)"),
_ => panic!("codegen: unsupported value variant {value:?}"),
}
}
#[cfg(test)]
mod tests {
use super::*;
use canopen_rs::{Address, ObjectDictionary};
const SAMPLE: &str = "\
[1000]
ParameterName=Device type
DataType=0x0007
AccessType=ro
DefaultValue=0x00040192
[1017]
ParameterName=Producer heartbeat time
DataType=0x0006
AccessType=rw
DefaultValue=1000
";
#[allow(clippy::needless_return)]
fn generated_sample() -> ObjectDictionary<2> {
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(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)
);
}
#[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<2> {"));
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();"
));
}
}