use crate::types::MotorIdentity;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct KnownDevice {
pub vendor_id: u32,
pub product_code: Option<u32>,
pub name: &'static str,
}
pub const KNOWN_DEVICES: &[KnownDevice] = &[
KnownDevice {
vendor_id: 0x0068_6578,
product_code: None,
name: "HexMeow Motor",
},
KnownDevice {
vendor_id: 0x4859_444C,
product_code: Some(0xAAAA_0001),
name: "CiA402 HEX-4310",
},
KnownDevice {
vendor_id: 0x4859_444C,
product_code: Some(0xAAAA_0002),
name: "CiA402 HEX-4342P",
},
KnownDevice {
vendor_id: 0x4859_444C,
product_code: Some(0xAAAA_0005),
name: "CiA402 HEX-4360P",
},
KnownDevice {
vendor_id: 0x4859_444C,
product_code: None,
name: "CiA402 HEX Motor (未知型号)",
},
];
pub fn lookup_known_device(vendor_id: u32, product_code: u32) -> Option<&'static KnownDevice> {
KNOWN_DEVICES
.iter()
.find(|d| d.vendor_id == vendor_id && d.product_code == Some(product_code))
.or_else(|| {
KNOWN_DEVICES
.iter()
.find(|d| d.vendor_id == vendor_id && d.product_code.is_none())
})
}
pub fn human_friendly_name(identity: &MotorIdentity) -> String {
if let Some(name) = identity.product_name.as_deref() {
let trimmed = name.trim();
if !trimmed.is_empty() {
return trimmed.to_string();
}
}
if let Some(known) = lookup_known_device(identity.vendor_id, identity.product_code) {
return known.name.to_string();
}
format!(
"Unknown CiA402 device (vendor 0x{:08X}, product 0x{:08X})",
identity.vendor_id, identity.product_code
)
}
#[cfg(test)]
mod tests {
use super::*;
fn identity(vendor: u32, product: u32, name: Option<&str>) -> MotorIdentity {
MotorIdentity {
node_id: 0x10,
vendor_id: vendor,
product_code: product,
revision_number: 0,
serial_number: 0,
product_name: name.map(|s| s.to_string()),
}
}
#[test]
fn lookup_hexmeow_wildcard_matches_any_product() {
let d = lookup_known_device(0x0068_6578, 0x1234_5678).unwrap();
assert_eq!(d.name, "HexMeow Motor");
assert_eq!(d.product_code, None);
}
#[test]
fn lookup_hex_series_exact_products() {
let d = lookup_known_device(0x4859_444C, 0xAAAA_0001).unwrap();
assert_eq!(d.name, "CiA402 HEX-4310");
assert_eq!(d.product_code, Some(0xAAAA_0001));
let d = lookup_known_device(0x4859_444C, 0xAAAA_0002).unwrap();
assert_eq!(d.name, "CiA402 HEX-4342P");
let d = lookup_known_device(0x4859_444C, 0xAAAA_0005).unwrap();
assert_eq!(d.name, "CiA402 HEX-4360P");
}
#[test]
fn lookup_hex_series_falls_back_to_vendor_wildcard() {
let d = lookup_known_device(0x4859_444C, 0xDEAD_BEEF).unwrap();
assert_eq!(d.name, "CiA402 HEX Motor (未知型号)");
assert_eq!(d.product_code, None);
}
#[test]
fn lookup_unknown_vendor_returns_none() {
assert!(lookup_known_device(0xDEAD_BEEF, 0).is_none());
}
#[test]
fn name_prefers_0x1008_when_present() {
let id = identity(0x0068_6578, 0xAAAA_0002, Some("Custom Label"));
assert_eq!(human_friendly_name(&id), "Custom Label");
}
#[test]
fn name_uses_table_when_no_0x1008() {
let id = identity(0x0068_6578, 0xAAAA_0002, None);
assert_eq!(human_friendly_name(&id), "HexMeow Motor");
}
#[test]
fn name_ignores_empty_0x1008() {
let id = identity(0x0068_6578, 0xAAAA_0002, Some(""));
assert_eq!(human_friendly_name(&id), "HexMeow Motor");
}
#[test]
fn name_ignores_whitespace_only_0x1008() {
let id = identity(0x0068_6578, 0x9999_9999, Some(" \t\n"));
assert_eq!(human_friendly_name(&id), "HexMeow Motor");
}
#[test]
fn name_falls_back_to_generic() {
let id = identity(0xDEAD_BEEF, 0xCAFEBABE, None);
let n = human_friendly_name(&id);
assert!(n.contains("Unknown CiA402"));
assert!(n.contains("0xDEADBEEF"));
assert!(n.contains("0xCAFEBABE"));
}
}