use log::debug;
use nusb::DeviceInfo;
use sdrr_fw_parser::Sdrr;
use wildmatch::WildMatch;
use crate::error::Error;
use crate::usb::enumerate_devices;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum DeviceState {
Unknown,
Stopped,
Running,
Limp,
}
impl std::fmt::Display for DeviceState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let state_str = match self {
DeviceState::Unknown => "Unknown",
DeviceState::Stopped => "Stopped",
DeviceState::Running => "Running",
DeviceState::Limp => "Limp Mode",
};
write!(f, "{state_str}")
}
}
pub struct Device {
pub vid: u16,
pub pid: u16,
pub bus_id: String,
pub address: u8,
pub serial: Option<String>,
#[allow(unused)]
pub device_info: DeviceInfo,
pub onerom: Option<Sdrr>,
pub state: DeviceState,
pub usb_can_run: bool,
}
impl std::fmt::Display for Device {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let serial = self.serial.as_deref().unwrap_or("(no serial)");
let info_str = if let Some(onerom) = self.onerom.as_ref()
&& let Some(info) = onerom.flash.as_ref()
&& let Some(board) = info.board.as_ref()
{
let model = board.model().to_string();
let chip_pins = board.chip_pins();
let hw_rev = board
.name()
.rsplit_once('-')
.map(|(_, rev)| rev)
.unwrap_or("(unknown revision)")
.to_uppercase();
let fw_version = &info.version;
format!("One ROM {model} {chip_pins} {hw_rev} - Firmware: {fw_version}")
} else {
"Unknown - Firmware: n/a ".to_string()
};
write!(f, "{info_str} State: {} Serial: {serial}", self.state)
}
}
impl std::fmt::Debug for Device {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Device")
.field("vid", &format_args!("{:#06x}", self.vid))
.field("pid", &format_args!("{:#06x}", self.pid))
.field("bus_id", &self.bus_id)
.field("address", &self.address)
.field("serial", &self.serial)
.finish()
}
}
impl Device {
pub fn is_recognised(&self) -> bool {
self.onerom
.as_ref()
.map(|o| o.flash.is_some() || o.ram.is_some())
.unwrap_or(false)
}
pub fn is_running(&self) -> bool {
self.state == DeviceState::Running
}
pub fn usb_can_run(&self) -> bool {
self.usb_can_run
}
pub fn update_onerom(&mut self, onerom: Sdrr) {
self.onerom = Some(onerom);
self.update_state();
}
fn update_state(&mut self) {
self.usb_can_run = false;
self.state = DeviceState::Unknown;
if self.onerom.is_none() {
return;
}
let onerom = self.onerom.as_ref().unwrap();
if onerom.flash.is_none() {
return;
}
let flash = onerom.flash.as_ref().unwrap();
if let Some(runtime_info) = &onerom.ram {
self.state = match runtime_info.limp_mode.as_ref() {
Some(limp_mode) if *limp_mode != sdrr_fw_parser::types::LimpMode::None => {
DeviceState::Limp
}
_ => DeviceState::Running,
}
} else {
self.state = DeviceState::Stopped;
}
self.usb_can_run = flash.is_usb_run_capable();
}
pub fn get_active_rom_set_index(&self) -> Option<u8> {
self.onerom
.as_ref()
.and_then(|o| o.ram.as_ref())
.map(|ram| ram.rom_set_index)
}
pub fn get_active_rom_set(&self) -> Option<&sdrr_fw_parser::SdrrRomSet> {
let flash_info = self.onerom.as_ref().and_then(|o| o.flash.as_ref())?;
let active_set_index = self.get_active_rom_set_index()? as usize;
flash_info.rom_sets.get(active_set_index)
}
pub fn get_active_rom_type(&self) -> Option<sdrr_fw_parser::SdrrRomType> {
if !self.is_running() {
return None;
}
self.get_active_rom_set()
.and_then(|set| set.roms.first())
.map(|rom| rom.rom_type)
}
pub fn get_active_rom_size(&self) -> Option<usize> {
self.get_active_rom_type()
.map(|rom_type| rom_type.rom_size())
}
pub fn matches_serial(&self, pattern: &str) -> bool {
matches_serial(self.serial.as_deref(), pattern)
}
pub fn sort_key(&self) -> (String, String) {
let board = self
.onerom
.as_ref()
.and_then(|o| o.flash.as_ref())
.and_then(|f| f.board.as_ref())
.map(|b| b.model().to_string())
.unwrap_or_else(|| "~".to_string()); let serial = self.serial.clone().unwrap_or_else(|| "~".to_string());
(board, serial)
}
}
pub fn matches_serial(serial: Option<&str>, pattern: &str) -> bool {
let pattern_upper = pattern.to_uppercase();
let matcher = WildMatch::new(&pattern_upper);
serial
.map(|s| matcher.matches(&s.to_uppercase()))
.unwrap_or(false)
}
pub async fn select_device(
selector: Option<&str>,
unrecognised: bool,
vid_pid: &[(u16, u16)],
) -> Result<Device, Error> {
let devices = enumerate_devices(unrecognised, vid_pid).await?;
if devices.is_empty() {
debug!("No devices found");
return Err(Error::NoDevices);
}
match selector {
None => {
if devices.len() > 1 {
let serials: Vec<String> = devices
.iter()
.map(|d| d.serial.as_deref().unwrap_or("(no serial)").to_string())
.collect();
debug!("Multiple devices found with no selector: {serials:?}");
Err(Error::MultipleDevices(serials))
} else {
let device = devices.into_iter().next().unwrap();
debug!("Auto-selected device: {device}");
Ok(device)
}
}
Some(pattern) => {
let mut matched: Vec<Device> = devices
.into_iter()
.filter(|d| matches_serial(d.serial.as_deref(), pattern))
.collect();
match matched.len() {
0 => Err(Error::DeviceNotFound(pattern.to_string())),
1 => Ok(matched.remove(0)),
_ => {
let serials: Vec<String> = matched
.iter()
.map(|d| d.serial.as_deref().unwrap_or("(no serial)").to_string())
.collect();
debug!("Multiple devices found with selector '{pattern}': {serials:?}");
Err(Error::MultipleDevices(serials))
}
}
}
}
}