diff --git a/cargo-espflash/Cargo.toml b/cargo-espflash/Cargo.toml index 3ac71878..c1eba36b 100644 --- a/cargo-espflash/Cargo.toml +++ b/cargo-espflash/Cargo.toml @@ -24,7 +24,7 @@ categories = [ ] [dependencies] -anyhow = "1.0" +miette = "2" cargo_metadata = "0.14" clap = "2.33" espflash = { version = "*", path = "../espflash" } @@ -32,3 +32,4 @@ guess_host_triple = "0.1" serde = { version = "1.0", features = ["derive"] } serial = "0.4" toml = "0.5" +thiserror = "1" diff --git a/cargo-espflash/src/error.rs b/cargo-espflash/src/error.rs new file mode 100644 index 00000000..53910e15 --- /dev/null +++ b/cargo-espflash/src/error.rs @@ -0,0 +1,29 @@ +use miette::Diagnostic; +use thiserror::Error; + +#[derive(Error, Debug, Diagnostic)] +#[non_exhaustive] +pub enum Error { + #[error("No executable artifact found")] + #[diagnostic( + code(cargo_espflash::no_artifact), + help("If you're trying to run an example you need to specify it using the `--example` argument") + )] + NoArtifact, + #[error("'build-std' not configured")] + #[diagnostic( + code(cargo_espflash::build_std), + help( + "cargo currently requires the unstable 'build-std' feature, ensure \ + that .cargo/config{{.toml}} has the appropriate options.\n \ + \tSee: https://doc.rust-lang.org/cargo/reference/unstable.html#build-std" + ) + )] + NoBuildStd, + #[error("Multiple build artifacts found")] + #[diagnostic( + code(cargo_espflash::multiple_artifacts), + help("Please specify which artifact to flash using --bin") + )] + MultipleArtifacts, +} diff --git a/cargo-espflash/src/main.rs b/cargo-espflash/src/main.rs index 91707f77..b2700a22 100644 --- a/cargo-espflash/src/main.rs +++ b/cargo-espflash/src/main.rs @@ -1,7 +1,8 @@ -use anyhow::{anyhow, bail, Context}; use cargo_metadata::Message; use clap::{App, Arg, SubCommand}; +use error::Error; use espflash::{Config, Flasher, PartitionTable}; +use miette::{IntoDiagnostic, Result, WrapErr}; use serial::{BaudRate, SerialPort}; use std::{ @@ -12,9 +13,11 @@ use std::{ }; mod cargo_config; +mod error; + use cargo_config::has_build_std; -fn main() -> anyhow::Result<()> { +fn main() -> Result<()> { let mut app = App::new(env!("CARGO_PKG_NAME")) .bin_name("cargo") .subcommand( @@ -84,7 +87,7 @@ fn main() -> anyhow::Result<()> { let matches = match matches.subcommand_matches("espflash") { Some(matches) => matches, None => { - app.print_help()?; + app.print_help().into_diagnostic()?; exit(0); } }; @@ -99,7 +102,7 @@ fn main() -> anyhow::Result<()> { } else if let Some(serial) = config.connection.serial { serial } else { - app.print_help()?; + app.print_help().into_diagnostic()?; exit(0); }; @@ -120,15 +123,19 @@ fn main() -> anyhow::Result<()> { // Attempt to open the serial port and set its initial baud rate. println!("Serial port: {}", port); println!("Connecting...\n"); - let mut serial = serial::open(&port).context(format!("Failed to open serial port {}", port))?; - serial.reconfigure(&|settings| { - settings.set_baud_rate(BaudRate::Baud115200)?; - Ok(()) - })?; + let mut serial = serial::open(&port) + .map_err(espflash::Error::from) + .wrap_err_with(|| format!("Failed to open serial port {}", port))?; + serial + .reconfigure(&|settings| { + settings.set_baud_rate(BaudRate::Baud115200)?; + Ok(()) + }) + .into_diagnostic()?; // Parse the baud rate if provided as as a command-line argument. let speed = if let Some(speed) = matches.value_of("speed") { - let speed = speed.parse::()?; + let speed = speed.parse::().into_diagnostic()?; Some(BaudRate::from_speed(speed)) } else { None @@ -145,8 +152,8 @@ fn main() -> anyhow::Result<()> { // If the '--bootloader' option is provided, load the binary file at the // specified path. let bootloader = if let Some(path) = matches.value_of("bootloader") { - let path = fs::canonicalize(path)?; - let data = fs::read(path)?; + let path = fs::canonicalize(path).into_diagnostic()?; + let data = fs::read(path).into_diagnostic()?; Some(data) } else { None @@ -155,19 +162,16 @@ fn main() -> anyhow::Result<()> { // If the '--partition-table' option is provided, load the partition table from // the CSV at the specified path. let partition_table = if let Some(path) = matches.value_of("partition_table") { - let path = fs::canonicalize(path)?; - let data = fs::read_to_string(path)?; - - match PartitionTable::try_from_str(data) { - Ok(t) => Some(t), - Err(e) => bail!("{}", e), - } + let path = fs::canonicalize(path).into_diagnostic()?; + let data = fs::read_to_string(path).into_diagnostic()?; + let table = PartitionTable::try_from_str(data)?; + Some(table) } else { None }; // Read the ELF data from the build path and load it to the target. - let elf_data = fs::read(path.unwrap())?; + let elf_data = fs::read(path.unwrap()).into_diagnostic()?; if matches.is_present("ram") { flasher.load_elf_to_ram(&elf_data)?; } else { @@ -183,16 +187,12 @@ fn board_info(flasher: &Flasher) { println!("Flash size: {}", flasher.flash_size()); } -fn build(release: bool, example: Option<&str>, features: Option<&str>) -> anyhow::Result { +fn build(release: bool, example: Option<&str>, features: Option<&str>) -> Result { // The 'build-std' unstable cargo feature is required to enable // cross-compilation. If it has not been set then we cannot build the // application. if !has_build_std(".") { - bail!( - "cargo currently requires the unstable 'build-std' feature, ensure \ - that .cargo/config{.toml} has the appropriate options.\n \ - See: https://doc.rust-lang.org/cargo/reference/unstable.html#build-std" - ); + return Err(Error::NoBuildStd.into()); }; // Build the list of arguments to pass to 'cargo build'. @@ -219,8 +219,10 @@ fn build(release: bool, example: Option<&str>, features: Option<&str>) -> anyhow .args(&["--message-format", "json-diagnostic-rendered-ansi"]) .stdout(Stdio::piped()) .stderr(Stdio::inherit()) - .spawn()? - .wait_with_output()?; + .spawn() + .into_diagnostic()? + .wait_with_output() + .into_diagnostic()?; // Parse build output. let messages = Message::parse_stream(&output.stdout[..]); @@ -229,12 +231,11 @@ fn build(release: bool, example: Option<&str>, features: Option<&str>) -> anyhow let mut target_artifact = None; for message in messages { - match message? { + match message.into_diagnostic()? { Message::CompilerArtifact(artifact) => { if artifact.executable.is_some() { if target_artifact.is_some() { - // We found multiple binary artifacts, so we don't know which one to use. - bail!("Multiple artifacts found, please specify one with --bin"); + return Err(Error::MultipleArtifacts.into()); } else { target_artifact = Some(artifact); } @@ -258,16 +259,9 @@ fn build(release: bool, example: Option<&str>, features: Option<&str>) -> anyhow } // If no target artifact was found, we don't have a path to return. - if target_artifact.is_none() { - bail!("Artifact not found"); - } + let target_artifact = target_artifact.ok_or(Error::NoArtifact)?; - let artifact_path = PathBuf::from( - target_artifact - .unwrap() - .executable - .ok_or_else(|| anyhow!("artifact executable path is missing"))?, - ); + let artifact_path = target_artifact.executable.unwrap().into(); Ok(artifact_path) } diff --git a/espflash/Cargo.toml b/espflash/Cargo.toml index 54e8004b..06022685 100644 --- a/espflash/Cargo.toml +++ b/espflash/Cargo.toml @@ -33,6 +33,7 @@ strum_macros = "0.21.1" csv = "1.1.6" regex = "1.5.4" flate2 = "1" +miette = "2" [dev-dependencies] pretty_assertions = "0.7.1" diff --git a/espflash/src/chip/esp8266.rs b/espflash/src/chip/esp8266.rs index 21c5004e..ff830916 100644 --- a/espflash/src/chip/esp8266.rs +++ b/espflash/src/chip/esp8266.rs @@ -4,6 +4,7 @@ use super::{ChipType, EspCommonHeader, SegmentHeader, ESP_MAGIC}; use crate::{ chip::{Chip, SpiRegisters}, elf::{update_checksum, CodeSegment, FirmwareImage, RomSegment, ESP_CHECKSUM_MAGIC}, + error::FlashDetectError, flasher::FlashSize, Error, PartitionTable, }; @@ -96,7 +97,7 @@ impl ChipType for Esp8266 { } } -fn encode_flash_size(size: FlashSize) -> Result { +fn encode_flash_size(size: FlashSize) -> Result { match size { FlashSize::Flash256Kb => Ok(0x10), FlashSize::Flash512Kb => Ok(0x00), @@ -105,7 +106,7 @@ fn encode_flash_size(size: FlashSize) -> Result { FlashSize::Flash4Mb => Ok(0x40), FlashSize::Flash8Mb => Ok(0x80), FlashSize::Flash16Mb => Ok(0x90), - FlashSize::FlashRetry => Err(Error::UnsupportedFlash(size as u8)), + FlashSize::FlashRetry => Err(FlashDetectError::from(size as u8)), } } diff --git a/espflash/src/chip/mod.rs b/espflash/src/chip/mod.rs index 7f3a2613..a1a6e1bf 100644 --- a/espflash/src/chip/mod.rs +++ b/espflash/src/chip/mod.rs @@ -7,8 +7,9 @@ use crate::{ Error, PartitionTable, }; -use std::{io::Write, str::FromStr}; +use std::io::Write; +use crate::error::{ChipDetectError, FlashDetectError}; use crate::flash_target::{Esp32Target, Esp8266Target, FlashTarget, RamTarget}; use crate::flasher::SpiAttachParams; pub use esp32::Esp32; @@ -102,14 +103,14 @@ pub enum Chip { } impl Chip { - pub fn from_magic(magic: u32) -> Option { + pub fn from_magic(magic: u32) -> Result { match magic { - Esp32::CHIP_DETECT_MAGIC_VALUE => Some(Chip::Esp32), + Esp32::CHIP_DETECT_MAGIC_VALUE => Ok(Chip::Esp32), Esp32c3::CHIP_DETECT_MAGIC_VALUE | Esp32c3::CHIP_DETECT_MAGIC_VALUE2 => { - Some(Chip::Esp32c3) + Ok(Chip::Esp32c3) } - Esp8266::CHIP_DETECT_MAGIC_VALUE => Some(Chip::Esp8266), - _ => None, + Esp8266::CHIP_DETECT_MAGIC_VALUE => Ok(Chip::Esp8266), + _ => Err(ChipDetectError::from(magic)), } } @@ -154,19 +155,6 @@ impl Chip { } } -impl FromStr for Chip { - type Err = Error; - - fn from_str(s: &str) -> Result { - match s { - "esp32" => Ok(Chip::Esp32), - "esp32c3" => Ok(Chip::Esp32c3), - "esp8266" => Ok(Chip::Esp8266), - _ => Err(Error::UnrecognizedChip), - } - } -} - #[derive(Copy, Clone, Zeroable, Pod, Debug)] #[repr(C)] struct EspCommonHeader { @@ -186,16 +174,16 @@ struct SegmentHeader { // Note that this function ONLY applies to the ESP32 and variants; the ESP8266 // has defined its own version rather than using this implementation. -fn encode_flash_size(size: FlashSize) -> Result { +fn encode_flash_size(size: FlashSize) -> Result { match size { - FlashSize::Flash256Kb => Err(Error::UnsupportedFlash(size as u8)), - FlashSize::Flash512Kb => Err(Error::UnsupportedFlash(size as u8)), + FlashSize::Flash256Kb => Err(FlashDetectError::from(size as u8)), + FlashSize::Flash512Kb => Err(FlashDetectError::from(size as u8)), FlashSize::Flash1Mb => Ok(0x00), FlashSize::Flash2Mb => Ok(0x10), FlashSize::Flash4Mb => Ok(0x20), FlashSize::Flash8Mb => Ok(0x30), FlashSize::Flash16Mb => Ok(0x40), - FlashSize::FlashRetry => Err(Error::UnsupportedFlash(size as u8)), + FlashSize::FlashRetry => Err(FlashDetectError::from(size as u8)), } } diff --git a/espflash/src/connection.rs b/espflash/src/connection.rs index 49b97bae..e5a3e8b0 100644 --- a/espflash/src/connection.rs +++ b/espflash/src/connection.rs @@ -3,7 +3,7 @@ use std::thread::sleep; use std::time::Duration; use crate::encoder::SlipEncoder; -use crate::error::{Error, RomError}; +use crate::error::{ConnectionError, Error, RomError}; use binread::io::Cursor; use binread::{BinRead, BinReaderExt}; use serial::{BaudRate, SerialPort, SerialPortSettings}; @@ -135,7 +135,7 @@ impl Connection { } } } - Err(Error::ConnectionFailed) + Err(Error::Connection(ConnectionError::ConnectionFailed)) } fn read(&mut self) -> Result, Error> { diff --git a/espflash/src/error.rs b/espflash/src/error.rs index 2c4173a7..c15cd46d 100644 --- a/espflash/src/error.rs +++ b/espflash/src/error.rs @@ -1,38 +1,122 @@ +use csv::Position; +use miette::{Diagnostic, SourceOffset, SourceSpan}; use slip_codec::Error as SlipError; +use std::io; use thiserror::Error; -#[derive(Error, Debug)] +#[derive(Error, Debug, Diagnostic)] #[non_exhaustive] pub enum Error { + #[error("Error while connecting to device")] + #[diagnostic(transparent)] + Connection(#[source] ConnectionError), + #[error("Communication error while flashing device")] + #[diagnostic(transparent)] + Flashing(#[source] ConnectionError), + #[error("Supplied elf image is not valid")] + #[diagnostic( + code(espflash::invalid_elf), + help("Try running `cargo clean` and rebuilding the image") + )] + InvalidElf(#[from] ElfError), + #[error("Supplied elf image can not be ran from ram as it includes segments mapped to rom addresses")] + #[diagnostic( + code(espflash::not_ram_loadable), + help("Either build the binary to be all in ram or remove the `--ram` option to load the image to flash") + )] + ElfNotRamLoadable, + #[error("The bootloader returned an error")] + #[diagnostic(transparent)] + RomError(#[from] RomError), + #[error("Chip not recognized, supported chip types are esp8266, esp32 and esp32-c3")] + #[diagnostic( + code(espflash::unrecognized_chip), + help("If your chip is supported, try hard-resetting the device and try again") + )] + UnrecognizedChip(#[from] ChipDetectError), + #[error("Flash chip not supported, flash sizes from 1 to 16MB are supported")] + #[diagnostic(code(espflash::unrecognized_flash))] + UnsupportedFlash(#[from] FlashDetectError), + #[error("Failed to connect to on-device flash")] + #[diagnostic(code(espflash::flash_connect))] + FlashConnect, + #[error(transparent)] + #[diagnostic(transparent)] + MalformedPartitionTable(#[from] PartitionTableError), +} + +#[derive(Error, Debug, Diagnostic)] +#[non_exhaustive] +pub enum ConnectionError { #[error("IO error while using serial port: {0}")] - Serial(#[from] serial::core::Error), + #[diagnostic(code(espflash::serial_error))] + Serial(#[source] serial::core::Error), #[error("Failed to connect to the device")] + #[diagnostic( + code(espflash::connection_failed), + help("Ensure that the device is connected and the reset and boot pins are not being held down") + )] ConnectionFailed, + #[error("Serial port not found")] + #[diagnostic( + code(espflash::connection_failed), + help("Ensure that the device is connected and your host recognizes the serial adapter") + )] + DeviceNotFound, #[error("Timeout while running command")] + #[diagnostic(code(espflash::timeout))] Timeout, - #[error("Invalid SLIP framing")] + #[error("Received packet has invalid SLIP framing")] + #[diagnostic( + code(espflash::slip_framing), + help("Try hard-resetting the device and try again, if the error persists your rom might be corrupted") + )] FramingError, - #[error("Packet to large for buffer")] + #[error("Received packet to large for buffer")] + #[diagnostic( + code(espflash::oversized_packet), + help("Try hard-resetting the device and try again, if the error persists your rom might be corrupted") + )] OverSizedPacket, - #[error("elf image is not valid")] - InvalidElf, - #[error("elf image can not be ran from ram")] - ElfNotRamLoadable, - #[error("bootloader returned an error: {0:?}")] - RomError(RomError), - #[error("chip not recognized, supported chip types are esp8266 and esp32")] - UnrecognizedChip, - #[error("flash chip not supported, flash id: {0:#x}")] - UnsupportedFlash(u8), } -impl From for Error { - fn from(err: std::io::Error) -> Self { - Self::Serial(serial::core::Error::from(err)) +impl From for ConnectionError { + fn from(err: serial::Error) -> Self { + match err.kind() { + serial::ErrorKind::Io(kind) => from_error_kind(kind, err), + serial::ErrorKind::NoDevice => ConnectionError::DeviceNotFound, + _ => ConnectionError::Serial(err), + } } } -impl From for Error { +impl From for Error { + fn from(err: serial::Error) -> Self { + Self::Connection(err.into()) + } +} + +impl From for ConnectionError { + fn from(err: io::Error) -> Self { + from_error_kind(err.kind(), err) + } +} + +impl From for Error { + fn from(err: io::Error) -> Self { + Self::Connection(err.into()) + } +} + +fn from_error_kind>(kind: io::ErrorKind, err: E) -> ConnectionError { + match kind { + io::ErrorKind::TimedOut => ConnectionError::Timeout, + io::ErrorKind::NotFound => ConnectionError::DeviceNotFound, + _ => ConnectionError::Serial(err.into()), + } +} + +impl From for ConnectionError { fn from(err: SlipError) -> Self { match err { SlipError::FramingError => Self::FramingError, @@ -43,26 +127,55 @@ impl From for Error { } } -impl From for Error { +impl From for Error { + fn from(err: SlipError) -> Self { + Self::Connection(err.into()) + } +} + +impl From for ConnectionError { fn from(err: binread::Error) -> Self { match err { - binread::Error::Io(e) => Error::from(e), + binread::Error::Io(e) => ConnectionError::from(e), _ => unreachable!(), } } } -#[derive(Copy, Clone, Debug)] +impl From for Error { + fn from(err: binread::Error) -> Self { + Self::Connection(err.into()) + } +} + +#[derive(Copy, Clone, Debug, Error, Diagnostic)] #[allow(dead_code)] #[repr(u8)] +#[non_exhaustive] pub enum RomError { + #[error("Invalid message received")] + #[diagnostic(code(espflash::rom::invalid_message))] InvalidMessage = 0x05, + #[error("Bootloader failed to execute command")] + #[diagnostic(code(espflash::rom::failed))] FailedToAct = 0x06, + #[error("Received message has invalid crc")] + #[diagnostic(code(espflash::rom::crc))] InvalidCrc = 0x07, + #[error("Bootloader failed to write to flash")] + #[diagnostic(code(espflash::rom::flash_write))] FlashWriteError = 0x08, + #[error("Bootloader failed to read from flash")] + #[diagnostic(code(espflash::rom::flash_read))] FlashReadError = 0x09, + #[error("Invalid length for flash read")] + #[diagnostic(code(espflash::rom::flash_read_length))] FlashReadLengthError = 0x0a, + #[error("Malformed compressed data received")] + #[diagnostic(code(espflash::rom::deflate))] DeflateError = 0x0b, + #[error("Other")] + #[diagnostic(code(espflash::rom::other))] Other = 0xff, } @@ -80,3 +193,113 @@ impl From for RomError { } } } + +pub(crate) trait ResultExt { + /// mark an error as having occurred during the flashing stage + fn flashing(self) -> Self; +} + +impl ResultExt for Result { + fn flashing(self) -> Self { + match self { + Err(Error::Connection(err)) => Err(Error::Flashing(err)), + res => res, + } + } +} + +#[derive(Debug, Error, Diagnostic)] +#[error("Malformed partition table")] +#[diagnostic( + code(espflash::mallformed_partition_table), + help("See the espressif documentation for information on the partition table format: + + https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/partition-tables.html#creating-custom-tables") +)] +pub struct PartitionTableError { + source: String, + #[snippet(source)] + snip: SourceSpan, + #[highlight(snip, label("{}", self.hint))] + err_span: SourceSpan, + hint: String, + #[source] + error: csv::Error, +} + +impl PartitionTableError { + pub fn new(error: csv::Error, source: String) -> Self { + let err_pos = match error.kind() { + csv::ErrorKind::Deserialize { pos: Some(pos), .. } => pos.clone(), + csv::ErrorKind::UnequalLengths { pos: Some(pos), .. } => pos.clone(), + _ => Position::new(), + }; + let hint = match error.kind() { + csv::ErrorKind::Deserialize { err, .. } => err.to_string(), + csv::ErrorKind::UnequalLengths { + expected_len, len, .. + } => format!( + "record has {} fields, but the previous record has {} fields", + len, expected_len + ), + _ => String::new(), + }; + let snip_start = + SourceOffset::from_location(&source, err_pos.line().saturating_sub(2) as usize, 0); + let snip_end = + SourceOffset::from_location(&source, err_pos.line().saturating_add(2) as usize, 0); + let snip = SourceSpan::new(snip_start, (snip_end.offset() - snip_start.offset()).into()); + + // since csv doesn't give us the position in the line the error occurs, we highlight the entire line + let line_length = (source + .lines() + .nth(err_pos.line() as usize - 1) + .unwrap() + .len() + - 1) + .into(); + let err_span = SourceSpan::new(pos_to_offset(err_pos), line_length); + + PartitionTableError { + source, + err_span, + snip, + hint, + error, + } + } +} + +fn pos_to_offset(pos: Position) -> SourceOffset { + (pos.byte() as usize + 1).into() +} + +#[derive(Debug, Error)] +#[error("{0}")] +pub struct ElfError(&'static str); + +impl From<&'static str> for ElfError { + fn from(err: &'static str) -> Self { + ElfError(err) + } +} + +#[derive(Debug, Error)] +#[error("Unrecognized magic value {0:#x}")] +pub struct ChipDetectError(u32); + +impl From for ChipDetectError { + fn from(err: u32) -> Self { + ChipDetectError(err) + } +} + +#[derive(Debug, Error)] +#[error("Unrecognized flash id {0:#x}")] +pub struct FlashDetectError(u8); + +impl From for FlashDetectError { + fn from(err: u8) -> Self { + FlashDetectError(err) + } +} diff --git a/espflash/src/flasher.rs b/espflash/src/flasher.rs index c3612cbf..0b717bfe 100644 --- a/espflash/src/flasher.rs +++ b/espflash/src/flasher.rs @@ -5,6 +5,7 @@ use strum_macros::Display; use std::thread::sleep; use crate::elf::RomSegment; +use crate::error::{ConnectionError, ElfError, FlashDetectError, ResultExt}; use crate::{ chip::Chip, connection::Connection, elf::FirmwareImage, encoder::SlipEncoder, error::RomError, Error, PartitionTable, @@ -107,7 +108,7 @@ impl FlashSize { 0x17 => Ok(FlashSize::Flash8Mb), 0x18 => Ok(FlashSize::Flash16Mb), 0xFF => Ok(FlashSize::FlashRetry), - _ => Err(Error::UnsupportedFlash(value)), + _ => Err(Error::UnsupportedFlash(FlashDetectError::from(value))), } } } @@ -247,12 +248,12 @@ impl Flasher { } // none of the spi parameters were successful - Err(Error::UnsupportedFlash(FlashSize::FlashRetry as u8)) + Err(Error::FlashConnect) } fn chip_detect(&mut self) -> Result<(), Error> { let magic = self.read_reg(CHIP_DETECT_MAGIC_REG_ADDR)?; - let chip = Chip::from_magic(magic).ok_or(Error::UnrecognizedChip)?; + let chip = Chip::from_magic(magic)?; self.chip = chip; Ok(()) @@ -310,7 +311,7 @@ impl Flasher { return Ok(()); } } - Err(Error::ConnectionFailed) + Err(Error::Connection(ConnectionError::ConnectionFailed)) } fn begin_command( @@ -422,7 +423,7 @@ impl Flasher { } i += 1; if i > 10 { - return Err(Error::Timeout); + return Err(Error::Connection(ConnectionError::Timeout)); } } @@ -468,26 +469,28 @@ impl Flasher { /// /// Note that this will not touch the flash on the device pub fn load_elf_to_ram(&mut self, elf_data: &[u8]) -> Result<(), Error> { - let image = FirmwareImage::from_data(elf_data).map_err(|_| Error::InvalidElf)?; + let image = FirmwareImage::from_data(elf_data).map_err(ElfError::from)?; let mut target = self.chip.ram_target(); - target.begin(&mut self.connection, &image)?; + target.begin(&mut self.connection, &image).flashing()?; if image.rom_segments(self.chip).next().is_some() { return Err(Error::ElfNotRamLoadable); } for segment in image.ram_segments(self.chip) { - target.write_segment( - &mut self.connection, - RomSegment { - addr: segment.addr, - data: segment.data.into(), - }, - )?; + target + .write_segment( + &mut self.connection, + RomSegment { + addr: segment.addr, + data: segment.data.into(), + }, + ) + .flashing()?; } - target.finish(&mut self.connection, true) + target.finish(&mut self.connection, true).flashing() } /// Load an elf image to flash and execute it @@ -497,20 +500,22 @@ impl Flasher { bootloader: Option>, partition_table: Option, ) -> Result<(), Error> { - let mut image = FirmwareImage::from_data(elf_data).map_err(|_| Error::InvalidElf)?; + let mut image = FirmwareImage::from_data(elf_data).map_err(ElfError::from)?; image.flash_size = self.flash_size(); let mut target = self.chip.flash_target(self.spi_params); - target.begin(&mut self.connection, &image)?; + target.begin(&mut self.connection, &image).flashing()?; for segment in self .chip .get_flash_segments(&image, bootloader, partition_table) { - target.write_segment(&mut self.connection, segment?)?; + target + .write_segment(&mut self.connection, segment?) + .flashing()?; } - target.finish(&mut self.connection, true)?; + target.finish(&mut self.connection, true).flashing()?; Ok(()) } diff --git a/espflash/src/main.rs b/espflash/src/main.rs index e2507e15..84ce3872 100644 --- a/espflash/src/main.rs +++ b/espflash/src/main.rs @@ -1,7 +1,7 @@ use std::fs::read; -use color_eyre::{eyre::WrapErr, Result}; -use espflash::{Config, Flasher}; +use espflash::{Config, Error, Flasher}; +use miette::{IntoDiagnostic, Result, WrapErr}; use pico_args::Arguments; use serial::{BaudRate, SerialPort}; @@ -22,8 +22,8 @@ fn main() -> Result<()> { let ram = args.contains("--ram"); let board_info = args.contains("--board-info"); - let mut serial: Option = args.opt_free_from_str()?; - let mut elf: Option = args.opt_free_from_str()?; + let mut serial: Option = args.opt_free_from_str().into_diagnostic()?; + let mut elf: Option = args.opt_free_from_str().into_diagnostic()?; if elf.is_none() && config.connection.serial.is_some() { elf = serial.take(); @@ -35,19 +35,22 @@ fn main() -> Result<()> { _ => return help(), }; - let mut serial = - serial::open(&serial).wrap_err_with(|| format!("Failed to open serial port {}", serial))?; - serial.reconfigure(&|settings| { - settings.set_baud_rate(BaudRate::Baud115200)?; + let mut serial = serial::open(&serial) + .map_err(Error::from) + .wrap_err_with(|| format!("Failed to open serial port {}", serial))?; + serial + .reconfigure(&|settings| { + settings.set_baud_rate(BaudRate::Baud115200)?; - Ok(()) - })?; + Ok(()) + }) + .into_diagnostic()?; let mut flasher = Flasher::connect(serial, None)?; if board_info { - println!("Chip type: {:?}", flasher.chip()); - println!("Flash size: {:?}", flasher.flash_size()); + println!("Chip type: {}", flasher.chip()); + println!("Flash size: {}", flasher.flash_size()); return Ok(()); } @@ -56,8 +59,9 @@ fn main() -> Result<()> { Some(input) => input, _ => return help(), }; - let input_bytes = - read(&input).wrap_err_with(|| format!("Failed to open elf image \"{}\"", input))?; + let input_bytes = read(&input) + .into_diagnostic() + .wrap_err_with(|| format!("Failed to open elf image \"{}\"", input))?; if ram { flasher.load_elf_to_ram(&input_bytes)?; diff --git a/espflash/src/partition_table.rs b/espflash/src/partition_table.rs index 4ac92884..40c388b7 100644 --- a/espflash/src/partition_table.rs +++ b/espflash/src/partition_table.rs @@ -2,7 +2,8 @@ use md5::{Context, Digest}; use regex::Regex; use serde::{Deserialize, Deserializer}; -use std::{error::Error, io::Write}; +use crate::error::PartitionTableError; +use std::io::Write; const MAX_PARTITION_LENGTH: usize = 0xC00; const PARTITION_TABLE_SIZE: usize = 0x1000; @@ -11,78 +12,67 @@ const MAX_PARTITION_TABLE_ENTRIES: usize = 95; #[derive(Copy, Clone, Debug, Deserialize)] #[repr(u8)] #[allow(dead_code)] +#[serde(rename_all = "lowercase")] pub enum Type { - #[serde(alias = "app")] App = 0x00, - #[serde(alias = "data")] Data = 0x01, } #[derive(Copy, Clone, Debug, Deserialize)] #[repr(u8)] #[allow(dead_code)] +#[serde(rename_all = "lowercase")] pub enum AppType { - #[serde(alias = "factory")] Factory = 0x00, - #[serde(alias = "ota_0")] + #[serde(rename = "ota_0")] Ota0 = 0x10, - #[serde(alias = "ota_1")] + #[serde(rename = "ota_1")] Ota1 = 0x11, - #[serde(alias = "ota_2")] + #[serde(rename = "ota_2")] Ota2 = 0x12, - #[serde(alias = "ota_3")] + #[serde(rename = "ota_3")] Ota3 = 0x13, - #[serde(alias = "ota_4")] + #[serde(rename = "ota_4")] Ota4 = 0x14, - #[serde(alias = "ota_5")] + #[serde(rename = "ota_5")] Ota5 = 0x15, - #[serde(alias = "ota_6")] + #[serde(rename = "ota_6")] Ota6 = 0x16, - #[serde(alias = "ota_7")] + #[serde(rename = "ota_7")] Ota7 = 0x17, - #[serde(alias = "ota_8")] + #[serde(rename = "ota_8")] Ota8 = 0x18, - #[serde(alias = "ota_9")] + #[serde(rename = "ota_9")] Ota9 = 0x19, - #[serde(alias = "ota_10")] + #[serde(rename = "ota_10")] Ota10 = 0x1a, - #[serde(alias = "ota_11")] + #[serde(rename = "ota_11")] Ota11 = 0x1b, - #[serde(alias = "ota_12")] + #[serde(rename = "ota_12")] Ota12 = 0x1c, - #[serde(alias = "ota_13")] + #[serde(rename = "ota_13")] Ota13 = 0x1d, - #[serde(alias = "ota_14")] + #[serde(rename = "ota_14")] Ota14 = 0x1e, - #[serde(alias = "ota_15")] + #[serde(rename = "ota_15")] Ota15 = 0x1f, - #[serde(alias = "test")] Test = 0x20, } #[derive(Copy, Clone, Debug, Deserialize)] #[repr(u8)] #[allow(dead_code)] +#[serde(rename_all = "lowercase")] pub enum DataType { - #[serde(alias = "ota")] Ota = 0x00, - #[serde(alias = "phy")] Phy = 0x01, - #[serde(alias = "nvs")] Nvs = 0x02, - #[serde(alias = "coredump")] CoreDump = 0x03, - #[serde(alias = "nvs_keys")] NvsKeys = 0x04, - #[serde(alias = "efuse")] EFuse = 0x05, - #[serde(alias = "undefined")] Undefined = 0x06, - #[serde(alias = "esphttpd")] EspHttpd = 0x80, - #[serde(alias = "fat")] Fat = 0x81, - #[serde(alias = "spiffs")] Spiffs = 0x82, } @@ -147,9 +137,9 @@ impl PartitionTable { } /// Attempt to parse a partition table from the given string. For more - /// information on the paritition table CSV format see: + /// information on the partition table CSV format see: /// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/partition-tables.html - pub fn try_from_str>(data: S) -> Result> { + pub fn try_from_str>(data: S) -> Result { let data = data.into(); let mut reader = csv::ReaderBuilder::new() .comment(Some(b'#')) @@ -159,7 +149,8 @@ impl PartitionTable { let mut partitions = Vec::with_capacity(MAX_PARTITION_TABLE_ENTRIES); for partition in reader.deserialize() { - let partition: Partition = partition?; + let partition: Partition = + partition.map_err(|e| PartitionTableError::new(e, data.clone()))?; partitions.push(partition); }