From b2a4a6974b136dce9d778ce9bc4a97741bab2e9f Mon Sep 17 00:00:00 2001 From: Austin Gill Date: Tue, 8 Aug 2023 11:41:38 -0500 Subject: [PATCH] WIP: Add Peak CAN driver --- Cargo.toml | 5 +- examples/forward.rs | 71 +++++++++++++--- src/driver/driver.rs | 12 +++ src/driver/mod.rs | 5 ++ src/driver/peak.rs | 187 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 270 insertions(+), 10 deletions(-) create mode 100644 src/driver/peak.rs diff --git a/Cargo.toml b/Cargo.toml index 4185d23..d081944 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ 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 } @@ -17,6 +18,8 @@ default = [] socketcan = ["dep:socketcan"] # Optional logging instrumentation tracing = ["dep:tracing"] +# Peak driver +peak = ["dep:pcan-basic"] [dev-dependencies] clap = { version = "4.3.19", features = ["derive"] } @@ -25,4 +28,4 @@ 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..b4f41c2 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(bus, baud)) + } + #[allow(unreachable_patterns)] + _ => unreachable!(), } } @@ -51,8 +104,8 @@ fn main() { 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..c555db0 100644 --- a/src/driver/driver.rs +++ b/src/driver/driver.rs @@ -6,6 +6,10 @@ use crate::driver::Frame; pub enum DriverOpenError { /// The driver failed to open with filesystem semantics IoError(std::io::Error), + // TODO: Here and throughout. I don't love the pcan errors. They're not real std::error::Error + // types, and it's not obvious what they mean. Maybe we should re-think this error design? + #[cfg(feature = "peak")] + PeakError(pcan_basic::error::PcanError), } impl std::fmt::Display for DriverOpenError { @@ -17,6 +21,10 @@ impl std::error::Error for DriverOpenError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match &self { DriverOpenError::IoError(e) => Some(e), + // PcanError doesn't implement the Error trait + // DriverOpenError::PeakError(e) => Some(e), + #[allow(unreachable_patterns)] + _ => None, } } } @@ -49,6 +57,8 @@ pub enum DriverReadError { ErrorFrame(), /// The driver failed to read with filesystem semantics IoError(std::io::Error), + #[cfg(feature = "peak")] + PeakError(pcan_basic::error::PcanError), } impl std::fmt::Display for DriverReadError { @@ -79,6 +89,8 @@ pub enum DriverWriteError { BusError(), /// Some fault with filesystem semantics IoError(std::io::Error), + #[cfg(feature = "peak")] + PeakError(pcan_basic::error::PcanError), } impl std::fmt::Display for DriverWriteError { diff --git a/src/driver/mod.rs b/src/driver/mod.rs index 0e39c02..49861ba 100644 --- a/src/driver/mod.rs +++ b/src/driver/mod.rs @@ -12,6 +12,9 @@ mod driver; mod frame; mod pgn; +#[cfg(feature = "peak")] +mod peak; + #[cfg(feature = "socketcan")] mod socketcan; @@ -21,5 +24,7 @@ pub use driver::{Driver, DriverCloseError, DriverOpenError, DriverReadError, Dri pub use frame::{Channel, Frame}; pub use pgn::Pgn; +#[cfg(feature = "peak")] +pub use self::peak::{Baudrate, PeakDriver}; #[cfg(feature = "socketcan")] pub use self::socketcan::SocketcanDriver; diff --git a/src/driver/peak.rs b/src/driver/peak.rs new file mode 100644 index 0000000..352af92 --- /dev/null +++ b/src/driver/peak.rs @@ -0,0 +1,187 @@ +pub use pcan_basic::bus::UsbBus; +// NOTE: PcanError is undocumented in the Rust source - you should go to +// https://github.com/tsabelmann/pcan-basic-sys/blob/master/header/PCANBasic.h for error +// documentation. +// +// PcanError::XmtFull - controller transmit buffer full +// PcanError::Overrun - controller was read too late +// PcanError::BusLight - error counter reached the light limit +// PcanError::BusHeavy - error counter reached the heavy limit +// PcanError::BusPassive +// PcanError::BusOff +// PcanError::AnyBusErr +// PcanError::QrcvEmpty - receive queue empty +// PcanError::QOverrun - receive queue overrun +// PcanError::QxmtFull - transmit queue full +// PcanError::RegTest - controller hardware registers test failed +// PcanError::NoDriver - driver not loaded +// PcanError::HwInUse - Hardware already in use by a network +// PcanError::NetInUse - A client is already connected to the Net +// PcanError::IllHw - Hardware handle is invalid +// PcanError::IllNet - Net handle is invalid +// PcanError::IllClient - Client handle is invalid +// PcanError::Resource - Resource (FIFO, Client, timeout) cannot be created +// PcanError::IllParamType - Invalid parameter +// PcanError::IllParamVal - Invalid parameter value +// PcanError::Unknown +// PcanError::IllData - Invalid data, function, or action +// PcanError::IllMode - Driver object state is wrong for the attempted operation +// PcanError::Caution - An opperation was successfully carried out, but irregularities were found +// PcanError::Initialize - Channel not initialized +// PcanError::IllOperation - Invalid operation +pub use pcan_basic::error::PcanError; +use pcan_basic::socket::usb::UsbCanSocket; +use pcan_basic::socket::{Baudrate as PcanBaudrate, RecvCan, SendCan}; + +use crate::driver::{ + Driver, DriverCloseError, DriverOpenError, DriverReadError, DriverWriteError, Frame, +}; +use crate::tracing; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Baudrate { + Baud1M, + Baud800K, + Baud500K, + Baud250K, + Baud125K, + Baud100K, + Baud95K, + Baud83K, + Baud50K, + Baud47K, + Baud33K, + Baud20K, + Baud10K, + Baud5K, +} + +// 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 PcanBaudrate { + fn from(b: Baudrate) -> PcanBaudrate { + match b { + Baudrate::Baud1M => PcanBaudrate::Baud1M, + Baudrate::Baud800K => PcanBaudrate::Baud800K, + Baudrate::Baud500K => PcanBaudrate::Baud500K, + Baudrate::Baud250K => PcanBaudrate::Baud250K, + Baudrate::Baud125K => PcanBaudrate::Baud125K, + Baudrate::Baud100K => PcanBaudrate::Baud100K, + Baudrate::Baud95K => PcanBaudrate::Baud95K, + Baudrate::Baud83K => PcanBaudrate::Baud83, + Baudrate::Baud50K => PcanBaudrate::Baud50K, + Baudrate::Baud47K => PcanBaudrate::Baud47K, + Baudrate::Baud33K => PcanBaudrate::Baud33K, + Baudrate::Baud20K => PcanBaudrate::Baud20K, + Baudrate::Baud10K => PcanBaudrate::Baud10K, + Baudrate::Baud5K => PcanBaudrate::Baud5K, + } + } +} + +impl From for DriverOpenError { + fn from(e: PcanError) -> DriverOpenError { + DriverOpenError::PeakError(e) + } +} +impl From for DriverReadError { + fn from(e: PcanError) -> DriverReadError { + match e { + PcanError::QrcvEmpty => DriverReadError::NoFrameReady, + _ => DriverReadError::PeakError(e), + } + } +} +impl From for DriverWriteError { + fn from(e: PcanError) -> DriverWriteError { + match e { + PcanError::QxmtFull | PcanError::XmtFull | PcanError::Overrun => { + DriverWriteError::NotReady + } + _ => DriverWriteError::PeakError(e), + } + } +} + +pub struct PeakDriver { + sock: Option, + bus: UsbBus, + baud: Baudrate, +} + +impl PeakDriver { + pub fn new(bus: UsbBus, baud: Baudrate) -> Self { + Self { + sock: None, + bus, + baud, + } + } +} + +impl Driver for PeakDriver { + fn is_valid(&self) -> bool { + self.sock.is_some() + } + fn open(&mut self) -> Result<(), DriverOpenError> { + // pcan's Baudrate type doesn't implement Clone, so you can't pass it to open() behind the + // &mut self mutable reference. + match UsbCanSocket::open(self.bus, self.baud.clone().into()) { + Ok(sock) => { + tracing::info!("Opened {:?} @ {:?}", self.bus, self.baud); + self.sock = Some(sock); + Ok(()) + } + Err(e) => { + let e = e.into(); + tracing::error!("Failed to open {:?} @ {:?}: {e:?}", self.bus, self.baud); + Err(e) + } + } + } + fn close(&mut self) -> Result<(), DriverCloseError> { + self.sock = None; + Ok(()) + } + + fn read_nonblocking(&mut self, _frame: &mut Frame) -> Result<(), DriverReadError> { + let Some(sock) = self.sock.as_mut() else { + tracing::warn!("Tried to read from closed interface {:?}", self.bus); + return Err(DriverReadError::DriverClosed); + }; + + match sock.recv() { + Ok((_frame, _timestamp)) => { + tracing::trace!("Received {_frame:?} @ {_timestamp:?}"); + Ok(()) + } + Err(PcanError::QrcvEmpty) => Err(DriverReadError::NoFrameReady), + Err(e) => { + tracing::error!("Error receiving frame: {e:?}"); + Err(e.into()) + } + } + } + + fn write_nonblocking(&mut self, _frame: &Frame) -> Result<(), DriverWriteError> { + let Some(sock) = self.sock.as_mut() else { + tracing::warn!("Tried to write to closed interface {:?}", self.bus); + return Err(DriverWriteError::DriverClosed); + }; + + let pcan_frame = pcan_basic::socket::CanFrame::default(); + match sock.send(pcan_frame) { + Ok(_) => { + tracing::trace!("Wrote frame: {_frame:?}"); + Ok(()) + } + Err(e @ (PcanError::QxmtFull | PcanError::XmtFull | PcanError::Overrun)) => { + Err(e.into()) + } + Err(e) => { + tracing::error!("Error: {e:?} writing frame: {_frame:?}"); + Err(e.into()) + } + } + } +}