pub mod registry;
#[cfg(all(
feature = "hardware",
any(target_os = "linux", target_os = "macos", target_os = "windows")
))]
pub mod discover;
#[cfg(all(
feature = "hardware",
any(target_os = "linux", target_os = "macos", target_os = "windows")
))]
pub mod introspect;
use serde::Serialize;
#[derive(Debug, Clone, Serialize)]
pub struct DiscoveredDevice {
pub name: String,
pub detail: Option<String>,
pub device_path: Option<String>,
pub vid: u16,
pub pid: u16,
pub architecture: Option<String>,
}
pub struct HardwareManager;
impl HardwareManager {
pub fn new() -> Self {
Self
}
pub fn discover_devices(&self) -> Vec<DiscoveredDevice> {
#[cfg(all(
feature = "hardware",
any(target_os = "linux", target_os = "macos", target_os = "windows")
))]
{
match discover::list_usb_devices() {
Ok(devices) => devices
.into_iter()
.map(|d| DiscoveredDevice {
name: d
.board_name
.unwrap_or_else(|| format!("{:04x}:{:04x}", d.vid, d.pid)),
detail: d.product_string,
device_path: None,
vid: d.vid,
pid: d.pid,
architecture: d.architecture,
})
.collect(),
Err(_) => Vec::new(),
}
}
#[cfg(not(all(
feature = "hardware",
any(target_os = "linux", target_os = "macos", target_os = "windows")
)))]
{
Vec::new()
}
}
pub fn device_info(&self, query: &str) -> Option<DiscoveredDevice> {
for board in registry::known_boards() {
if board.name == query {
return Some(DiscoveredDevice {
name: board.name.to_string(),
detail: None,
device_path: None,
vid: board.vid,
pid: board.pid,
architecture: board.architecture.map(String::from),
});
}
}
if let Some((vid_str, pid_str)) = query.split_once(':') {
if let (Ok(vid), Ok(pid)) = (
u16::from_str_radix(vid_str, 16),
u16::from_str_radix(pid_str, 16),
) {
if let Some(board) = registry::lookup_board(vid, pid) {
return Some(DiscoveredDevice {
name: board.name.to_string(),
detail: None,
device_path: None,
vid: board.vid,
pid: board.pid,
architecture: board.architecture.map(String::from),
});
}
}
}
let devices = self.discover_devices();
devices.into_iter().find(|d| d.name == query)
}
}
impl Default for HardwareManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_discovered_device_construction() {
let dev = DiscoveredDevice {
name: "nucleo-f401re".to_string(),
detail: Some("STM32 Nucleo".to_string()),
device_path: Some("/dev/ttyACM0".to_string()),
vid: 0x0483,
pid: 0x374b,
architecture: Some("ARM Cortex-M4".to_string()),
};
assert_eq!(dev.name, "nucleo-f401re");
assert_eq!(dev.vid, 0x0483);
assert_eq!(dev.pid, 0x374b);
assert!(dev.architecture.is_some());
}
#[test]
fn test_discovered_device_serialize() {
let dev = DiscoveredDevice {
name: "arduino-uno".to_string(),
detail: None,
device_path: None,
vid: 0x2341,
pid: 0x0043,
architecture: Some("AVR ATmega328P".to_string()),
};
let json = serde_json::to_value(&dev).unwrap();
assert_eq!(json["name"], "arduino-uno");
assert_eq!(json["vid"], 0x2341);
}
#[test]
fn test_hardware_manager_default() {
let mgr = HardwareManager;
let devices = mgr.discover_devices();
let _ = devices;
}
#[test]
fn test_hardware_manager_device_info_by_name() {
let mgr = HardwareManager::new();
let info = mgr.device_info("nucleo-f401re");
assert!(info.is_some());
let dev = info.unwrap();
assert_eq!(dev.name, "nucleo-f401re");
assert_eq!(dev.vid, 0x0483);
assert_eq!(dev.pid, 0x374b);
}
#[test]
fn test_hardware_manager_device_info_by_vid_pid() {
let mgr = HardwareManager::new();
let info = mgr.device_info("0483:374b");
assert!(info.is_some());
assert_eq!(info.unwrap().name, "nucleo-f401re");
}
#[test]
fn test_hardware_manager_device_info_unknown() {
let mgr = HardwareManager::new();
let info = mgr.device_info("nonexistent-board");
assert!(info.is_none());
}
#[test]
fn test_hardware_manager_device_info_invalid_vid_pid() {
let mgr = HardwareManager::new();
let info = mgr.device_info("ZZZZ:YYYY");
assert!(info.is_none());
}
}