1use crate::core::firmware::FirmwareVersion;
10use serde::Serialize;
11use serde_json::Value;
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
15pub struct Module {
16 pub name: String,
18 pub hw_ver: Option<String>,
20 pub sw_ver: Option<String>,
22 pub product_name: Option<String>,
25}
26
27impl Module {
28 fn from_value(v: &Value) -> Option<Module> {
30 let name = v.get("name").and_then(Value::as_str)?.to_string();
31 let nonempty = |key: &str| {
32 v.get(key)
33 .and_then(Value::as_str)
34 .filter(|s| !s.is_empty())
35 .map(str::to_owned)
36 };
37 Some(Module {
38 name,
39 hw_ver: nonempty("hw_ver"),
40 sw_ver: nonempty("sw_ver"),
41 product_name: nonempty("product_name"),
42 })
43 }
44}
45
46#[derive(Debug, Clone, PartialEq, Serialize)]
48pub struct DeviceVersion {
49 pub firmware: Option<FirmwareVersion>,
53 pub modules: Vec<Module>,
55}
56
57impl DeviceVersion {
58 pub fn from_info(info: &Value) -> Self {
61 let modules: Vec<Module> = info
62 .get("module")
63 .and_then(Value::as_array)
64 .map(|arr| arr.iter().filter_map(Module::from_value).collect())
65 .unwrap_or_default();
66 let firmware = modules
67 .iter()
68 .find(|m| m.name == "ota")
69 .and_then(|m| m.sw_ver.as_deref())
70 .and_then(|s| FirmwareVersion::parse(s).ok());
71 DeviceVersion { firmware, modules }
72 }
73
74 pub fn module(&self, name: &str) -> Option<&Module> {
76 self.modules.iter().find(|m| m.name == name)
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83 use serde_json::json;
84
85 fn fixture() -> Value {
86 let raw = include_str!("../../tests/fixtures/get_version-a1mini.json");
87 serde_json::from_str(raw).expect("valid fixture json")
88 }
89
90 #[test]
91 fn parses_firmware_from_the_ota_module() {
92 let info = &fixture()["message"]["info"];
93 let v = DeviceVersion::from_info(info);
94 assert_eq!(
96 v.firmware.as_ref().map(ToString::to_string).as_deref(),
97 Some("01.07.02.00")
98 );
99 }
100
101 #[test]
102 fn parses_all_modules_in_order() {
103 let v = DeviceVersion::from_info(&fixture()["message"]["info"]);
104 let names: Vec<&str> = v.modules.iter().map(|m| m.name.as_str()).collect();
105 assert_eq!(names, ["ota", "esp32", "mc", "th", "ams_f1/0"]);
106 }
107
108 #[test]
109 fn module_carries_hw_and_product_name() {
110 let v = DeviceVersion::from_info(&fixture()["message"]["info"]);
111 let ota = v.module("ota").unwrap();
112 assert_eq!(ota.hw_ver.as_deref(), Some("OTA"));
113 assert_eq!(ota.product_name.as_deref(), Some("Bambu Lab A1 mini"));
114 let ams = v.module("ams_f1/0").unwrap();
116 assert_eq!(ams.hw_ver.as_deref(), Some("AMS_F102"));
117 assert_eq!(ams.product_name.as_deref(), Some("AMS Lite"));
118 assert_eq!(v.module("esp32").unwrap().product_name, None);
120 }
121
122 #[test]
123 fn missing_ota_module_yields_no_firmware() {
124 let info = json!({ "command": "get_version", "module": [
125 { "name": "esp32", "sw_ver": "01.16.39.58" }
126 ]});
127 let v = DeviceVersion::from_info(&info);
128 assert_eq!(v.firmware, None);
129 assert_eq!(v.modules.len(), 1);
130 }
131
132 #[test]
133 fn empty_or_absent_module_array_is_empty_inventory() {
134 let v = DeviceVersion::from_info(&json!({ "command": "get_version" }));
135 assert_eq!(v.firmware, None);
136 assert!(v.modules.is_empty());
137 }
138}