Skip to content

Commit 9a96231

Browse files
authored
Merge pull request #99 from esp-rs/known-serial-adapters
add list of known serial adapters used on dev boards
2 parents 84cea6d + 624c7fa commit 9a96231

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)