Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ jobs:
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: 1.55
toolchain: 1.56
override: true
- uses: Swatinem/rust-cache@v1
- uses: actions-rs/cargo@v1
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ This repository contains two applications:
| [cargo-espflash] | Cargo subcommand for espflash |
| [espflash] | Library and `espflash` binary (_without_ Cargo integration) |

> **NOTE:** requires `rustc >= 1.55.0` in order to build either application
> **NOTE:** requires `rustc >= 1.56.0` in order to build either application

## Installation

Expand Down
2 changes: 1 addition & 1 deletion cargo-espflash/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ authors = [
"Jesse Braham <jesse@beta7.io>",
]
edition = "2018"
rust-version = "1.55"
rust-version = "1.56"
description = "Cargo subcommand for flashing Espressif devices over serial"
repository = "https://github.com/esp-rs/espflash"
license = "GPL-2.0"
Expand Down
2 changes: 1 addition & 1 deletion cargo-espflash/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ fn main() -> Result<()> {

let CargoSubCommand::Espflash(opts) = Opts::parse().sub_cmd;

let config = Config::load();
let config = Config::load()?;
let metadata = CargoEspFlashMeta::load("Cargo.toml")?;
let cargo_config = parse_cargo_config(".")?;

Expand Down
3 changes: 2 additions & 1 deletion espflash/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "espflash"
version = "1.1.0"
authors = ["Robin Appelman <robin@icewind.nl>"]
edition = "2018"
rust-version = "1.55"
rust-version = "1.56"
license = "GPL-2.0"
description = "ESP8266 and ESP32 serial flasher"
repository = "https://github.com/esp-rs/espflash"
Expand Down Expand Up @@ -38,6 +38,7 @@ miette = { version = "3", features = ["fancy"] }
crossterm = "0.22"
directories-next = "2"
dialoguer = "0.9"
serde-hex = "0.1"

[package.metadata.binstall]
pkg-url = "{ repo }/releases/download/v{ version }/{ name }"
Expand Down
43 changes: 33 additions & 10 deletions espflash/src/cli/config.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
use std::fs::read;

use directories_next::ProjectDirs;
use serde::Deserialize;
use miette::{IntoDiagnostic, Result, WrapErr};
use serde::{Deserialize, Serialize};
use serde_hex::{Compact, SerHex};
use serialport::UsbPortInfo;
use std::fs::{create_dir_all, read, write};
use std::path::PathBuf;

#[derive(Debug, Deserialize, Default)]
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
pub struct Config {
#[serde(default)]
pub connection: Connection,
#[serde(default)]
pub usb_device: Vec<UsbDevice>,
#[serde(skip)]
save_path: PathBuf,
}

#[derive(Debug, Deserialize, Default)]
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
pub struct Connection {
pub serial: Option<String>,
}

#[derive(Debug, Deserialize, Default)]
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
pub struct UsbDevice {
#[serde(with = "SerHex::<Compact>")]
pub vid: u16,
#[serde(with = "SerHex::<Compact>")]
pub pid: u16,
}

Expand All @@ -31,14 +37,31 @@ impl UsbDevice {

impl Config {
/// Load the config from config file
pub fn load() -> Self {
pub fn load() -> Result<Self> {
let dirs = ProjectDirs::from("rs", "esp", "espflash").unwrap();
let file = dirs.config_dir().join("espflash.toml");

if let Ok(data) = read(&file) {
toml::from_slice(&data).unwrap()
let mut config = if let Ok(data) = read(&file) {
toml::from_slice(&data).into_diagnostic()?
} else {
Self::default()
}
};
config.save_path = file;
Ok(config)
}

pub fn save_with<F: Fn(&mut Self)>(&self, modify_fn: F) -> Result<()> {
let mut copy = self.clone();
modify_fn(&mut copy);

let serialized = toml::to_string(&copy)
.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()))
}
}
64 changes: 47 additions & 17 deletions espflash/src/cli/serial.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,27 @@ pub fn get_serial_port(matches: &ConnectArgs, config: &Config) -> Result<String,
} else if let Some(serial) = &config.connection.serial {
Ok(serial.to_owned())
} else if let Ok(ports) = detect_usb_serial_ports() {
select_serial_port(ports, &config.usb_device)
let (port, matches) = select_serial_port(ports, config)?;
match port.port_type {
SerialPortType::UsbPort(usb_info) if !matches => {
if Confirm::with_theme(&ColorfulTheme::default())
.with_prompt("Remember this serial port for future use?")
.interact_opt()?
.unwrap_or_default()
{
if let Err(e) = config.save_with(|config| {
config.usb_device.push(UsbDevice {
vid: usb_info.vid,
pid: usb_info.pid,
})
}) {
eprintln!("Failed to save config {:#}", e);
}
}
}
_ => {}
}
Ok(port.port_name)
} else {
Err(Error::NoSerial)
}
Expand Down Expand Up @@ -56,10 +76,11 @@ const KNOWN_DEVICES: &[UsbDevice] = &[

fn select_serial_port(
ports: Vec<SerialPortInfo>,
configured_devices: &[UsbDevice],
) -> Result<String, Error> {
config: &Config,
) -> Result<(SerialPortInfo, bool), Error> {
let device_matches = |info| {
configured_devices
config
.usb_device
.iter()
.chain(KNOWN_DEVICES.iter())
.any(|dev| dev.matches(info))
Expand Down Expand Up @@ -97,7 +118,15 @@ fn select_serial_port(
.ok_or(Error::Canceled)?;

match ports.get(index) {
Some(port_info) => Ok(port_info.port_name.to_owned()),
Some(
port_info
@
SerialPortInfo {
port_type: SerialPortType::UsbPort(usb_info),
..
},
) => Ok((port_info.clone(), device_matches(usb_info))),
Some(port_info) => Ok((port_info.clone(), false)),
None => Err(Error::NoSerial),
}
} else if let [port] = ports.as_slice() {
Expand All @@ -108,19 +137,20 @@ fn select_serial_port(
_ => unreachable!(),
};

if device_matches(port_info)
|| Confirm::with_theme(&ColorfulTheme::default())
.with_prompt({
if let Some(product) = &port_info.product {
format!("Use serial port '{}' - {}?", port_name, product)
} else {
format!("Use serial port '{}'?", port_name)
}
})
.interact_opt()?
.ok_or(Error::Canceled)?
if device_matches(port_info) {
Ok((port.clone(), true))
} else if Confirm::with_theme(&ColorfulTheme::default())
.with_prompt({
if let Some(product) = &port_info.product {
format!("Use serial port '{}' - {}?", port_name, product)
} else {
format!("Use serial port '{}'?", port_name)
}
})
.interact_opt()?
.ok_or(Error::Canceled)?
{
Ok(port_name)
Ok((port.clone(), false))
} else {
Err(Error::NoSerial)
}
Expand Down
2 changes: 1 addition & 1 deletion espflash/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ fn main() -> Result<()> {
miette::set_panic_hook();

let mut opts = Opts::parse();
let config = Config::load();
let config = Config::load()?;

// If only a single argument is passed, it's always going to be the ELF file. In
// the case that the serial port was not provided as a command-line argument,
Expand Down