Skip to content

Commit 1a86d2f

Browse files
authored
Merge pull request #43 from esp-rs/serial-monitor
Add basic serial monitor
2 parents 4abfa1b + a4dac99 commit 1a86d2f

File tree

7 files changed

+230
-19
lines changed

7 files changed

+230
-19
lines changed

.github/workflows/rust.yml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,15 @@ jobs:
102102
- uses: actions-rs/cargo@v1
103103
with:
104104
command: build
105-
args: --release --bin espflash --target x86_64-unknown-linux-musl
105+
args: --release --all --target x86_64-unknown-linux-musl
106106
- uses: actions/upload-artifact@v2
107107
with:
108108
name: espflash
109109
path: target/x86_64-unknown-linux-musl/release/espflash
110+
- uses: actions/upload-artifact@v2
111+
with:
112+
name: cargo-espflash
113+
path: target/x86_64-unknown-linux-musl/release/cargo-espflash
110114

111115
build-windows:
112116
name: Build Static Windows Binaries
@@ -123,8 +127,12 @@ jobs:
123127
with:
124128
use-cross: true
125129
command: build
126-
args: --release --bin espflash --target x86_64-pc-windows-gnu
130+
args: --release --all --target x86_64-pc-windows-gnu
127131
- uses: actions/upload-artifact@v2
128132
with:
129133
name: espflash.exe
130-
path: target/x86_64-pc-windows-gnu/release/espflash.exe
134+
path: target/x86_64-pc-windows-gnu/release/espflash.exe
135+
- uses: actions/upload-artifact@v2
136+
with:
137+
name: cargo-espflash.exe
138+
path: target/x86_64-pc-windows-gnu/release/cargo-espflash.exe

cargo-espflash/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +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-
thiserror = "1"
36+
thiserror = "1"

cargo-espflash/src/line_endings.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Adapted from https://github.com/derekdreery/normalize-line-endings
2+
3+
struct Normalized<I> {
4+
iter: I,
5+
prev_was_cr: bool,
6+
peeked: Option<u8>,
7+
}
8+
9+
pub fn normalized(iter: impl Iterator<Item = u8>) -> impl Iterator<Item = u8> {
10+
Normalized {
11+
iter,
12+
prev_was_cr: false,
13+
peeked: None,
14+
}
15+
}
16+
17+
impl<I> Iterator for Normalized<I>
18+
where
19+
I: Iterator<Item = u8>,
20+
{
21+
type Item = u8;
22+
fn next(&mut self) -> Option<u8> {
23+
if let Some(peeked) = self.peeked.take() {
24+
return Some(peeked);
25+
}
26+
match self.iter.next() {
27+
Some(b'\n') if !self.prev_was_cr => {
28+
self.peeked = Some(b'\n');
29+
self.prev_was_cr = false;
30+
Some(b'\r')
31+
}
32+
Some(b'\r') => {
33+
self.prev_was_cr = true;
34+
Some(b'\r')
35+
}
36+
any => {
37+
self.prev_was_cr = false;
38+
any
39+
}
40+
}
41+
}
42+
}
43+
44+
// tests
45+
#[cfg(test)]
46+
mod tests {
47+
use std::iter::FromIterator;
48+
49+
#[test]
50+
fn normalized() {
51+
let input = b"This is a string \n with \n some \n\r\n random newlines\r\n\n";
52+
assert_eq!(
53+
&Vec::from_iter(super::normalized(input.iter().copied())),
54+
b"This is a string \r\n with \r\n some \r\n\r\n random newlines\r\n\r\n"
55+
);
56+
}
57+
}

cargo-espflash/src/main.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,20 @@ use error::Error;
44
use espflash::{Config, Flasher, PartitionTable};
55
use miette::{IntoDiagnostic, Result, WrapErr};
66
use serial::{BaudRate, SerialPort};
7-
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;
17-
18-
use cargo_config::has_build_std;
19+
mod line_endings;
20+
mod monitor;
1921

2022
fn main() -> Result<()> {
2123
let mut app = App::new(env!("CARGO_PKG_NAME"))
@@ -80,6 +82,11 @@ fn main() -> Result<()> {
8082
.takes_value(true)
8183
.value_name("SERIAL")
8284
.help("Serial port connected to target device"),
85+
)
86+
.arg(
87+
Arg::with_name("monitor")
88+
.long("monitor")
89+
.help("Open a serial monitor after flashing"),
8390
),
8491
);
8592

@@ -178,6 +185,10 @@ fn main() -> Result<()> {
178185
flasher.load_elf_to_flash(&elf_data, bootloader, partition_table)?;
179186
}
180187

188+
if matches.is_present("monitor") {
189+
monitor(flasher.into_serial()).into_diagnostic()?;
190+
}
191+
181192
// We're all done!
182193
Ok(())
183194
}

cargo-espflash/src/monitor.rs

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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+
let data = String::from_utf8_lossy(&data);
101+
stdout.write_all(data.as_bytes()).ok();
102+
stdout.flush()?;
103+
}
104+
if poll(Duration::from_secs(0))? {
105+
if let Event::Key(key) = read()? {
106+
if key.modifiers.contains(KeyModifiers::CONTROL) {
107+
match key.code {
108+
KeyCode::Char('c') => break,
109+
KeyCode::Char('r') => {
110+
serial.set_dtr(false)?;
111+
serial.set_rts(true)?;
112+
113+
sleep(Duration::from_millis(100));
114+
115+
serial.set_rts(false)?;
116+
continue;
117+
}
118+
_ => {}
119+
}
120+
}
121+
if let Some(bytes) = handle_key_event(key) {
122+
serial.write_all(&bytes)?;
123+
serial.flush()?;
124+
}
125+
}
126+
}
127+
}
128+
Ok(())
129+
}

espflash/src/connection.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ use crate::encoder::SlipEncoder;
66
use crate::error::{ConnectionError, Error, RomError};
77
use binread::io::Cursor;
88
use binread::{BinRead, BinReaderExt};
9-
use serial::{BaudRate, SerialPort, SerialPortSettings};
9+
use serial::{BaudRate, SerialPort, SerialPortSettings, SystemPort};
1010
use slip_codec::Decoder;
1111

1212
pub struct Connection {
13-
serial: Box<dyn SerialPort>,
13+
serial: SystemPort,
1414
decoder: Decoder,
1515
}
1616

@@ -25,9 +25,9 @@ pub struct CommandResponse {
2525
}
2626

2727
impl Connection {
28-
pub fn new(serial: impl SerialPort + 'static) -> Self {
28+
pub fn new(serial: SystemPort) -> Self {
2929
Connection {
30-
serial: Box::new(serial),
30+
serial,
3131
decoder: Decoder::new(),
3232
}
3333
}
@@ -99,7 +99,7 @@ impl Connection {
9999
pub fn write_command(
100100
&mut self,
101101
command: u8,
102-
data: impl LazyBytes<Box<dyn SerialPort>>,
102+
data: impl LazyBytes<SystemPort>,
103103
check: u32,
104104
) -> Result<(), Error> {
105105
let mut encoder = SlipEncoder::new(&mut self.serial)?;
@@ -112,7 +112,7 @@ impl Connection {
112112
Ok(())
113113
}
114114

115-
pub fn command<Data: LazyBytes<Box<dyn SerialPort>>>(
115+
pub fn command<Data: LazyBytes<SystemPort>>(
116116
&mut self,
117117
command: u8,
118118
data: Data,
@@ -148,6 +148,10 @@ impl Connection {
148148
self.serial.flush()?;
149149
Ok(())
150150
}
151+
152+
pub fn into_serial(self) -> SystemPort {
153+
self.serial
154+
}
151155
}
152156

153157
pub trait LazyBytes<W: Write> {

espflash/src/flasher.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use bytemuck::{__core::time::Duration, bytes_of, Pod, Zeroable};
2-
use serial::{BaudRate, SerialPort};
2+
use serial::{BaudRate, SystemPort};
33
use strum_macros::Display;
44

55
use std::thread::sleep;
@@ -11,7 +11,7 @@ use crate::{
1111
Error, PartitionTable,
1212
};
1313

14-
pub(crate) type Encoder<'a> = SlipEncoder<'a, Box<dyn SerialPort>>;
14+
pub(crate) type Encoder<'a> = SlipEncoder<'a, SystemPort>;
1515

1616
pub(crate) const FLASH_SECTOR_SIZE: usize = 0x1000;
1717
const FLASH_BLOCK_SIZE: usize = 0x100;
@@ -205,10 +205,7 @@ pub struct Flasher {
205205
}
206206

207207
impl Flasher {
208-
pub fn connect(
209-
serial: impl SerialPort + 'static,
210-
speed: Option<BaudRate>,
211-
) -> Result<Self, Error> {
208+
pub fn connect(serial: SystemPort, speed: Option<BaudRate>) -> Result<Self, Error> {
212209
let mut flasher = Flasher {
213210
connection: Connection::new(serial), // default baud is always 115200
214211
chip: Chip::Esp8266, // dummy, set properly later
@@ -537,6 +534,10 @@ impl Flasher {
537534
self.connection.flush()?;
538535
Ok(())
539536
}
537+
538+
pub fn into_serial(self) -> SystemPort {
539+
self.connection.into_serial()
540+
}
540541
}
541542

542543
pub(crate) fn get_erase_size(offset: usize, size: usize) -> usize {

0 commit comments

Comments
 (0)