diff --git a/Cargo.toml b/Cargo.toml index 4185d23..d2b5980 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,11 +8,14 @@ keywords = ["agriculture", "can", "canbus", "isobus", "j1939", "agritech", "smar [dependencies] rand = "0.8.5" +pcan-basic = { version = "1.0.2", optional = true } socketcan = { version = "2.0.0", optional = true } tracing = { version = "0.1.37", optional = true } [features] default = [] +# Peak driver +peak = ["dep:pcan-basic"] # Socketcan driver socketcan = ["dep:socketcan"] # Optional logging instrumentation @@ -21,8 +24,9 @@ tracing = ["dep:tracing"] [dev-dependencies] clap = { version = "4.3.19", features = ["derive"] } ctrlc = "3.4.0" +tracing = "0.1.37" tracing-subscriber = "0.3.17" [[example]] name = "forward" -required-features = ["socketcan", "tracing"] +required-features = ["tracing"] diff --git a/examples/forward.rs b/examples/forward.rs index e843bca..a71ef8f 100644 --- a/examples/forward.rs +++ b/examples/forward.rs @@ -2,9 +2,46 @@ use std::sync::mpsc::channel; -use ag_iso_stack::driver::{Driver, Frame, SocketcanDriver}; +#[cfg(feature = "peak")] +use ag_iso_stack::driver::PeakDriver; +#[cfg(feature = "socketcan")] +use ag_iso_stack::driver::SocketcanDriver; +use ag_iso_stack::driver::{Driver, Frame}; use ag_iso_stack::tracing; -use clap::Parser; +use clap::{Parser, ValueEnum}; +#[cfg(feature = "peak")] +use pcan_basic::bus::UsbBus; + +fn parse_usb_bus(s: &str) -> Option { + let s = s.to_uppercase(); + match s.as_str() { + "USB1" => Some(UsbBus::USB1), + "USB2" => Some(UsbBus::USB2), + "USB3" => Some(UsbBus::USB3), + "USB4" => Some(UsbBus::USB4), + "USB5" => Some(UsbBus::USB5), + "USB6" => Some(UsbBus::USB6), + "USB7" => Some(UsbBus::USB7), + "USB8" => Some(UsbBus::USB8), + "USB9" => Some(UsbBus::USB9), + "USB10" => Some(UsbBus::USB10), + "USB11" => Some(UsbBus::USB11), + "USB12" => Some(UsbBus::USB12), + "USB13" => Some(UsbBus::USB13), + "USB14" => Some(UsbBus::USB14), + "USB15" => Some(UsbBus::USB15), + "USB16" => Some(UsbBus::USB16), + _ => None, + } +} + +#[derive(Debug, Clone, ValueEnum)] +enum CanDriver { + #[cfg(feature = "socketcan")] + Socketcan, + #[cfg(feature = "peak")] + Pcan, +} /// Forward CAN traffic from one interface to another #[derive(Debug, Parser)] @@ -25,13 +62,29 @@ struct Options { /// Can be either a string interface name, or an integer interface index #[clap(short, long, default_value_t = String::from("can1"))] pub output_interface: String, + + #[clap(short, long)] + pub driver: CanDriver, } -fn create_driver(iface: &str) -> impl Driver { - if let Ok(index) = iface.parse::() { - SocketcanDriver::new_by_index(index) - } else { - SocketcanDriver::new_by_name(iface) +fn create_driver(iface: &str, driver: CanDriver) -> Box { + match driver { + #[cfg(feature = "socketcan")] + CanDriver::Socketcan => { + if let Ok(index) = iface.parse::() { + Box::new(SocketcanDriver::new_by_index(index)) + } else { + Box::new(SocketcanDriver::new_by_name(iface)) + } + } + #[cfg(feature = "peak")] + CanDriver::Pcan => { + let bus = parse_usb_bus(iface).unwrap(); + let baud = ag_iso_stack::driver::Baudrate::Baud250K; + Box::new(PeakDriver::new_usb(bus, baud)) + } + #[allow(unreachable_patterns)] + _ => unreachable!(), } } @@ -45,14 +98,16 @@ fn main() { .map_err(|_err| eprintln!("Unable to set global default subscriber")) .unwrap(); + tracing::info!("AgIsoStack-rs example starts..."); + tracing::info!( "Forwarding CAN traffic from {} to {}", opts.input_interface, opts.output_interface ); - let mut input = create_driver(&opts.input_interface); - let mut output = create_driver(&opts.output_interface); + let mut input = create_driver(&opts.input_interface, opts.driver.clone()); + let mut output = create_driver(&opts.output_interface, opts.driver); input.open().unwrap(); output.open().unwrap(); diff --git a/src/driver/driver.rs b/src/driver/driver.rs index fa7d0dc..1dc6eec 100644 --- a/src/driver/driver.rs +++ b/src/driver/driver.rs @@ -1,6 +1,24 @@ // Copyright 2023 Raven Industries inc. use crate::driver::Frame; +#[derive(Debug, PartialEq, Copy, Clone)] +pub enum Baudrate { + Baud1M, + Baud800K, + Baud500K, + Baud250K, + Baud125K, + Baud100K, + Baud95K, + Baud83K, + Baud50K, + Baud47K, + Baud33K, + Baud20K, + Baud10K, + Baud5K, +} + #[derive(Debug)] #[non_exhaustive] pub enum DriverOpenError { diff --git a/src/driver/mod.rs b/src/driver/mod.rs index 0e39c02..7213ce6 100644 --- a/src/driver/mod.rs +++ b/src/driver/mod.rs @@ -15,11 +15,17 @@ mod pgn; #[cfg(feature = "socketcan")] mod socketcan; +#[cfg(feature = "peak")] +mod peak; + pub use address::Address; pub use can_id::{CanId, Priority, Type}; -pub use driver::{Driver, DriverCloseError, DriverOpenError, DriverReadError, DriverWriteError}; +pub use driver::{Baudrate, Driver, DriverCloseError, DriverOpenError, DriverReadError, DriverWriteError}; pub use frame::{Channel, Frame}; pub use pgn::Pgn; #[cfg(feature = "socketcan")] pub use self::socketcan::SocketcanDriver; + +#[cfg(feature = "peak")] +pub use self::peak::PeakDriver; diff --git a/src/driver/peak.rs b/src/driver/peak.rs new file mode 100644 index 0000000..9bbadb1 --- /dev/null +++ b/src/driver/peak.rs @@ -0,0 +1,357 @@ +// Copyright 2023 Raven Industries inc. +use std::io::ErrorKind; +use std::time::Instant; + +use pcan_basic::bus::{DngBus, IsaBus, LanBus, PccBus, PciBus, UsbBus}; +use pcan_basic::error::PcanError; +use pcan_basic::socket::dng::DngCanSocket; +use pcan_basic::socket::isa::IsaCanSocket; +use pcan_basic::socket::lan::LanCanSocket; +use pcan_basic::socket::pcc::PccCanSocket; +use pcan_basic::socket::pci::PciCanSocket; +use pcan_basic::socket::usb::UsbCanSocket; +use pcan_basic::socket::{Baudrate as PeakBaudrate, CanFrame, MessageType, RecvCan, SendCan}; + +use crate::driver::{ + Baudrate, CanId, Channel, Driver, DriverCloseError, DriverOpenError, DriverReadError, + DriverWriteError, Frame as InternalFrame, Type, +}; + +impl From for DriverOpenError { + fn from(e: PcanError) -> DriverOpenError { + match e { + PcanError::RegTest => DriverOpenError::IoError(std::io::Error::new( + ErrorKind::Other, + "Test of the CAN controller hardware registers failed (no hardware found)", + )), + PcanError::NoDriver => { + DriverOpenError::IoError(std::io::Error::new(ErrorKind::Other, "Driver not loaded")) + } + PcanError::HwInUse => DriverOpenError::IoError(std::io::Error::new( + ErrorKind::AddrInUse, + "Hardware already in use by a Net", + )), + PcanError::NetInUse => DriverOpenError::IoError(std::io::Error::new( + ErrorKind::AddrInUse, + "A Client is already connected to the Net", + )), + PcanError::IllHw => DriverOpenError::IoError(std::io::Error::new( + ErrorKind::Other, + "Hardware handle is invalid", + )), + PcanError::IllNet => DriverOpenError::IoError(std::io::Error::new( + ErrorKind::Other, + "Net handle is invalid", + )), + PcanError::IllClient => DriverOpenError::IoError(std::io::Error::new( + ErrorKind::Other, + "Client handle is invalid", + )), + PcanError::Resource => DriverOpenError::IoError(std::io::Error::new( + ErrorKind::Other, + "Resource (FIFO, Client, timeout) cannot be created", + )), + PcanError::IllParamType => { + DriverOpenError::IoError(std::io::Error::new(ErrorKind::Other, "Invalid parameter")) + } + PcanError::IllParamVal => DriverOpenError::IoError(std::io::Error::new( + ErrorKind::Other, + "Invalid parameter value", + )), + PcanError::Unknown => { + DriverOpenError::IoError(std::io::Error::new(ErrorKind::Other, "Unknown error")) + } + PcanError::IllData => DriverOpenError::IoError(std::io::Error::new( + ErrorKind::InvalidData, + "Invalid data, function, or action", + )), + PcanError::IllMode => DriverOpenError::IoError(std::io::Error::new( + ErrorKind::Other, + "Driver object state is wrong for the attempted operation", + )), + PcanError::Initialize => DriverOpenError::IoError(std::io::Error::new( + ErrorKind::Other, + "Channel is not initialized [Value was changed from 0x40000 to 0x4000000]", + )), + PcanError::IllOperation => DriverOpenError::IoError(std::io::Error::new( + ErrorKind::Other, + "Invalid operation [Value was changed from 0x80000 to 0x8000000]", + )), + _ => DriverOpenError::IoError(std::io::Error::new(ErrorKind::Other, "Unknown")), + } + } +} + +impl From for DriverReadError { + fn from(e: PcanError) -> DriverReadError { + match e { + PcanError::QrcvEmpty | PcanError::QOverrun => DriverReadError::NoFrameReady, + PcanError::Resource => DriverReadError::IoError(std::io::Error::new( + ErrorKind::Other, + "Resource (FIFO, Client, timeout) cannot be created", + )), + PcanError::IllParamType => { + DriverReadError::IoError(std::io::Error::new(ErrorKind::Other, "Invalid parameter")) + } + PcanError::IllParamVal => DriverReadError::IoError(std::io::Error::new( + ErrorKind::Other, + "Invalid parameter value", + )), + PcanError::Unknown => { + DriverReadError::IoError(std::io::Error::new(ErrorKind::Other, "Unknown error")) + } + PcanError::IllData => DriverReadError::IoError(std::io::Error::new( + ErrorKind::InvalidData, + "Invalid data, function, or action", + )), + _ => DriverReadError::IoError(std::io::Error::new(ErrorKind::Other, "Unknown")), + } + } +} + +impl From for DriverWriteError { + fn from(e: PcanError) -> DriverWriteError { + match e { + PcanError::AnyBusErr => DriverWriteError::BusError(), + PcanError::XmtFull | PcanError::QxmtFull => DriverWriteError::NotReady, + PcanError::IllOperation => DriverWriteError::IoError(std::io::Error::new( + ErrorKind::Other, + "Invalid operation [Value was changed from 0x80000 to 0x8000000]", + )), + _ => DriverWriteError::IoError(std::io::Error::new(ErrorKind::Other, "Unknown")), + } + } +} + +impl From<&InternalFrame> for CanFrame { + fn from(f: &InternalFrame) -> CanFrame { + let msg_type = match f.id.type_() { + Type::Standard => MessageType::Standard, + Type::Extended => MessageType::Extended, + }; + CanFrame::new( + f.id.raw(), + msg_type, + &f.data[..f.data_length.min(8) as usize], + ) + // guaranteed to not crash, because `f.data` is an [u8; 8] + .expect("Can frame had too much data") + } +} + +// It's unfortunate that we have to duplicate pcan's baudrate enum, but it doesn't implement Clone +// or Debug, so we have to. +impl From for PeakBaudrate { + fn from(b: Baudrate) -> PeakBaudrate { + match b { + Baudrate::Baud1M => PeakBaudrate::Baud1M, + Baudrate::Baud800K => PeakBaudrate::Baud800K, + Baudrate::Baud500K => PeakBaudrate::Baud500K, + Baudrate::Baud250K => PeakBaudrate::Baud250K, + Baudrate::Baud125K => PeakBaudrate::Baud125K, + Baudrate::Baud100K => PeakBaudrate::Baud100K, + Baudrate::Baud95K => PeakBaudrate::Baud95K, + Baudrate::Baud83K => PeakBaudrate::Baud83, + Baudrate::Baud50K => PeakBaudrate::Baud50K, + Baudrate::Baud47K => PeakBaudrate::Baud47K, + Baudrate::Baud33K => PeakBaudrate::Baud33K, + Baudrate::Baud20K => PeakBaudrate::Baud20K, + Baudrate::Baud10K => PeakBaudrate::Baud10K, + Baudrate::Baud5K => PeakBaudrate::Baud5K, + } + } +} + +enum PeakIface { + Dng(DngBus), + Isa(IsaBus), + Lan(LanBus), + Pcc(PccBus), + Pci(PciBus), + Usb(UsbBus), +} + +enum PeakSocket { + Dng(DngCanSocket), + Isa(IsaCanSocket), + Lan(LanCanSocket), + Pcc(PccCanSocket), + Pci(PciCanSocket), + Usb(UsbCanSocket), +} + +/// PCan Basic Driver [Driver] +/// +/// Enabled with the optional `pcan-basic` feature +pub struct PeakDriver { + iface: PeakIface, + baudrate: Baudrate, + socket: Option, + opened_timestamp: Instant, +} + +impl PeakDriver { + pub fn new_dng(if_bus: DngBus, baudrate: Baudrate) -> Self { + Self { + iface: PeakIface::Dng(if_bus), + baudrate, + socket: None, + opened_timestamp: Instant::now(), + } + } + + pub fn new_isa(if_bus: IsaBus, baudrate: Baudrate) -> Self { + Self { + iface: PeakIface::Isa(if_bus), + baudrate, + socket: None, + opened_timestamp: Instant::now(), + } + } + + pub fn new_lan(if_bus: LanBus, baudrate: Baudrate) -> Self { + Self { + iface: PeakIface::Lan(if_bus), + baudrate, + socket: None, + opened_timestamp: Instant::now(), + } + } + + pub fn new_pcc(if_bus: PccBus, baudrate: Baudrate) -> Self { + Self { + iface: PeakIface::Pcc(if_bus), + socket: None, + baudrate, + opened_timestamp: Instant::now(), + } + } + + pub fn new_pci(if_bus: PciBus, baudrate: Baudrate) -> Self { + Self { + iface: PeakIface::Pci(if_bus), + baudrate, + socket: None, + opened_timestamp: Instant::now(), + } + } + + pub fn new_usb(if_bus: UsbBus, baudrate: Baudrate) -> Self { + Self { + iface: PeakIface::Usb(if_bus), + baudrate, + socket: None, + opened_timestamp: Instant::now(), + } + } + + fn to_frame(&self, frame: CanFrame) -> InternalFrame { + let timestamp = self.opened_timestamp.elapsed(); + let raw_id = frame.can_id(); + let extended = frame.is_extended_frame(); + let frame_type = if extended { + Type::Extended + } else { + Type::Standard + }; + + let id = CanId::new(raw_id, frame_type); + // TODO: The Driver trait doesn't know anything about Channels yet. + // + // The channel exists so that we can tie Frames and CANMessages back to the network + // manager they originated from. This channel value should be passed to the Driver + // when it's created (or opened?) + let channel = Channel::default(); + let mut data = [0; 8]; + let data_length = usize::from(frame.dlc().min(8)); + data[..data_length].copy_from_slice(frame.data()); + let data_length = data_length as u8; + + InternalFrame { + timestamp, + id, + channel, + data, + data_length, + extended, + } + } +} + +impl Driver for PeakDriver { + fn is_valid(&self) -> bool { + self.socket.is_some() + } + fn open(&mut self) -> Result<(), DriverOpenError> { + self.socket = match &self.iface { + PeakIface::Dng(dng) => Some(PeakSocket::Dng(DngCanSocket::open( + *dng, + PeakBaudrate::from(self.baudrate), + )?)), + PeakIface::Isa(isa) => Some(PeakSocket::Isa(IsaCanSocket::open( + *isa, + PeakBaudrate::from(self.baudrate), + )?)), + PeakIface::Lan(lan) => Some(PeakSocket::Lan(LanCanSocket::open( + *lan, + PeakBaudrate::from(self.baudrate), + )?)), + PeakIface::Pcc(pcc) => Some(PeakSocket::Pcc(PccCanSocket::open( + *pcc, + PeakBaudrate::from(self.baudrate), + )?)), + PeakIface::Pci(pci) => Some(PeakSocket::Pci(PciCanSocket::open( + *pci, + PeakBaudrate::from(self.baudrate), + )?)), + PeakIface::Usb(usb) => Some(PeakSocket::Usb(UsbCanSocket::open( + *usb, + PeakBaudrate::from(self.baudrate), + )?)), + }; + self.opened_timestamp = Instant::now(); + + Ok(()) + } + fn close(&mut self) -> Result<(), DriverCloseError> { + self.socket = None; + Ok(()) + } + + /// Read a frame from the driver, if possible + /// + /// The timestamp on the frame is the duration since [`open`](Self::open) was last called. + fn read_nonblocking(&mut self, frame: &mut InternalFrame) -> Result<(), DriverReadError> { + let Some(sock) = self.socket.as_mut() else { + return Err(DriverReadError::DriverClosed); + }; + let peak_frame = match sock { + PeakSocket::Dng(dng) => dng.recv()?, + PeakSocket::Isa(isa) => isa.recv()?, + PeakSocket::Lan(lan) => lan.recv()?, + PeakSocket::Pcc(pcc) => pcc.recv()?, + PeakSocket::Pci(pci) => pci.recv()?, + PeakSocket::Usb(usb) => usb.recv()?, + }; + + *frame = self.to_frame(peak_frame.0); + Ok(()) + } + fn write_nonblocking(&mut self, frame: &InternalFrame) -> Result<(), DriverWriteError> { + let Some(sock) = self.socket.as_mut() else { + return Err(DriverWriteError::DriverClosed); + }; + let peak_frame: CanFrame = frame.into(); + + match sock { + PeakSocket::Dng(dng) => dng.send(peak_frame)?, + PeakSocket::Isa(isa) => isa.send(peak_frame)?, + PeakSocket::Lan(lan) => lan.send(peak_frame)?, + PeakSocket::Pcc(pcc) => pcc.send(peak_frame)?, + PeakSocket::Pci(pci) => pci.send(peak_frame)?, + PeakSocket::Usb(usb) => usb.send(peak_frame)?, + }; + + Ok(()) + } +}