1use std::{collections::HashMap, path::Path};
2
3use serde::{de, Deserialize, Deserializer};
4use snafu::{ResultExt, Snafu};
5use zencan_common::CanId;
6
7#[derive(Debug, Snafu)]
9pub enum ConfigError {
10 #[snafu(display("IO error loading {path}: {source:?}"))]
11 Io {
12 path: String,
13 source: std::io::Error,
14 },
15 #[snafu(display("Error parsing TOML: {source}"))]
16 TomlDeserialization { source: toml::de::Error },
17}
18
19#[derive(Clone, Debug, PartialEq)]
21pub struct Store {
22 pub index: u16,
24 pub sub: u8,
26 pub value: StoreValue,
28}
29
30impl Store {
31 pub fn raw_value(&self) -> Vec<u8> {
33 self.value.raw()
34 }
35}
36
37#[derive(Clone, Debug, Deserialize, PartialEq)]
39pub enum StoreValue {
40 U32(u32),
41 U16(u16),
42 U8(u8),
43 I32(i32),
44 I16(i16),
45 I8(i8),
46 F32(f32),
47 String(String),
48}
49
50impl StoreValue {
51 pub fn raw(&self) -> Vec<u8> {
52 match self {
53 StoreValue::U32(v) => v.to_le_bytes().to_vec(),
54 StoreValue::U16(v) => v.to_le_bytes().to_vec(),
55 StoreValue::U8(v) => vec![*v],
56 StoreValue::I32(v) => v.to_le_bytes().to_vec(),
57 StoreValue::I16(v) => v.to_le_bytes().to_vec(),
58 StoreValue::I8(v) => vec![*v as u8],
59 StoreValue::F32(v) => v.to_le_bytes().to_vec(),
60 StoreValue::String(ref s) => s.as_bytes().to_vec(),
61 }
62 }
63}
64
65#[derive(Debug, Clone)]
71pub struct NodeConfig(NodeConfigSerializer);
72
73impl NodeConfig {
74 pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<NodeConfig, ConfigError> {
76 let path = path.as_ref();
77 let content = std::fs::read_to_string(path).context(IoSnafu {
78 path: path.to_string_lossy(),
79 })?;
80 Self::load_from_str(&content)
81 }
82
83 pub fn load_from_str(s: &str) -> Result<NodeConfig, ConfigError> {
85 let raw_config: NodeConfigSerializer =
86 toml::from_str(s).context(TomlDeserializationSnafu)?;
87
88 Ok(NodeConfig(raw_config))
89 }
90
91 pub fn tpdos(&self) -> &HashMap<usize, PdoConfig> {
93 &self.0.tpdo
94 }
95
96 pub fn rpdos(&self) -> &HashMap<usize, PdoConfig> {
98 &self.0.rpdo
99 }
100
101 pub fn stores(&self) -> &[Store] {
105 &self.0.store
106 }
107}
108
109#[derive(Clone, Debug, Deserialize)]
110#[serde(deny_unknown_fields)]
111struct NodeConfigSerializer {
112 #[serde(deserialize_with = "deserialize_pdo_map", default)]
113 pub tpdo: HashMap<usize, PdoConfig>,
114 #[serde(deserialize_with = "deserialize_pdo_map", default)]
115 pub rpdo: HashMap<usize, PdoConfig>,
116 #[serde(default, deserialize_with = "deserialize_store")]
117 pub store: Vec<Store>,
118}
119
120#[derive(Clone, Debug, Deserialize)]
122#[serde(deny_unknown_fields)]
123struct PdoConfigSerializer {
124 pub cob: u32,
126 #[serde(default)]
127 pub extended: bool,
128 pub enabled: bool,
130 pub mappings: Vec<PdoMapping>,
132 pub transmission_type: u8,
139}
140
141#[derive(Clone, Debug, Deserialize)]
143#[serde(try_from = "PdoConfigSerializer")]
144pub struct PdoConfig {
145 pub cob: CanId,
147 pub enabled: bool,
149 pub mappings: Vec<PdoMapping>,
151 pub transmission_type: u8,
158}
159
160#[derive(Clone, Debug, Snafu)]
161#[snafu(display("{message}"))]
162pub struct PdoConfigParseError {
163 message: String,
164}
165
166impl TryFrom<PdoConfigSerializer> for PdoConfig {
167 type Error = PdoConfigParseError;
168
169 fn try_from(value: PdoConfigSerializer) -> Result<Self, Self::Error> {
170 let cob = if value.extended {
171 CanId::extended(value.cob)
172 } else {
173 if value.cob > 0x7ff {
174 return Err(PdoConfigParseError {
175 message: format!(
176 "COB ID 0x{:x} is out of range for standard ID. Set `extended` to true.",
177 value.cob
178 ),
179 });
180 }
181 CanId::std(value.cob as u16)
182 };
183
184 Ok(PdoConfig {
185 cob,
186 enabled: value.enabled,
187 mappings: value.mappings,
188 transmission_type: value.transmission_type,
189 })
190 }
191}
192
193#[derive(Clone, Copy, Debug, Deserialize)]
197#[serde(deny_unknown_fields)]
198pub struct PdoMapping {
199 pub index: u16,
201 pub sub: u8,
203 pub size: u8,
205}
206
207impl PdoMapping {
208 pub fn to_object_value(&self) -> u32 {
210 ((self.index as u32) << 16) | ((self.sub as u32) << 8) | (self.size as u32)
211 }
212
213 pub fn from_object_value(value: u32) -> Self {
215 let index = (value >> 16) as u16;
216 let sub = ((value >> 8) & 0xff) as u8;
217 let size = (value & 0xff) as u8;
218 Self { index, sub, size }
219 }
220}
221
222#[derive(Debug, Clone, Copy, Deserialize)]
223#[serde(rename_all = "lowercase")]
224enum StoreType {
225 U32,
226 U16,
227 U8,
228 I32,
229 I16,
230 I8,
231 F32,
232 String,
233}
234
235#[derive(Debug, Deserialize)]
236#[serde(deny_unknown_fields)]
237struct StoreSerializer {
238 pub index: u16,
239 pub sub: u8,
240 pub value: toml::Value,
241 #[serde(rename = "type")]
242 pub ty: StoreType,
243}
244
245fn deserialize_store<'de, D>(deserializer: D) -> Result<Vec<Store>, D::Error>
246where
247 D: Deserializer<'de>,
248{
249 let raw_store = Vec::<StoreSerializer>::deserialize(deserializer)?;
250
251 let store = raw_store
252 .into_iter()
253 .map(|raw| {
254 let value = match raw.ty {
255 StoreType::U32 => {
256 let value = raw.value.as_integer().ok_or(de::Error::invalid_type(
257 de::Unexpected::Str(&raw.value.to_string()),
258 &"an integer",
259 ))?;
260 Ok(StoreValue::U32(value.try_into().map_err(|_| {
261 de::Error::invalid_value(
262 de::Unexpected::Signed(value),
263 &"an integer in range [0..2^32]",
264 )
265 })?))
266 }
267 StoreType::U16 => {
268 let value = raw.value.as_integer().ok_or(de::Error::invalid_type(
269 de::Unexpected::Str(&raw.value.to_string()),
270 &"an integer",
271 ))?;
272 Ok(StoreValue::U16(value.try_into().map_err(|_| {
273 de::Error::invalid_value(
274 de::Unexpected::Signed(value),
275 &"an integer in range [0..65536]",
276 )
277 })?))
278 }
279 StoreType::U8 => {
280 let value = raw.value.as_integer().ok_or(de::Error::invalid_type(
281 de::Unexpected::Str(&raw.value.to_string()),
282 &"an integer",
283 ))?;
284 Ok(StoreValue::U8(value.try_into().map_err(|_| {
285 de::Error::invalid_value(
286 de::Unexpected::Signed(value),
287 &"an integer in range [0..256]",
288 )
289 })?))
290 }
291 StoreType::I32 => {
292 let value = raw.value.as_integer().ok_or(de::Error::invalid_type(
293 de::Unexpected::Str(&raw.value.to_string()),
294 &"an integer",
295 ))?;
296 Ok(StoreValue::I32(value.try_into().map_err(|_| {
297 de::Error::invalid_value(
298 de::Unexpected::Signed(value),
299 &"an integer in range [-2^31..2^31]",
300 )
301 })?))
302 }
303 StoreType::I16 => {
304 let value = raw.value.as_integer().ok_or(de::Error::invalid_type(
305 de::Unexpected::Str(&raw.value.to_string()),
306 &"an integer",
307 ))?;
308 Ok(StoreValue::I16(value.try_into().map_err(|_| {
309 de::Error::invalid_value(
310 de::Unexpected::Signed(value),
311 &"an integer in range [-32767..32768]",
312 )
313 })?))
314 }
315 StoreType::I8 => {
316 let value = raw.value.as_integer().ok_or(de::Error::invalid_type(
317 de::Unexpected::Str(&raw.value.to_string()),
318 &"an integer",
319 ))?;
320 Ok(StoreValue::I8(value.try_into().map_err(|_| {
321 de::Error::invalid_value(
322 de::Unexpected::Signed(value),
323 &"an integer in range [-127..128]",
324 )
325 })?))
326 }
327 StoreType::F32 => {
328 let value = raw.value.as_float().ok_or(de::Error::invalid_type(
329 de::Unexpected::Str(&raw.value.to_string()),
330 &"a float",
331 ))?;
332 Ok(StoreValue::F32(value as f32))
333 }
334 StoreType::String => {
335 let value = raw.value.as_str().ok_or(de::Error::invalid_type(
336 de::Unexpected::Str(&raw.value.to_string()),
337 &"a string",
338 ))?;
339 Ok(StoreValue::String(value.to_string()))
340 }
341 }?;
342 Ok(Store {
343 index: raw.index,
344 sub: raw.sub,
345 value,
346 })
347 })
348 .collect::<Result<Vec<_>, _>>()?;
349
350 Ok(store)
351}
352
353fn deserialize_pdo_map<'de, D>(deserializer: D) -> Result<HashMap<usize, PdoConfig>, D::Error>
354where
355 D: Deserializer<'de>,
356{
357 let str_map = HashMap::<String, PdoConfig>::deserialize(deserializer)?;
358 let original_len = str_map.len();
359 let data = {
360 str_map
361 .into_iter()
362 .map(|(str_key, value)| match str_key.parse() {
363 Ok(int_key) => Ok((int_key, value)),
364 Err(_) => Err({
365 de::Error::invalid_value(
366 de::Unexpected::Str(&str_key),
367 &"a non-negative integer",
368 )
369 }),
370 })
371 .collect::<Result<HashMap<_, _>, _>>()?
372 };
373 if data.len() < original_len {
375 return Err(de::Error::custom("detected duplicate integer key"));
376 }
377 Ok(data)
378}
379
380#[cfg(test)]
381mod test {
382 use super::*;
383 use assertables::assert_contains;
384
385 #[test]
386 fn test_out_of_range_standard_id() {
387 let str = r#"
388 [tpdo.0]
389 enabled = true
390 cob = 0x800
391 transmission_type = 254
392 mappings = [
393 { index=0x1000, sub=1, size=8 },
394 ]
395 "#;
396
397 let result = NodeConfig::load_from_str(str);
398 assert!(result.is_err());
399 let err = result.unwrap_err();
400 assert_contains!(
401 &err.to_string(),
402 "COB ID 0x800 is out of range for standard ID"
403 );
404 }
405
406 #[test]
407 fn test_extended_cob() {
408 let str = r#"
409 [tpdo.0]
410 enabled = true
411 cob = 0x800
412 extended = true
413 transmission_type = 254
414 mappings = [
415 { index=0x1000, sub=1, size=8 },
416 ]
417 "#;
418
419 let result = NodeConfig::load_from_str(str).unwrap();
420 assert_eq!(1, result.tpdos().len());
421 let tpdo = result.tpdos().get(&0).unwrap();
422 assert_eq!(CanId::extended(0x800), tpdo.cob);
423 }
424
425 #[test]
426 fn test_node_config_parse() {
427 let str = r#"
428 [tpdo.0]
429 enabled = true
430 cob = 0x181
431 transmission_type = 254
432 mappings = [
433 { index=0x1000, sub=1, size=8 },
434 { index=0x1000, sub=2, size=16 },
435 ]
436
437 [[store]]
438 type = "u32"
439 value = 12
440 index = 0x1000
441 sub = 0
442 "#;
443
444 let config = match NodeConfig::load_from_str(str) {
445 Ok(config) => config,
446 Err(e) => {
447 println!("{}", e);
448 panic!("Failed to parse config");
449 }
450 };
451
452 println!("{config:?}");
453 assert_eq!(1, config.tpdos().len());
454 assert_eq!(1, config.stores().len());
455 }
456
457 #[test]
458 fn test_out_of_range_integer() {
459 let str = r#"
460 [[store]]
461 type = "u8"
462 value = 256
463 index = 0x1000
464 sub = 0
465 "#;
466
467 let result = NodeConfig::load_from_str(str);
468 assert!(result.is_err());
469 assert!(result
470 .unwrap_err()
471 .to_string()
472 .contains("expected an integer in range [0..256]"));
473 }
474}