use anyhow::{Context, Result};
use clap::{ArgAction, Command, arg};
use socketcan::{CanAnyFrame, CanFdSocket, Socket, dump::Reader};
use std::{
process, thread,
time::{Duration, Instant},
};
const VERSION: &str = env!("CARGO_PKG_VERSION");
fn play(filename: &str, iface: &str, fast: bool) -> Result<()> {
let sock = CanFdSocket::open(iface)
.with_context(|| format!("Failed to open FD socket on interface '{}'", iface))?;
let reader = Reader::from_file(filename)
.with_context(|| format!("Error opening log file '{}'", filename))?;
let start = Instant::now();
let mut first_t_us: Option<u64> = None;
for rec in reader {
let rec = rec?;
let first = *first_t_us.get_or_insert(rec.t_us);
if !fast {
let offset = Duration::from_micros(rec.t_us.saturating_sub(first));
if let Some(delay) = offset.checked_sub(start.elapsed()) {
thread::sleep(delay);
}
}
println!("{}", rec);
use CanAnyFrame::*;
match rec.frame {
Normal(frame) => sock.write_frame(&frame)?,
Remote(frame) => sock.write_frame(&frame)?,
Fd(frame) => sock.write_frame(&frame)?,
Error(frame) => sock.write_frame(&frame)?,
}
}
Ok(())
}
fn main() {
let opts = Command::new("can")
.author("Frank Pagliughi")
.version(VERSION)
.about("SocketCAN example to play a candump file")
.disable_help_flag(true)
.arg(
arg!(--help "Print help information")
.short('?')
.action(ArgAction::Help)
.global(true),
)
.arg(arg!(<iface> "The CAN interface to use, like 'can0', 'vcan0', etc").required(true))
.arg(arg!(<file> "The candump log file to read").required(true))
.arg(
arg!(--fast "Send as fast as possible, ignoring the recorded timestamps")
.action(ArgAction::SetTrue),
)
.get_matches();
let iface = opts.get_one::<String>("iface").unwrap();
let filename = opts.get_one::<String>("file").unwrap();
let fast = opts.get_flag("fast");
if let Err(err) = play(filename, iface, fast) {
eprintln!("{}", err);
process::exit(1);
}
}