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 Value::VisibleString(s) => string_literal("VisibleString", s.as_bytes()),
92 Value::OctetString(s) => string_literal("OctetString", s.as_bytes()),
93 Value::Domain(s) => string_literal("Domain", s.as_bytes()),
94 _ => panic!("codegen: unsupported value variant {value:?}"),
96 }
97}
98
99fn string_literal(variant: &str, bytes: &[u8]) -> String {
103 let mut list = String::new();
104 for (i, b) in bytes.iter().enumerate() {
105 if i > 0 {
106 list.push_str(", ");
107 }
108 let _ = write!(list, "{b:#04x}");
109 }
110 format!("canopen_rs::Value::{variant}(canopen_rs::ByteString::from_bytes(&[{list}]).unwrap())")
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116 use canopen_rs::{Address, ObjectDictionary};
117
118 const SAMPLE: &str = "\
119[1000]
120ParameterName=Device type
121DataType=0x0007
122AccessType=ro
123DefaultValue=0x00040192
124
125[1008]
126ParameterName=Device name
127DataType=0x0009
128AccessType=const
129DefaultValue=Widget
130
131[1017]
132ParameterName=Producer heartbeat time
133DataType=0x0006
134AccessType=rw
135DefaultValue=1000
136";
137
138 #[allow(clippy::needless_return)]
142 fn generated_sample() -> ObjectDictionary<3> {
143 let mut od = ObjectDictionary::new();
144 od.insert(
145 Address::new(0x1000, 0x00),
146 canopen_rs::Entry {
147 value: canopen_rs::Value::Unsigned32(0x00040192),
148 access: AccessType::Ro,
149 },
150 )
151 .unwrap();
152 od.insert(
153 Address::new(0x1008, 0x00),
154 canopen_rs::Entry {
155 value: canopen_rs::Value::VisibleString(
156 canopen_rs::ByteString::from_bytes(&[0x57, 0x69, 0x64, 0x67, 0x65, 0x74])
157 .unwrap(),
158 ),
159 access: AccessType::Const,
160 },
161 )
162 .unwrap();
163 od.insert(
164 Address::new(0x1017, 0x00),
165 canopen_rs::Entry {
166 value: canopen_rs::Value::Unsigned16(0x03e8),
167 access: AccessType::Rw,
168 },
169 )
170 .unwrap();
171 od
172 }
173
174 #[test]
175 fn generated_shape_builds_a_correct_od() {
176 let od = generated_sample();
177 assert_eq!(
178 od.read(Address::new(0x1000, 0)).unwrap(),
179 Value::Unsigned32(0x0004_0192)
180 );
181 assert_eq!(
182 od.read(Address::new(0x1017, 0)).unwrap(),
183 Value::Unsigned16(1000)
184 );
185 match od.read(Address::new(0x1008, 0)).unwrap() {
186 Value::VisibleString(bs) => assert_eq!(bs.as_str(), Some("Widget")),
187 other => panic!("expected VISIBLE_STRING, got {other:?}"),
188 }
189 }
190
191 #[test]
192 fn generator_emits_the_expected_source() {
193 let eds = Eds::parse(SAMPLE).unwrap();
194 let src = generate_object_dictionary(&eds, "device_od");
195
196 assert!(src.contains("pub fn device_od() -> canopen_rs::ObjectDictionary<3> {"));
197 assert!(src.contains(
198 "od.insert(canopen_rs::Address::new(0x1000, 0x00), canopen_rs::Entry { value: canopen_rs::Value::Unsigned32(0x00040192), access: canopen_rs::AccessType::Ro }).unwrap();"
199 ));
200 assert!(src.contains(
201 "od.insert(canopen_rs::Address::new(0x1017, 0x00), canopen_rs::Entry { value: canopen_rs::Value::Unsigned16(0x03e8), access: canopen_rs::AccessType::Rw }).unwrap();"
202 ));
203 assert!(src.contains(
205 "canopen_rs::Value::VisibleString(canopen_rs::ByteString::from_bytes(&[0x57, 0x69, 0x64, 0x67, 0x65, 0x74]).unwrap())"
206 ));
207 }
208}