1use core::fmt::Write as _;
30
31use canopen_rs::{AccessType, Value};
32
33use crate::eds::Eds;
34
35pub fn generate_object_dictionary(eds: &Eds, fn_name: &str) -> String {
42 let n = eds.objects.len();
43 let mut out = String::new();
44 let _ = writeln!(
45 out,
46 "/// Object dictionary generated from an EDS by canopen-host."
47 );
48 let _ = writeln!(
49 out,
50 "pub fn {fn_name}() -> canopen_rs::ObjectDictionary<{n}> {{"
51 );
52 let _ = writeln!(out, " let mut od = canopen_rs::ObjectDictionary::new();");
53 for obj in &eds.objects {
54 let _ = writeln!(
55 out,
56 " od.insert(canopen_rs::Address::new({:#06x}, {:#04x}), canopen_rs::Entry {{ value: {}, access: canopen_rs::AccessType::{} }}).unwrap();",
57 obj.address.index,
58 obj.address.subindex,
59 value_literal(&obj.default_value),
60 access_variant(obj.access),
61 );
62 }
63 let _ = writeln!(out, " od");
64 let _ = writeln!(out, "}}");
65 out
66}
67
68fn access_variant(access: AccessType) -> &'static str {
69 match access {
70 AccessType::Ro => "Ro",
71 AccessType::Wo => "Wo",
72 AccessType::Rw => "Rw",
73 AccessType::Const => "Const",
74 }
75}
76
77fn value_literal(value: &Value) -> String {
79 match value {
80 Value::Boolean(b) => format!("canopen_rs::Value::Boolean({b})"),
81 Value::Unsigned8(x) => format!("canopen_rs::Value::Unsigned8({x:#04x})"),
82 Value::Unsigned16(x) => format!("canopen_rs::Value::Unsigned16({x:#06x})"),
83 Value::Unsigned32(x) => format!("canopen_rs::Value::Unsigned32({x:#010x})"),
84 Value::Unsigned64(x) => format!("canopen_rs::Value::Unsigned64({x:#018x})"),
85 Value::Integer8(x) => format!("canopen_rs::Value::Integer8({x})"),
86 Value::Integer16(x) => format!("canopen_rs::Value::Integer16({x})"),
87 Value::Integer32(x) => format!("canopen_rs::Value::Integer32({x})"),
88 Value::Integer64(x) => format!("canopen_rs::Value::Integer64({x})"),
89 Value::Real32(x) => format!("canopen_rs::Value::Real32({x:?}f32)"),
90 Value::Real64(x) => format!("canopen_rs::Value::Real64({x:?}f64)"),
91 _ => panic!("codegen: unsupported value variant {value:?}"),
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use canopen_rs::{Address, ObjectDictionary};
100
101 const SAMPLE: &str = "\
102[1000]
103ParameterName=Device type
104DataType=0x0007
105AccessType=ro
106DefaultValue=0x00040192
107
108[1017]
109ParameterName=Producer heartbeat time
110DataType=0x0006
111AccessType=rw
112DefaultValue=1000
113";
114
115 #[allow(clippy::needless_return)]
119 fn generated_sample() -> ObjectDictionary<2> {
120 let mut od = ObjectDictionary::new();
121 od.insert(
122 Address::new(0x1000, 0x00),
123 canopen_rs::Entry {
124 value: canopen_rs::Value::Unsigned32(0x00040192),
125 access: AccessType::Ro,
126 },
127 )
128 .unwrap();
129 od.insert(
130 Address::new(0x1017, 0x00),
131 canopen_rs::Entry {
132 value: canopen_rs::Value::Unsigned16(0x03e8),
133 access: AccessType::Rw,
134 },
135 )
136 .unwrap();
137 od
138 }
139
140 #[test]
141 fn generated_shape_builds_a_correct_od() {
142 let od = generated_sample();
143 assert_eq!(
144 od.read(Address::new(0x1000, 0)).unwrap(),
145 Value::Unsigned32(0x0004_0192)
146 );
147 assert_eq!(
148 od.read(Address::new(0x1017, 0)).unwrap(),
149 Value::Unsigned16(1000)
150 );
151 }
152
153 #[test]
154 fn generator_emits_the_expected_source() {
155 let eds = Eds::parse(SAMPLE).unwrap();
156 let src = generate_object_dictionary(&eds, "device_od");
157
158 assert!(src.contains("pub fn device_od() -> canopen_rs::ObjectDictionary<2> {"));
159 assert!(src.contains(
160 "od.insert(canopen_rs::Address::new(0x1000, 0x00), canopen_rs::Entry { value: canopen_rs::Value::Unsigned32(0x00040192), access: canopen_rs::AccessType::Ro }).unwrap();"
161 ));
162 assert!(src.contains(
163 "od.insert(canopen_rs::Address::new(0x1017, 0x00), canopen_rs::Entry { value: canopen_rs::Value::Unsigned16(0x03e8), access: canopen_rs::AccessType::Rw }).unwrap();"
164 ));
165 }
166}