#![allow(rustdoc::broken_intra_doc_links)]
#![allow(rustdoc::invalid_html_tags)]
#![allow(rustdoc::bare_urls)]
pub mod control;
pub mod firmware;
pub mod inspect;
pub mod plugin;
pub mod program;
pub mod scan;
pub mod update;
use clap::{Parser, Subcommand};
use enum_dispatch::enum_dispatch;
use log::debug;
use onerom_cli::LogLevel;
use crate::utils::parse_u16_hex_only;
use onerom_cli::{Error, Options};
use control::{
ControlArgs, ControlCommands, ControlEraseArgs, ControlGpioArgs, ControlLedArgs,
ControlLedBeaconArgs, ControlLedCommands, ControlLedFlameArgs, ControlLedOffArgs,
ControlLedOnArgs, ControlPokeArgs, ControlPokeCommands, ControlPokeLiveArgs,
ControlPokeMemoryArgs, ControlRebootArgs, ControlResetArgs, ControlSelectArgs,
};
use firmware::{
FirmwareArgs, FirmwareBuildArgs, FirmwareChipsArgs, FirmwareCommands, FirmwareDownloadArgs,
FirmwareInspectArgs, FirmwareReleasesArgs,
};
use inspect::{
InspectArgs, InspectCommands, InspectGpioArgs, InspectImageArgs, InspectInfoArgs,
InspectPeekArgs, InspectPeekCommands, InspectPeekLiveArgs, InspectPeekMemoryArgs,
InspectSlotsArgs, InspectTelemetryArgs,
};
use plugin::PluginArgs;
use program::ProgramArgs;
use scan::ScanArgs;
use update::{UpdateArgs, UpdateCommands, UpdateCommitArgs, UpdateOtpArgs, UpdateSlotArgs};
#[enum_dispatch]
pub trait CommandTrait {
fn requires_device(&self) -> bool;
}
#[derive(Debug, Parser)]
#[command(name = "onerom", version = concat!("v", env!("CARGO_PKG_VERSION")), about, long_about)]
pub struct Cli {
#[arg(global = true, long, short, value_name = "DEVICE")]
pub serial: Option<String>,
#[arg(global = true, long, short='i', visible_alias="id", value_name = "VID:PID", value_parser = parse_vid_pid, action = clap::ArgAction::Append)]
pub vid_pid: Vec<(u16, u16)>,
#[arg(global = true, visible_alias = "unrecognized", long, short)]
pub unrecognised: bool,
#[arg(global = true, long, short)]
pub yes: bool,
#[arg(global = true, long, short)]
pub verbose: bool,
#[arg(global = true, long, value_enum, default_value_t = LogLevel::Warn)]
pub log_level: LogLevel,
#[command(subcommand)]
pub command: Commands,
}
fn parse_vid_pid(s: &str) -> Result<(u16, u16), String> {
let (vid, pid) = s
.split_once(':')
.ok_or_else(|| format!("expected VID:PID, got '{s}'"))?;
let vid = parse_u16_hex_only(vid).map_err(|e| format!("invalid VID '{vid}': {e}"))?;
let pid = parse_u16_hex_only(pid).map_err(|e| format!("invalid PID '{pid}': {e}"))?;
Ok((vid, pid))
}
fn check_vid_pid_unique(vid_pid_list: &[(u16, u16)]) -> Result<(), Error> {
let mut seen = std::collections::HashSet::new();
for (vid, pid) in vid_pid_list {
if !seen.insert((*vid, *pid)) {
return Err(Error::InvalidArgument(
"global".to_string(),
format!("Duplicate VID:PID pair '{:04x}:{:04x}'", vid, pid),
));
}
}
Ok(())
}
impl Cli {
pub async fn try_into_options(&mut self) -> Result<Options, Error> {
let mut options = Options {
log_level: self.log_level.clone(),
verbose: self.verbose,
yes: self.yes,
unrecognised: self.unrecognised,
device: None,
vid_pid: self.vid_pid.clone(),
};
check_vid_pid_unique(&options.vid_pid)?;
let requires_device = self.command.requires_device();
if let Some(device) = self.serial.as_ref()
&& !requires_device
{
debug!("Device {device} specified but not required, retrieving it anyway");
}
if let Commands::Scan(scan) = &mut self.command {
scan.serial = self.serial.clone();
return Ok(options);
} else if let Some(serial) = self.serial.as_ref() {
if options.verbose {
println!("Scanning for device with serial '{}' ...", serial);
}
match onerom_cli::device::select_device(
Some(serial),
options.unrecognised,
&options.vid_pid,
)
.await
{
Ok(device) => {
if options.verbose {
println!("Found device: {device}");
}
options.device = Some(device);
}
Err(e) => {
eprintln!("Error selecting device with serial '{}': {e}", serial);
std::process::exit(1);
}
}
}
if options.device.is_none() {
if options.verbose {
println!("No device specified, scanning for connected devices ...");
}
match onerom_cli::device::select_device(None, options.unrecognised, &options.vid_pid)
.await
{
Ok(device) => {
if options.verbose {
println!("Found device: {device}");
}
options.device = Some(device);
}
Err(Error::NoDevices) => {
if requires_device {
debug!("No devices found.");
}
}
Err(e) => {
return Err(e);
}
}
}
Ok(options)
}
}
#[enum_dispatch(CommandTrait)]
#[derive(Debug, Subcommand)]
pub enum Commands {
Scan(ScanArgs),
Program(ProgramArgs),
#[command(
subcommand_value_name = "COMMAND",
subcommand_help_heading = "Commands"
)]
Inspect(InspectArgs),
#[command(
subcommand_value_name = "COMMAND",
subcommand_help_heading = "Commands"
)]
Control(ControlArgs),
#[command(
subcommand_value_name = "COMMAND",
subcommand_help_heading = "Commands"
)]
Update(UpdateArgs),
#[command(
subcommand_value_name = "COMMAND",
subcommand_help_heading = "Commands"
)]
Peek(InspectPeekLiveArgs),
#[command(
subcommand_value_name = "COMMAND",
subcommand_help_heading = "Commands"
)]
Poke(ControlPokeLiveArgs),
Reboot(ControlRebootArgs),
#[command(
subcommand_value_name = "COMMAND",
subcommand_help_heading = "Commands"
)]
Firmware(FirmwareArgs),
Plugin(PluginArgs),
}