Skip to content

Commit 5e54a91

Browse files
committed
switch to crossterm for serial monitor
1 parent d07e3d5 commit 5e54a91

File tree

3 files changed

+136
-59
lines changed

3 files changed

+136
-59
lines changed

cargo-espflash/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ categories = [
2727
miette = "2"
2828
cargo_metadata = "0.14"
2929
clap = "2.33"
30+
crossterm = "0.21"
3031
espflash = { version = "*", path = "../espflash" }
3132
guess_host_triple = "0.1"
3233
serde = { version = "1.0", features = ["derive"] }
3334
serial = "0.4"
3435
toml = "0.5"
35-
termion = "1"
36-
thiserror = "1"
36+
thiserror = "1"

cargo-espflash/src/main.rs

Lines changed: 6 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,21 @@ use clap::{App, Arg, SubCommand};
33
use error::Error;
44
use espflash::{Config, Flasher, PartitionTable};
55
use miette::{IntoDiagnostic, Result, WrapErr};
6-
use serial::{BaudRate, SerialPort, SystemPort};
7-
6+
use serial::{BaudRate, SerialPort};
87
use std::{
98
fs,
109
path::PathBuf,
1110
process::{exit, Command, ExitStatus, Stdio},
1211
string::ToString,
1312
};
1413

14+
use cargo_config::has_build_std;
15+
use monitor::monitor;
16+
1517
mod cargo_config;
1618
mod error;
1719
mod line_endings;
18-
19-
use crate::line_endings::normalized;
20-
use cargo_config::has_build_std;
21-
use std::io::{stdout, ErrorKind, Read, Write};
22-
use std::thread::sleep;
23-
use std::time::Duration;
24-
use termion::{async_stdin, raw::IntoRawMode};
20+
mod monitor;
2521

2622
fn main() -> Result<()> {
2723
let mut app = App::new(env!("CARGO_PKG_NAME"))
@@ -190,7 +186,7 @@ fn main() -> Result<()> {
190186
}
191187

192188
if matches.is_present("monitor") {
193-
monitor(flasher.into_serial())?;
189+
monitor(flasher.into_serial()).into_diagnostic()?;
194190
}
195191

196192
// We're all done!
@@ -295,50 +291,3 @@ fn exit_with_process_status(status: ExitStatus) -> ! {
295291

296292
exit(code)
297293
}
298-
299-
const KEYCODE_CTRL_C: u8 = 3;
300-
const KEYCODE_CTRL_R: u8 = 18;
301-
302-
fn monitor(mut serial: SystemPort) -> anyhow::Result<()> {
303-
println!("Commands:");
304-
println!(" CTRL+R Reset chip");
305-
println!(" CTRL+C Exit");
306-
println!();
307-
308-
let mut buff = [0; 128];
309-
serial.set_timeout(Duration::from_millis(5))?;
310-
311-
let mut stdin = async_stdin().bytes();
312-
let stdout = stdout().into_raw_mode()?;
313-
let mut stdout = stdout.lock();
314-
loop {
315-
let read = match serial.read(&mut buff) {
316-
Ok(count) => Ok(count),
317-
Err(e) if e.kind() == ErrorKind::TimedOut => Ok(0),
318-
err => err,
319-
}?;
320-
if read > 0 {
321-
let data: Vec<u8> = normalized(buff[0..read].iter().copied()).collect();
322-
stdout.write_all(&data).ok();
323-
stdout.flush()?;
324-
}
325-
if let Some(Ok(byte)) = stdin.next() {
326-
match byte {
327-
KEYCODE_CTRL_C => break,
328-
KEYCODE_CTRL_R => {
329-
serial.set_dtr(false)?;
330-
serial.set_rts(true)?;
331-
332-
sleep(Duration::from_millis(100));
333-
334-
serial.set_rts(false)?;
335-
}
336-
_ => {
337-
serial.write_all(&[byte])?;
338-
serial.flush()?;
339-
}
340-
}
341-
}
342-
}
343-
Ok(())
344-
}

cargo-espflash/src/monitor.rs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
use crate::line_endings::normalized;
2+
use crossterm::event::{poll, read, Event, KeyCode, KeyEvent, KeyModifiers};
3+
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
4+
use miette::{IntoDiagnostic, Result};
5+
use serial::{SerialPort, SystemPort};
6+
use std::io::{stdout, ErrorKind, Read, Write};
7+
use std::thread::sleep;
8+
use std::time::Duration;
9+
10+
/// Converts key events from crossterm into appropriate character/escape sequences which are then
11+
/// sent over the serial connection.
12+
///
13+
/// Adapted from https://github.com/dhylands/serial-monitor
14+
fn handle_key_event(key_event: KeyEvent) -> Option<Vec<u8>> {
15+
// The following escape sequences come from the MicroPython codebase.
16+
//
17+
// Up ESC [A
18+
// Down ESC [B
19+
// Right ESC [C
20+
// Left ESC [D
21+
// Home ESC [H or ESC [1~
22+
// End ESC [F or ESC [4~
23+
// Del ESC [3~
24+
// Insert ESC [2~
25+
26+
let mut buf = [0; 4];
27+
28+
let key_str: Option<&[u8]> = match key_event.code {
29+
KeyCode::Backspace => Some(b"\x08"),
30+
KeyCode::Enter => Some(b"\r"),
31+
KeyCode::Left => Some(b"\x1b[D"),
32+
KeyCode::Right => Some(b"\x1b[C"),
33+
KeyCode::Home => Some(b"\x1b[H"),
34+
KeyCode::End => Some(b"\x1b[F"),
35+
KeyCode::Up => Some(b"\x1b[A"),
36+
KeyCode::Down => Some(b"\x1b[B"),
37+
KeyCode::Tab => Some(b"\x09"),
38+
KeyCode::Delete => Some(b"\x1b[3~"),
39+
KeyCode::Insert => Some(b"\x1b[2~"),
40+
KeyCode::Esc => Some(b"\x1b"),
41+
KeyCode::Char(ch) => {
42+
if key_event.modifiers & KeyModifiers::CONTROL == KeyModifiers::CONTROL {
43+
buf[0] = ch as u8;
44+
if ('a'..='z').contains(&ch) || (ch == ' ') {
45+
buf[0] &= 0x1f;
46+
Some(&buf[0..1])
47+
} else if ('4'..='7').contains(&ch) {
48+
// crossterm returns Control-4 thru 7 for \x1c thru \x1f
49+
buf[0] = (buf[0] + 8) & 0x1f;
50+
Some(&buf[0..1])
51+
} else {
52+
Some(ch.encode_utf8(&mut buf).as_bytes())
53+
}
54+
} else {
55+
Some(ch.encode_utf8(&mut buf).as_bytes())
56+
}
57+
}
58+
_ => None,
59+
};
60+
key_str.map(|slice| slice.into())
61+
}
62+
63+
struct RawModeGuard;
64+
65+
impl RawModeGuard {
66+
pub fn new() -> Result<Self> {
67+
enable_raw_mode().into_diagnostic()?;
68+
Ok(RawModeGuard)
69+
}
70+
}
71+
72+
impl Drop for RawModeGuard {
73+
fn drop(&mut self) {
74+
if let Err(e) = disable_raw_mode() {
75+
eprintln!("{:#}", e)
76+
}
77+
}
78+
}
79+
80+
pub fn monitor(mut serial: SystemPort) -> serial::Result<()> {
81+
println!("Commands:");
82+
println!(" CTRL+R Reset chip");
83+
println!(" CTRL+C Exit");
84+
println!();
85+
86+
let mut buff = [0; 128];
87+
serial.set_timeout(Duration::from_millis(5))?;
88+
89+
let _raw_mode = RawModeGuard::new();
90+
let stdout = stdout();
91+
let mut stdout = stdout.lock();
92+
loop {
93+
let read_count = match serial.read(&mut buff) {
94+
Ok(count) => Ok(count),
95+
Err(e) if e.kind() == ErrorKind::TimedOut => Ok(0),
96+
err => err,
97+
}?;
98+
if read_count > 0 {
99+
let data: Vec<u8> = normalized(buff[0..read_count].iter().copied()).collect();
100+
stdout.write_all(&data).ok();
101+
stdout.flush()?;
102+
}
103+
if poll(Duration::from_secs(0))? {
104+
if let Event::Key(key) = read()? {
105+
if key.modifiers.contains(KeyModifiers::CONTROL) {
106+
match key.code {
107+
KeyCode::Char('c') => break,
108+
KeyCode::Char('r') => {
109+
serial.set_dtr(false)?;
110+
serial.set_rts(true)?;
111+
112+
sleep(Duration::from_millis(100));
113+
114+
serial.set_rts(false)?;
115+
continue;
116+
}
117+
_ => {}
118+
}
119+
}
120+
if let Some(bytes) = handle_key_event(key) {
121+
serial.write_all(&bytes)?;
122+
serial.flush()?;
123+
}
124+
}
125+
}
126+
}
127+
Ok(())
128+
}

0 commit comments

Comments
 (0)