Skip to content
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

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

Open
wants to merge 4 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
87 changes: 83 additions & 4 deletions ravedude/src/console.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,37 @@
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 colored::Colorize as _;

use crate::task_message;
use crate::Args;
use crate::OutputMode::*;

pub fn open(args: Args) -> anyhow::Result<()> {
let baudrate = args
.baudrate
.context("-b/--baudrate is needed for the serial console")?;

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

task_message!("Console", "{} at {} baud", port.display(), baudrate);
task_message!(
"Output",
"{}",
match args.output_mode {
Some(Ascii) | None => "ascii",
Some(Hex) => "hexadecimal",
Some(Dec) => "decimal",
Some(Bin) => "binary",
}
);
task_message!("", "{}", "CTRL+C to exit.".dimmed());
// Empty line for visual consistency
eprintln!();

let mut rx = serialport::new(port.to_string_lossy(), baudrate)
.timeout(std::time::Duration::from_secs(2))
.open_native()
Expand All @@ -14,12 +43,29 @@ 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 newline_after = match args.newline_after {
Some(n) => n,
None => match args.output_mode {
Some(Hex) | Some(Dec) => 16,
Some(Bin) => 8,
_ => 0,
},
};

let (spaces, space_after) = if args.newline_on.is_none() && newline_after % 4 == 0 {
(true, 4)
} else {
(false, 0)
};

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 +87,40 @@ pub fn open(port: &std::path::Path, baudrate: u32) -> anyhow::Result<()> {
}
}
}
stdout.write(&buf[..count]).unwrap();
match args.output_mode {
Some(Ascii) | None => {
stdout.write_all(&buf).unwrap();
}
_ => {
for byte in &buf[..count] {
byte_count += 1;
match args.output_mode {
Some(Ascii) | None => unreachable!(),
Some(Hex) => {
write!(stdout, "{:02x} ", byte).unwrap();
}
Some(Dec) => {
write!(stdout, "{:03} ", byte).unwrap();
}
Some(Bin) => {
write!(stdout, "{:08b} ", byte).unwrap();
}
}
// don’t execute in ascii mode, ascii is unreachable here
if spaces && byte_count % space_after == 0 {
write!(stdout, " ").unwrap();
}
if args.newline_on.is_none() && byte_count % newline_after == 0 {
writeln!(stdout).unwrap();
}
if args.newline_on.is_some()
&& *byte as char == args.newline_on.unwrap()
{
writeln!(stdout).unwrap();
}
}
}
}
stdout.flush().unwrap();
}
Err(e) => {
Expand Down
107 changes: 94 additions & 13 deletions ravedude/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use anyhow::Context as _;
use anyhow::{bail, Context as _};
use colored::Colorize as _;
use structopt::clap::AppSettings;

use std::str::FromStr;
use std::thread;
use std::time::Duration;

Expand All @@ -13,6 +14,72 @@ mod ui;
/// This represents the minimum (Major, Minor) version raverdude requires avrdude to meet.
const MIN_VERSION_AVRDUDE: (u8, u8) = (6, 3);

#[derive(Debug)]
enum OutputMode {
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")),
}
}
}

// this could have fewer nested if’s, but this produces better error messages
fn parse_newline_on(s: &str) -> Result<char, anyhow::Error> {
if let Ok(c) = s.parse::<char>() {
Ok(c)

// if it starts with 0x then parse the hex byte
} else if &s[0..2] == "0x" {
if s.len() == 4 {
if let Ok(n) = u8::from_str_radix(&s[2..4], 16) {
Ok(n as char)
} else {
bail!("invalid hex byte")
}
} else {
bail!("hex byte must have 2 characters")
}
// if it starts with 0b then parse the binary byte
} else if &s[0..2] == "0b" {
if s.len() == 10 {
if let Ok(n) = u8::from_str_radix(&s[2..10], 2) {
Ok(n as char)
} else {
bail!("invalid binary byte")
}
} else {
bail!("binary byte must have 8 characters")
}
} else {
bail!("must be a single character or a byte in hex or binary notation")
}
}

#[test]
fn test_parse_newline_on() {
assert_eq!(parse_newline_on("a").unwrap(), 'a');
assert_eq!(parse_newline_on("\n").unwrap(), '\n');
assert_eq!(parse_newline_on("0x41").unwrap(), 'A');
assert_eq!(parse_newline_on("0b01000001").unwrap(), 'A');
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());
}

/// ravedude is a rust wrapper around avrdude for providing the smoothest possible development
/// experience with rust on AVR microcontrollers.
///
Expand Down Expand Up @@ -52,6 +119,26 @@ struct Args {
#[structopt(long = "debug-avrdude")]
debug_avrdude: bool,

/// Output mode.
/// Can be ascii, hex, dec or bin
#[structopt(short = "o")]
output_mode: Option<OutputMode>,

/// 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
#[structopt(long = "newline-on", parse(try_from_str = parse_newline_on), verbatim_doc_comment)]
newline_on: Option<char>,

/// 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
#[structopt(long = "newline-after", verbatim_doc_comment)]
newline_after: Option<u8>,

/// Which board to interact with.
///
/// Must be one of the known board identifiers:
Expand Down Expand Up @@ -97,6 +184,10 @@ fn ravedude() -> anyhow::Result<()> {

let board = board::get_board(&args.board).expect("board not found");

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

task_message!("Board", "{}", board.display_name());

if let Some(wait_time) = args.reset_delay {
Expand All @@ -118,7 +209,7 @@ fn ravedude() -> anyhow::Result<()> {
}
}

let port = match args.port {
let port = match args.port.clone() {
Some(port) => Ok(Some(port)),
None => match board.guess_port() {
Some(Ok(port)) => Ok(Some(port)),
Expand Down Expand Up @@ -160,17 +251,7 @@ fn ravedude() -> anyhow::Result<()> {
}

if args.open_console {
let baudrate = args
.baudrate
.context("-b/--baudrate is needed for the serial console")?;

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

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)?;
console::open(args)?;
} else if args.bin.is_none() && port.is_some() {
warning!("you probably meant to add -c/--open-console?");
}
Expand Down