Skip to content

Commit 624c7fa

Browse files
committed
add list of known serial adapters used on dev boards
serial ports from these adapters are used by default when autodetecting serial port
1 parent 84cea6d commit 624c7fa

File tree

2 files changed

+136
-108
lines changed

2 files changed

+136
-108
lines changed

espflash/src/cli/mod.rs

Lines changed: 5 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,122 +1,19 @@
1+
use self::clap::ConnectArgs;
12
/// CLI utilities shared between espflash and cargo-espflash
23
///
34
/// No stability guaranties applies
45
use config::Config;
5-
use crossterm::style::Stylize;
6-
use dialoguer::{theme::ColorfulTheme, Confirm, Select};
7-
use miette::{IntoDiagnostic, Result, WrapErr};
8-
use serialport::{available_ports, FlowControl, SerialPortInfo, SerialPortType};
6+
use miette::{Result, WrapErr};
7+
use serialport::FlowControl;
98

10-
use self::clap::ConnectArgs;
11-
use crate::cli::config::UsbDevice;
9+
use crate::cli::serial::get_serial_port;
1210
use crate::{error::Error, Flasher};
1311

1412
pub mod clap;
1513
pub mod config;
1614
mod line_endings;
1715
pub mod monitor;
18-
19-
fn get_serial_port(matches: &ConnectArgs, config: &Config) -> Result<String, Error> {
20-
// A serial port should be specified either as a command-line argument or in a
21-
// configuration file. In the case that both have been provided the command-line
22-
// argument takes precedence.
23-
//
24-
// Users may optionally specify the device's VID and PID in the configuration
25-
// file. If no VID/PID have been provided, the user will always be prompted to
26-
// select a serial device. If some VID/PID have been provided the user will be
27-
// prompted to select a serial device, unless there is only one found and its
28-
// VID/PID matches the configured values.
29-
if let Some(serial) = &matches.serial {
30-
Ok(serial.to_owned())
31-
} else if let Some(serial) = &config.connection.serial {
32-
Ok(serial.to_owned())
33-
} else if let Ok(ports) = detect_usb_serial_ports() {
34-
select_serial_port(ports, &config.usb_device)
35-
} else {
36-
Err(Error::NoSerial)
37-
}
38-
}
39-
40-
fn detect_usb_serial_ports() -> Result<Vec<SerialPortInfo>> {
41-
let ports = available_ports().into_diagnostic()?;
42-
let ports = ports
43-
.iter()
44-
.filter_map(|port_info| match port_info.port_type {
45-
SerialPortType::UsbPort(..) => Some(port_info.to_owned()),
46-
_ => None,
47-
})
48-
.collect::<Vec<_>>();
49-
50-
Ok(ports)
51-
}
52-
53-
fn select_serial_port(ports: Vec<SerialPortInfo>, devices: &[UsbDevice]) -> Result<String, Error> {
54-
let device_matches = |info| devices.iter().any(|dev| dev.matches(info));
55-
56-
if ports.len() > 1 {
57-
// Multiple serial ports detected
58-
println!(
59-
"Detected {} serial ports. Ports with VID/PID matching configured values are bolded.\n",
60-
ports.len()
61-
);
62-
63-
let port_names = ports
64-
.iter()
65-
.map(|port_info| match &port_info.port_type {
66-
SerialPortType::UsbPort(info) => {
67-
let formatted = if device_matches(info) {
68-
port_info.port_name.as_str().bold()
69-
} else {
70-
port_info.port_name.as_str().reset()
71-
};
72-
if let Some(product) = &info.product {
73-
format!("{} - {}", formatted, product)
74-
} else {
75-
format!("{}", formatted)
76-
}
77-
}
78-
_ => port_info.port_name.clone(),
79-
})
80-
.collect::<Vec<_>>();
81-
let index = Select::with_theme(&ColorfulTheme::default())
82-
.items(&port_names)
83-
.default(0)
84-
.interact_opt()?
85-
.ok_or(Error::Canceled)?;
86-
87-
match ports.get(index) {
88-
Some(port_info) => Ok(port_info.port_name.to_owned()),
89-
None => Err(Error::NoSerial),
90-
}
91-
} else if let [port] = ports.as_slice() {
92-
// Single serial port detected
93-
let port_name = port.port_name.clone();
94-
let port_info = match &port.port_type {
95-
SerialPortType::UsbPort(info) => info,
96-
_ => unreachable!(),
97-
};
98-
99-
if device_matches(port_info)
100-
|| Confirm::with_theme(&ColorfulTheme::default())
101-
.with_prompt({
102-
if let Some(product) = &port_info.product {
103-
format!("Use serial port '{}' - {}?", port_name, product)
104-
} else {
105-
format!("Use serial port '{}'?", port_name)
106-
}
107-
})
108-
.interact_opt()?
109-
.ok_or(Error::Canceled)?
110-
{
111-
Ok(port_name)
112-
} else {
113-
Err(Error::NoSerial)
114-
}
115-
} else {
116-
// No serial ports detected
117-
Err(Error::NoSerial)
118-
}
119-
}
16+
mod serial;
12017

12118
pub fn connect(matches: &ConnectArgs, config: &Config) -> Result<Flasher> {
12219
let port = get_serial_port(matches, config)?;

espflash/src/cli/serial.rs

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
use super::config::Config;
2+
use crossterm::style::Stylize;
3+
use dialoguer::{theme::ColorfulTheme, Confirm, Select};
4+
use miette::{IntoDiagnostic, Result};
5+
use serialport::{available_ports, SerialPortInfo, SerialPortType};
6+
7+
use super::clap::ConnectArgs;
8+
use crate::cli::config::UsbDevice;
9+
use crate::error::Error;
10+
11+
pub fn get_serial_port(matches: &ConnectArgs, config: &Config) -> Result<String, Error> {
12+
// A serial port should be specified either as a command-line argument or in a
13+
// configuration file. In the case that both have been provided the command-line
14+
// argument takes precedence.
15+
//
16+
// Users may optionally specify the device's VID and PID in the configuration
17+
// file. If no VID/PID have been provided, the user will always be prompted to
18+
// select a serial device. If some VID/PID have been provided the user will be
19+
// prompted to select a serial device, unless there is only one found and its
20+
// VID/PID matches the configured values.
21+
if let Some(serial) = &matches.serial {
22+
Ok(serial.to_owned())
23+
} else if let Some(serial) = &config.connection.serial {
24+
Ok(serial.to_owned())
25+
} else if let Ok(ports) = detect_usb_serial_ports() {
26+
select_serial_port(ports, &config.usb_device)
27+
} else {
28+
Err(Error::NoSerial)
29+
}
30+
}
31+
32+
fn detect_usb_serial_ports() -> Result<Vec<SerialPortInfo>> {
33+
let ports = available_ports().into_diagnostic()?;
34+
let ports = ports
35+
.iter()
36+
.filter_map(|port_info| match port_info.port_type {
37+
SerialPortType::UsbPort(..) => Some(port_info.to_owned()),
38+
_ => None,
39+
})
40+
.collect::<Vec<_>>();
41+
42+
Ok(ports)
43+
}
44+
45+
/// USB UART adapters which are known to be on common dev boards
46+
const KNOWN_DEVICES: &[UsbDevice] = &[
47+
UsbDevice {
48+
vid: 0x10c4,
49+
pid: 0xea60,
50+
}, // Silicon Labs CP210x UART Bridge
51+
UsbDevice {
52+
vid: 0x1a86,
53+
pid: 0x7523,
54+
}, // QinHeng Electronics CH340 serial converter
55+
];
56+
57+
fn select_serial_port(
58+
ports: Vec<SerialPortInfo>,
59+
configured_devices: &[UsbDevice],
60+
) -> Result<String, Error> {
61+
let device_matches = |info| {
62+
configured_devices
63+
.iter()
64+
.chain(KNOWN_DEVICES.iter())
65+
.any(|dev| dev.matches(info))
66+
};
67+
68+
if ports.len() > 1 {
69+
// Multiple serial ports detected
70+
println!(
71+
"Detected {} serial ports. Ports with match a known common dev board are highlighted.\n",
72+
ports.len()
73+
);
74+
75+
let port_names = ports
76+
.iter()
77+
.map(|port_info| match &port_info.port_type {
78+
SerialPortType::UsbPort(info) => {
79+
let formatted = if device_matches(info) {
80+
port_info.port_name.as_str().bold()
81+
} else {
82+
port_info.port_name.as_str().reset()
83+
};
84+
if let Some(product) = &info.product {
85+
format!("{} - {}", formatted, product)
86+
} else {
87+
format!("{}", formatted)
88+
}
89+
}
90+
_ => port_info.port_name.clone(),
91+
})
92+
.collect::<Vec<_>>();
93+
let index = Select::with_theme(&ColorfulTheme::default())
94+
.items(&port_names)
95+
.default(0)
96+
.interact_opt()?
97+
.ok_or(Error::Canceled)?;
98+
99+
match ports.get(index) {
100+
Some(port_info) => Ok(port_info.port_name.to_owned()),
101+
None => Err(Error::NoSerial),
102+
}
103+
} else if let [port] = ports.as_slice() {
104+
// Single serial port detected
105+
let port_name = port.port_name.clone();
106+
let port_info = match &port.port_type {
107+
SerialPortType::UsbPort(info) => info,
108+
_ => unreachable!(),
109+
};
110+
111+
if device_matches(port_info)
112+
|| Confirm::with_theme(&ColorfulTheme::default())
113+
.with_prompt({
114+
if let Some(product) = &port_info.product {
115+
format!("Use serial port '{}' - {}?", port_name, product)
116+
} else {
117+
format!("Use serial port '{}'?", port_name)
118+
}
119+
})
120+
.interact_opt()?
121+
.ok_or(Error::Canceled)?
122+
{
123+
Ok(port_name)
124+
} else {
125+
Err(Error::NoSerial)
126+
}
127+
} else {
128+
// No serial ports detected
129+
Err(Error::NoSerial)
130+
}
131+
}

0 commit comments

Comments
 (0)