diff --git a/cargo-espflash/src/main.rs b/cargo-espflash/src/main.rs index 6bc85f84..9d73af88 100644 --- a/cargo-espflash/src/main.rs +++ b/cargo-espflash/src/main.rs @@ -7,7 +7,12 @@ use std::{ use cargo_metadata::{Message, MetadataCommand}; use clap::{Args, CommandFactory, Parser, Subcommand}; use espflash::{ - cli::{self, config::Config, monitor::monitor, *}, + cli::{ + self, + config::{Config, PortConfig}, + monitor::monitor, + *, + }, flasher::FlashSize, logging::initialize_logger, targets::{Chip, XtalFrequency}, @@ -225,24 +230,27 @@ fn main() -> Result<()> { // Load any user configuration, if present. let config = Config::load()?; + // Load any user ports configuration, if present. + let ports_config = PortConfig::load()?; + // Execute the correct action based on the provided subcommand and its // associated arguments. match args { - Commands::BoardInfo(args) => board_info(&args, &config), - Commands::ChecksumMd5(args) => checksum_md5(&args, &config), + Commands::BoardInfo(args) => board_info(&args, &config, &ports_config), + Commands::ChecksumMd5(args) => checksum_md5(&args, &config, &ports_config), Commands::Completions(args) => completions(&args, &mut Cli::command(), "cargo"), - Commands::EraseFlash(args) => erase_flash(args, &config), - Commands::EraseParts(args) => erase_parts(args, &config), - Commands::EraseRegion(args) => erase_region(args, &config), - Commands::Flash(args) => flash(args, &config), - Commands::HoldInReset(args) => hold_in_reset(args, &config), - Commands::ListPorts(args) => list_ports(&args, &config), - Commands::Monitor(args) => serial_monitor(args, &config), + Commands::EraseFlash(args) => erase_flash(args, &config, &ports_config), + Commands::EraseParts(args) => erase_parts(args, &config, &ports_config), + Commands::EraseRegion(args) => erase_region(args, &config, &ports_config), + Commands::Flash(args) => flash(args, &config, &ports_config), + Commands::HoldInReset(args) => hold_in_reset(args, &config, &ports_config), + Commands::ListPorts(args) => list_ports(&args, &ports_config), + Commands::Monitor(args) => serial_monitor(args, &config, &ports_config), Commands::PartitionTable(args) => partition_table(args), - Commands::ReadFlash(args) => read_flash(args, &config), - Commands::Reset(args) => reset(args, &config), + Commands::ReadFlash(args) => read_flash(args, &config, &ports_config), + Commands::Reset(args) => reset(args, &config, &ports_config), Commands::SaveImage(args) => save_image(args, &config), - Commands::WriteBin(args) => write_bin(args, &config), + Commands::WriteBin(args) => write_bin(args, &config, &ports_config), } } @@ -253,7 +261,7 @@ struct BuildContext { pub partition_table_path: Option, } -pub fn erase_parts(args: ErasePartsArgs, config: &Config) -> Result<()> { +pub fn erase_parts(args: ErasePartsArgs, config: &Config, ports_config: &PortConfig) -> Result<()> { if args.connect_args.no_stub { return Err(EspflashError::StubRequired).into_diagnostic(); } @@ -263,7 +271,7 @@ pub fn erase_parts(args: ErasePartsArgs, config: &Config) -> Result<()> { .as_deref() .or(config.partition_table.as_deref()); - let mut flasher = connect(&args.connect_args, config, false, false)?; + let mut flasher = connect(&args.connect_args, config, ports_config, false, false)?; let chip = flasher.chip(); let partition_table = match partition_table { Some(path) => Some(parse_partition_table(path)?), @@ -280,13 +288,14 @@ pub fn erase_parts(args: ErasePartsArgs, config: &Config) -> Result<()> { Ok(()) } -fn flash(args: FlashArgs, config: &Config) -> Result<()> { +fn flash(args: FlashArgs, config: &Config, ports_config: &PortConfig) -> Result<()> { let metadata = PackageMetadata::load(&args.build_args.package)?; let cargo_config = CargoConfig::load(&metadata.workspace_root, &metadata.package_root); let mut flasher = connect( &args.connect_args, config, + ports_config, args.flash_args.no_verify, args.flash_args.no_skip, )?; diff --git a/espflash/src/bin/espflash.rs b/espflash/src/bin/espflash.rs index d8802c7d..c7d6614f 100644 --- a/espflash/src/bin/espflash.rs +++ b/espflash/src/bin/espflash.rs @@ -2,7 +2,12 @@ use std::{fs, path::PathBuf}; use clap::{Args, CommandFactory, Parser, Subcommand}; use espflash::{ - cli::{self, config::Config, monitor::monitor, *}, + cli::{ + self, + config::{Config, PortConfig}, + monitor::monitor, + *, + }, flasher::FlashSize, logging::initialize_logger, targets::{Chip, XtalFrequency}, @@ -156,33 +161,36 @@ fn main() -> Result<()> { // Load any user configuration, if present. let config = Config::load()?; + // Load any user ports configuration, if present. + let ports_config = PortConfig::load()?; + // Execute the correct action based on the provided subcommand and its // associated arguments. match args { - Commands::BoardInfo(args) => board_info(&args, &config), - Commands::ChecksumMd5(args) => checksum_md5(&args, &config), + Commands::BoardInfo(args) => board_info(&args, &config, &ports_config), + Commands::ChecksumMd5(args) => checksum_md5(&args, &config, &ports_config), Commands::Completions(args) => completions(&args, &mut Cli::command(), "espflash"), - Commands::EraseFlash(args) => erase_flash(args, &config), - Commands::EraseParts(args) => erase_parts(args, &config), - Commands::EraseRegion(args) => erase_region(args, &config), - Commands::Flash(args) => flash(args, &config), - Commands::HoldInReset(args) => hold_in_reset(args, &config), - Commands::ListPorts(args) => list_ports(&args, &config), - Commands::Monitor(args) => serial_monitor(args, &config), + Commands::EraseFlash(args) => erase_flash(args, &config, &ports_config), + Commands::EraseParts(args) => erase_parts(args, &config, &ports_config), + Commands::EraseRegion(args) => erase_region(args, &config, &ports_config), + Commands::Flash(args) => flash(args, &config, &ports_config), + Commands::HoldInReset(args) => hold_in_reset(args, &config, &ports_config), + Commands::ListPorts(args) => list_ports(&args, &ports_config), + Commands::Monitor(args) => serial_monitor(args, &config, &ports_config), Commands::PartitionTable(args) => partition_table(args), - Commands::ReadFlash(args) => read_flash(args, &config), - Commands::Reset(args) => reset(args, &config), + Commands::ReadFlash(args) => read_flash(args, &config, &ports_config), + Commands::Reset(args) => reset(args, &config, &ports_config), Commands::SaveImage(args) => save_image(args, &config), - Commands::WriteBin(args) => write_bin(args, &config), + Commands::WriteBin(args) => write_bin(args, &config, &ports_config), } } -pub fn erase_parts(args: ErasePartsArgs, config: &Config) -> Result<()> { +pub fn erase_parts(args: ErasePartsArgs, config: &Config, ports_config: &PortConfig) -> Result<()> { if args.connect_args.no_stub { return Err(Error::StubRequired.into()); } - let mut flasher = connect(&args.connect_args, config, false, false)?; + let mut flasher = connect(&args.connect_args, config, ports_config, false, false)?; let chip = flasher.chip(); let partition_table = match args.partition_table { Some(path) => Some(parse_partition_table(&path)?), @@ -201,10 +209,11 @@ pub fn erase_parts(args: ErasePartsArgs, config: &Config) -> Result<()> { Ok(()) } -fn flash(args: FlashArgs, config: &Config) -> Result<()> { +fn flash(args: FlashArgs, config: &Config, ports_config: &PortConfig) -> Result<()> { let mut flasher = connect( &args.connect_args, config, + ports_config, args.flash_args.no_verify, args.flash_args.no_skip, )?; diff --git a/espflash/src/cli/config.rs b/espflash/src/cli/config.rs index d49a2722..d40a71c6 100644 --- a/espflash/src/cli/config.rs +++ b/espflash/src/cli/config.rs @@ -80,18 +80,12 @@ pub struct Config { /// Bootloader path #[serde(default)] pub bootloader: Option, - /// Preferred serial port connection information - #[serde(default)] - pub connection: Connection, /// Partition table path #[serde(default)] pub partition_table: Option, /// Partition table offset #[serde(default)] pub partition_table_offset: Option, - /// Preferred USB devices - #[serde(default)] - pub usb_device: Vec, /// Flash settings #[serde(default)] pub flash: FlashSettings, @@ -164,6 +158,71 @@ impl Config { } } +/// Deserialized contents of a serial port configuration file +#[derive(Debug, Deserialize, Serialize, Default, Clone)] +pub struct PortConfig { + /// Preferred serial port connection information + #[serde(default)] + pub connection: Connection, + /// Preferred USB devices + #[serde(default)] + pub usb_device: Vec, + /// Path of the file to save the configuration to + #[serde(skip)] + save_path: PathBuf, +} + +impl PortConfig { + /// Gets the path to the configuration file. + pub fn get_config_path() -> Result { + let local_config = std::env::current_dir()?.join("espflash_ports.toml"); + if local_config.exists() { + return Ok(local_config); + } + if let Some(parent_folder) = std::env::current_dir()?.parent() { + let workspace_config = parent_folder.join("espflash_ports.toml"); + if workspace_config.exists() { + return Ok(workspace_config); + } + } + + let project_dirs = ProjectDirs::from("rs", "esp", "espflash").unwrap(); + let global_config = project_dirs.config_dir().join("espflash_ports.toml"); + Ok(global_config) + } + + /// Load configuration from the configuration file + pub fn load() -> Result { + let file = Self::get_config_path()?; + + let mut config = if let Ok(data) = read_to_string(&file) { + toml::from_str(&data).into_diagnostic()? + } else { + Self::default() + }; + + config.save_path = file; + debug!("Config: {:#?}", &config); + Ok(config) + } + + /// Save configuration to the configuration file + pub fn save_with(&self, modify_fn: F) -> Result<()> { + let mut copy = self.clone(); + modify_fn(&mut copy); + + let serialized = toml::to_string(©) + .into_diagnostic() + .wrap_err("Failed to serialize config")?; + create_dir_all(self.save_path.parent().unwrap()) + .into_diagnostic() + .wrap_err("Failed to create config directory")?; + write(&self.save_path, serialized) + .into_diagnostic() + .wrap_err_with(|| format!("Failed to write config to {}", self.save_path.display())) + } +} + #[cfg(test)] mod tests { use serde::Deserialize; diff --git a/espflash/src/cli/mod.rs b/espflash/src/cli/mod.rs index d850e4cf..fe810893 100644 --- a/espflash/src/cli/mod.rs +++ b/espflash/src/cli/mod.rs @@ -21,6 +21,7 @@ use std::{ use clap::Args; use clap_complete::Shell; use comfy_table::{modifiers, presets::UTF8_FULL, Attribute, Cell, Color, Table}; +use config::PortConfig; use esp_idf_part::{DataType, Partition, PartitionTable}; use indicatif::{style::ProgressStyle, HumanCount, ProgressBar}; use log::{debug, info, warn}; @@ -357,6 +358,7 @@ pub fn parse_u32(input: &str) -> Result { pub fn connect( args: &ConnectArgs, config: &Config, + ports_config: &PortConfig, no_verify: bool, no_skip: bool, ) -> Result { @@ -369,7 +371,7 @@ pub fn connect( ); } - let port_info = get_serial_port_info(args, config)?; + let port_info = get_serial_port_info(args, ports_config)?; // Attempt to open the serial port and set its initial baud rate. info!("Serial port: '{}'", port_info.port_name); @@ -412,8 +414,8 @@ pub fn connect( } /// Connect to a target device and print information about its chip -pub fn board_info(args: &ConnectArgs, config: &Config) -> Result<()> { - let mut flasher = connect(args, config, true, true)?; +pub fn board_info(args: &ConnectArgs, config: &Config, ports_config: &PortConfig) -> Result<()> { + let mut flasher = connect(args, config, ports_config, true, true)?; print_board_info(&mut flasher)?; if flasher.chip() != Chip::Esp32 { @@ -427,8 +429,12 @@ pub fn board_info(args: &ConnectArgs, config: &Config) -> Result<()> { } /// Connect to a target device and calculate the checksum of the given region -pub fn checksum_md5(args: &ChecksumMd5Args, config: &Config) -> Result<()> { - let mut flasher = connect(&args.connect_args, config, true, true)?; +pub fn checksum_md5( + args: &ChecksumMd5Args, + config: &Config, + ports_config: &PortConfig, +) -> Result<()> { + let mut flasher = connect(&args.connect_args, config, ports_config, true, true)?; let checksum = flasher.checksum_md5(args.address, args.size)?; println!("0x{:x}", checksum); @@ -436,7 +442,7 @@ pub fn checksum_md5(args: &ChecksumMd5Args, config: &Config) -> Result<()> { Ok(()) } -pub fn list_ports(args: &ListPortsArgs, config: &Config) -> Result<()> { +pub fn list_ports(args: &ListPortsArgs, config: &PortConfig) -> Result<()> { let mut ports: Vec = serial::detect_usb_serial_ports(true)? .into_iter() .filter(|p| args.list_all_ports || serial::known_ports_filter(p, config)) @@ -567,8 +573,8 @@ pub fn print_board_info(flasher: &mut Flasher) -> Result<()> { } /// Open a serial monitor -pub fn serial_monitor(args: MonitorArgs, config: &Config) -> Result<()> { - let mut flasher = connect(&args.connect_args, config, true, true)?; +pub fn serial_monitor(args: MonitorArgs, config: &Config, ports_config: &PortConfig) -> Result<()> { + let mut flasher = connect(&args.connect_args, config, ports_config, true, true)?; let pid = flasher.usb_pid(); let elf = if let Some(elf_path) = args.monitor_args.elf.clone() { @@ -722,12 +728,12 @@ impl ProgressCallbacks for EspflashProgress { } } -pub fn erase_flash(args: EraseFlashArgs, config: &Config) -> Result<()> { +pub fn erase_flash(args: EraseFlashArgs, config: &Config, ports_config: &PortConfig) -> Result<()> { if args.connect_args.no_stub { return Err(Error::StubRequired.into()); } - let mut flasher = connect(&args.connect_args, config, true, true)?; + let mut flasher = connect(&args.connect_args, config, ports_config, true, true)?; info!("Erasing Flash..."); let chip = flasher.chip(); @@ -742,7 +748,11 @@ pub fn erase_flash(args: EraseFlashArgs, config: &Config) -> Result<()> { Ok(()) } -pub fn erase_region(args: EraseRegionArgs, config: &Config) -> Result<()> { +pub fn erase_region( + args: EraseRegionArgs, + config: &Config, + ports_config: &PortConfig, +) -> Result<()> { if args.connect_args.no_stub { return Err(Error::StubRequired).into_diagnostic(); } @@ -755,7 +765,7 @@ pub fn erase_region(args: EraseRegionArgs, config: &Config) -> Result<()> { .into_diagnostic(); } - let mut flasher = connect(&args.connect_args, config, true, true)?; + let mut flasher = connect(&args.connect_args, config, ports_config, true, true)?; let chip = flasher.chip(); info!( @@ -856,8 +866,8 @@ fn erase_partition(flasher: &mut Flasher, part: &Partition) -> Result<()> { } /// Read flash content and write it to a file -pub fn read_flash(args: ReadFlashArgs, config: &Config) -> Result<()> { - let mut flasher = connect(&args.connect_args, config, false, false)?; +pub fn read_flash(args: ReadFlashArgs, config: &Config, ports_config: &PortConfig) -> Result<()> { + let mut flasher = connect(&args.connect_args, config, ports_config, false, false)?; print_board_info(&mut flasher)?; if args.connect_args.no_stub { @@ -1015,8 +1025,8 @@ pub fn make_flash_data( } /// Write a binary to the flash memory of a target device -pub fn write_bin(args: WriteBinArgs, config: &Config) -> Result<()> { - let mut flasher = connect(&args.connect_args, config, false, false)?; +pub fn write_bin(args: WriteBinArgs, config: &Config, ports_config: &PortConfig) -> Result<()> { + let mut flasher = connect(&args.connect_args, config, ports_config, false, false)?; print_board_info(&mut flasher)?; let chip = flasher.chip(); @@ -1059,18 +1069,18 @@ pub fn write_bin(args: WriteBinArgs, config: &Config) -> Result<()> { Ok(()) } -pub fn reset(args: ConnectArgs, config: &Config) -> Result<()> { +pub fn reset(args: ConnectArgs, config: &Config, ports_config: &PortConfig) -> Result<()> { let mut args = args.clone(); args.no_stub = true; - let mut flash = connect(&args, config, true, true)?; + let mut flasher = connect(&args, config, ports_config, true, true)?; info!("Resetting target device"); - flash.connection().reset()?; + flasher.connection().reset()?; Ok(()) } -pub fn hold_in_reset(args: ConnectArgs, config: &Config) -> Result<()> { - connect(&args, config, true, true)?; +pub fn hold_in_reset(args: ConnectArgs, config: &Config, ports_config: &PortConfig) -> Result<()> { + connect(&args, config, ports_config, true, true)?; info!("Holding target device in reset"); Ok(()) diff --git a/espflash/src/cli/serial.rs b/espflash/src/cli/serial.rs index 2f0a9961..91b52238 100644 --- a/espflash/src/cli/serial.rs +++ b/espflash/src/cli/serial.rs @@ -8,7 +8,7 @@ use miette::{IntoDiagnostic, Result}; use serialport::{available_ports, SerialPortInfo, SerialPortType}; use crate::{ - cli::{config::UsbDevice, Config, ConnectArgs}, + cli::{config::UsbDevice, ConnectArgs, PortConfig}, Error, }; @@ -16,7 +16,7 @@ use crate::{ /// ways of choosing a port. pub fn get_serial_port_info( matches: &ConnectArgs, - config: &Config, + config: &PortConfig, ) -> Result { // A serial port should be specified either as a command-line argument or in a // configuration file. In the case that both have been provided the command-line @@ -42,7 +42,6 @@ pub fn get_serial_port_info( } else { let ports = detect_usb_serial_ports(matches.list_all_ports).unwrap_or_default(); let (port, matches) = select_serial_port(ports, config, matches.confirm_port)?; - match &port.port_type { SerialPortType::UsbPort(usb_info) if !matches => { let remember = Confirm::with_theme(&ColorfulTheme::default()) @@ -142,7 +141,7 @@ const KNOWN_DEVICES: &[UsbDevice] = &[ }, // QinHeng Electronics CH340 serial converter ]; -pub(super) fn known_ports_filter(port: &SerialPortInfo, config: &Config) -> bool { +pub(super) fn known_ports_filter(port: &SerialPortInfo, config: &PortConfig) -> bool { // Does this port match a known one? match &port.port_type { SerialPortType::UsbPort(info) => config @@ -157,7 +156,7 @@ pub(super) fn known_ports_filter(port: &SerialPortInfo, config: &Config) -> bool /// Ask the user to select a serial port from a list of detected serial ports. fn select_serial_port( mut ports: Vec, - config: &Config, + config: &PortConfig, force_confirm_port: bool, ) -> Result<(SerialPortInfo, bool), Error> { if let [port] = ports