From 351a4d91af979d2ab715ae9c802d260109f26b1b Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 10 Sep 2021 23:18:14 +0200 Subject: [PATCH 1/5] unbox serial --- espflash/src/connection.rs | 16 ++++++++++------ espflash/src/flasher.rs | 13 +++++++------ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/espflash/src/connection.rs b/espflash/src/connection.rs index e5a3e8b0..7e0bc720 100644 --- a/espflash/src/connection.rs +++ b/espflash/src/connection.rs @@ -6,11 +6,11 @@ use crate::encoder::SlipEncoder; use crate::error::{ConnectionError, Error, RomError}; use binread::io::Cursor; use binread::{BinRead, BinReaderExt}; -use serial::{BaudRate, SerialPort, SerialPortSettings}; +use serial::{BaudRate, SerialPort, SerialPortSettings, SystemPort}; use slip_codec::Decoder; pub struct Connection { - serial: Box, + serial: SystemPort, decoder: Decoder, } @@ -25,9 +25,9 @@ pub struct CommandResponse { } impl Connection { - pub fn new(serial: impl SerialPort + 'static) -> Self { + pub fn new(serial: SystemPort) -> Self { Connection { - serial: Box::new(serial), + serial, decoder: Decoder::new(), } } @@ -99,7 +99,7 @@ impl Connection { pub fn write_command( &mut self, command: u8, - data: impl LazyBytes>, + data: impl LazyBytes, check: u32, ) -> Result<(), Error> { let mut encoder = SlipEncoder::new(&mut self.serial)?; @@ -112,7 +112,7 @@ impl Connection { Ok(()) } - pub fn command>>( + pub fn command>( &mut self, command: u8, data: Data, @@ -148,6 +148,10 @@ impl Connection { self.serial.flush()?; Ok(()) } + + pub fn into_serial(self) -> SystemPort { + self.serial + } } pub trait LazyBytes { diff --git a/espflash/src/flasher.rs b/espflash/src/flasher.rs index 0b717bfe..2d0cc6fe 100644 --- a/espflash/src/flasher.rs +++ b/espflash/src/flasher.rs @@ -1,5 +1,5 @@ use bytemuck::{__core::time::Duration, bytes_of, Pod, Zeroable}; -use serial::{BaudRate, SerialPort}; +use serial::{BaudRate, SystemPort}; use strum_macros::Display; use std::thread::sleep; @@ -11,7 +11,7 @@ use crate::{ Error, PartitionTable, }; -pub(crate) type Encoder<'a> = SlipEncoder<'a, Box>; +pub(crate) type Encoder<'a> = SlipEncoder<'a, SystemPort>; pub(crate) const FLASH_SECTOR_SIZE: usize = 0x1000; const FLASH_BLOCK_SIZE: usize = 0x100; @@ -205,10 +205,7 @@ pub struct Flasher { } impl Flasher { - pub fn connect( - serial: impl SerialPort + 'static, - speed: Option, - ) -> Result { + pub fn connect(serial: SystemPort, speed: Option) -> Result { let mut flasher = Flasher { connection: Connection::new(serial), // default baud is always 115200 chip: Chip::Esp8266, // dummy, set properly later @@ -537,6 +534,10 @@ impl Flasher { self.connection.flush()?; Ok(()) } + + pub fn into_serial(self) -> SystemPort { + self.connection.into_serial() + } } pub(crate) fn get_erase_size(offset: usize, size: usize) -> usize { From 88639c467585d9bc0057825dd1c480128d3d0a50 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sat, 11 Sep 2021 00:50:09 +0200 Subject: [PATCH 2/5] implement basic serial monitor --- cargo-espflash/Cargo.toml | 1 + cargo-espflash/src/line_endings.rs | 57 ++++++++++++++++++++++++++ cargo-espflash/src/main.rs | 64 +++++++++++++++++++++++++++++- 3 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 cargo-espflash/src/line_endings.rs diff --git a/cargo-espflash/Cargo.toml b/cargo-espflash/Cargo.toml index c1eba36b..fd0330f0 100644 --- a/cargo-espflash/Cargo.toml +++ b/cargo-espflash/Cargo.toml @@ -32,4 +32,5 @@ guess_host_triple = "0.1" serde = { version = "1.0", features = ["derive"] } serial = "0.4" toml = "0.5" +termion = "1" thiserror = "1" diff --git a/cargo-espflash/src/line_endings.rs b/cargo-espflash/src/line_endings.rs new file mode 100644 index 00000000..cb2092be --- /dev/null +++ b/cargo-espflash/src/line_endings.rs @@ -0,0 +1,57 @@ +// Adapted from https://github.com/derekdreery/normalize-line-endings + +struct Normalized { + iter: I, + prev_was_cr: bool, + peeked: Option, +} + +pub fn normalized(iter: impl Iterator) -> impl Iterator { + Normalized { + iter, + prev_was_cr: false, + peeked: None, + } +} + +impl Iterator for Normalized +where + I: Iterator, +{ + type Item = u8; + fn next(&mut self) -> Option { + if let Some(peeked) = self.peeked.take() { + return Some(peeked); + } + match self.iter.next() { + Some(b'\n') if !self.prev_was_cr => { + self.peeked = Some(b'\n'); + self.prev_was_cr = false; + Some(b'\r') + } + Some(b'\r') => { + self.prev_was_cr = true; + Some(b'\r') + } + any => { + self.prev_was_cr = false; + any + } + } + } +} + +// tests +#[cfg(test)] +mod tests { + use std::iter::FromIterator; + + #[test] + fn normalized() { + let input = b"This is a string \n with \n some \n\r\n random newlines\r\n\n"; + assert_eq!( + &Vec::from_iter(super::normalized(input.iter().copied())), + b"This is a string \r\n with \r\n some \r\n\r\n random newlines\r\n\r\n" + ); + } +} diff --git a/cargo-espflash/src/main.rs b/cargo-espflash/src/main.rs index b2700a22..5a310a79 100644 --- a/cargo-espflash/src/main.rs +++ b/cargo-espflash/src/main.rs @@ -3,7 +3,7 @@ use clap::{App, Arg, SubCommand}; use error::Error; use espflash::{Config, Flasher, PartitionTable}; use miette::{IntoDiagnostic, Result, WrapErr}; -use serial::{BaudRate, SerialPort}; +use serial::{BaudRate, SerialPort, SystemPort}; use std::{ fs, @@ -14,8 +14,14 @@ use std::{ mod cargo_config; mod error; +mod line_endings; +use crate::line_endings::normalized; use cargo_config::has_build_std; +use std::io::{stdout, ErrorKind, Read, Write}; +use std::thread::sleep; +use std::time::Duration; +use termion::{async_stdin, raw::IntoRawMode}; fn main() -> Result<()> { let mut app = App::new(env!("CARGO_PKG_NAME")) @@ -80,6 +86,11 @@ fn main() -> Result<()> { .takes_value(true) .value_name("SERIAL") .help("Serial port connected to target device"), + ) + .arg( + Arg::with_name("monitor") + .long("monitor") + .help("Open a serial monitor after flashing"), ), ); @@ -178,6 +189,10 @@ fn main() -> Result<()> { flasher.load_elf_to_flash(&elf_data, bootloader, partition_table)?; } + if matches.is_present("monitor") { + monitor(flasher.into_serial())?; + } + // We're all done! Ok(()) } @@ -280,3 +295,50 @@ fn exit_with_process_status(status: ExitStatus) -> ! { exit(code) } + +const KEYCODE_CTRL_C: u8 = 3; +const KEYCODE_CTRL_R: u8 = 18; + +fn monitor(mut serial: SystemPort) -> anyhow::Result<()> { + println!("Commands:"); + println!(" CTRL+R Reset chip"); + println!(" CTRL+C Exit"); + println!(); + + let mut buff = [0; 128]; + serial.set_timeout(Duration::from_millis(5))?; + + let mut stdin = async_stdin().bytes(); + let stdout = stdout().into_raw_mode()?; + let mut stdout = stdout.lock(); + loop { + let read = match serial.read(&mut buff) { + Ok(count) => Ok(count), + Err(e) if e.kind() == ErrorKind::TimedOut => Ok(0), + err => err, + }?; + if read > 0 { + let data: Vec = normalized(buff[0..read].iter().copied()).collect(); + stdout.write_all(&data).ok(); + stdout.flush()?; + } + if let Some(Ok(byte)) = stdin.next() { + match byte { + KEYCODE_CTRL_C => break, + KEYCODE_CTRL_R => { + serial.set_dtr(false)?; + serial.set_rts(true)?; + + sleep(Duration::from_millis(100)); + + serial.set_rts(false)?; + } + _ => { + serial.write_all(&[byte])?; + serial.flush()?; + } + } + } + } + Ok(()) +} From d07e3d599fc3d5a0cff90048ce8b12fcad3115b6 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sat, 11 Sep 2021 13:40:00 +0200 Subject: [PATCH 3/5] extend ci to build cargo-espflash too --- .github/workflows/rust.yml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 9de00683..1f5e9050 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -102,11 +102,15 @@ jobs: - uses: actions-rs/cargo@v1 with: command: build - args: --release --bin espflash --target x86_64-unknown-linux-musl + args: --release --all --target x86_64-unknown-linux-musl - uses: actions/upload-artifact@v2 with: name: espflash path: target/x86_64-unknown-linux-musl/release/espflash + - uses: actions/upload-artifact@v2 + with: + name: cargo-espflash + path: target/x86_64-unknown-linux-musl/release/cargo-espflash build-windows: name: Build Static Windows Binaries @@ -123,8 +127,12 @@ jobs: with: use-cross: true command: build - args: --release --bin espflash --target x86_64-pc-windows-gnu + args: --release --all --target x86_64-pc-windows-gnu - uses: actions/upload-artifact@v2 with: name: espflash.exe - path: target/x86_64-pc-windows-gnu/release/espflash.exe \ No newline at end of file + path: target/x86_64-pc-windows-gnu/release/espflash.exe + - uses: actions/upload-artifact@v2 + with: + name: cargo-espflash.exe + path: target/x86_64-pc-windows-gnu/release/cargo-espflash.exe \ No newline at end of file From 5e54a91ce2cb78a06a3941688dfaf4b95c9b4c2b Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sat, 11 Sep 2021 14:38:05 +0200 Subject: [PATCH 4/5] switch to crossterm for serial monitor --- cargo-espflash/Cargo.toml | 4 +- cargo-espflash/src/main.rs | 63 ++--------------- cargo-espflash/src/monitor.rs | 128 ++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 59 deletions(-) create mode 100644 cargo-espflash/src/monitor.rs diff --git a/cargo-espflash/Cargo.toml b/cargo-espflash/Cargo.toml index fd0330f0..0fb571a3 100644 --- a/cargo-espflash/Cargo.toml +++ b/cargo-espflash/Cargo.toml @@ -27,10 +27,10 @@ categories = [ miette = "2" cargo_metadata = "0.14" clap = "2.33" +crossterm = "0.21" espflash = { version = "*", path = "../espflash" } guess_host_triple = "0.1" serde = { version = "1.0", features = ["derive"] } serial = "0.4" toml = "0.5" -termion = "1" -thiserror = "1" +thiserror = "1" \ No newline at end of file diff --git a/cargo-espflash/src/main.rs b/cargo-espflash/src/main.rs index 5a310a79..3bb95afc 100644 --- a/cargo-espflash/src/main.rs +++ b/cargo-espflash/src/main.rs @@ -3,8 +3,7 @@ use clap::{App, Arg, SubCommand}; use error::Error; use espflash::{Config, Flasher, PartitionTable}; use miette::{IntoDiagnostic, Result, WrapErr}; -use serial::{BaudRate, SerialPort, SystemPort}; - +use serial::{BaudRate, SerialPort}; use std::{ fs, path::PathBuf, @@ -12,16 +11,13 @@ use std::{ string::ToString, }; +use cargo_config::has_build_std; +use monitor::monitor; + mod cargo_config; mod error; mod line_endings; - -use crate::line_endings::normalized; -use cargo_config::has_build_std; -use std::io::{stdout, ErrorKind, Read, Write}; -use std::thread::sleep; -use std::time::Duration; -use termion::{async_stdin, raw::IntoRawMode}; +mod monitor; fn main() -> Result<()> { let mut app = App::new(env!("CARGO_PKG_NAME")) @@ -190,7 +186,7 @@ fn main() -> Result<()> { } if matches.is_present("monitor") { - monitor(flasher.into_serial())?; + monitor(flasher.into_serial()).into_diagnostic()?; } // We're all done! @@ -295,50 +291,3 @@ fn exit_with_process_status(status: ExitStatus) -> ! { exit(code) } - -const KEYCODE_CTRL_C: u8 = 3; -const KEYCODE_CTRL_R: u8 = 18; - -fn monitor(mut serial: SystemPort) -> anyhow::Result<()> { - println!("Commands:"); - println!(" CTRL+R Reset chip"); - println!(" CTRL+C Exit"); - println!(); - - let mut buff = [0; 128]; - serial.set_timeout(Duration::from_millis(5))?; - - let mut stdin = async_stdin().bytes(); - let stdout = stdout().into_raw_mode()?; - let mut stdout = stdout.lock(); - loop { - let read = match serial.read(&mut buff) { - Ok(count) => Ok(count), - Err(e) if e.kind() == ErrorKind::TimedOut => Ok(0), - err => err, - }?; - if read > 0 { - let data: Vec = normalized(buff[0..read].iter().copied()).collect(); - stdout.write_all(&data).ok(); - stdout.flush()?; - } - if let Some(Ok(byte)) = stdin.next() { - match byte { - KEYCODE_CTRL_C => break, - KEYCODE_CTRL_R => { - serial.set_dtr(false)?; - serial.set_rts(true)?; - - sleep(Duration::from_millis(100)); - - serial.set_rts(false)?; - } - _ => { - serial.write_all(&[byte])?; - serial.flush()?; - } - } - } - } - Ok(()) -} diff --git a/cargo-espflash/src/monitor.rs b/cargo-espflash/src/monitor.rs new file mode 100644 index 00000000..dab4620d --- /dev/null +++ b/cargo-espflash/src/monitor.rs @@ -0,0 +1,128 @@ +use crate::line_endings::normalized; +use crossterm::event::{poll, read, Event, KeyCode, KeyEvent, KeyModifiers}; +use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; +use miette::{IntoDiagnostic, Result}; +use serial::{SerialPort, SystemPort}; +use std::io::{stdout, ErrorKind, Read, Write}; +use std::thread::sleep; +use std::time::Duration; + +/// Converts key events from crossterm into appropriate character/escape sequences which are then +/// sent over the serial connection. +/// +/// Adapted from https://github.com/dhylands/serial-monitor +fn handle_key_event(key_event: KeyEvent) -> Option> { + // The following escape sequences come from the MicroPython codebase. + // + // Up ESC [A + // Down ESC [B + // Right ESC [C + // Left ESC [D + // Home ESC [H or ESC [1~ + // End ESC [F or ESC [4~ + // Del ESC [3~ + // Insert ESC [2~ + + let mut buf = [0; 4]; + + let key_str: Option<&[u8]> = match key_event.code { + KeyCode::Backspace => Some(b"\x08"), + KeyCode::Enter => Some(b"\r"), + KeyCode::Left => Some(b"\x1b[D"), + KeyCode::Right => Some(b"\x1b[C"), + KeyCode::Home => Some(b"\x1b[H"), + KeyCode::End => Some(b"\x1b[F"), + KeyCode::Up => Some(b"\x1b[A"), + KeyCode::Down => Some(b"\x1b[B"), + KeyCode::Tab => Some(b"\x09"), + KeyCode::Delete => Some(b"\x1b[3~"), + KeyCode::Insert => Some(b"\x1b[2~"), + KeyCode::Esc => Some(b"\x1b"), + KeyCode::Char(ch) => { + if key_event.modifiers & KeyModifiers::CONTROL == KeyModifiers::CONTROL { + buf[0] = ch as u8; + if ('a'..='z').contains(&ch) || (ch == ' ') { + buf[0] &= 0x1f; + Some(&buf[0..1]) + } else if ('4'..='7').contains(&ch) { + // crossterm returns Control-4 thru 7 for \x1c thru \x1f + buf[0] = (buf[0] + 8) & 0x1f; + Some(&buf[0..1]) + } else { + Some(ch.encode_utf8(&mut buf).as_bytes()) + } + } else { + Some(ch.encode_utf8(&mut buf).as_bytes()) + } + } + _ => None, + }; + key_str.map(|slice| slice.into()) +} + +struct RawModeGuard; + +impl RawModeGuard { + pub fn new() -> Result { + enable_raw_mode().into_diagnostic()?; + Ok(RawModeGuard) + } +} + +impl Drop for RawModeGuard { + fn drop(&mut self) { + if let Err(e) = disable_raw_mode() { + eprintln!("{:#}", e) + } + } +} + +pub fn monitor(mut serial: SystemPort) -> serial::Result<()> { + println!("Commands:"); + println!(" CTRL+R Reset chip"); + println!(" CTRL+C Exit"); + println!(); + + let mut buff = [0; 128]; + serial.set_timeout(Duration::from_millis(5))?; + + let _raw_mode = RawModeGuard::new(); + let stdout = stdout(); + let mut stdout = stdout.lock(); + loop { + let read_count = match serial.read(&mut buff) { + Ok(count) => Ok(count), + Err(e) if e.kind() == ErrorKind::TimedOut => Ok(0), + err => err, + }?; + if read_count > 0 { + let data: Vec = normalized(buff[0..read_count].iter().copied()).collect(); + stdout.write_all(&data).ok(); + stdout.flush()?; + } + if poll(Duration::from_secs(0))? { + if let Event::Key(key) = read()? { + if key.modifiers.contains(KeyModifiers::CONTROL) { + match key.code { + KeyCode::Char('c') => break, + KeyCode::Char('r') => { + serial.set_dtr(false)?; + serial.set_rts(true)?; + + sleep(Duration::from_millis(100)); + + serial.set_rts(false)?; + continue; + } + _ => {} + } + } + if let Some(bytes) = handle_key_event(key) { + serial.write_all(&bytes)?; + serial.flush()?; + } + } + } + } + Ok(()) +} From a4dac997211c26f90f450dc1cc2d7d56043aa1f8 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 15 Sep 2021 19:49:18 +0200 Subject: [PATCH 5/5] only send utf8 to terminal --- cargo-espflash/src/monitor.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cargo-espflash/src/monitor.rs b/cargo-espflash/src/monitor.rs index dab4620d..4300ca1f 100644 --- a/cargo-espflash/src/monitor.rs +++ b/cargo-espflash/src/monitor.rs @@ -97,7 +97,8 @@ pub fn monitor(mut serial: SystemPort) -> serial::Result<()> { }?; if read_count > 0 { let data: Vec = normalized(buff[0..read_count].iter().copied()).collect(); - stdout.write_all(&data).ok(); + let data = String::from_utf8_lossy(&data); + stdout.write_all(data.as_bytes()).ok(); stdout.flush()?; } if poll(Duration::from_secs(0))? {