From 777b292cb2f567e1f76efea795bbd6b17310405f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Sat, 26 Aug 2023 09:01:48 +0200 Subject: [PATCH 01/10] Print defmt frames output by esp-println --- Cargo.lock | 98 ++++++++++++++++- espflash/Cargo.toml | 4 +- espflash/src/cli/monitor/mod.rs | 189 ++++++++++++++++++++++++++------ 3 files changed, 251 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8c426642..956a0fe1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,10 +30,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "cpp_demangle", - "fallible-iterator", - "gimli", + "fallible-iterator 0.3.0", + "gimli 0.28.0", "memmap2", - "object", + "object 0.32.1", "rustc-demangle", "smallvec", ] @@ -163,7 +163,7 @@ dependencies = [ "cfg-if", "libc", "miniz_oxide", - "object", + "object 0.32.1", "rustc-demangle", ] @@ -542,6 +542,17 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "colored" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" +dependencies = [ + "is-terminal", + "lazy_static", + "windows-sys 0.48.0", +] + [[package]] name = "comfy-table" version = "7.0.1" @@ -821,6 +832,47 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "defmt-decoder" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69494bdf0927405e6d22641d1307c664ad3e9483452b85078940529883945044" +dependencies = [ + "anyhow", + "byteorder", + "colored", + "defmt-json-schema", + "defmt-parser", + "dissimilar", + "gimli 0.27.3", + "log", + "nom", + "object 0.31.1", + "ryu", + "serde", + "serde_json", + "time", +] + +[[package]] +name = "defmt-json-schema" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b04d228e57a61cf385d86bc8980bb41b47c6fc0eace90592668df97b2dad6a" +dependencies = [ + "log", + "serde", +] + +[[package]] +name = "defmt-parser" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "269924c02afd7f94bc4cecbfa5c379f6ffcf9766b3408fe63d22c728654eccd0" +dependencies = [ + "thiserror", +] + [[package]] name = "deku" version = "0.16.0" @@ -906,6 +958,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "dissimilar" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86e3bdc80eee6e16b2b6b0f87fbc98c04bee3455e35174c0de1a125d0688c632" + [[package]] name = "dunce" version = "1.0.4" @@ -1048,6 +1106,8 @@ dependencies = [ "comfy-table", "crossterm 0.25.0", "ctrlc", + "defmt-decoder", + "defmt-parser", "dialoguer", "directories", "env_logger", @@ -1072,6 +1132,12 @@ dependencies = [ "xmas-elf", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + [[package]] name = "fallible-iterator" version = "0.3.0" @@ -1262,13 +1328,23 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gimli" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +dependencies = [ + "fallible-iterator 0.2.0", + "stable_deref_trait", +] + [[package]] name = "gimli" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" dependencies = [ - "fallible-iterator", + "fallible-iterator 0.3.0", "stable_deref_trait", ] @@ -2451,6 +2527,9 @@ name = "log" version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +dependencies = [ + "serde", +] [[package]] name = "mach" @@ -2642,6 +2721,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" +[[package]] +name = "object" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +dependencies = [ + "memchr", +] + [[package]] name = "object" version = "0.32.1" diff --git a/espflash/Cargo.toml b/espflash/Cargo.toml index 5f58e9ff..5ed527e2 100644 --- a/espflash/Cargo.toml +++ b/espflash/Cargo.toml @@ -39,7 +39,9 @@ clap = { version = "4.4.6", features = ["derive", "env", "wrap_help"], optional clap_complete = { version = "4.4.3", optional = true } comfy-table = { version = "7.0.1", optional = true } crossterm = { version = "0.25.0", optional = true } # 0.26.x causes issues on Windows -ctrlc = { version = "3.4.1", optional = true } +ctrlc = { version = "3.4.0", optional = true } +defmt-decoder = { version = "=0.3.8", features = ["unstable"] } +defmt-parser = { version = "=0.3.3", features = ["unstable"] } dialoguer = { version = "0.10.4", optional = true } directories = { version = "5.0.1", optional = true } env_logger = { version = "0.10.0", optional = true } diff --git a/espflash/src/cli/monitor/mod.rs b/espflash/src/cli/monitor/mod.rs index b8b05de6..e5c8a902 100644 --- a/espflash/src/cli/monitor/mod.rs +++ b/espflash/src/cli/monitor/mod.rs @@ -21,6 +21,7 @@ use crossterm::{ terminal::{disable_raw_mode, enable_raw_mode}, QueueableCommand, }; +use defmt_decoder::{Frame, StreamDecoder, Table}; use lazy_static::lazy_static; use log::error; use miette::{IntoDiagnostic, Result}; @@ -107,6 +108,66 @@ impl Drop for RawModeGuard { } } +enum FrameKind<'a> { + Defmt(Frame<'a>), + Raw(&'a [u8]), +} + +struct FrameDelimiter<'a> { + buffer: Vec, + decoder: Option>, + in_frame: bool, +} + +const FRAME_START: &[u8] = &[0xFF, 0x00]; +const FRAME_END: &[u8] = &[0x00]; + +fn search(haystack: &[u8], look_for_end: bool) -> Option<(&[u8], usize)> { + let needle = if look_for_end { FRAME_END } else { FRAME_START }; + let start = if look_for_end { + // skip leading zeros + haystack.iter().position(|&b| b != 0)? + } else { + 0 + }; + + let end = haystack[start..] + .windows(needle.len()) + .position(|window| window == needle)?; + + Some((&haystack[start..][..end], start + end + needle.len())) +} + +impl FrameDelimiter<'_> { + pub fn feed(&mut self, buffer: &[u8], mut process: impl FnMut(FrameKind<'_>)) { + let Some(table) = self.decoder.as_mut() else { + process(FrameKind::Raw(buffer)); + return; + }; + + self.buffer.extend_from_slice(buffer); + + while let Some((frame, consumed)) = search(&self.buffer, self.in_frame) { + if !self.in_frame { + process(FrameKind::Raw(frame)); + self.in_frame = true; + } else { + table.received(frame); + // small reliance on rzcobs internals: we need to feed the terminating zero + table.received(FRAME_END); + if let Ok(frame) = table.decode() { + process(FrameKind::Defmt(frame)); + } else { + log::warn!("Failed to decode defmt frame"); + } + self.in_frame = false; + }; + + self.buffer.drain(..consumed); + } + } +} + /// Open a serial monitor on the given interface pub fn monitor( mut serial: Interface, @@ -127,10 +188,13 @@ pub fn monitor( .set_timeout(Duration::from_millis(5))?; // Load symbols from the ELF file (if provided) and initialize the context. - let symbols = if let Some(bytes) = elf { - Symbols::try_from(bytes).ok() + let (symbols, defmt_data) = if let Some(bytes) = elf { + ( + Symbols::try_from(bytes).ok(), + Table::parse(bytes).ok().flatten(), + ) } else { - None + (None, None) }; let mut ctx = SerialContext::new(symbols); @@ -140,7 +204,32 @@ pub fn monitor( let stdout = stdout(); let mut stdout = stdout.lock(); + let defmt_encoding = defmt_data + .map(|table| { + let encoding = table.encoding(); + (table, encoding) + }) + .and_then(|(table, encoding)| { + // We only support rzcobs encoding because it is the only way to multiplex + // a defmt stream and an ASCII log stream over the same serial port. + if encoding == defmt_decoder::Encoding::Rzcobs { + Some((table, encoding)) + } else { + log::warn!("Unsupported defmt encoding: {:?}", encoding); + None + } + }); + let mut buff = [0; 1024]; + + let mut delimiter = FrameDelimiter { + buffer: Vec::new(), + decoder: defmt_encoding + .as_ref() + .map(|(table, _)| table.new_stream_decoder()), + in_frame: false, + }; + loop { let read_count = match serial.serial_port_mut().read(&mut buff) { Ok(count) => Ok(count), @@ -149,9 +238,13 @@ pub fn monitor( err => err, }?; - if read_count > 0 { - handle_serial(&mut ctx, &buff[0..read_count], &mut stdout); - } + delimiter.feed(&buff[0..read_count], |frame| match frame { + FrameKind::Defmt(frame) => handle_defmt(&mut ctx, frame, &mut stdout), + FrameKind::Raw(bytes) => handle_serial(&mut ctx, bytes, &mut stdout), + }); + + // Don't forget to flush the writer! + stdout.flush().ok(); if poll(Duration::from_secs(0))? { if let Event::Key(key) = read()? { @@ -177,6 +270,35 @@ pub fn monitor( Ok(()) } +fn handle_defmt(ctx: &mut SerialContext, frame: Frame<'_>, out: &mut dyn Write) { + let message = frame.display_message().to_string(); + + match frame.level() { + Some(level) => { + let color = match level { + defmt_parser::Level::Trace => Color::Cyan, + defmt_parser::Level::Debug => Color::Blue, + defmt_parser::Level::Info => Color::Green, + defmt_parser::Level::Warn => Color::Yellow, + defmt_parser::Level::Error => Color::Red, + }; + out.queue(PrintStyledContent(message.as_str().with(color))) + .ok() + } + None => out.queue(Print(message.as_str())).ok(), + }; + + // Remember to begin a new line after we have printed this one! + out.write_all(b"\r\n").ok(); + + // If we have loaded some symbols... + if let Some(symbols) = &ctx.symbols { + for line in message.lines() { + resolve_addresses(symbols, line, out); + } + } +} + /// Handles and writes the received serial data to the given output stream. fn handle_serial(ctx: &mut SerialContext, buff: &[u8], out: &mut dyn Write) { let text = ctx.process_utf8(buff); @@ -208,38 +330,16 @@ fn handle_serial(ctx: &mut SerialContext, buff: &[u8], out: &mut dyn Write) { // The previous fragment has been completed (by this current line). ctx.previous_frag = None; + // Remember to begin a new line after we have printed this one! + out.write_all(b"\r\n").ok(); + // If we have loaded some symbols... if let Some(symbols) = &ctx.symbols { // And there was previously a line printed to the terminal... if let Some(line) = &ctx.previous_line { - // Check the previous line for function addresses. For each address found, - // attempt to look up the associated function's name and location and write both - // to the terminal. - for matched in RE_FN_ADDR.find_iter(line).map(|m| m.as_str()) { - // Since our regular expression already confirms that this is a correctly - // formatted hex literal, we can (fairly) safely assume that it will parse - // successfully into an integer. - let addr = parse_int::parse::(matched).unwrap(); - - let name = symbols.get_name(addr).unwrap_or_else(|| "??".into()); - let (file, line_num) = - if let Some((file, line_num)) = symbols.get_location(addr) { - (file, line_num.to_string()) - } else { - ("??".into(), "??".into()) - }; - - out.queue(PrintStyledContent( - format!("\r\n{matched} - {name}\r\n at {file}:{line_num}") - .with(Color::Yellow), - )) - .unwrap(); - } + resolve_addresses(symbols, line, out); } } - - // Remember to begin a new line after we have printed this one! - out.write_all(b"\r\n").ok(); } // If there is an incomplete line we will still print it. However, we will not @@ -253,9 +353,30 @@ fn handle_serial(ctx: &mut SerialContext, buff: &[u8], out: &mut dyn Write) { ctx.previous_frag = Some(line.to_string()); } } +} + +fn resolve_addresses(symbols: &Symbols<'_>, line: &str, out: &mut dyn Write) { + // Check the previous line for function addresses. For each address found, + // attempt to look up the associated function's name and location and write both + // to the terminal. + for matched in RE_FN_ADDR.find_iter(line).map(|m| m.as_str()) { + // Since our regular expression already confirms that this is a correctly + // formatted hex literal, we can (fairly) safely assume that it will parse + // successfully into an integer. + let addr = parse_int::parse::(matched).unwrap(); + + let name = symbols.get_name(addr).unwrap_or_else(|| "??".into()); + let (file, line_num) = if let Some((file, line_num)) = symbols.get_location(addr) { + (file, line_num.to_string()) + } else { + ("??".into(), "??".into()) + }; - // Don't forget to flush the writer! - out.flush().ok(); + out.queue(PrintStyledContent( + format!("{matched} - {name}\r\n at {file}:{line_num}\r\n").with(Color::Yellow), + )) + .unwrap(); + } } // Converts key events from crossterm into appropriate character/escape From 3531eb51036ce5db4ef5869202cb08e644f50c98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Sat, 26 Aug 2023 22:44:48 +0200 Subject: [PATCH 02/10] Add changelog entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c86f2e02..3a2dafb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Read esp-println generated defmt messages (#466) + ### Fixed ### Changed From c4b10d38efa178f2e9de79566a4ab96bff247d31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Thu, 31 Aug 2023 19:24:01 +0200 Subject: [PATCH 03/10] Display log level --- espflash/src/cli/monitor/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/espflash/src/cli/monitor/mod.rs b/espflash/src/cli/monitor/mod.rs index e5c8a902..21f6e650 100644 --- a/espflash/src/cli/monitor/mod.rs +++ b/espflash/src/cli/monitor/mod.rs @@ -282,8 +282,10 @@ fn handle_defmt(ctx: &mut SerialContext, frame: Frame<'_>, out: &mut dyn Write) defmt_parser::Level::Warn => Color::Yellow, defmt_parser::Level::Error => Color::Red, }; - out.queue(PrintStyledContent(message.as_str().with(color))) - .ok() + out.queue(PrintStyledContent( + format!("[{}] - {}", level.as_str().to_uppercase(), message.as_str()).with(color), + )) + .ok() } None => out.queue(Print(message.as_str())).ok(), }; From 88944d0600ae5fd36de1b15f9e68f1a11b3209c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Tue, 12 Sep 2023 17:58:12 +0200 Subject: [PATCH 04/10] Bump MSRV to 1.70 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a2dafb0..40d1caf4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Update dependencies to their latest versions (#482) +- **breaking** Bumped MSRV to 1.70.0 + +### Removed ## [2.0.1] - 2023-07-13 From 1a6d42f204c1525c61668cfa0dad386ae4cdd9a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Wed, 13 Sep 2023 21:52:03 +0200 Subject: [PATCH 05/10] Refactor for opt-in defmt --- espflash/src/cli/monitor/mod.rs | 367 +------------------ espflash/src/cli/monitor/parser/esp_defmt.rs | 140 +++++++ espflash/src/cli/monitor/parser/mod.rs | 247 +++++++++++++ espflash/src/cli/monitor/parser/serial.rs | 11 + 4 files changed, 417 insertions(+), 348 deletions(-) create mode 100644 espflash/src/cli/monitor/parser/esp_defmt.rs create mode 100644 espflash/src/cli/monitor/parser/mod.rs create mode 100644 espflash/src/cli/monitor/parser/serial.rs diff --git a/espflash/src/cli/monitor/mod.rs b/espflash/src/cli/monitor/mod.rs index 21f6e650..946d77c7 100644 --- a/espflash/src/cli/monitor/mod.rs +++ b/espflash/src/cli/monitor/mod.rs @@ -17,79 +17,21 @@ use std::{ use crossterm::{ event::{poll, read, Event, KeyCode, KeyEvent, KeyModifiers}, - style::{Color, Print, PrintStyledContent, Stylize}, terminal::{disable_raw_mode, enable_raw_mode}, - QueueableCommand, }; -use defmt_decoder::{Frame, StreamDecoder, Table}; -use lazy_static::lazy_static; use log::error; use miette::{IntoDiagnostic, Result}; -use regex::Regex; -use self::{line_endings::normalized, symbols::Symbols}; -use crate::{connection::reset_after_flash, interface::Interface}; +use crate::{ + cli::monitor::parser::{esp_defmt::EspDefmt, InputParser, ResolvingPrinter}, + connection::reset_after_flash, + interface::Interface, +}; mod line_endings; +mod parser; mod symbols; -// Pattern to much a function address in serial output. -lazy_static! { - static ref RE_FN_ADDR: Regex = Regex::new(r"0x[[:xdigit:]]{8}").unwrap(); -} - -/// Context for the serial monitor -#[derive(Default)] -struct SerialContext<'ctx> { - symbols: Option>, - previous_frag: Option, - previous_line: Option, - incomplete_utf8_buffer: Vec, -} - -impl<'ctx> SerialContext<'ctx> { - fn new(symbols: Option>) -> Self { - Self { - symbols, - ..Self::default() - } - } - - fn process_utf8(&mut self, buff: &[u8]) -> String { - let mut buffer = std::mem::take(&mut self.incomplete_utf8_buffer); - buffer.extend(normalized(buff.iter().copied())); - - // look for longest slice that we can then lossily convert without introducing errors for - // partial sequences (#457) - let mut len = 0; - - loop { - match std::str::from_utf8(&buffer[len..]) { - // whole input is valid - Ok(str) if len == 0 => return String::from(str), - - // input is valid after the last error, and we could ignore the last error, so - // let's process the whole input - Ok(_) => return String::from_utf8_lossy(&buffer).to_string(), - - // input has some errors. We can ignore invalid sequences and replace them later, - // but we have to stop if we encounter an incomplete sequence. - Err(e) => { - len += e.valid_up_to(); - if let Some(error_len) = e.error_len() { - len += error_len; - } else { - // incomplete sequence. We split it off, save it for later - let (bytes, incomplete) = buffer.split_at(len); - self.incomplete_utf8_buffer = incomplete.to_vec(); - return String::from_utf8_lossy(bytes).to_string(); - } - } - } - } - } -} - /// Type that ensures that raw mode is disabled when dropped. struct RawModeGuard; @@ -108,72 +50,23 @@ impl Drop for RawModeGuard { } } -enum FrameKind<'a> { - Defmt(Frame<'a>), - Raw(&'a [u8]), -} - -struct FrameDelimiter<'a> { - buffer: Vec, - decoder: Option>, - in_frame: bool, -} - -const FRAME_START: &[u8] = &[0xFF, 0x00]; -const FRAME_END: &[u8] = &[0x00]; - -fn search(haystack: &[u8], look_for_end: bool) -> Option<(&[u8], usize)> { - let needle = if look_for_end { FRAME_END } else { FRAME_START }; - let start = if look_for_end { - // skip leading zeros - haystack.iter().position(|&b| b != 0)? - } else { - 0 - }; - - let end = haystack[start..] - .windows(needle.len()) - .position(|window| window == needle)?; - - Some((&haystack[start..][..end], start + end + needle.len())) -} - -impl FrameDelimiter<'_> { - pub fn feed(&mut self, buffer: &[u8], mut process: impl FnMut(FrameKind<'_>)) { - let Some(table) = self.decoder.as_mut() else { - process(FrameKind::Raw(buffer)); - return; - }; - - self.buffer.extend_from_slice(buffer); - - while let Some((frame, consumed)) = search(&self.buffer, self.in_frame) { - if !self.in_frame { - process(FrameKind::Raw(frame)); - self.in_frame = true; - } else { - table.received(frame); - // small reliance on rzcobs internals: we need to feed the terminating zero - table.received(FRAME_END); - if let Ok(frame) = table.decode() { - process(FrameKind::Defmt(frame)); - } else { - log::warn!("Failed to decode defmt frame"); - } - self.in_frame = false; - }; - - self.buffer.drain(..consumed); - } - } -} - /// Open a serial monitor on the given interface pub fn monitor( + serial: Interface, + elf: Option<&[u8]>, + pid: u16, + baud: u32, +) -> serialport::Result<()> { + monitor_with(serial, elf, pid, baud, EspDefmt::new(elf)) +} + +/// Open a serial monitor on the given interface, using the given input parser. +pub fn monitor_with( mut serial: Interface, elf: Option<&[u8]>, pid: u16, baud: u32, + mut parser: L, ) -> serialport::Result<()> { println!("Commands:"); println!(" CTRL+R Reset chip"); @@ -187,49 +80,13 @@ pub fn monitor( .serial_port_mut() .set_timeout(Duration::from_millis(5))?; - // Load symbols from the ELF file (if provided) and initialize the context. - let (symbols, defmt_data) = if let Some(bytes) = elf { - ( - Symbols::try_from(bytes).ok(), - Table::parse(bytes).ok().flatten(), - ) - } else { - (None, None) - }; - let mut ctx = SerialContext::new(symbols); - // We are in raw mode until `_raw_mode` is dropped (ie. this function returns). let _raw_mode = RawModeGuard::new(); let stdout = stdout(); - let mut stdout = stdout.lock(); - - let defmt_encoding = defmt_data - .map(|table| { - let encoding = table.encoding(); - (table, encoding) - }) - .and_then(|(table, encoding)| { - // We only support rzcobs encoding because it is the only way to multiplex - // a defmt stream and an ASCII log stream over the same serial port. - if encoding == defmt_decoder::Encoding::Rzcobs { - Some((table, encoding)) - } else { - log::warn!("Unsupported defmt encoding: {:?}", encoding); - None - } - }); + let mut stdout = ResolvingPrinter::new(elf, stdout.lock()); let mut buff = [0; 1024]; - - let mut delimiter = FrameDelimiter { - buffer: Vec::new(), - decoder: defmt_encoding - .as_ref() - .map(|(table, _)| table.new_stream_decoder()), - in_frame: false, - }; - loop { let read_count = match serial.serial_port_mut().read(&mut buff) { Ok(count) => Ok(count), @@ -238,10 +95,7 @@ pub fn monitor( err => err, }?; - delimiter.feed(&buff[0..read_count], |frame| match frame { - FrameKind::Defmt(frame) => handle_defmt(&mut ctx, frame, &mut stdout), - FrameKind::Raw(bytes) => handle_serial(&mut ctx, bytes, &mut stdout), - }); + parser.feed(&buff[0..read_count], &mut stdout); // Don't forget to flush the writer! stdout.flush().ok(); @@ -270,117 +124,6 @@ pub fn monitor( Ok(()) } -fn handle_defmt(ctx: &mut SerialContext, frame: Frame<'_>, out: &mut dyn Write) { - let message = frame.display_message().to_string(); - - match frame.level() { - Some(level) => { - let color = match level { - defmt_parser::Level::Trace => Color::Cyan, - defmt_parser::Level::Debug => Color::Blue, - defmt_parser::Level::Info => Color::Green, - defmt_parser::Level::Warn => Color::Yellow, - defmt_parser::Level::Error => Color::Red, - }; - out.queue(PrintStyledContent( - format!("[{}] - {}", level.as_str().to_uppercase(), message.as_str()).with(color), - )) - .ok() - } - None => out.queue(Print(message.as_str())).ok(), - }; - - // Remember to begin a new line after we have printed this one! - out.write_all(b"\r\n").ok(); - - // If we have loaded some symbols... - if let Some(symbols) = &ctx.symbols { - for line in message.lines() { - resolve_addresses(symbols, line, out); - } - } -} - -/// Handles and writes the received serial data to the given output stream. -fn handle_serial(ctx: &mut SerialContext, buff: &[u8], out: &mut dyn Write) { - let text = ctx.process_utf8(buff); - - // Split the text into lines, storing the last of which separately if it is - // incomplete (ie. does not end with '\n') because these need special handling. - let mut lines = text.lines().collect::>(); - let incomplete = if text.ends_with('\n') { - None - } else { - lines.pop() - }; - - // Iterate through all *complete* lines (ie. those ending with '\n') ... - for line in lines { - // ... and print the line. - out.queue(Print(line)).ok(); - - // If there is a previous line fragment, that means that the current line must - // be appended to it in order to form the complete line. Since we want to look - // for function addresses in the *entire* previous line we combine these prior - // to performing the symbol lookup(s). - ctx.previous_line = if let Some(frag) = &ctx.previous_frag { - Some(format!("{frag}{line}")) - } else { - Some(line.to_string()) - }; - - // The previous fragment has been completed (by this current line). - ctx.previous_frag = None; - - // Remember to begin a new line after we have printed this one! - out.write_all(b"\r\n").ok(); - - // If we have loaded some symbols... - if let Some(symbols) = &ctx.symbols { - // And there was previously a line printed to the terminal... - if let Some(line) = &ctx.previous_line { - resolve_addresses(symbols, line, out); - } - } - } - - // If there is an incomplete line we will still print it. However, we will not - // perform function name lookups or terminate it with a newline. - if let Some(line) = incomplete { - out.queue(Print(line)).ok(); - - if let Some(frag) = &ctx.previous_frag { - ctx.previous_frag = Some(format!("{frag}{line}")); - } else { - ctx.previous_frag = Some(line.to_string()); - } - } -} - -fn resolve_addresses(symbols: &Symbols<'_>, line: &str, out: &mut dyn Write) { - // Check the previous line for function addresses. For each address found, - // attempt to look up the associated function's name and location and write both - // to the terminal. - for matched in RE_FN_ADDR.find_iter(line).map(|m| m.as_str()) { - // Since our regular expression already confirms that this is a correctly - // formatted hex literal, we can (fairly) safely assume that it will parse - // successfully into an integer. - let addr = parse_int::parse::(matched).unwrap(); - - let name = symbols.get_name(addr).unwrap_or_else(|| "??".into()); - let (file, line_num) = if let Some((file, line_num)) = symbols.get_location(addr) { - (file, line_num.to_string()) - } else { - ("??".into(), "??".into()) - }; - - out.queue(PrintStyledContent( - format!("{matched} - {name}\r\n at {file}:{line_num}\r\n").with(Color::Yellow), - )) - .unwrap(); - } -} - // Converts key events from crossterm into appropriate character/escape // sequences which are then sent over the serial connection. // @@ -435,75 +178,3 @@ fn handle_key_event(key_event: KeyEvent) -> Option> { key_str.map(|slice| slice.into()) } - -#[cfg(test)] -mod test { - #[test] - fn returns_valid_strings_immediately() { - let mut ctx = super::SerialContext::default(); - let buff = b"Hello, world!"; - let text = ctx.process_utf8(buff); - assert_eq!(text, "Hello, world!"); - } - - #[test] - fn does_not_repeat_valid_strings() { - let mut ctx = super::SerialContext::default(); - let text = ctx.process_utf8(b"Hello, world!"); - assert_eq!(text, "Hello, world!"); - let text = ctx.process_utf8(b"Something else"); - assert_eq!(text, "Something else"); - } - - #[test] - fn replaces_invalid_sequence() { - let mut ctx = super::SerialContext::default(); - let text = ctx.process_utf8(b"Hello, \xFF world!"); - assert_eq!(text, "Hello, \u{FFFD} world!"); - } - - #[test] - fn can_replace_unfinished_incomplete_sequence() { - let mut ctx = super::SerialContext::default(); - let mut incomplete = Vec::from("Hello, ".as_bytes()); - let utf8 = "🙈".as_bytes(); - incomplete.extend_from_slice(&utf8[..utf8.len() - 1]); - let text = ctx.process_utf8(&incomplete); - assert_eq!(text, "Hello, "); - - let text = ctx.process_utf8(b" world!"); - assert_eq!(text, "\u{FFFD} world!"); - } - - #[test] - fn can_merge_incomplete_sequence() { - let mut ctx = super::SerialContext::default(); - let mut incomplete = Vec::from("Hello, ".as_bytes()); - let utf8 = "🙈".as_bytes(); - incomplete.extend_from_slice(&utf8[..utf8.len() - 1]); - - let text = ctx.process_utf8(&incomplete); - assert_eq!(text, "Hello, "); - - let text = ctx.process_utf8(&utf8[utf8.len() - 1..]); - assert_eq!(text, "🙈"); - } - - #[test] - fn issue_457() { - let mut ctx = super::SerialContext::default(); - let mut result = String::new(); - - result.push_str(&ctx.process_utf8(&[0x48])); - result.push_str(&ctx.process_utf8(&[0x65, 0x6C, 0x6C])); - result.push_str(&ctx.process_utf8(&[ - 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x21, 0x20, 0x77, 0x69, 0x74, - ])); - result.push_str(&ctx.process_utf8(&[ - 0x68, 0x20, 0x55, 0x54, 0x46, 0x3A, 0x20, 0x77, 0x79, 0x73, 0x79, - ])); - result.push_str(&ctx.process_utf8(&[0xC5, 0x82, 0x61, 0x6D, 0x0A])); - - assert_eq!(result, "Hello world! with UTF: wysyłam\r\n"); - } -} diff --git a/espflash/src/cli/monitor/parser/esp_defmt.rs b/espflash/src/cli/monitor/parser/esp_defmt.rs new file mode 100644 index 00000000..33e295d0 --- /dev/null +++ b/espflash/src/cli/monitor/parser/esp_defmt.rs @@ -0,0 +1,140 @@ +use std::io::Write; + +use crossterm::{ + style::{Color, Print, PrintStyledContent, Stylize}, + QueueableCommand, +}; +use defmt_decoder::{Frame, Table}; + +use crate::cli::monitor::parser::InputParser; + +enum FrameKind<'a> { + Defmt(Frame<'a>), + Raw(&'a [u8]), +} + +struct FrameDelimiter { + buffer: Vec, + table: Option, + in_frame: bool, +} + +// Framing info added by esp-println +const FRAME_START: &[u8] = &[0xFF, 0x00]; +const FRAME_END: &[u8] = &[0x00]; + +fn search(haystack: &[u8], look_for_end: bool) -> Option<(&[u8], usize)> { + let needle = if look_for_end { FRAME_END } else { FRAME_START }; + let start = if look_for_end { + // skip leading zeros + haystack.iter().position(|&b| b != 0)? + } else { + 0 + }; + + let end = haystack[start..] + .windows(needle.len()) + .position(|window| window == needle)?; + + Some((&haystack[start..][..end], start + end + needle.len())) +} + +impl FrameDelimiter { + pub fn feed(&mut self, buffer: &[u8], mut process: impl FnMut(FrameKind<'_>)) { + let Some(table) = self.table.as_mut() else { + process(FrameKind::Raw(buffer)); + return; + }; + + let mut decoder = table.new_stream_decoder(); + + self.buffer.extend_from_slice(buffer); + + while let Some((frame, consumed)) = search(&self.buffer, self.in_frame) { + if !self.in_frame { + process(FrameKind::Raw(frame)); + self.in_frame = true; + } else { + decoder.received(frame); + // small reliance on rzcobs internals: we need to feed the terminating zero + decoder.received(FRAME_END); + if let Ok(frame) = decoder.decode() { + process(FrameKind::Defmt(frame)); + } else { + log::warn!("Failed to decode defmt frame"); + } + self.in_frame = false; + }; + + self.buffer.drain(..consumed); + } + } +} + +pub struct EspDefmt { + delimiter: FrameDelimiter, +} + +impl EspDefmt { + fn load_table(elf: Option<&[u8]>) -> Option
{ + // Load symbols from the ELF file (if provided) and initialize the context. + Table::parse(elf?).ok().flatten().and_then(|table| { + let encoding = table.encoding(); + + // We only support rzcobs encoding because it is the only way to multiplex + // a defmt stream and an ASCII log stream over the same serial port. + if encoding == defmt_decoder::Encoding::Rzcobs { + Some(table) + } else { + log::warn!("Unsupported defmt encoding: {:?}", encoding); + None + } + }) + } + + pub fn new(elf: Option<&[u8]>) -> Self { + Self { + delimiter: FrameDelimiter { + buffer: Vec::new(), + table: Self::load_table(elf), + in_frame: false, + }, + } + } +} + +impl InputParser for EspDefmt { + fn feed(&mut self, bytes: &[u8], out: &mut impl Write) { + self.delimiter.feed(bytes, |frame| match frame { + FrameKind::Defmt(frame) => { + match frame.level() { + Some(level) => { + let color = match level { + defmt_parser::Level::Trace => Color::Cyan, + defmt_parser::Level::Debug => Color::Blue, + defmt_parser::Level::Info => Color::Green, + defmt_parser::Level::Warn => Color::Yellow, + defmt_parser::Level::Error => Color::Red, + }; + out.queue(PrintStyledContent( + format!( + "[{}] - {}", + level.as_str().to_uppercase(), + frame.display_message() + ) + .with(color), + )) + .unwrap(); + } + None => { + out.queue(Print(frame.display_message())).unwrap(); + } + }; + + // Remember to begin a new line after we have printed this one! + out.write_all(b"\r\n").unwrap(); + } + FrameKind::Raw(bytes) => out.write_all(bytes).unwrap(), + }); + } +} diff --git a/espflash/src/cli/monitor/parser/mod.rs b/espflash/src/cli/monitor/parser/mod.rs new file mode 100644 index 00000000..1ae9c85a --- /dev/null +++ b/espflash/src/cli/monitor/parser/mod.rs @@ -0,0 +1,247 @@ +pub mod esp_defmt; +pub mod serial; + +use std::{borrow::Cow, io::Write}; + +use crossterm::{ + style::{Color, Print, PrintStyledContent, Stylize}, + QueueableCommand, +}; +use lazy_static::lazy_static; +use regex::Regex; + +use crate::cli::monitor::{line_endings::normalized, symbols::Symbols}; + +pub trait InputParser { + fn feed(&mut self, bytes: &[u8], out: &mut impl Write); +} + +// Pattern to much a function address in serial output. +lazy_static! { + static ref RE_FN_ADDR: Regex = Regex::new(r"0x[[:xdigit:]]{8}").unwrap(); +} + +fn resolve_addresses( + symbols: &Symbols<'_>, + line: &str, + out: &mut dyn Write, +) -> std::io::Result<()> { + // Check the previous line for function addresses. For each address found, + // attempt to look up the associated function's name and location and write both + // to the terminal. + for matched in RE_FN_ADDR.find_iter(line).map(|m| m.as_str()) { + // Since our regular expression already confirms that this is a correctly + // formatted hex literal, we can (fairly) safely assume that it will parse + // successfully into an integer. + let addr = parse_int::parse::(matched).unwrap(); + + let name = symbols.get_name(addr); + let location = symbols.get_location(addr); + + let name = name.as_deref().unwrap_or("??"); + let output = if let Some((file, line_num)) = location { + format!("{matched} - {name}\r\n at {file}:{line_num}\r\n") + } else { + format!("{matched} - {name}\r\n at ??:??\r\n") + }; + + out.queue(PrintStyledContent(output.with(Color::Yellow)))?; + } + + Ok(()) +} + +struct Utf8Merger { + incomplete_utf8_buffer: Vec, +} + +impl Utf8Merger { + fn new() -> Self { + Self { + incomplete_utf8_buffer: Vec::new(), + } + } + + fn process_utf8(&mut self, buff: &[u8]) -> String { + let mut buffer = std::mem::take(&mut self.incomplete_utf8_buffer); + buffer.extend(normalized(buff.iter().copied())); + + // look for longest slice that we can then lossily convert without introducing errors for + // partial sequences (#457) + let mut len = 0; + + loop { + match std::str::from_utf8(&buffer[len..]) { + // whole input is valid + Ok(str) if len == 0 => return String::from(str), + + // input is valid after the last error, and we could ignore the last error, so + // let's process the whole input + Ok(_) => return String::from_utf8_lossy(&buffer).to_string(), + + // input has some errors. We can ignore invalid sequences and replace them later, + // but we have to stop if we encounter an incomplete sequence. + Err(e) => { + len += e.valid_up_to(); + if let Some(error_len) = e.error_len() { + len += error_len; + } else { + // incomplete sequence. We split it off, save it for later + let (bytes, incomplete) = buffer.split_at(len); + self.incomplete_utf8_buffer = incomplete.to_vec(); + return String::from_utf8_lossy(bytes).to_string(); + } + } + } + } + } +} + +pub struct ResolvingPrinter<'ctx, W: Write> { + writer: W, + symbols: Option>, + merger: Utf8Merger, + line_fragment: String, +} + +impl<'ctx, W: Write> ResolvingPrinter<'ctx, W> { + pub fn new(elf: Option<&'ctx [u8]>, writer: W) -> Self { + Self { + writer, + symbols: elf.and_then(|elf| Symbols::try_from(elf).ok()), + merger: Utf8Merger::new(), + line_fragment: String::new(), + } + } +} + +impl Write for ResolvingPrinter<'_, W> { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + let text = self.merger.process_utf8(buf); + + // Split the text into lines, storing the last of which separately if it is + // incomplete (ie. does not end with '\n') because these need special handling. + let mut lines = text.lines().collect::>(); + let incomplete = if text.ends_with('\n') { + None + } else { + lines.pop() + }; + + // Iterate through all *complete* lines (ie. those ending with '\n') ... + for line in lines { + // ... and print the line. + self.writer.queue(Print(line))?; + + // If there is a previous line fragment, that means that the current line must + // be appended to it in order to form the complete line. Since we want to look + // for function addresses in the *entire* previous line we combine these prior + // to performing the symbol lookup(s). + let fragment = std::mem::take(&mut self.line_fragment); + let line = if fragment.is_empty() { + Cow::from(line) + } else { + // The previous fragment has been completed (by this current line). + Cow::from(format!("{fragment}{line}")) + }; + + // Remember to begin a new line after we have printed this one! + self.writer.write_all(b"\r\n")?; + + // If we have loaded some symbols... + if let Some(symbols) = self.symbols.as_ref() { + // Try to print the names of addresses in the current line. + resolve_addresses(symbols, &line, &mut self.writer)?; + } + } + + // If there is an incomplete line we will still print it. However, we will not + // perform function name lookups or terminate it with a newline. + if let Some(line) = incomplete { + self.writer.queue(Print(line))?; + + let fragment = std::mem::take(&mut self.line_fragment); + self.line_fragment = format!("{fragment}{line}"); + } + + Ok(text.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + self.writer.flush() + } +} + +#[cfg(test)] +mod test { + use super::Utf8Merger; + + #[test] + fn returns_valid_strings_immediately() { + let mut ctx = Utf8Merger::new(); + let buff = b"Hello, world!"; + let text = ctx.process_utf8(buff); + assert_eq!(text, "Hello, world!"); + } + + #[test] + fn does_not_repeat_valid_strings() { + let mut ctx = Utf8Merger::new(); + let text = ctx.process_utf8(b"Hello, world!"); + assert_eq!(text, "Hello, world!"); + let text = ctx.process_utf8(b"Something else"); + assert_eq!(text, "Something else"); + } + + #[test] + fn replaces_invalid_sequence() { + let mut ctx = Utf8Merger::new(); + let text = ctx.process_utf8(b"Hello, \xFF world!"); + assert_eq!(text, "Hello, \u{FFFD} world!"); + } + + #[test] + fn can_replace_unfinished_incomplete_sequence() { + let mut ctx = Utf8Merger::new(); + let mut incomplete = Vec::from("Hello, ".as_bytes()); + let utf8 = "🙈".as_bytes(); + incomplete.extend_from_slice(&utf8[..utf8.len() - 1]); + let text = ctx.process_utf8(&incomplete); + assert_eq!(text, "Hello, "); + + let text = ctx.process_utf8(b" world!"); + assert_eq!(text, "\u{FFFD} world!"); + } + + #[test] + fn can_merge_incomplete_sequence() { + let mut ctx = Utf8Merger::new(); + let mut incomplete = Vec::from("Hello, ".as_bytes()); + let utf8 = "🙈".as_bytes(); + incomplete.extend_from_slice(&utf8[..utf8.len() - 1]); + + let text = ctx.process_utf8(&incomplete); + assert_eq!(text, "Hello, "); + + let text = ctx.process_utf8(&utf8[utf8.len() - 1..]); + assert_eq!(text, "🙈"); + } + + #[test] + fn issue_457() { + let mut ctx = Utf8Merger::new(); + let mut result = String::new(); + + result.push_str(&ctx.process_utf8(&[0x48])); + result.push_str(&ctx.process_utf8(&[0x65, 0x6C, 0x6C])); + result.push_str(&ctx.process_utf8(&[ + 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x21, 0x20, 0x77, 0x69, 0x74, + ])); + result.push_str(&ctx.process_utf8(&[ + 0x68, 0x20, 0x55, 0x54, 0x46, 0x3A, 0x20, 0x77, 0x79, 0x73, 0x79, + ])); + result.push_str(&ctx.process_utf8(&[0xC5, 0x82, 0x61, 0x6D, 0x0A])); + + assert_eq!(result, "Hello world! with UTF: wysyłam\r\n"); + } +} diff --git a/espflash/src/cli/monitor/parser/serial.rs b/espflash/src/cli/monitor/parser/serial.rs new file mode 100644 index 00000000..5a2adb05 --- /dev/null +++ b/espflash/src/cli/monitor/parser/serial.rs @@ -0,0 +1,11 @@ +use std::io::Write; + +use crate::cli::monitor::parser::InputParser; + +pub struct Serial; + +impl InputParser for Serial { + fn feed(&mut self, bytes: &[u8], out: &mut impl Write) { + out.write_all(bytes).unwrap(); + } +} From da4b29bd3534a57bcb5f51759dcabbb6c16fa749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Fri, 15 Sep 2023 13:23:02 +0200 Subject: [PATCH 06/10] Make defmt a default feature --- espflash/Cargo.toml | 10 +++++++--- espflash/src/cli/monitor/mod.rs | 13 ++++++++++--- espflash/src/cli/monitor/parser/mod.rs | 1 + 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/espflash/Cargo.toml b/espflash/Cargo.toml index 5ed527e2..b760de4c 100644 --- a/espflash/Cargo.toml +++ b/espflash/Cargo.toml @@ -40,8 +40,8 @@ clap_complete = { version = "4.4.3", optional = true } comfy-table = { version = "7.0.1", optional = true } crossterm = { version = "0.25.0", optional = true } # 0.26.x causes issues on Windows ctrlc = { version = "3.4.0", optional = true } -defmt-decoder = { version = "=0.3.8", features = ["unstable"] } -defmt-parser = { version = "=0.3.3", features = ["unstable"] } +defmt-decoder = { version = "=0.3.8", features = ["unstable"], optional = true } +defmt-parser = { version = "=0.3.3", features = ["unstable"], optional = true } dialoguer = { version = "0.10.4", optional = true } directories = { version = "5.0.1", optional = true } env_logger = { version = "0.10.0", optional = true } @@ -66,7 +66,7 @@ update-informer = { version = "1.1.0", optional = true } xmas-elf = "0.9.0" [features] -default = ["cli"] +default = ["cli", "defmt"] cli = [ "dep:addr2line", "dep:clap", @@ -84,4 +84,8 @@ cli = [ "dep:regex", "dep:update-informer", ] +defmt = [ + "dep:defmt-decoder", + "dep:defmt-parser", +] raspberry = ["dep:rppal"] diff --git a/espflash/src/cli/monitor/mod.rs b/espflash/src/cli/monitor/mod.rs index 946d77c7..3e08b0fe 100644 --- a/espflash/src/cli/monitor/mod.rs +++ b/espflash/src/cli/monitor/mod.rs @@ -23,13 +23,14 @@ use log::error; use miette::{IntoDiagnostic, Result}; use crate::{ - cli::monitor::parser::{esp_defmt::EspDefmt, InputParser, ResolvingPrinter}, + cli::monitor::parser::{InputParser, ResolvingPrinter}, connection::reset_after_flash, interface::Interface, }; +pub mod parser; + mod line_endings; -mod parser; mod symbols; /// Type that ensures that raw mode is disabled when dropped. @@ -57,7 +58,13 @@ pub fn monitor( pid: u16, baud: u32, ) -> serialport::Result<()> { - monitor_with(serial, elf, pid, baud, EspDefmt::new(elf)) + #[cfg(feature = "defmt")] + let parser = parser::esp_defmt::EspDefmt::new(elf); + + #[cfg(not(feature = "defmt"))] + let parser = parser::serial::Serial; + + monitor_with(serial, elf, pid, baud, parser) } /// Open a serial monitor on the given interface, using the given input parser. diff --git a/espflash/src/cli/monitor/parser/mod.rs b/espflash/src/cli/monitor/parser/mod.rs index 1ae9c85a..a5491764 100644 --- a/espflash/src/cli/monitor/parser/mod.rs +++ b/espflash/src/cli/monitor/parser/mod.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "defmt")] pub mod esp_defmt; pub mod serial; From 9d16586441d1008539bb0a7fc131935f3903009b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Fri, 6 Oct 2023 16:33:25 +0200 Subject: [PATCH 07/10] Update CHANGELOG.md Co-authored-by: Sergio Gasquez Arcos --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40d1caf4..3a2dafb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,9 +30,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Update dependencies to their latest versions (#482) -- **breaking** Bumped MSRV to 1.70.0 - -### Removed ## [2.0.1] - 2023-07-13 From 06e66ef5532dd8cff80e150f9e93acd6112f4498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 9 Oct 2023 11:14:07 +0200 Subject: [PATCH 08/10] Fix panic on frame containing multiple lines --- espflash/src/cli/monitor/parser/esp_defmt.rs | 30 ++++++++++++-------- espflash/src/cli/monitor/parser/mod.rs | 2 +- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/espflash/src/cli/monitor/parser/esp_defmt.rs b/espflash/src/cli/monitor/parser/esp_defmt.rs index 33e295d0..80bc209d 100644 --- a/espflash/src/cli/monitor/parser/esp_defmt.rs +++ b/espflash/src/cli/monitor/parser/esp_defmt.rs @@ -116,23 +116,29 @@ impl InputParser for EspDefmt { defmt_parser::Level::Warn => Color::Yellow, defmt_parser::Level::Error => Color::Red, }; - out.queue(PrintStyledContent( - format!( - "[{}] - {}", - level.as_str().to_uppercase(), - frame.display_message() - ) - .with(color), - )) - .unwrap(); + // Print/PrintStyledContent panics if its content has a bare \n in it, so + // we feed the input line by line. + // As an additional benefit we can print the level before each + // line which looks better. + let level = level.as_str().to_uppercase(); + for line in frame.display_message().to_string().lines() { + out.queue(PrintStyledContent( + format!("[{level}] - {line}\r\n").with(color), + )) + .unwrap(); + } } None => { - out.queue(Print(frame.display_message())).unwrap(); + // Print/PrintStyledContent panics if its content has a bare \n in it, so + // we feed the input line by line. + for line in frame.display_message().to_string().lines() { + out.queue(Print(line)).unwrap(); + out.queue(Print("\r\n")).unwrap(); + } } }; - // Remember to begin a new line after we have printed this one! - out.write_all(b"\r\n").unwrap(); + out.flush().unwrap(); } FrameKind::Raw(bytes) => out.write_all(bytes).unwrap(), }); diff --git a/espflash/src/cli/monitor/parser/mod.rs b/espflash/src/cli/monitor/parser/mod.rs index a5491764..ad8cee8a 100644 --- a/espflash/src/cli/monitor/parser/mod.rs +++ b/espflash/src/cli/monitor/parser/mod.rs @@ -147,7 +147,7 @@ impl Write for ResolvingPrinter<'_, W> { }; // Remember to begin a new line after we have printed this one! - self.writer.write_all(b"\r\n")?; + self.writer.queue(Print("\r\n"))?; // If we have loaded some symbols... if let Some(symbols) = self.symbols.as_ref() { From cdfdd34c70bba51b83a3980516c7763e8877a723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Wed, 11 Oct 2023 12:12:40 +0200 Subject: [PATCH 09/10] Fix panic due to newline normalization --- espflash/src/cli/monitor/parser/esp_defmt.rs | 15 +++++---------- espflash/src/cli/monitor/parser/mod.rs | 2 +- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/espflash/src/cli/monitor/parser/esp_defmt.rs b/espflash/src/cli/monitor/parser/esp_defmt.rs index 80bc209d..818fd691 100644 --- a/espflash/src/cli/monitor/parser/esp_defmt.rs +++ b/espflash/src/cli/monitor/parser/esp_defmt.rs @@ -116,10 +116,8 @@ impl InputParser for EspDefmt { defmt_parser::Level::Warn => Color::Yellow, defmt_parser::Level::Error => Color::Red, }; - // Print/PrintStyledContent panics if its content has a bare \n in it, so - // we feed the input line by line. - // As an additional benefit we can print the level before each - // line which looks better. + + // Print the level before each line. let level = level.as_str().to_uppercase(); for line in frame.display_message().to_string().lines() { out.queue(PrintStyledContent( @@ -129,12 +127,9 @@ impl InputParser for EspDefmt { } } None => { - // Print/PrintStyledContent panics if its content has a bare \n in it, so - // we feed the input line by line. - for line in frame.display_message().to_string().lines() { - out.queue(Print(line)).unwrap(); - out.queue(Print("\r\n")).unwrap(); - } + out.queue(Print(frame.display_message().to_string())) + .unwrap(); + out.queue(Print("\r\n")).unwrap(); } }; diff --git a/espflash/src/cli/monitor/parser/mod.rs b/espflash/src/cli/monitor/parser/mod.rs index ad8cee8a..c2fc0329 100644 --- a/espflash/src/cli/monitor/parser/mod.rs +++ b/espflash/src/cli/monitor/parser/mod.rs @@ -165,7 +165,7 @@ impl Write for ResolvingPrinter<'_, W> { self.line_fragment = format!("{fragment}{line}"); } - Ok(text.len()) + Ok(buf.len()) } fn flush(&mut self) -> std::io::Result<()> { From 1f000b2ccf0ee76d2cfaf1649cf679e0d1dfaae2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Wed, 11 Oct 2023 13:41:30 +0200 Subject: [PATCH 10/10] Don't enable defmt by default --- espflash/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/espflash/Cargo.toml b/espflash/Cargo.toml index b760de4c..aa7e6205 100644 --- a/espflash/Cargo.toml +++ b/espflash/Cargo.toml @@ -66,7 +66,7 @@ update-informer = { version = "1.1.0", optional = true } xmas-elf = "0.9.0" [features] -default = ["cli", "defmt"] +default = ["cli"] cli = [ "dep:addr2line", "dep:clap",