use std::fs::OpenOptions; use std::time::{SystemTime, UNIX_EPOCH}; use extcap::*; use futures::channel::mpsc::{self, Sender}; use futures::prelude::*; use log::{debug, warn, LevelFilter}; use pcap_file::{pcap::Packet, pcap::PcapHeader, DataLink}; use serialport::available_ports; use simplelog::{Config, SimpleLogger, WriteLogger}; use tokio_serial::{SerialPortBuilderExt, SerialStream}; use tokio_util::codec::{FramedRead, LinesCodec}; const GRP_SERIAL: &str = "Serial"; const OPT_PORT: &str = "port"; const OPT_BAUD: &str = "baud"; const BAUD_RATES: &[u32] = &[9_600, 14_400, 115_200]; struct TestSerialDump {} impl ExtcapListener for TestSerialDump { fn init_log(&mut self, _extcap: &Extcap, debug: bool, debug_file: Option<&str>) { let lvl = if debug { LevelFilter::Debug } else { LevelFilter::Warn }; if let Some(file) = debug_file { let _ = WriteLogger::init( lvl, Config::default(), OpenOptions::new() .create(true) .append(true) .open(file) .unwrap(), ); } else { let _ = SimpleLogger::init(lvl, Config::default()); } } fn capture_header(&mut self, _extcap: &Extcap, _ifc: &IFace) -> PcapHeader { debug!("capture_header()"); PcapHeader { datalink: DataLink::USER10, ..Default::default() } } fn capture_async(&mut self, extcap: &Extcap, _ifc: &IFace) -> ExtcapResult { debug!("capture_async()"); // Log list of available ports (already used earlier but now it can be written into log file) match available_ports() { Ok(ports) => debug!("available_ports: {:?}", ports), Err(err) => warn!("available_ports retrieving failed: {:?}", err), } let port = extcap.get_matches().value_of(OPT_PORT).unwrap(); debug!("port={}", port); let mut builder = tokio_serial::new(port, 9_600); if let Some(baud) = extcap .get_matches() .value_of(OPT_BAUD) .and_then(|s| s.parse::().ok()) { debug!("baud={}", baud); builder = builder.baud_rate(baud); } let port = builder .open_native_async() .map_err(|serr| ExtcapError::user_error(serr.to_string()))?; let (snd, rcv) = mpsc::channel(128); tokio::spawn(task(port, snd)); debug!("capture_async() started"); Ok(rcv) } } async fn task(port: SerialStream, mut sender: Sender>) { let mut reader = FramedRead::new(port, LinesCodec::new()); while let Some(res) = reader.next().await { match res { Ok(msg) => { debug!("line received: '{:?}'", msg); write_pkt(&mut sender, &msg).await; } Err(err) => { debug!("error during receiving: {:?}", err); break; } } } } async fn write_pkt(snd: &mut Sender>, msg: &str) { debug!("write_msg() {}", msg); let ts = SystemTime::now() .duration_since(UNIX_EPOCH) .expect("SystemTime before UNIX EPOCH"); let pkt = Packet::new_owned( ts.as_secs() as u32, ts.subsec_micros(), msg.as_bytes().to_vec(), msg.as_bytes().len() as u32, ); let _ = snd.send(pkt).await; } #[tokio::main] async fn main() -> Result<(), Box> { let mut ex = Extcap::new("test_serial_dump"); ex.version("0.0.1"); ex.about("Test serial input (Rust extcap example)"); // Interface let mut tser1 = IFace::new("tser1") .description("Test serial input") .dlt(DataLink::USER10.into()); // Interface arguments let mut arg_port = IfArg::new_selector(OPT_PORT) .display("Port name") .tooltip("COM port name") .group(GRP_SERIAL); let mut ports = available_ports()?; ports.sort_by(|a, b| a.port_name.cmp(&b.port_name)); for p in ports { arg_port.add_val(IfArgVal::new(p.port_name)); } tser1.add_arg(arg_port); let mut arg_baud = IfArg::new_selector(OPT_BAUD) .display("Baud rate") .tooltip("COM baud rate") .group(GRP_SERIAL); for b in BAUD_RATES { arg_baud.add_val(IfArgVal::new(b)) } tser1.add_arg(arg_baud); tser1.config_debug(); ex.add_interface(tser1); let user = TestSerialDump {}; ex.run_async(user).await?; debug!("DONE"); Ok(()) }