Skip to content

[ravedude] add output modes, newline after n bytes or after char #549

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions ravedude/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,22 @@ port = "/dev/ttyACM0"
# ravedude should open a serial console after flashing
open-console = true

# console output mode. Can be ascii, hex, dec or bin
output-mode = "ascii"

# Print a newline after this byte
# not used with output_mode ascii
# hex (0x) and bin (0b) notations are supported.
# matching chars/bytes are NOT removed
# to add newlines after \n (in non-ascii mode), use \n, 0x0a or 0b00001010
# newline-on = '\n'

# Print a newline after n bytes
# not used with output_mode ascii
# defaults to 16 for hex and dec and 8 for bin
# if dividable by 4, bytes will be grouped to 4
# newline-after = 16

# baudrate for the serial console (this is **not** the avrdude flashing baudrate)
serial-baudrate = 57600

Expand Down
124 changes: 123 additions & 1 deletion ravedude/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use anyhow::Context as _;
use serde::{Deserialize, Serialize};
use std::num::NonZeroU32;
use std::{num::NonZeroU32, str::FromStr};

#[derive(serde::Serialize, serde::Deserialize, Debug, Default)]
#[serde(rename_all = "kebab-case")]
Expand Down Expand Up @@ -48,12 +49,42 @@ impl RavedudeConfig {
port: args.port.clone(),
reset_delay: args.reset_delay,
board: args.legacy_board_name().clone(),
output_mode: args.output_mode.unwrap_or_default(),
newline_after: None,
newline_on: None,
},
board_config: Default::default(),
})
}
}

impl RavedudeGeneralConfig {
pub fn newline_mode(&self) -> anyhow::Result<NewlineMode> {
if self.output_mode == OutputMode::Ascii {
if self.newline_on.is_some() || self.newline_on.is_some() {
anyhow::bail!(
"newline_on and newline_after cannot be used with output_mode = \"ascii\""
)
}

return Ok(NewlineMode::Off);
}

Ok(match (self.newline_on.as_ref(), self.newline_after) {
(Some(_), Some(_)) => {
anyhow::bail!("newline_on and newline_after cannot be used at the same time")
}
(Some(on_str), None) => NewlineMode::On(parse_newline_on(on_str)?),
(None, Some(after)) => NewlineMode::After(after),
(None, None) => NewlineMode::After(match self.output_mode {
OutputMode::Hex | OutputMode::Dec => 16,
OutputMode::Bin => 8,
OutputMode::Ascii => unreachable!(),
}),
})
}
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Default)]
#[serde(rename_all = "kebab-case")]
pub struct RavedudeGeneralConfig {
Expand All @@ -63,6 +94,10 @@ pub struct RavedudeGeneralConfig {
pub port: Option<std::path::PathBuf>,
pub reset_delay: Option<u64>,
pub board: Option<String>,
#[serde(default)]
pub output_mode: OutputMode,
pub newline_on: Option<String>,
pub newline_after: Option<u8>,
}

impl RavedudeGeneralConfig {
Expand All @@ -83,6 +118,9 @@ impl RavedudeGeneralConfig {
if let Some(reset_delay) = args.reset_delay {
self.reset_delay = Some(reset_delay);
}
if let Some(output_mode) = args.output_mode {
self.output_mode = output_mode;
}
Ok(())
}
}
Expand Down Expand Up @@ -181,3 +219,87 @@ impl BoardConfig {
}
}
}

#[derive(Debug, Clone, Copy, Deserialize, Serialize, Default, PartialEq)]
pub enum OutputMode {
#[default]
Ascii,
Hex,
Dec,
Bin,
}

impl FromStr for OutputMode {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"ascii" => Ok(Self::Ascii),
"hex" => Ok(Self::Hex),
"dec" => Ok(Self::Dec),
"bin" => Ok(Self::Bin),
_ => Err(anyhow::anyhow!("unknown output mode")),
}
}
}

#[derive(Copy, Clone)]
pub enum NewlineMode {
/// Break lines when encountering this byte
On(u8),
/// Break lines after this many bytes
After(u8),
Off,
}

impl NewlineMode {
pub fn space_after(&self) -> Option<u8> {
if let NewlineMode::After(bytes) = self {
if bytes % 4 == 0 {
return Some(4);
}
};
None
}
}

fn parse_newline_on(s: &str) -> Result<u8, anyhow::Error> {
if let Ok(c) = s.parse::<char>() {
return u8::try_from(c).context("non-byte character in `newline-on`");
}

// if it starts with 0x then parse the hex byte
if &s[0..2] == "0x" {
if s.len() != 4 {
anyhow::bail!("hex byte must have 2 digits");
}
return u8::from_str_radix(&s[2..4], 16).context("invalid hex byte");
}

// if it starts with 0b then parse the binary byte
if &s[0..2] == "0b" {
if s.len() != 10 {
anyhow::bail!("binary byte must have 8 digits");
}
return u8::from_str_radix(&s[2..10], 2).context("invalid binary byte");
}

anyhow::bail!("must be a single character or a byte in hex or binary notation");
}

#[cfg(test)]
mod tests {
use super::parse_newline_on;

#[test]
fn test_parse_newline_on() {
assert_eq!(parse_newline_on("a").unwrap(), 'a' as u8);
assert_eq!(parse_newline_on("\n").unwrap(), '\n' as u8);
assert_eq!(parse_newline_on("0x41").unwrap(), 0x41);
assert_eq!(parse_newline_on("0b01000001").unwrap(), 0b01000001);
assert!(parse_newline_on("not a char").is_err());
assert!(parse_newline_on("0x").is_err());
assert!(parse_newline_on("0xzz").is_err());
assert!(parse_newline_on("0b").is_err());
assert!(parse_newline_on("0b0a0a0a0a").is_err());
}
}
52 changes: 48 additions & 4 deletions ravedude/src/console.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
use anyhow::Context as _;
use std::io::Read as _;
use std::io::Write as _;

pub fn open(port: &std::path::Path, baudrate: u32) -> anyhow::Result<()> {
use anyhow::Context as _;

use crate::config::NewlineMode;
use crate::config::OutputMode;
use crate::config::OutputMode::*;

pub fn open(
port: &std::path::PathBuf,
baudrate: u32,
output_mode: OutputMode,
newline_mode: NewlineMode,
space_after: Option<u8>,
) -> anyhow::Result<()> {
let mut rx = serialport::new(port.to_string_lossy(), baudrate)
.timeout(std::time::Duration::from_secs(2))
.open_native()
Expand All @@ -14,12 +25,14 @@ pub fn open(port: &std::path::Path, baudrate: u32) -> anyhow::Result<()> {

// Set a CTRL+C handler to terminate cleanly instead of with an error.
ctrlc::set_handler(move || {
eprintln!("");
eprintln!();
eprintln!("Exiting.");
std::process::exit(0);
})
.context("failed setting a CTRL+C handler")?;

let mut byte_count = 0;

// Spawn a thread for the receiving end because stdio is not portably non-blocking...
std::thread::spawn(move || loop {
#[cfg(not(target_os = "windows"))]
Expand All @@ -41,7 +54,38 @@ pub fn open(port: &std::path::Path, baudrate: u32) -> anyhow::Result<()> {
}
}
}
stdout.write(&buf[..count]).unwrap();
if output_mode == Ascii {
stdout.write(&buf[..count]).unwrap();
} else {
for byte in &buf[..count] {
byte_count += 1;
match output_mode {
Ascii => unreachable!(),
Hex => write!(stdout, "{:02x} ", byte).unwrap(),
Dec => write!(stdout, "{:03} ", byte).unwrap(),
Bin => write!(stdout, "{:08b} ", byte).unwrap(),
}

if let Some(space_after) = space_after {
if byte_count % space_after == 0 {
write!(stdout, " ").unwrap();
}
}
match newline_mode {
NewlineMode::On(newline_on) => {
if *byte == newline_on {
writeln!(stdout).unwrap()
}
}
NewlineMode::After(newline_after) => {
if byte_count % newline_after == 0 {
writeln!(stdout).unwrap();
}
}
NewlineMode::Off => {}
}
}
}
stdout.flush().unwrap();
}
Err(e) => {
Expand Down
24 changes: 22 additions & 2 deletions ravedude/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
//! For reference, take a look at [`boards.toml`](https://github.com/Rahix/avr-hal/blob/main/ravedude/src/boards.toml).
use anyhow::Context as _;
use colored::Colorize as _;
use config::OutputMode;

use std::path::Path;
use std::thread;
Expand Down Expand Up @@ -159,7 +160,13 @@ struct Args {
/// Should not be used in newer configurations.
#[clap(name = "LEGACY BINARY", value_parser)]
bin_legacy: Option<std::path::PathBuf>,

/// Output mode.
/// Can be ascii, hex, dec or bin
#[clap(short = 'o', long = "output-mode")]
output_mode: Option<OutputMode>,
}

impl Args {
/// Get the board name for legacy configurations.
/// `None` if the configuration isn't a legacy configuration or the board name doesn't exist.
Expand Down Expand Up @@ -257,6 +264,12 @@ fn ravedude() -> anyhow::Result<()> {
anyhow::bail!("no named board given and no board options provided");
};

if ravedude_config.general_options.newline_on.is_some()
&& ravedude_config.general_options.newline_after.is_some()
{
anyhow::bail!("newline_on and newline_after cannot be used at the same time");
}

let board_avrdude_options = board
.avrdude
.take()
Expand All @@ -269,7 +282,7 @@ fn ravedude() -> anyhow::Result<()> {
);

let port = match ravedude_config.general_options.port {
Some(port) => Ok(Some(port)),
Some(ref port) => Ok(Some(port.clone())),
None => match board.guess_port() {
Some(Ok(port)) => Ok(Some(port)),
p @ Some(Err(_)) => p.transpose().context(
Expand Down Expand Up @@ -335,12 +348,19 @@ fn ravedude() -> anyhow::Result<()> {
})?;

let port = port.context("console can only be opened for devices with USB-to-Serial")?;
let newline_mode = ravedude_config.general_options.newline_mode()?;

task_message!("Console", "{} at {} baud", port.display(), baudrate);
task_message!("", "{}", "CTRL+C to exit.".dimmed());
// Empty line for visual consistency
eprintln!();
console::open(&port, baudrate.get())?;
console::open(
&port,
baudrate.get(),
ravedude_config.general_options.output_mode,
newline_mode,
newline_mode.space_after(),
)?;
} else if args.bin.is_none() && port.is_some() {
warning!("you probably meant to add -c/--open-console?");
}
Expand Down