use clap::Parser;
use jeep::listener::{Error, Listener, Message};
use serde::{Deserialize, Serialize};
use std::{
fs::File,
sync::mpsc::sync_channel,
time::{Instant, SystemTime, UNIX_EPOCH},
};
const PAUSE: std::time::Duration = std::time::Duration::from_nanos(8330000);
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
enum DumpFormat {
Json,
}
#[derive(Parser, Debug)]
#[command(
author,
version,
about = "Listen to Jeep events on the CAN bus",
long_about = None
)]
struct Args {
#[arg(long)]
device: String,
#[arg(short, long)]
verbose: bool,
#[arg(long)]
dump: Option<String>,
}
fn ns_since_unix_epoch() -> Result<u128, Box<dyn std::error::Error>> {
Ok(SystemTime::now().duration_since(UNIX_EPOCH)?.as_nanos())
}
fn write_json<W>(
writer: &mut W,
message: &Message,
timestamp: u128,
) -> Result<(), Box<dyn std::error::Error>>
where
W: std::io::Write,
{
#[derive(Serialize, Deserialize)]
struct TimestampedPayload<P> {
timestamp: u128,
payload: P,
}
let json = match message {
Ok(event) => serde_json::to_string(&TimestampedPayload {
timestamp,
payload: event,
})?,
Err(err) => match err {
Error::ParseError(err) => {
serde_json::to_string(&TimestampedPayload {
timestamp,
payload: err,
})?
}
Error::IoError(err) => {
serde_json::to_string(&TimestampedPayload {
timestamp,
payload: format!("{err}"),
})?
}
Error::BadLen(err) => serde_json::to_string(&TimestampedPayload {
timestamp,
payload: err,
})?,
},
};
writeln!(writer, "{json}")?;
Ok(())
}
fn print_message(timestamp: u128, message: &Message, verbose: bool) {
match message {
Ok(event) => println!("{timestamp}:Ok({event})"),
Err(error) => {
if verbose {
println!("{timestamp}:Err({error})")
}
}
}
}
fn handle_message<W>(
writer: &mut W,
message: Message,
verbose: bool,
) -> Result<(), Box<dyn std::error::Error>>
where
W: std::io::Write,
{
let timestamp = ns_since_unix_epoch()?;
print_message(timestamp, &message, verbose);
write_json(writer, &message, timestamp)?;
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();
let mut dump = match args.dump {
Some(filename) => Some(File::create(filename)?),
None => None,
};
let listener = Listener::connect(&args.device, false)?;
let (tx, rx) = sync_channel(0);
ctrlc::set_handler(move || tx.send(()).expect("rx disconnected somehow."))
.expect("Error setting Ctrl-C handler");
loop {
let loop_start = Instant::now();
for message in listener.messages() {
if let Some(file) = &mut dump {
handle_message(file, message, args.verbose)?;
}
}
if rx.try_recv().is_ok() {
println!("CTRL+C Received.");
break;
}
let elapsed = loop_start.elapsed();
if elapsed < PAUSE {
std::thread::sleep(PAUSE - elapsed);
}
}
Ok(())
}