use std::{fs, mem::swap, path::PathBuf, str::FromStr};
use clap::{IntoApp, Parser};
use espflash::{
cli::{
board_info, check_for_updates, connect, flash_elf_image, monitor::monitor, partition_table,
save_elf_as_image, serial_monitor, write_bin_to_flash, ConnectOpts, FlashConfigOpts,
FlashOpts, PartitionTableOpts, WriteBinToFlashOpts,
},
Chip, Config, ImageFormatId,
};
use log::debug;
use miette::{IntoDiagnostic, Result, WrapErr};
use strum::VariantNames;
use tracing_subscriber::{filter::LevelFilter, EnvFilter};
#[derive(Debug, Parser)]
#[clap(version, propagate_version = true)]
struct Opts {
#[clap(long, possible_values = &["bootloader", "direct-boot"])]
pub format: Option<String>,
#[clap(flatten)]
pub flash_config_opts: FlashConfigOpts,
#[clap(flatten)]
flash_opts: FlashOpts,
#[clap(flatten)]
connect_opts: ConnectOpts,
image: Option<String>,
#[clap(subcommand)]
subcommand: Option<SubCommand>,
#[clap(long, default_value = "info", env)]
log_level: LevelFilter,
}
#[derive(Debug, Parser)]
pub enum SubCommand {
BoardInfo(ConnectOpts),
SaveImage(SaveImageOpts),
SerialMonitor(ConnectOpts),
PartitionTable(PartitionTableOpts),
WriteBinToFlash(WriteBinToFlashOpts),
}
#[derive(Debug, Parser)]
pub struct SaveImageOpts {
#[clap(flatten)]
pub flash_config_opts: FlashConfigOpts,
#[clap(long, possible_values = &["bootloader", "direct-boot"])]
format: Option<String>,
#[clap(possible_values = Chip::VARIANTS)]
chip: Chip,
image: PathBuf,
file: PathBuf,
#[clap(long, short = 'M')]
pub merge: bool,
#[clap(long, short = 'B')]
pub bootloader: Option<PathBuf>,
#[clap(long, short = 'T')]
pub partition_table: Option<PathBuf>,
#[clap(long, short = 'P')]
pub skip_padding: bool,
}
fn main() -> Result<()> {
miette::set_panic_hook();
check_for_updates(env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
let mut opts = Opts::parse();
let config = Config::load()?;
debug!("options: {:?}", opts);
if opts.flash_opts.erase_otadata {
opts.connect_opts.use_stub = true;
}
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env().add_directive(opts.log_level.into()))
.init();
if opts.subcommand.is_none() {
if opts.image.is_none() && opts.connect_opts.serial.is_none() {
Opts::command().print_help().ok();
return Ok(());
}
if opts.image.is_none() && opts.connect_opts.serial.is_some() {
swap(&mut opts.image, &mut opts.connect_opts.serial);
}
}
if let Some(subcommand) = opts.subcommand {
use SubCommand::*;
match subcommand {
BoardInfo(opts) => board_info(opts, config),
SaveImage(opts) => save_image(opts),
SerialMonitor(opts) => serial_monitor(opts, config),
PartitionTable(opts) => partition_table(opts),
WriteBinToFlash(opts) => write_bin_to_flash(opts),
}
} else {
flash(opts, config)
}
}
fn flash(opts: Opts, config: Config) -> Result<()> {
let mut flasher = connect(&opts.connect_opts, &config)?;
flasher.board_info()?;
let elf = if let Some(elf) = opts.image {
elf
} else {
Opts::command().print_help().ok();
return Ok(());
};
let elf_data = fs::read(&elf).into_diagnostic()?;
if opts.flash_opts.ram {
flasher.load_elf_to_ram(&elf_data)?;
} else {
let bootloader = opts.flash_opts.bootloader.as_deref();
let partition_table = opts.flash_opts.partition_table.as_deref();
let image_format = opts
.format
.as_deref()
.map(ImageFormatId::from_str)
.transpose()?;
flash_elf_image(
&mut flasher,
&elf_data,
bootloader,
partition_table,
image_format,
opts.flash_config_opts.flash_mode,
opts.flash_config_opts.flash_size,
opts.flash_config_opts.flash_freq,
opts.flash_opts.erase_otadata,
)?;
}
if opts.flash_opts.monitor {
let pid = flasher.get_usb_pid()?;
monitor(
flasher.into_serial(),
Some(&elf_data),
pid,
opts.connect_opts.monitor_speed.unwrap_or(115200),
)
.into_diagnostic()?;
}
Ok(())
}
fn save_image(opts: SaveImageOpts) -> Result<()> {
let elf_data = fs::read(&opts.image)
.into_diagnostic()
.wrap_err_with(|| format!("Failed to open image {}", opts.image.display()))?;
let image_format = opts
.format
.as_deref()
.map(ImageFormatId::from_str)
.transpose()?;
save_elf_as_image(
opts.chip,
&elf_data,
opts.file,
image_format,
opts.flash_config_opts.flash_mode,
opts.flash_config_opts.flash_size,
opts.flash_config_opts.flash_freq,
opts.merge,
opts.bootloader,
opts.partition_table,
opts.skip_padding,
)?;
Ok(())
}