use std::{fs, path::PathBuf};
use clap::{Args, CommandFactory, Parser, Subcommand};
use espflash::{
Error,
cli::{
self,
config::Config,
monitor::{check_monitor_args, monitor},
*,
},
flasher::FlashSize,
image_format::{ImageFormat, ImageFormatKind, idf::check_idf_bootloader},
logging::initialize_logger,
update::check_for_update,
};
use log::{LevelFilter, debug, info};
use miette::{IntoDiagnostic, Result, WrapErr};
#[derive(Debug, Parser)]
#[command(about, max_term_width = 100, propagate_version = true, version)]
pub struct Cli {
#[command(subcommand)]
subcommand: Commands,
#[clap(
short = 'S',
long,
global = true,
env = "ESPFLASH_SKIP_UPDATE_CHECK",
action
)]
skip_update_check: bool,
}
#[derive(Debug, Subcommand)]
enum Commands {
BoardInfo(ConnectArgs),
ChecksumMd5(ChecksumMd5Args),
Completions(CompletionsArgs),
EraseFlash(EraseFlashArgs),
EraseParts(ErasePartsArgs),
EraseRegion(EraseRegionArgs),
Flash(FlashArgs),
HoldInReset(ConnectArgs),
ListPorts(ListPortsArgs),
Monitor(MonitorArgs),
PartitionTable(PartitionTableArgs),
ReadFlash(ReadFlashArgs),
Reset(ConnectArgs),
SaveImage(SaveImageArgs),
WriteBin(WriteBinArgs),
}
#[derive(Debug, Args)]
#[non_exhaustive]
pub struct ErasePartsArgs {
#[clap(flatten)]
pub connect_args: ConnectArgs,
#[arg(value_name = "LABELS", value_delimiter = ',')]
pub erase_parts: Vec<String>,
#[arg(long, value_name = "FILE")]
pub partition_table: Option<PathBuf>,
}
#[derive(Debug, Args)]
#[non_exhaustive]
struct FlashArgs {
#[clap(flatten)]
connect_args: ConnectArgs,
#[clap(flatten)]
pub flash_config_args: FlashConfigArgs,
#[clap(flatten)]
flash_args: cli::FlashArgs,
image: PathBuf,
#[clap(long, default_value = "esp-idf")]
format: ImageFormatKind,
#[clap(flatten)]
idf_format_args: cli::IdfFormatArgs,
}
#[derive(Debug, Args)]
#[non_exhaustive]
struct SaveImageArgs {
image: PathBuf,
#[clap(flatten)]
pub flash_config_args: FlashConfigArgs,
#[clap(flatten)]
save_image_args: cli::SaveImageArgs,
#[clap(long, default_value = "esp-idf")]
format: ImageFormatKind,
#[clap(flatten)]
idf_format_args: cli::IdfFormatArgs,
}
fn main() -> Result<()> {
miette::set_panic_hook();
initialize_logger(LevelFilter::Info);
let cli = Cli::parse();
let args = cli.subcommand;
debug!("{:#?}, {:#?}", args, cli.skip_update_check);
if !cli.skip_update_check {
check_for_update(env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
}
let config = Config::load()?;
match args {
Commands::BoardInfo(args) => board_info(&args, &config),
Commands::ChecksumMd5(args) => checksum_md5(&args, &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.port_config),
Commands::Monitor(args) => serial_monitor(args, &config),
Commands::PartitionTable(args) => partition_table(args),
Commands::ReadFlash(args) => read_flash(args, &config),
Commands::Reset(args) => reset(args, &config),
Commands::SaveImage(args) => save_image(args, &config),
Commands::WriteBin(args) => write_bin(args, &config),
}
}
fn erase_parts(args: ErasePartsArgs, config: &Config) -> Result<()> {
if args.connect_args.no_stub {
return Err(Error::StubRequired.into());
}
let mut flasher = connect(&args.connect_args, config, false, false)?;
let chip = flasher.chip();
let partition_table = match args.partition_table {
Some(path) => Some(parse_partition_table(&path)?),
None => None,
};
info!("Erasing the following partitions: {:?}", args.erase_parts);
erase_partitions(&mut flasher, partition_table, Some(args.erase_parts), None)?;
flasher
.connection()
.reset_after(!args.connect_args.no_stub, chip)?;
info!("Specified partitions successfully erased!");
Ok(())
}
fn flash(args: FlashArgs, config: &Config) -> Result<()> {
let mut monitor_args = args.flash_args.monitor_args;
monitor_args.elf = Some(args.image.clone());
check_monitor_args(
&args.flash_args.monitor,
&monitor_args,
args.connect_args.non_interactive,
)?;
check_idf_args(
args.format,
&args.flash_args.erase_parts,
&args.flash_args.erase_data_parts,
)?;
let mut flasher = connect(
&args.connect_args,
config,
args.flash_args.no_verify,
args.flash_args.no_skip,
)?;
if !flasher.secure_download_mode() {
flasher.verify_minimum_revision(args.flash_args.image.min_chip_rev)?;
}
if let Some(flash_size) = args.flash_config_args.flash_size {
flasher.set_flash_size(flash_size);
} else if let Some(flash_size) = config.project_config.flash.size {
flasher.set_flash_size(flash_size);
}
let chip = flasher.chip();
let target_xtal_freq = chip.xtal_frequency(flasher.connection())?;
let elf_data = fs::read(&args.image).into_diagnostic()?;
if args.flash_args.image.check_app_descriptor && args.format == ImageFormatKind::EspIdf {
check_idf_bootloader(&elf_data)?;
}
let dev_info = print_board_info(&mut flasher)?;
let detected_chip_revision = dev_info
.revision
.map(|(major, minor)| (major * 100 + minor) as u16);
ensure_chip_compatibility(chip, Some(elf_data.as_slice()))?;
let mut flash_config = args.flash_config_args;
flash_config.flash_size = flash_config
.flash_size .or(config.project_config.flash.size) .or_else(|| flasher.flash_detect().ok().flatten()) .or_else(|| Some(FlashSize::default()));
if args.flash_args.ram {
flasher.load_elf_to_ram(&elf_data, &mut EspflashProgress::default())?;
} else {
let flash_data = make_flash_data(
args.flash_args.image,
&flash_config,
config,
chip,
target_xtal_freq,
);
let image_format = make_image_format_with_chip_revision(
&elf_data,
&flash_data,
args.format,
config,
Some(args.idf_format_args),
None,
None,
detected_chip_revision,
)?;
if let ImageFormat::EspIdf(idf_format) = &image_format
&& (args.flash_args.erase_parts.is_some() || args.flash_args.erase_data_parts.is_some())
{
erase_partitions(
&mut flasher,
Some(idf_format.partition_table()),
args.flash_args.erase_parts,
args.flash_args.erase_data_parts,
)?;
}
flash_image(&mut flasher, image_format)?;
}
if args.flash_args.monitor {
let pid = flasher.connection().usb_pid();
preprocess_monitor_args(chip, target_xtal_freq, &mut monitor_args);
monitor_args.elf = Some(args.image);
let elfs = load_monitor_elfs(Some(elf_data.as_ref()), &monitor_args, &dev_info)?;
let elf_refs = elfs.refs();
monitor(
flasher.into(),
elf_refs,
pid,
monitor_args,
args.connect_args.non_interactive,
)
} else {
Ok(())
}
}
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()))?;
if args.save_image_args.image.check_app_descriptor && args.format == ImageFormatKind::EspIdf {
check_idf_bootloader(&elf_data)?;
}
println!("Chip type: {}", args.save_image_args.chip);
println!("Merge: {}", args.save_image_args.merge);
println!("Skip padding: {}", args.save_image_args.skip_padding);
let mut flash_config = args.flash_config_args;
flash_config.flash_size = flash_config
.flash_size .or(config.project_config.flash.size) .or_else(|| Some(FlashSize::default()));
let xtal_freq = args
.save_image_args
.xtal_freq
.unwrap_or(args.save_image_args.chip.default_xtal_frequency());
let flash_data = make_flash_data(
args.save_image_args.image,
&flash_config,
config,
args.save_image_args.chip,
xtal_freq,
);
let image_format = make_image_format(
&elf_data,
&flash_data,
args.format,
config,
Some(args.idf_format_args),
None,
None,
)?;
save_elf_as_image(
args.save_image_args.file,
flash_data.flash_settings.size,
args.save_image_args.merge,
args.save_image_args.skip_padding,
image_format,
)?;
Ok(())
}