diff --git a/CHANGELOG.md b/CHANGELOG.md index 1716ac3f..146a02b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Created `FlashData`, `FlashDataBuilder` and `FlashSettings` structs to reduce number of input arguments in some functions (#512, #566) - `espflash` will now exit with an error if `defmt` is selected but not usable (#524) +- Unify configuration methods (#551) ### Removed diff --git a/cargo-espflash/README.md b/cargo-espflash/README.md index 12335eb0..f59318d8 100644 --- a/cargo-espflash/README.md +++ b/cargo-espflash/README.md @@ -17,9 +17,8 @@ Supports the **ESP32**, **ESP32-C2/C3/C6**, **ESP32-H2**, **ESP32-P4**, **ESP32- - [Permissions on Linux](#permissions-on-linux) - [Windows Subsystem for Linux](#windows-subsystem-for-linux) - [Bootloader and Partition Table](#bootloader-and-partition-table) -- [Package Metadata](#package-metadata) - [Configuration File](#configuration-file) - - [Configuration Examples](#configuration-examples) + - [Configuration precedence](#configuration-precedence) - [Logging Format](#logging-format) - [License](#license) - [Contribution](#contribution) @@ -83,7 +82,9 @@ Commands: flash Flash an application in ELF format to a target device monitor Open the serial monitor without flashing the connected target device partition-table Convert partition tables between CSV and binary format + read-flash Read SPI flash content save-image Generate a binary application image and save it to a local disk + checksum-md5 Calculate the MD5 checksum of the given region help Print this message or the help of the given subcommand(s) Options: @@ -113,43 +114,46 @@ If the `--bootloader` and/or `--partition-table` options are provided then these [esp-idf-sys]: https://github.com/esp-rs/esp-idf-sys -## Package Metadata - -You're able to specify paths to bootloader and partition table files and image format in your package's Cargo metadata for per-project configuration: - -```toml -[package.metadata.espflash] -bootloader = "bootloader.bin" # Must be a binary file -partition_table = "partitions.csv" # Supports CSV and binary formats -format = "direct-boot" # Can be 'esp-bootloader' or 'direct-boot' -``` - ## Configuration File -It's possible to specify a serial port and/or USB VID/PID values by setting them in a configuration file. The location of this file differs based on your operating system: - -| Operating System | Configuration Path | -| :--------------- | :---------------------------------------------------------------- | -| Linux | `$HOME/.config/espflash/espflash.toml` | -| macOS | `$HOME/Library/Application Support/rs.esp.espflash/espflash.toml` | -| Windows | `%APPDATA%\esp\espflash\espflash.toml` | - -### Configuration Examples - -You can either configure the serial port name like so: - -``` -[connection] -serial = "/dev/ttyUSB0" -``` - -Or specify one or more USB `vid`/`pid` couple: - -``` -[[usb_device]] -vid = "303a" -pid = "1001" -``` +The configuration file allows you to define various parameters for your application: +- Serial port: + - By name: + ```toml + [connection] + serial = "/dev/ttyUSB0" + ``` + - By USB VID/PID values: + ```toml + [[usb_device]] + vid = "303a" + pid = "1001" + ``` +- Baudrate: + ```toml + baudrate = 460800 + ``` +- Bootloader: + ```toml + bootloader = "path/to/custom/bootloader.bin" + ``` +- Partition table + ```toml + partition_table = "path/to/custom/partition-table.bin" + ``` + +You can have a local and/or a global configuration file: +- For local configurations, store the file under the current working directory with the name `espflash.toml` +- Global file location differs based on your operating system: + - Linux: `$HOME/.config/espflash/espflash.toml` + - macOS: `$HOME/Library/Application Support/rs.esp.espflash/espflash.toml` + - Windows: `%APPDATA%\esp\espflash\espflash.toml` + +### Configuration precedence + +1. Environment variables: If `ESPFLASH_PORT` or `ESPFLASH_BAUD` are set, the will be used instead of the config file value. +2. Local configuration file +3. Global configuration file ## Logging Format diff --git a/cargo-espflash/src/error.rs b/cargo-espflash/src/error.rs index 0afa8f92..2bcb645d 100644 --- a/cargo-espflash/src/error.rs +++ b/cargo-espflash/src/error.rs @@ -10,14 +10,6 @@ use thiserror::Error; #[derive(Debug, Diagnostic, Error)] #[non_exhaustive] pub enum Error { - #[error("Specified bootloader path is not a .bin file")] - #[diagnostic(code(cargo_espflash::invalid_bootloader_path))] - InvalidBootloaderPath, - - #[error("Specified partition table path is not a .bin or .csv file")] - #[diagnostic(code(cargo_espflash::invalid_partition_table_path))] - InvalidPartitionTablePath, - #[error("The current workspace is invalid, and could not be loaded")] #[diagnostic( code(cargo_espflash::invalid_workspace), diff --git a/cargo-espflash/src/main.rs b/cargo-espflash/src/main.rs index ab30c8c1..81cfee2f 100644 --- a/cargo-espflash/src/main.rs +++ b/cargo-espflash/src/main.rs @@ -222,7 +222,7 @@ fn main() -> Result<()> { Commands::Monitor(args) => serial_monitor(args, &config), Commands::PartitionTable(args) => partition_table(args), Commands::ReadFlash(args) => read_flash(args, &config), - Commands::SaveImage(args) => save_image(args), + Commands::SaveImage(args) => save_image(args, &config), Commands::ChecksumMd5(args) => checksum_md5(&args, &config), } } @@ -239,14 +239,10 @@ pub fn erase_parts(args: ErasePartsArgs, config: &Config) -> Result<()> { return Err(EspflashError::StubRequired).into_diagnostic(); } - let metadata_partition_table = PackageMetadata::load(&args.package) - .ok() - .and_then(|m| m.partition_table); - let partition_table = args .partition_table .as_deref() - .or(metadata_partition_table.as_deref()); + .or(config.partition_table.as_deref()); let mut flash = connect(&args.connect_args, config, false, false)?; let partition_table = match partition_table { @@ -303,14 +299,14 @@ fn flash(args: FlashArgs, config: &Config) -> Result<()> { .flash_args .bootloader .as_deref() - .or(metadata.bootloader.as_deref()) + .or(config.bootloader.as_deref()) .or(build_ctx.bootloader_path.as_deref()); let partition_table = args .flash_args .partition_table .as_deref() - .or(metadata.partition_table.as_deref()) + .or(config.partition_table.as_deref()) .or(build_ctx.partition_table_path.as_deref()); if let Some(path) = &bootloader { @@ -330,7 +326,7 @@ fn flash(args: FlashArgs, config: &Config) -> Result<()> { bootloader, partition_table, args.flash_args.partition_table_offset, - args.flash_args.format.or(metadata.format), + args.flash_args.format, args.flash_args.target_app_partition, flash_settings, args.flash_args.min_chip_rev, @@ -534,7 +530,7 @@ fn build( Ok(build_ctx) } -fn save_image(args: SaveImageArgs) -> Result<()> { +fn save_image(args: SaveImageArgs, config: &Config) -> Result<()> { let metadata = PackageMetadata::load(&args.build_args.package)?; let cargo_config = CargoConfig::load(&metadata.workspace_root, &metadata.package_root); @@ -545,7 +541,7 @@ fn save_image(args: SaveImageArgs) -> Result<()> { .save_image_args .bootloader .as_deref() - .or(metadata.bootloader.as_deref()) + .or(config.bootloader.as_deref()) .or(build_ctx.bootloader_path.as_deref()) .map(|p| p.to_path_buf()); @@ -553,7 +549,7 @@ fn save_image(args: SaveImageArgs) -> Result<()> { .save_image_args .partition_table .as_deref() - .or(metadata.partition_table.as_deref()) + .or(config.partition_table.as_deref()) .or(build_ctx.partition_table_path.as_deref()) .map(|p| p.to_path_buf()); @@ -582,7 +578,7 @@ fn save_image(args: SaveImageArgs) -> Result<()> { bootloader.as_deref(), partition_table.as_deref(), args.save_image_args.partition_table_offset, - args.format.or(metadata.format), + args.format, args.save_image_args.target_app_partition, flash_settings, args.save_image_args.min_chip_rev, diff --git a/cargo-espflash/src/package_metadata.rs b/cargo-espflash/src/package_metadata.rs index d42206d2..60a0f308 100644 --- a/cargo-espflash/src/package_metadata.rs +++ b/cargo-espflash/src/package_metadata.rs @@ -1,10 +1,9 @@ -use std::{ffi::OsStr, path::PathBuf, str::FromStr}; +use std::path::PathBuf; use cargo::{ core::{Package, Workspace}, util::Config, }; -use espflash::image_format::ImageFormatKind; use miette::{IntoDiagnostic, Result}; use serde::Deserialize; @@ -14,9 +13,6 @@ use crate::error::Error; pub struct PackageMetadata { pub workspace_root: PathBuf, pub package_root: PathBuf, - pub bootloader: Option, - pub format: Option, - pub partition_table: Option, } impl PackageMetadata { @@ -37,19 +33,6 @@ impl PackageMetadata { let package = Self::load_package(&workspace, package_name)?; let metadata = Self::load_metadata(&workspace, &package)?; - if let Some(table) = &metadata.partition_table { - match table.extension() { - Some(ext) if ext == "bin" || ext == "csv" => {} - _ => return Err(Error::InvalidPartitionTablePath.into()), - } - } - - if let Some(bootloader) = &metadata.bootloader { - if bootloader.extension() != Some(OsStr::new("bin")) { - return Err(Error::InvalidBootloaderPath.into()); - } - } - Ok(metadata) } @@ -72,35 +55,11 @@ impl PackageMetadata { } fn load_metadata(workspace: &Workspace, package: &Package) -> Result { - let mut espflash_meta = PackageMetadata { + let espflash_meta = PackageMetadata { workspace_root: workspace.root_manifest().parent().unwrap().to_path_buf(), package_root: package.root().to_path_buf(), - - ..PackageMetadata::default() }; - match package.manifest().custom_metadata() { - Some(meta) if meta.is_table() => match meta.as_table().unwrap().get("espflash") { - Some(meta) if meta.is_table() => { - let meta = meta.as_table().unwrap(); - - espflash_meta.bootloader = meta - .get("bootloader") - .map(|bl| package.root().join(bl.as_str().unwrap())); - - espflash_meta.format = meta - .get("format") - .map(|fmt| ImageFormatKind::from_str(fmt.as_str().unwrap()).unwrap()); - - espflash_meta.partition_table = meta - .get("partition_table") - .map(|pt| package.root().join(pt.as_str().unwrap())); - } - _ => {} - }, - _ => {} - } - Ok(espflash_meta) } } diff --git a/espflash/README.md b/espflash/README.md index 21da4d52..ba3fc787 100644 --- a/espflash/README.md +++ b/espflash/README.md @@ -20,7 +20,7 @@ Supports the **ESP32**, **ESP32-C2/C3/C6**, **ESP32-H2**, **ESP32-P4**,**ESP32-S - [Cargo Runner](#cargo-runner) - [Using `espflash` as a Library](#using-espflash-as-a-library) - [Configuration File](#configuration-file) - - [Configuration Examples](#configuration-examples) + - [Configuration precedence](#configuration-precedence) - [Logging Format](#logging-format) - [License](#license) - [Contribution](#contribution) @@ -78,8 +78,10 @@ Commands: flash Flash an application in ELF format to a connected target device monitor Open the serial monitor without flashing the connected target device partition-table Convert partition tables between CSV and binary format + read-flash Read SPI flash content save-image Generate a binary application image and save it to a local disk write-bin Write a binary file to a specific address in a target device's flash + checksum-md5 Calculate the MD5 checksum of the given region help Print this message or the help of the given subcommand(s) Options: @@ -133,30 +135,44 @@ espflash = { version = "2.1", default-features = false, features = ["raspberry"] ## Configuration File -It's possible to specify a serial port and/or USB VID/PID values by setting them in a configuration file. The location of this file differs based on your operating system: - -| Operating System | Configuration Path | -| :--------------- | :---------------------------------------------------------------- | -| Linux | `$HOME/.config/espflash/espflash.toml` | -| macOS | `$HOME/Library/Application Support/rs.esp.espflash/espflash.toml` | -| Windows | `%APPDATA%\esp\espflash\espflash.toml` | - -### Configuration Examples - -You can either configure the serial port name like so: - -``` -[connection] -serial = "/dev/ttyUSB0" -``` - -Or specify one or more USB `vid`/`pid` couple: - -``` -[[usb_device]] -vid = "303a" -pid = "1001" -``` +The configuration file allows you to define various parameters for your application: +- Serial port: + - By name: + ```toml + [connection] + serial = "/dev/ttyUSB0" + ``` + - By USB VID/PID values: + ```toml + [[usb_device]] + vid = "303a" + pid = "1001" + ``` +- Baudrate: + ```toml + baudrate = 460800 + ``` +- Bootloader: + ```toml + bootloader = "path/to/custom/bootloader.bin" + ``` +- Partition table + ```toml + partition_table = "path/to/custom/partition-table.bin" + ``` + +You can have a local and/or a global configuration file: +- For local configurations, store the file under the current working directory with the name `espflash.toml` +- Global file location differs based on your operating system: + - Linux: `$HOME/.config/espflash/espflash.toml` + - macOS: `$HOME/Library/Application Support/rs.esp.espflash/espflash.toml` + - Windows: `%APPDATA%\esp\espflash\espflash.toml` + +### Configuration precedence + +1. Environment variables: If `ESPFLASH_PORT` or `ESPFLASH_BAUD` are set, the will be used instead of the config file value. +2. Local configuration file +3. Global configuration file ## Logging Format diff --git a/espflash/src/bin/espflash.rs b/espflash/src/bin/espflash.rs index bf42833a..50992e69 100644 --- a/espflash/src/bin/espflash.rs +++ b/espflash/src/bin/espflash.rs @@ -179,8 +179,8 @@ fn main() -> Result<()> { Commands::Flash(args) => flash(args, &config), Commands::Monitor(args) => serial_monitor(args, &config), Commands::PartitionTable(args) => partition_table(args), - Commands::SaveImage(args) => save_image(args), Commands::ReadFlash(args) => read_flash(args, &config), + Commands::SaveImage(args) => save_image(args, &config), Commands::WriteBin(args) => write_bin(args, &config), Commands::ChecksumMd5(args) => checksum_md5(&args, &config), } @@ -234,8 +234,16 @@ fn flash(args: FlashArgs, config: &Config) -> Result<()> { if args.flash_args.ram { flasher.load_elf_to_ram(&elf_data, Some(&mut EspflashProgress::default()))?; } else { - let bootloader = args.flash_args.bootloader.as_deref(); - let partition_table = args.flash_args.partition_table.as_deref(); + let bootloader = args + .flash_args + .bootloader + .as_deref() + .or(config.bootloader.as_deref()); + let partition_table = args + .flash_args + .partition_table + .as_deref() + .or(config.partition_table.as_deref()); if let Some(path) = bootloader { println!("Bootloader: {}", path.display()); @@ -297,11 +305,22 @@ fn flash(args: FlashArgs, config: &Config) -> Result<()> { } } -fn save_image(args: SaveImageArgs) -> Result<()> { +fn save_image(args: SaveImageArgs, config: &Config) -> Result<()> { let elf_data = fs::read(&args.image) .into_diagnostic() .wrap_err_with(|| format!("Failed to open image {}", args.image.display()))?; + let bootloader = args + .save_image_args + .bootloader + .as_deref() + .or(config.bootloader.as_deref()); + let partition_table = args + .save_image_args + .partition_table + .as_deref() + .or(config.partition_table.as_deref()); + // Since we have no `Flasher` instance and as such cannot print the board // information, we will print whatever information we _do_ have. println!("Chip type: {}", args.save_image_args.chip); @@ -310,10 +329,10 @@ fn save_image(args: SaveImageArgs) -> Result<()> { } println!("Merge: {}", args.save_image_args.merge); println!("Skip padding: {}", args.save_image_args.skip_padding); - if let Some(path) = &args.save_image_args.bootloader { + if let Some(path) = &bootloader { println!("Bootloader: {}", path.display()); } - if let Some(path) = &args.save_image_args.partition_table { + if let Some(path) = &partition_table { println!("Partition table: {}", path.display()); } @@ -324,8 +343,8 @@ fn save_image(args: SaveImageArgs) -> Result<()> { ); let flash_data = FlashData::new( - args.save_image_args.bootloader.as_deref(), - args.save_image_args.partition_table.as_deref(), + bootloader, + partition_table, args.save_image_args.partition_table_offset, args.format, args.save_image_args.target_app_partition, diff --git a/espflash/src/cli/config.rs b/espflash/src/cli/config.rs index 0fdb0605..8c3c8e51 100644 --- a/espflash/src/cli/config.rs +++ b/espflash/src/cli/config.rs @@ -8,15 +8,19 @@ //! [espflash]: https://crates.io/crates/espflash use std::{ + ffi::OsStr, fs::{create_dir_all, read_to_string, write}, path::PathBuf, }; use directories::ProjectDirs; +use log::debug; use miette::{IntoDiagnostic, Result, WrapErr}; use serde::{Deserialize, Serialize}; use serialport::UsbPortInfo; +use crate::error::Error; + /// A configured, known serial connection #[derive(Debug, Deserialize, Serialize, Default, Clone)] pub struct Connection { @@ -76,9 +80,18 @@ impl UsbDevice { /// Deserialized contents of a configuration file #[derive(Debug, Deserialize, Serialize, Default, Clone)] pub struct Config { + /// Baudrate + #[serde(default)] + pub baudrate: Option, + /// 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, /// Preferred USB devices #[serde(default)] pub usb_device: Vec, @@ -88,17 +101,43 @@ pub struct Config { } impl Config { + /// Gets the path to the configuration file. + pub fn get_config_path() -> Result { + let local_config = std::env::current_dir()?.join("espflash.toml"); + if local_config.exists() { + return Ok(local_config); + } + + let project_dirs = ProjectDirs::from("rs", "esp", "espflash").unwrap(); + let global_config = project_dirs.config_dir().join("espflash.toml"); + Ok(global_config) + } + /// Load configuration from the configuration file pub fn load() -> Result { - let dirs = ProjectDirs::from("rs", "esp", "espflash").unwrap(); - let file = dirs.config_dir().join("espflash.toml"); + 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() }; + + if let Some(table) = &config.partition_table { + match table.extension() { + Some(ext) if ext == "bin" || ext == "csv" => {} + _ => return Err(Error::InvalidPartitionTablePath.into()), + } + } + + if let Some(bootloader) = &config.bootloader { + if bootloader.extension() != Some(OsStr::new("bin")) { + return Err(Error::InvalidBootloaderPath.into()); + } + } + config.save_path = file; + debug!("Config: {:#?}", &config); Ok(config) } diff --git a/espflash/src/cli/mod.rs b/espflash/src/cli/mod.rs index b650a19b..708a9def 100644 --- a/espflash/src/cli/mod.rs +++ b/espflash/src/cli/mod.rs @@ -342,7 +342,7 @@ pub fn connect( Ok(Flasher::connect( interface, port_info, - args.baud, + args.baud.or(config.baudrate), !args.no_stub, !no_verify, !no_skip, diff --git a/espflash/src/error.rs b/espflash/src/error.rs index 936ef27d..071db926 100644 --- a/espflash/src/error.rs +++ b/espflash/src/error.rs @@ -96,6 +96,10 @@ pub enum Error { #[error("The provided bootloader binary is invalid")] InvalidBootloader, + #[error("Specified bootloader path is not a .bin file")] + #[diagnostic(code(espflash::invalid_bootloader_path))] + InvalidBootloaderPath, + #[error("Binary is not set up correctly to support direct boot")] #[diagnostic( code(espflash::invalid_direct_boot), @@ -117,6 +121,10 @@ pub enum Error { #[error(transparent)] IoError(#[from] std::io::Error), + #[error("Specified partition table path is not a .bin or .csv file")] + #[diagnostic(code(espflash::invalid_partition_table_path))] + InvalidPartitionTablePath, + #[error("No serial ports could be detected")] #[diagnostic( code(espflash::no_serial),