use crate::core::firmware::FirmwareVersion;
use serde::Serialize;
use serde_json::Value;
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct Module {
pub name: String,
pub hw_ver: Option<String>,
pub sw_ver: Option<String>,
pub product_name: Option<String>,
}
impl Module {
fn from_value(v: &Value) -> Option<Module> {
let name = v.get("name").and_then(Value::as_str)?.to_string();
let nonempty = |key: &str| {
v.get(key)
.and_then(Value::as_str)
.filter(|s| !s.is_empty())
.map(str::to_owned)
};
Some(Module {
name,
hw_ver: nonempty("hw_ver"),
sw_ver: nonempty("sw_ver"),
product_name: nonempty("product_name"),
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct DeviceVersion {
pub firmware: Option<FirmwareVersion>,
pub modules: Vec<Module>,
}
impl DeviceVersion {
pub fn from_info(info: &Value) -> Self {
let modules: Vec<Module> = info
.get("module")
.and_then(Value::as_array)
.map(|arr| arr.iter().filter_map(Module::from_value).collect())
.unwrap_or_default();
let firmware = modules
.iter()
.find(|m| m.name == "ota")
.and_then(|m| m.sw_ver.as_deref())
.and_then(|s| FirmwareVersion::parse(s).ok());
DeviceVersion { firmware, modules }
}
pub fn module(&self, name: &str) -> Option<&Module> {
self.modules.iter().find(|m| m.name == name)
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn fixture() -> Value {
let raw = include_str!("../../tests/fixtures/get_version-a1mini.json");
serde_json::from_str(raw).expect("valid fixture json")
}
#[test]
fn parses_firmware_from_the_ota_module() {
let info = &fixture()["message"]["info"];
let v = DeviceVersion::from_info(info);
assert_eq!(
v.firmware.as_ref().map(ToString::to_string).as_deref(),
Some("01.07.02.00")
);
}
#[test]
fn parses_all_modules_in_order() {
let v = DeviceVersion::from_info(&fixture()["message"]["info"]);
let names: Vec<&str> = v.modules.iter().map(|m| m.name.as_str()).collect();
assert_eq!(names, ["ota", "esp32", "mc", "th", "ams_f1/0"]);
}
#[test]
fn module_carries_hw_and_product_name() {
let v = DeviceVersion::from_info(&fixture()["message"]["info"]);
let ota = v.module("ota").unwrap();
assert_eq!(ota.hw_ver.as_deref(), Some("OTA"));
assert_eq!(ota.product_name.as_deref(), Some("Bambu Lab A1 mini"));
let ams = v.module("ams_f1/0").unwrap();
assert_eq!(ams.hw_ver.as_deref(), Some("AMS_F102"));
assert_eq!(ams.product_name.as_deref(), Some("AMS Lite"));
assert_eq!(v.module("esp32").unwrap().product_name, None);
}
#[test]
fn missing_ota_module_yields_no_firmware() {
let info = json!({ "command": "get_version", "module": [
{ "name": "esp32", "sw_ver": "01.16.39.58" }
]});
let v = DeviceVersion::from_info(&info);
assert_eq!(v.firmware, None);
assert_eq!(v.modules.len(), 1);
}
#[test]
fn empty_or_absent_module_array_is_empty_inventory() {
let v = DeviceVersion::from_info(&json!({ "command": "get_version" }));
assert_eq!(v.firmware, None);
assert!(v.modules.is_empty());
}
}