use serde::{Deserialize, Deserializer, Serialize};
fn deserialize_hex_u64<'de, D: Deserializer<'de>>(d: D) -> Result<u64, D::Error> {
let s = String::deserialize(d)?;
s.strip_prefix("0x")
.or_else(|| s.strip_prefix("0X"))
.map_or_else(
|| s.parse::<u64>().map_err(serde::de::Error::custom),
|hex| u64::from_str_radix(hex, 16).map_err(serde::de::Error::custom),
)
}
fn deserialize_opt_hex_u64<'de, D: Deserializer<'de>>(d: D) -> Result<Option<u64>, D::Error> {
deserialize_hex_u64(d).map(Some)
}
#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
pub struct Boards {
#[serde(rename = "board")]
pub boards: Vec<Board>,
}
#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
pub struct Board {
pub vendor: String,
pub name: String,
pub revision: Option<String>,
pub uuid: Option<String>,
#[serde(rename = "salesContact")]
pub sales_contact: Option<String>,
#[serde(rename = "orderForm")]
pub order_form: Option<String>,
#[serde(rename = "description", default)]
pub description: Vec<String>,
#[serde(rename = "feature", default)]
pub features: Vec<Feature>,
#[serde(rename = "mountedDevice", default)]
pub mounted_devices: Vec<MountedDevice>,
#[serde(rename = "compatibleDevice", default)]
pub compatible_devices: Vec<CompatibleDevice>,
#[serde(rename = "mountedPart", default)]
pub mounted_parts: Vec<MountedPart>,
pub image: Option<Image>,
#[serde(rename = "debugInterface", default)]
pub debug_interfaces: Vec<DebugInterface>,
#[serde(rename = "book", default)]
pub books: Vec<Book>,
#[serde(rename = "debugProbe")]
pub debug_probe: Option<DebugProbe>,
#[serde(rename = "memory", default)]
pub memories: Vec<Memory>,
#[serde(rename = "algorithm", default)]
pub algorithms: Vec<Algorithm>,
#[serde(rename = "environment", default)]
pub environments: Vec<BoardEnvironment>,
}
#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
pub struct Feature {
#[serde(rename = "type")]
pub feature_type: String,
pub n: Option<String>,
pub m: Option<String>,
pub name: Option<String>,
}
#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
pub struct MountedDevice {
#[serde(rename = "deviceIndex")]
pub device_index: Option<String>,
#[serde(rename = "Dvendor")]
pub device_vendor: String,
#[serde(rename = "Dname")]
pub device_name: String,
#[serde(rename = "Dfamily")]
pub device_family: Option<String>,
#[serde(rename = "DsubFamily")]
pub device_sub_family: Option<String>,
}
#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
pub struct CompatibleDevice {
#[serde(rename = "deviceIndex")]
pub device_index: Option<String>,
#[serde(rename = "Dvendor")]
pub device_vendor: Option<String>,
#[serde(rename = "Dname")]
pub device_name: Option<String>,
#[serde(rename = "Dfamily")]
pub device_family: Option<String>,
#[serde(rename = "DsubFamily")]
pub device_sub_family: Option<String>,
}
#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
pub struct MountedPart {
pub n: String,
#[serde(rename = "Hvendor")]
pub part_vendor: String,
#[serde(rename = "Hname")]
pub part_name: String,
#[serde(rename = "Hvariant")]
pub part_variant: Option<String>,
#[serde(rename = "Hrevision")]
pub part_revision: Option<String>,
}
#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
pub struct Image {
pub large: Option<String>,
pub small: Option<String>,
pub bottom: Option<String>,
pub perspective: Option<String>,
pub public: Option<bool>,
}
#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
pub struct DebugInterface {
pub adapter: Option<String>,
pub connector: Option<String>,
}
#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
pub struct Book {
pub category: Option<String>,
pub name: Option<String>,
pub title: Option<String>,
pub public: Option<bool>,
}
#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
pub struct DebugProbe {
#[serde(rename = "deviceIndex")]
pub device_index: Option<String>,
pub name: Option<String>,
pub version: Option<String>,
#[serde(rename = "debugLink")]
pub debug_link: Option<String>,
#[serde(
rename = "debugClock",
default,
deserialize_with = "deserialize_opt_hex_u64"
)]
pub debug_clock: Option<u64>,
pub connector: Option<String>,
}
#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
pub struct Memory {
#[serde(rename = "Pname")]
pub processor_name: Option<String>,
pub id: Option<String>,
pub name: Option<String>,
pub access: Option<String>,
#[serde(deserialize_with = "deserialize_hex_u64")]
pub start: u64,
#[serde(deserialize_with = "deserialize_hex_u64")]
pub size: u64,
pub default: Option<bool>,
pub startup: Option<bool>,
pub uninit: Option<bool>,
pub init: Option<bool>,
pub alias: Option<String>,
}
#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
pub struct Algorithm {
#[serde(rename = "Pname")]
pub processor_name: Option<String>,
#[serde(rename = "deviceIndex")]
pub device_index: Option<String>,
pub name: String,
#[serde(default, deserialize_with = "deserialize_opt_hex_u64")]
pub start: Option<u64>,
#[serde(default, deserialize_with = "deserialize_opt_hex_u64")]
pub size: Option<u64>,
pub parameter: Option<String>,
pub endian: Option<String>,
#[serde(
rename = "RAMstart",
default,
deserialize_with = "deserialize_opt_hex_u64"
)]
pub ram_start: Option<u64>,
#[serde(
rename = "RAMsize",
default,
deserialize_with = "deserialize_opt_hex_u64"
)]
pub ram_size: Option<u64>,
pub default: Option<bool>,
pub style: Option<String>,
}
#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
pub struct BoardEnvironment {
pub name: String,
#[serde(rename = "Pname")]
pub processor_name: Option<String>,
}
#[cfg(test)]
mod tests {
use crate::boards::{
Algorithm, BoardEnvironment, Boards, Book, CompatibleDevice, DebugProbe, Feature, Image,
Memory, MountedDevice, MountedPart,
};
#[test]
fn parse_boards() {
let xml_str = r#"<?xml version="1.0" encoding="UTF-8"?>
<boards>
<board vendor="STMicroelectronics" name="NUCLEO-F401RE" revision="Rev.C">
<description>STM32 Nucleo-64 development board with STM32F401RE MCU</description>
<feature type="XTAL" n="8" name="High-speed crystal oscillator"/>
<mountedDevice Dvendor="STMicroelectronics:13" Dname="STM32F401RETx"/>
<compatibleDevice Dvendor="STMicroelectronics:13" Dname="STM32F401*"/>
<image large="images/nucleo_large.png" small="images/nucleo_small.png"/>
<book category="setup" name="docs/setup.pdf" title="Getting Started" public="true"/>
<memory name="FLASH" access="rx" start="0x08000000" size="0x80000" default="true" startup="true"/>
<algorithm name="Flash/STM32F4xx.FLM" start="0x08000000" size="0x80000" default="true"/>
</board>
</boards>"#;
let boards: Boards = serde_roxmltree::from_str(xml_str).unwrap();
assert_eq!(boards.boards.len(), 1);
let board = &boards.boards[0];
assert_eq!(board.vendor, "STMicroelectronics");
assert_eq!(board.name, "NUCLEO-F401RE");
assert_eq!(board.revision, Some("Rev.C".to_string()));
assert_eq!(
board.description,
vec!["STM32 Nucleo-64 development board with STM32F401RE MCU".to_string()]
);
assert_eq!(
board.features,
vec![Feature {
feature_type: "XTAL".to_string(),
n: Some("8".to_string()),
m: None,
name: Some("High-speed crystal oscillator".to_string()),
}]
);
assert_eq!(
board.mounted_devices,
vec![MountedDevice {
device_index: None,
device_vendor: "STMicroelectronics:13".to_string(),
device_name: "STM32F401RETx".to_string(),
device_family: None,
device_sub_family: None,
}]
);
assert_eq!(
board.compatible_devices,
vec![CompatibleDevice {
device_index: None,
device_vendor: Some("STMicroelectronics:13".to_string()),
device_name: Some("STM32F401*".to_string()),
device_family: None,
device_sub_family: None,
}]
);
assert_eq!(
board.image,
Some(Image {
large: Some("images/nucleo_large.png".to_string()),
small: Some("images/nucleo_small.png".to_string()),
bottom: None,
perspective: None,
public: None,
})
);
assert_eq!(
board.books,
vec![Book {
category: Some("setup".to_string()),
name: Some("docs/setup.pdf".to_string()),
title: Some("Getting Started".to_string()),
public: Some(true),
}]
);
assert_eq!(
board.memories,
vec![Memory {
processor_name: None,
id: None,
name: Some("FLASH".to_string()),
access: Some("rx".to_string()),
start: 0x08000000,
size: 0x80000,
default: Some(true),
startup: Some(true),
uninit: None,
init: None,
alias: None,
}]
);
assert_eq!(
board.algorithms,
vec![Algorithm {
processor_name: None,
device_index: None,
name: "Flash/STM32F4xx.FLM".to_string(),
start: Some(0x08000000),
size: Some(0x80000),
parameter: None,
endian: None,
ram_start: None,
ram_size: None,
default: Some(true),
style: None,
}]
);
assert_eq!(board.mounted_parts, vec![]);
assert_eq!(board.debug_interfaces, vec![]);
assert_eq!(board.debug_probe, None);
}
#[test]
fn parse_board_minimal() {
let xml_str = r#"<?xml version="1.0" encoding="UTF-8"?>
<boards>
<board vendor="Example" name="MyBoard">
<description>A minimal test board</description>
<feature type="LED" n="2"/>
<mountedDevice Dvendor="ARM:82" Dname="ARMCM0"/>
</board>
</boards>"#;
let boards: Boards = serde_roxmltree::from_str(xml_str).unwrap();
let board = &boards.boards[0];
assert_eq!(board.vendor, "Example");
assert_eq!(board.name, "MyBoard");
assert_eq!(board.revision, None);
assert_eq!(board.uuid, None);
assert_eq!(board.description, vec!["A minimal test board".to_string()]);
assert_eq!(
board.features,
vec![Feature {
feature_type: "LED".to_string(),
n: Some("2".to_string()),
m: None,
name: None,
}]
);
assert_eq!(
board.mounted_devices,
vec![MountedDevice {
device_index: None,
device_vendor: "ARM:82".to_string(),
device_name: "ARMCM0".to_string(),
device_family: None,
device_sub_family: None,
}]
);
assert_eq!(board.compatible_devices, vec![]);
assert_eq!(board.mounted_parts, vec![]);
assert_eq!(board.books, vec![]);
assert_eq!(board.memories, vec![]);
assert_eq!(board.algorithms, vec![]);
assert_eq!(board.image, None);
assert_eq!(board.debug_probe, None);
}
#[test]
fn parse_board_debug_probe() {
let xml_str = r#"<?xml version="1.0" encoding="UTF-8"?>
<boards>
<board vendor="ARM" name="MPS2" salesContact="support@arm.com">
<description>ARM MPS2 FPGA prototyping board</description>
<feature type="JTAG" n="1"/>
<mountedDevice Dvendor="ARM:82" Dname="ARMCM3"/>
<mountedPart n="1" Hvendor="Xilinx" Hname="XC7A200T" Hvariant="-1FBG484C"/>
<debugProbe name="CMSIS-DAP" version="2.0" debugLink="swd" debugClock="10000000" connector="USB-C"/>
</board>
</boards>"#;
let boards: Boards = serde_roxmltree::from_str(xml_str).unwrap();
let board = &boards.boards[0];
assert_eq!(board.vendor, "ARM");
assert_eq!(board.name, "MPS2");
assert_eq!(board.sales_contact, Some("support@arm.com".to_string()));
assert_eq!(
board.mounted_parts,
vec![MountedPart {
n: "1".to_string(),
part_vendor: "Xilinx".to_string(),
part_name: "XC7A200T".to_string(),
part_variant: Some("-1FBG484C".to_string()),
part_revision: None,
}]
);
assert_eq!(
board.debug_probe,
Some(DebugProbe {
device_index: None,
name: Some("CMSIS-DAP".to_string()),
version: Some("2.0".to_string()),
debug_link: Some("swd".to_string()),
debug_clock: Some(10_000_000),
connector: Some("USB-C".to_string()),
})
);
assert_eq!(board.compatible_devices, vec![]);
assert_eq!(board.memories, vec![]);
assert_eq!(board.algorithms, vec![]);
}
#[test]
fn parse_board_environments() {
let xml_str = r#"<?xml version="1.0" encoding="UTF-8"?>
<boards>
<board vendor="Example" name="EnvBoard">
<description>Board with IDE environments</description>
<mountedDevice Dvendor="ARM:82" Dname="ARMCM4"/>
<environment name="uvision"/>
<environment name="iar" Pname="Core0"/>
</board>
</boards>"#;
let boards: Boards = serde_roxmltree::from_str(xml_str).unwrap();
let board = &boards.boards[0];
assert_eq!(board.vendor, "Example");
assert_eq!(board.name, "EnvBoard");
assert_eq!(board.environments.len(), 2);
assert_eq!(
board.environments[0],
BoardEnvironment {
name: "uvision".to_string(),
processor_name: None,
}
);
assert_eq!(
board.environments[1],
BoardEnvironment {
name: "iar".to_string(),
processor_name: Some("Core0".to_string()),
}
);
}
}