1use serde::{Deserialize, Deserializer, Serialize};
4
5fn deserialize_hex_u64<'de, D: Deserializer<'de>>(d: D) -> Result<u64, D::Error> {
7 let s = String::deserialize(d)?;
8 s.strip_prefix("0x")
9 .or_else(|| s.strip_prefix("0X"))
10 .map_or_else(
11 || s.parse::<u64>().map_err(serde::de::Error::custom),
12 |hex| u64::from_str_radix(hex, 16).map_err(serde::de::Error::custom),
13 )
14}
15
16fn deserialize_opt_hex_u64<'de, D: Deserializer<'de>>(d: D) -> Result<Option<u64>, D::Error> {
18 deserialize_hex_u64(d).map(Some)
19}
20
21#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
22pub struct Boards {
24 #[serde(rename = "board")]
26 pub boards: Vec<Board>,
27}
28
29#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
30pub struct Board {
32 pub vendor: String,
34
35 pub name: String,
37
38 pub revision: Option<String>,
40
41 pub uuid: Option<String>,
43
44 #[serde(rename = "salesContact")]
46 pub sales_contact: Option<String>,
47
48 #[serde(rename = "orderForm")]
50 pub order_form: Option<String>,
51
52 #[serde(rename = "description", default)]
54 pub description: Vec<String>,
55
56 #[serde(rename = "feature", default)]
58 pub features: Vec<Feature>,
59
60 #[serde(rename = "mountedDevice", default)]
62 pub mounted_devices: Vec<MountedDevice>,
63
64 #[serde(rename = "compatibleDevice", default)]
66 pub compatible_devices: Vec<CompatibleDevice>,
67
68 #[serde(rename = "mountedPart", default)]
70 pub mounted_parts: Vec<MountedPart>,
71
72 pub image: Option<Image>,
74
75 #[serde(rename = "debugInterface", default)]
77 pub debug_interfaces: Vec<DebugInterface>,
78
79 #[serde(rename = "book", default)]
81 pub books: Vec<Book>,
82
83 #[serde(rename = "debugProbe")]
85 pub debug_probe: Option<DebugProbe>,
86
87 #[serde(rename = "memory", default)]
89 pub memories: Vec<Memory>,
90
91 #[serde(rename = "algorithm", default)]
93 pub algorithms: Vec<Algorithm>,
94
95 #[serde(rename = "environment", default)]
97 pub environments: Vec<BoardEnvironment>,
98}
99
100#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
101pub struct Feature {
103 #[serde(rename = "type")]
105 pub feature_type: String,
106
107 pub n: Option<String>,
109
110 pub m: Option<String>,
112
113 pub name: Option<String>,
115}
116
117#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
118pub struct MountedDevice {
120 #[serde(rename = "deviceIndex")]
122 pub device_index: Option<String>,
123
124 #[serde(rename = "Dvendor")]
126 pub device_vendor: String,
127
128 #[serde(rename = "Dname")]
130 pub device_name: String,
131
132 #[serde(rename = "Dfamily")]
134 pub device_family: Option<String>,
135
136 #[serde(rename = "DsubFamily")]
138 pub device_sub_family: Option<String>,
139}
140
141#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
142pub struct CompatibleDevice {
144 #[serde(rename = "deviceIndex")]
146 pub device_index: Option<String>,
147
148 #[serde(rename = "Dvendor")]
150 pub device_vendor: Option<String>,
151
152 #[serde(rename = "Dname")]
154 pub device_name: Option<String>,
155
156 #[serde(rename = "Dfamily")]
158 pub device_family: Option<String>,
159
160 #[serde(rename = "DsubFamily")]
162 pub device_sub_family: Option<String>,
163}
164
165#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
166pub struct MountedPart {
168 pub n: String,
170
171 #[serde(rename = "Hvendor")]
173 pub part_vendor: String,
174
175 #[serde(rename = "Hname")]
177 pub part_name: String,
178
179 #[serde(rename = "Hvariant")]
181 pub part_variant: Option<String>,
182
183 #[serde(rename = "Hrevision")]
185 pub part_revision: Option<String>,
186}
187
188#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
189pub struct Image {
191 pub large: Option<String>,
193
194 pub small: Option<String>,
196
197 pub bottom: Option<String>,
199
200 pub perspective: Option<String>,
202
203 pub public: Option<bool>,
205}
206
207#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
208pub struct DebugInterface {
210 pub adapter: Option<String>,
212
213 pub connector: Option<String>,
215}
216
217#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
218pub struct Book {
220 pub category: Option<String>,
222
223 pub name: Option<String>,
225
226 pub title: Option<String>,
228
229 pub public: Option<bool>,
231}
232
233#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
234pub struct DebugProbe {
236 #[serde(rename = "deviceIndex")]
238 pub device_index: Option<String>,
239
240 pub name: Option<String>,
242
243 pub version: Option<String>,
245
246 #[serde(rename = "debugLink")]
248 pub debug_link: Option<String>,
249
250 #[serde(
252 rename = "debugClock",
253 default,
254 deserialize_with = "deserialize_opt_hex_u64"
255 )]
256 pub debug_clock: Option<u64>,
257
258 pub connector: Option<String>,
260}
261
262#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
263pub struct Memory {
265 #[serde(rename = "Pname")]
267 pub processor_name: Option<String>,
268
269 pub id: Option<String>,
271
272 pub name: Option<String>,
274
275 pub access: Option<String>,
277
278 #[serde(deserialize_with = "deserialize_hex_u64")]
280 pub start: u64,
281
282 #[serde(deserialize_with = "deserialize_hex_u64")]
284 pub size: u64,
285
286 pub default: Option<bool>,
288
289 pub startup: Option<bool>,
291
292 pub uninit: Option<bool>,
294
295 pub init: Option<bool>,
297
298 pub alias: Option<String>,
300}
301
302#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
303pub struct Algorithm {
305 #[serde(rename = "Pname")]
307 pub processor_name: Option<String>,
308
309 #[serde(rename = "deviceIndex")]
311 pub device_index: Option<String>,
312
313 pub name: String,
315
316 #[serde(default, deserialize_with = "deserialize_opt_hex_u64")]
318 pub start: Option<u64>,
319
320 #[serde(default, deserialize_with = "deserialize_opt_hex_u64")]
322 pub size: Option<u64>,
323
324 pub parameter: Option<String>,
326
327 pub endian: Option<String>,
329
330 #[serde(
332 rename = "RAMstart",
333 default,
334 deserialize_with = "deserialize_opt_hex_u64"
335 )]
336 pub ram_start: Option<u64>,
337
338 #[serde(
340 rename = "RAMsize",
341 default,
342 deserialize_with = "deserialize_opt_hex_u64"
343 )]
344 pub ram_size: Option<u64>,
345
346 pub default: Option<bool>,
348
349 pub style: Option<String>,
351}
352
353#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
354pub struct BoardEnvironment {
356 pub name: String,
358
359 #[serde(rename = "Pname")]
361 pub processor_name: Option<String>,
362}
363
364#[cfg(test)]
365mod tests {
366 use crate::boards::{
367 Algorithm, BoardEnvironment, Boards, Book, CompatibleDevice, DebugProbe, Feature, Image,
368 Memory, MountedDevice, MountedPart,
369 };
370
371 #[test]
372 fn parse_boards() {
373 let xml_str = r#"<?xml version="1.0" encoding="UTF-8"?>
374<boards>
375 <board vendor="STMicroelectronics" name="NUCLEO-F401RE" revision="Rev.C">
376 <description>STM32 Nucleo-64 development board with STM32F401RE MCU</description>
377 <feature type="XTAL" n="8" name="High-speed crystal oscillator"/>
378 <mountedDevice Dvendor="STMicroelectronics:13" Dname="STM32F401RETx"/>
379 <compatibleDevice Dvendor="STMicroelectronics:13" Dname="STM32F401*"/>
380 <image large="images/nucleo_large.png" small="images/nucleo_small.png"/>
381 <book category="setup" name="docs/setup.pdf" title="Getting Started" public="true"/>
382 <memory name="FLASH" access="rx" start="0x08000000" size="0x80000" default="true" startup="true"/>
383 <algorithm name="Flash/STM32F4xx.FLM" start="0x08000000" size="0x80000" default="true"/>
384 </board>
385</boards>"#;
386
387 let boards: Boards = serde_roxmltree::from_str(xml_str).unwrap();
388 assert_eq!(boards.boards.len(), 1);
389
390 let board = &boards.boards[0];
391 assert_eq!(board.vendor, "STMicroelectronics");
392 assert_eq!(board.name, "NUCLEO-F401RE");
393 assert_eq!(board.revision, Some("Rev.C".to_string()));
394 assert_eq!(
395 board.description,
396 vec!["STM32 Nucleo-64 development board with STM32F401RE MCU".to_string()]
397 );
398 assert_eq!(
399 board.features,
400 vec![Feature {
401 feature_type: "XTAL".to_string(),
402 n: Some("8".to_string()),
403 m: None,
404 name: Some("High-speed crystal oscillator".to_string()),
405 }]
406 );
407 assert_eq!(
408 board.mounted_devices,
409 vec![MountedDevice {
410 device_index: None,
411 device_vendor: "STMicroelectronics:13".to_string(),
412 device_name: "STM32F401RETx".to_string(),
413 device_family: None,
414 device_sub_family: None,
415 }]
416 );
417 assert_eq!(
418 board.compatible_devices,
419 vec![CompatibleDevice {
420 device_index: None,
421 device_vendor: Some("STMicroelectronics:13".to_string()),
422 device_name: Some("STM32F401*".to_string()),
423 device_family: None,
424 device_sub_family: None,
425 }]
426 );
427 assert_eq!(
428 board.image,
429 Some(Image {
430 large: Some("images/nucleo_large.png".to_string()),
431 small: Some("images/nucleo_small.png".to_string()),
432 bottom: None,
433 perspective: None,
434 public: None,
435 })
436 );
437 assert_eq!(
438 board.books,
439 vec![Book {
440 category: Some("setup".to_string()),
441 name: Some("docs/setup.pdf".to_string()),
442 title: Some("Getting Started".to_string()),
443 public: Some(true),
444 }]
445 );
446 assert_eq!(
447 board.memories,
448 vec![Memory {
449 processor_name: None,
450 id: None,
451 name: Some("FLASH".to_string()),
452 access: Some("rx".to_string()),
453 start: 0x08000000,
454 size: 0x80000,
455 default: Some(true),
456 startup: Some(true),
457 uninit: None,
458 init: None,
459 alias: None,
460 }]
461 );
462 assert_eq!(
463 board.algorithms,
464 vec![Algorithm {
465 processor_name: None,
466 device_index: None,
467 name: "Flash/STM32F4xx.FLM".to_string(),
468 start: Some(0x08000000),
469 size: Some(0x80000),
470 parameter: None,
471 endian: None,
472 ram_start: None,
473 ram_size: None,
474 default: Some(true),
475 style: None,
476 }]
477 );
478 assert_eq!(board.mounted_parts, vec![]);
479 assert_eq!(board.debug_interfaces, vec![]);
480 assert_eq!(board.debug_probe, None);
481 }
482
483 #[test]
484 fn parse_board_minimal() {
485 let xml_str = r#"<?xml version="1.0" encoding="UTF-8"?>
486<boards>
487 <board vendor="Example" name="MyBoard">
488 <description>A minimal test board</description>
489 <feature type="LED" n="2"/>
490 <mountedDevice Dvendor="ARM:82" Dname="ARMCM0"/>
491 </board>
492</boards>"#;
493
494 let boards: Boards = serde_roxmltree::from_str(xml_str).unwrap();
495 let board = &boards.boards[0];
496
497 assert_eq!(board.vendor, "Example");
498 assert_eq!(board.name, "MyBoard");
499 assert_eq!(board.revision, None);
500 assert_eq!(board.uuid, None);
501 assert_eq!(board.description, vec!["A minimal test board".to_string()]);
502 assert_eq!(
503 board.features,
504 vec![Feature {
505 feature_type: "LED".to_string(),
506 n: Some("2".to_string()),
507 m: None,
508 name: None,
509 }]
510 );
511 assert_eq!(
512 board.mounted_devices,
513 vec![MountedDevice {
514 device_index: None,
515 device_vendor: "ARM:82".to_string(),
516 device_name: "ARMCM0".to_string(),
517 device_family: None,
518 device_sub_family: None,
519 }]
520 );
521 assert_eq!(board.compatible_devices, vec![]);
522 assert_eq!(board.mounted_parts, vec![]);
523 assert_eq!(board.books, vec![]);
524 assert_eq!(board.memories, vec![]);
525 assert_eq!(board.algorithms, vec![]);
526 assert_eq!(board.image, None);
527 assert_eq!(board.debug_probe, None);
528 }
529
530 #[test]
531 fn parse_board_debug_probe() {
532 let xml_str = r#"<?xml version="1.0" encoding="UTF-8"?>
533<boards>
534 <board vendor="ARM" name="MPS2" salesContact="support@arm.com">
535 <description>ARM MPS2 FPGA prototyping board</description>
536 <feature type="JTAG" n="1"/>
537 <mountedDevice Dvendor="ARM:82" Dname="ARMCM3"/>
538 <mountedPart n="1" Hvendor="Xilinx" Hname="XC7A200T" Hvariant="-1FBG484C"/>
539 <debugProbe name="CMSIS-DAP" version="2.0" debugLink="swd" debugClock="10000000" connector="USB-C"/>
540 </board>
541</boards>"#;
542
543 let boards: Boards = serde_roxmltree::from_str(xml_str).unwrap();
544 let board = &boards.boards[0];
545
546 assert_eq!(board.vendor, "ARM");
547 assert_eq!(board.name, "MPS2");
548 assert_eq!(board.sales_contact, Some("support@arm.com".to_string()));
549 assert_eq!(
550 board.mounted_parts,
551 vec![MountedPart {
552 n: "1".to_string(),
553 part_vendor: "Xilinx".to_string(),
554 part_name: "XC7A200T".to_string(),
555 part_variant: Some("-1FBG484C".to_string()),
556 part_revision: None,
557 }]
558 );
559 assert_eq!(
560 board.debug_probe,
561 Some(DebugProbe {
562 device_index: None,
563 name: Some("CMSIS-DAP".to_string()),
564 version: Some("2.0".to_string()),
565 debug_link: Some("swd".to_string()),
566 debug_clock: Some(10_000_000),
567 connector: Some("USB-C".to_string()),
568 })
569 );
570 assert_eq!(board.compatible_devices, vec![]);
571 assert_eq!(board.memories, vec![]);
572 assert_eq!(board.algorithms, vec![]);
573 }
574
575 #[test]
576 fn parse_board_environments() {
577 let xml_str = r#"<?xml version="1.0" encoding="UTF-8"?>
578<boards>
579 <board vendor="Example" name="EnvBoard">
580 <description>Board with IDE environments</description>
581 <mountedDevice Dvendor="ARM:82" Dname="ARMCM4"/>
582 <environment name="uvision"/>
583 <environment name="iar" Pname="Core0"/>
584 </board>
585</boards>"#;
586
587 let boards: Boards = serde_roxmltree::from_str(xml_str).unwrap();
588 let board = &boards.boards[0];
589
590 assert_eq!(board.vendor, "Example");
591 assert_eq!(board.name, "EnvBoard");
592 assert_eq!(board.environments.len(), 2);
593 assert_eq!(
594 board.environments[0],
595 BoardEnvironment {
596 name: "uvision".to_string(),
597 processor_name: None,
598 }
599 );
600 assert_eq!(
601 board.environments[1],
602 BoardEnvironment {
603 name: "iar".to_string(),
604 processor_name: Some("Core0".to_string()),
605 }
606 );
607 }
608}