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: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add `non-interactive` flag to `flash` subcommand (#737)
- Add `no-reset` flag to `monitor` subcommands (#737)
- Add an environment variable to set monitoring baudrate (`MONITOR_BAUD`) (#737)
Add list-ports command to list available serial ports.
- Add list-ports command to list available serial ports.

### Changed
- Split the baudrate for connecting and monitorinig in `flash` subcommand (#737)
Expand Down
10 changes: 6 additions & 4 deletions cargo-espflash/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,27 +54,29 @@ OPENSSL_NO_VENDOR=1 cargo install cargo-espflash
```text
Cargo subcommand for flashing Espressif devices

Usage: cargo espflash <COMMAND>
Usage: cargo espflash [OPTIONS] <COMMAND>

Commands:
board-info Print information about a connected target device
checksum-md5 Calculate the MD5 checksum of the given region
completions Generate completions for the given shell
erase-flash Erase Flash entirely
erase-parts Erase specified partitions
erase-region Erase specified region
flash Flash an application in ELF format to a target device
hold-in-reset Hold the target device in reset
list-ports List serial ports available for flashing
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
reset Reset the target device
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:
-h, --help Print help
-V, --version Print version
-S, --skip-update-check Do not check for updates
-h, --help Print help
-V, --version Print version
```

### Permissions on Linux
Expand Down
12 changes: 9 additions & 3 deletions cargo-espflash/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ enum Commands {
/// Automatically detects and prints the chip type, crystal frequency, flash
/// size, chip features, and MAC address of a connected target device.
BoardInfo(ConnectArgs),
/// Calculate the MD5 checksum of the given region
ChecksumMd5(ChecksumMd5Args),
/// Generate completions for the given shell
///
/// The completions are printed to stdout, and can be redirected as needed.
Expand Down Expand Up @@ -86,6 +88,11 @@ enum Commands {
Flash(FlashArgs),
/// Hold the target device in reset
HoldInReset(ConnectArgs),
/// List serial ports available for flashing.
///
/// The default behavior is to only list ports of devices known to be used
/// on development boards.
ListPorts(ListPortsArgs),
/// Open the serial monitor without flashing the connected target device
Monitor(MonitorArgs),
/// Convert partition tables between CSV and binary format
Expand All @@ -110,8 +117,6 @@ enum Commands {
/// Otherwise, each segment will be saved as individual binaries, prefixed
/// with their intended addresses in flash.
SaveImage(SaveImageArgs),
/// Calculate the MD5 checksum of the given region
ChecksumMd5(ChecksumMd5Args),
}

#[derive(Debug, Args)]
Expand Down Expand Up @@ -222,18 +227,19 @@ fn main() -> Result<()> {
// associated arguments.
match args {
Commands::BoardInfo(args) => board_info(&args, &config),
Commands::ChecksumMd5(args) => checksum_md5(&args, &config),
Commands::Completions(args) => completions(&args, &mut Cli::command(), "cargo"),
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),
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::ChecksumMd5(args) => checksum_md5(&args, &config),
}
}

Expand Down
10 changes: 6 additions & 4 deletions espflash/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,28 +49,30 @@ cargo binstall espflash
```text
A command-line tool for flashing Espressif devices

Usage: espflash <COMMAND>
Usage: espflash [OPTIONS] <COMMAND>

Commands:
board-info Print information about a connected target device
checksum-md5 Calculate the MD5 checksum of the given region
completions Generate completions for the given shell
erase-flash Erase Flash entirely
erase-parts Erase specified partitions
erase-region Erase specified region
flash Flash an application in ELF format to a connected target device
hold-in-reset Hold the target device in reset
list-ports List serial ports available for flashing
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
reset Reset the target device
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:
-h, --help Print help
-V, --version Print version
-S, --skip-update-check Do not check for updates
-h, --help Print help
-V, --version Print version
```

### Permissions on Linux
Expand Down
12 changes: 9 additions & 3 deletions espflash/src/bin/espflash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ enum Commands {
/// Automatically detects and prints the chip type, crystal frequency, flash
/// size, chip features, and MAC address of a connected target device.
BoardInfo(ConnectArgs),
/// Calculate the MD5 checksum of the given region
ChecksumMd5(ChecksumMd5Args),
/// Generate completions for the given shell
///
/// The completions are printed to stdout, and can be redirected as needed.
Expand Down Expand Up @@ -61,6 +63,11 @@ enum Commands {
Flash(FlashArgs),
/// Hold the target device in reset
HoldInReset(ConnectArgs),
/// List serial ports available for flashing.
///
/// The default behavior is to only list ports of devices known to be used
/// on development boards.
ListPorts(ListPortsArgs),
/// Open the serial monitor without flashing the connected target device
Monitor(MonitorArgs),
/// Convert partition tables between CSV and binary format
Expand All @@ -87,8 +94,6 @@ enum Commands {
SaveImage(SaveImageArgs),
/// Write a binary file to a specific address in a target device's flash
WriteBin(WriteBinArgs),
/// Calculate the MD5 checksum of the given region
ChecksumMd5(ChecksumMd5Args),
}

/// Erase named partitions based on provided partition table
Expand Down Expand Up @@ -173,19 +178,20 @@ fn main() -> Result<()> {
// associated arguments.
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),
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),
Commands::ChecksumMd5(args) => checksum_md5(&args, &config),
}
}

Expand Down
90 changes: 89 additions & 1 deletion espflash/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use esp_idf_part::{DataType, Partition, PartitionTable};
use indicatif::{style::ProgressStyle, HumanCount, ProgressBar};
use log::{debug, info, warn};
use miette::{IntoDiagnostic, Result, WrapErr};
use serialport::{FlowControl, SerialPortType, UsbPortInfo};
use serialport::{FlowControl, SerialPortInfo, SerialPortType, UsbPortInfo};

use self::{
config::Config,
Expand Down Expand Up @@ -301,6 +301,19 @@ pub struct ChecksumMd5Args {
connect_args: ConnectArgs,
}

#[derive(Debug, Args)]
#[non_exhaustive]
pub struct ListPortsArgs {
/// List all available serial ports, instead of just those likely to be
/// development boards. Includes non-usb ports such as PCI devices.
#[arg(short = 'a', long)]
pub list_all_ports: bool,

/// Only print the name of the ports and nothing else. Useful for scripting.
#[arg(short, long)]
pub name_only: bool,
}

/// Parses an integer, in base-10 or hexadecimal format, into a [u32]
pub fn parse_u32(input: &str) -> Result<u32, ParseIntError> {
let input: &str = &input.replace('_', "");
Expand Down Expand Up @@ -396,6 +409,81 @@ pub fn checksum_md5(args: &ChecksumMd5Args, config: &Config) -> Result<()> {
Ok(())
}

pub fn list_ports(args: &ListPortsArgs, config: &Config) -> Result<()> {
let mut ports: Vec<SerialPortInfo> = serial::detect_usb_serial_ports(true)?
.into_iter()
.filter(|p| args.list_all_ports || serial::known_ports_filter(p, config))
.collect();
if ports.is_empty() {
if !args.name_only {
println!(
"No {}serial ports found.",
if args.list_all_ports { "" } else { "known " }
);
}
} else {
// We want display columns so we determine a width for each field
let name_width = ports.iter().map(|p| p.port_name.len()).max().unwrap_or(13) + 2;
let manufacturer_width = ports
.iter()
.filter_map(|p| match &p.port_type {
SerialPortType::UsbPort(p) => p.manufacturer.as_ref().map(|m| m.len()),
_ => None,
})
.max()
.unwrap_or(15)
+ 2;

ports.sort_by(|p1, p2| {
p1.port_name
.to_lowercase()
.cmp(&p2.port_name.to_lowercase())
});
for port in ports {
if args.name_only {
println!("{}", port.port_name)
} else {
match port.port_type {
SerialPortType::BluetoothPort => {
println!(
"{0: <name_width$}Bluetooth serial port",
port.port_name,
name_width = name_width + 11
)
}
SerialPortType::UsbPort(p) => {
println!(
"{0: <name_width$}{3:04X}:{4:04X} {1: <manufacturer_width$}{2}",
port.port_name,
p.manufacturer.unwrap_or_default(),
p.product.unwrap_or_default(),
p.pid,
p.vid,
name_width = name_width,
manufacturer_width = manufacturer_width,
)
}
SerialPortType::PciPort => {
println!(
"{0: <name_width$}PCI serial port",
port.port_name,
name_width = name_width + 11
)
}
SerialPortType::Unknown => {
println!(
"{0: <name_width$}Unknown type of port",
port.port_name,
name_width = name_width + 11
)
}
}
}
}
}
Ok(())
}

/// Generate shell completions for the given shell
pub fn completions(args: &CompletionsArgs, app: &mut clap::Command, bin_name: &str) -> Result<()> {
clap_complete::generate(args.shell, app, bin_name, &mut std::io::stdout());
Expand Down
28 changes: 15 additions & 13 deletions espflash/src/cli/serial.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ fn find_serial_port(ports: &[SerialPortInfo], name: &str) -> Result<SerialPortIn
}

/// Returns a vector with available USB serial ports.
fn detect_usb_serial_ports(list_all_ports: bool) -> Result<Vec<SerialPortInfo>> {
pub(super) fn detect_usb_serial_ports(list_all_ports: bool) -> Result<Vec<SerialPortInfo>> {
let ports = available_ports().into_diagnostic()?;
let ports = ports
.into_iter()
Expand Down Expand Up @@ -142,25 +142,27 @@ const KNOWN_DEVICES: &[UsbDevice] = &[
}, // QinHeng Electronics CH340 serial converter
];

/// Ask the user to select a serial port from a list of detected serial ports.
fn select_serial_port(
mut ports: Vec<SerialPortInfo>,
config: &Config,
force_confirm_port: bool,
) -> Result<(SerialPortInfo, bool), Error> {
pub(super) fn known_ports_filter(port: &SerialPortInfo, config: &Config) -> bool {
// Does this port match a known one?
let matches = |port: &SerialPortInfo| match &port.port_type {
match &port.port_type {
SerialPortType::UsbPort(info) => config
.usb_device
.iter()
.chain(KNOWN_DEVICES.iter())
.any(|dev| dev.matches(info)),
_ => false,
};
}
}

/// Ask the user to select a serial port from a list of detected serial ports.
fn select_serial_port(
mut ports: Vec<SerialPortInfo>,
config: &Config,
force_confirm_port: bool,
) -> Result<(SerialPortInfo, bool), Error> {
if let [port] = ports
.iter()
.filter(|&p| matches(p))
.filter(|&p| known_ports_filter(p, config))
.collect::<Vec<_>>()
.as_slice()
{
Expand All @@ -176,12 +178,12 @@ fn select_serial_port(
info!("Ports which match a known common dev board are highlighted");
info!("Please select a port");

ports.sort_by_key(|a| !matches(a));
ports.sort_by_key(|a| !known_ports_filter(a, config));

let port_names = ports
.iter()
.map(|port_info| {
let formatted = if matches(port_info) {
let formatted = if known_ports_filter(port_info, config) {
port_info.port_name.as_str().bold()
} else {
port_info.port_name.as_str().reset()
Expand Down Expand Up @@ -213,7 +215,7 @@ fn select_serial_port(
.ok_or(Error::Cancelled)?;

match ports.get(index) {
Some(port_info) => Ok((port_info.to_owned(), matches(port_info))),
Some(port_info) => Ok((port_info.to_owned(), known_ports_filter(port_info, config))),
None => Err(Error::SerialNotFound(
port_names.get(index).unwrap().to_string(),
)),
Expand Down
Loading