use jeep::Event;
use clap::Parser;
use serde::{Deserialize, Serialize};
use socketcan::CANFrame;
use std::fs::File;
use std::io::{BufRead, BufReader};
#[derive(Parser, Debug)]
#[command(
author,
version,
about = "Parse a `candump -L` file into events or errors",
long_about = None
)]
struct Args {
#[arg(short, long)]
in_file: String,
#[arg(short, long)]
out_file: String,
#[arg(short, long, value_parser=clap_num::maybe_hex::<u32>)]
filters: Option<Vec<u32>>,
}
fn parse_candump_line(
line: &str,
filters: &Option<Vec<u32>>,
) -> Option<(u128, CANFrame)> {
let split: Vec<&str> = line.split(['.', ' ', '#']).collect();
let components: [&str; 5] = split.try_into().ok()?;
let [timestamp_sec, timestamp_subsec, _, id, hex_data] = components;
let id = u32::from_str_radix(id, 16).ok()?;
if let Some(filters) = filters {
if !filters.contains(&id) {
return None;
}
}
let timestamp_sec = timestamp_sec.replace('(', "");
let timestamp_subsec = timestamp_subsec.replace(')', "");
let timestamp: u128 = (timestamp_sec.to_owned() + ×tamp_subsec)
.parse()
.ok()?;
let data: Vec<u8> = (0..hex_data.len())
.step_by(2)
.map(|i| {
u8::from_str_radix(&hex_data[i..i + 2], 16)
.expect(&format!("invalid hex character in data:{line}"))
})
.collect();
debug_assert!(data.len() <= 8);
Some((timestamp, CANFrame::new(id, &data, false, false).ok()?))
}
fn write_json<W, M>(
writer: &mut W,
message: &M,
timestamp: u128,
) -> Result<(), Box<dyn std::error::Error>>
where
M: Serialize,
W: std::io::Write,
{
#[derive(Serialize, Deserialize)]
struct TimestampedPayload<P> {
timestamp: u128,
payload: P,
}
let json = serde_json::to_string(&TimestampedPayload {
timestamp,
payload: message,
})?;
writeln!(writer, "{json}")?;
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();
let in_file = File::open(args.in_file)?;
let mut out_file = File::create(args.out_file)?;
let mut lines = BufReader::new(in_file).lines();
while let Some(Ok(line)) = lines.next() {
if let Some((timestamp, frame)) =
parse_candump_line(&line, &args.filters)
{
let result = Event::parse(frame);
write_json(&mut out_file, &result, timestamp)?;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::{parse_candump_line, CANFrame};
#[test]
fn test_from_candump_line() {
let filters = Some(vec![0x44, 0x236]);
let lines_frames: [(&str, Option<CANFrame>); 3] = [
(
"(1436509052.249713) vcan0 044#2A366C2BBA",
Some(
CANFrame::new(
0x044,
&[0x2A, 0x36, 0x6C, 0x2B, 0xBA],
false,
false,
)
.unwrap(),
),
),
("(1436509052.449847) vcan0 0F6#7ADFE07BD2", None),
(
"(1436509052.650004) vcan0 236#C3406B09F4C88036",
Some(
CANFrame::new(
0x236,
&[0xC3, 0x40, 0x6B, 0x09, 0xF4, 0xC8, 0x80, 0x36],
false,
false,
)
.unwrap(),
),
),
];
for (line, expected) in lines_frames {
let actual = parse_candump_line(line, &filters);
if let Some((_, frame)) = actual {
assert_eq!(frame.id(), expected.unwrap().id());
assert_eq!(frame.data(), expected.unwrap().data());
} else {
assert!(expected.is_none());
}
}
}
}