diff --git a/Cargo.toml b/Cargo.toml index c9efc1f..ab3c2ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,12 +9,11 @@ keywords = ["agriculture", "can", "canbus", "isobus", "j1939", "agritech", "smar [dependencies] bitvec = "1.0.1" rand = "0.8.5" -socketcan = { version = "2.0.0", optional = true } strum_macros = "0.25.2" +embedded-can = "0.4.1" [features] default = [] -socketcan = ["dep:socketcan"] [dev-dependencies] clap = { version = "4.3.19", features = ["derive"] } @@ -23,6 +22,8 @@ ctrlc = "3.4.0" tracing = "0.1.37" tracing-subscriber = "0.3.17" +[target.'cfg(unix)'.dev-dependencies] +socketcan = { version = "3.3.0" } + [[example]] name = "forward" -required-features = ["socketcan"] diff --git a/examples/forward.rs b/examples/forward.rs index a90c5fd..e8f7295 100644 --- a/examples/forward.rs +++ b/examples/forward.rs @@ -1,10 +1,10 @@ // Copyright 2023 Raven Industries inc. -use std::sync::mpsc::channel; - -use ag_iso_stack::driver::{Driver, DriverReadError, Frame, SocketcanDriver}; use clap::Parser; +#[cfg(target_os = "linux")] +use socketcan::{BlockingCan, CanSocket, Socket}; + /// Forward CAN traffic from one interface to another #[derive(Debug, Parser)] #[clap(name = "forward", verbatim_doc_comment)] @@ -16,22 +16,25 @@ struct Options { /// The interface to read traffic from /// /// Can be either a string interface name, or an integer interface index - #[clap(short, long, default_value_t = String::from("can0"))] + #[clap(short, long, default_value_t = String::from("vcan0"))] pub input_interface: String, /// The interface to write traffic to /// /// Can be either a string interface name, or an integer interface index - #[clap(short, long, default_value_t = String::from("can1"))] + #[clap(short, long, default_value_t = String::from("vcan1"))] pub output_interface: String, } -fn create_driver(iface: &str) -> impl Driver { - if let Ok(index) = iface.parse::() { - SocketcanDriver::new_by_index(index) - } else { - SocketcanDriver::new_by_name(iface) - } +#[cfg(target_os = "linux")] +fn open_can_interface(input_name: &str, output_name: &str) -> (CanSocket, CanSocket) { + let mut input = + CanSocket::open(input_name).expect("The given input interface cannot be opened!"); + + let mut output = + CanSocket::open(output_name).expect("The given output interface cannot be opened!"); + + (input, output) } fn main() { @@ -50,33 +53,26 @@ fn main() { opts.output_interface ); - let mut input = create_driver(&opts.input_interface); - let mut output = create_driver(&opts.output_interface); - - input.open().unwrap(); - output.open().unwrap(); - - let (tx, rx) = channel(); - ctrlc::set_handler(move || tx.send(true).unwrap()).unwrap(); - - loop { - if rx.try_recv().is_ok() { - break; - } - - let mut frame = Frame::default(); + #[cfg(target_os = "linux")] + |opts: Options| { + let (input, output) = open_can_interface(&opts.input_interface, &opts.output_interface); + input + .set_nonblocking(true) + .expect("Could not set input bus to non-blocking!"); + output + .set_nonblocking(true) + .expect("Could not set output bus to non-blocking!"); - match input.read_nonblocking(&mut frame) { - Ok(_) => { - tracing::info!("Read frame: {frame:?}"); - tracing::info!("Attempting to write frame"); - match output.write_nonblocking(&frame) { - Ok(_) => tracing::info!("Wrote frame: {frame:?}"), - Err(e) => tracing::info!("Failed to write frame: {e:?}"), + loop { + match input.borrow().receive() { + Ok(frame) => { + output + .borrow() + .transmit(&frame) + .expect("Could not forward received message!"); } + Err(_err) => continue, } - Err(DriverReadError::NoFrameReady) => {} - Err(e) => tracing::error!("Failed to read frame: {e:?}"), } - } + }; } diff --git a/src/driver/address.rs b/src/driver/address.rs deleted file mode 100644 index ca65d05..0000000 --- a/src/driver/address.rs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2023 Raven Industries inc. - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[repr(transparent)] -pub struct Address(pub u8); - -impl Address { - /// Address representing broadcasts for destination specific PGNs - pub const GLOBAL: Address = Address(0xFF); - /// Alias for the global address - pub const BROADCAST: Address = Address(0xFF); - /// The null address is used by ECUs without an address such as during address claiming - pub const NULL: Address = Address(0xFE); -} - -// TODO: custom Debug impl and helpers diff --git a/src/driver/can_id.rs b/src/driver/can_id.rs deleted file mode 100644 index 89575fe..0000000 --- a/src/driver/can_id.rs +++ /dev/null @@ -1,297 +0,0 @@ -// Copyright 2023 Raven Industries inc. -use crate::driver::{Address, Pgn}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum Priority { - /// You may also use [`Priority::Highest`] as an alias - Zero = 0x0, - One = 0x1, - Two = 0x2, - Three = 0x3, - Four = 0x4, - Five = 0x5, - /// You may also use [`Priority::Default`] as an alias - Six = 0x6, - /// You may also use [`Priority::Lowest`] as an alias - Seven = 0x7, -} - -#[allow(non_upper_case_globals)] -impl Priority { - pub const Highest: Priority = Priority::Zero; - pub const Default: Priority = Priority::Six; - pub const Lowest: Priority = Priority::Seven; -} - -impl From for Priority { - fn from(value: u8) -> Priority { - match value { - 0x0 => Priority::Zero, - 0x1 => Priority::One, - 0x2 => Priority::Two, - 0x3 => Priority::Three, - 0x4 => Priority::Four, - 0x5 => Priority::Five, - 0x6 => Priority::Six, - 0x7 => Priority::Seven, - _ => unreachable!( - "Internal error converting a value larger than 3 bits to a CAN ID priority" - ), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum Type { - /// 11-bit CAN ID - Standard = 0x0, - /// 29-bit CAN ID - Extended = 0x1, -} - -#[derive(Debug, Clone)] -pub struct EncodingError { - pub priority: Priority, - pub parameter_group_number: Pgn, - pub source_address: Address, - pub destination_address: Address, -} - -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] -#[repr(transparent)] -pub struct CanId(u32); - -// Linux uses the top three unused bits to indicate whether the frame is standard/extended, remote, -// or an error frame. We do the same, because it's convenient. -const CAN_EFF_FLAG: u32 = 0x80000000; -// const CAN_RTR_FLAG: u32 = 0x40000000; -// const CAN_ERR_FLAG: u32 = 0x20000000; - -const CAN_EFF_MASK: u32 = 0x1FFFFFFF; -const CAN_SFF_MASK: u32 = 0x000007FF; - -impl CanId { - pub fn new(raw: u32, type_: Type) -> Self { - let raw = match type_ { - Type::Extended => (raw & CAN_EFF_MASK) | CAN_EFF_FLAG, - Type::Standard => raw & CAN_SFF_MASK, - }; - Self(raw) - } - - /// Encodes a new extended ID using the discrete parts of an identifier - pub fn try_encode( - parameter_group_number: Pgn, - source_address: Address, - destination_address: Address, - priority: Priority, - ) -> Result { - if destination_address != Address::GLOBAL && parameter_group_number.is_broadcast() { - return Err(EncodingError { - priority, - parameter_group_number, - source_address, - destination_address, - }); - } - Ok(unsafe { - CanId::encode_unchecked( - parameter_group_number, - source_address, - destination_address, - priority, - ) - }) - } - - /// Encodes a new extended ID using the discrete parts of an identifier but won't validate - /// your combination of PGN and destination address. - /// - /// # Safety - /// Calling this without validating your PGN and destination address combination may result in your PGN field - /// getting trashed. Specifically, the risk is when you are using a broadcast PGN but supply a non-0xFF - /// destination address. - pub unsafe fn encode_unchecked( - parameter_group_number: Pgn, - source_address: Address, - destination_address: Address, - priority: Priority, - ) -> CanId { - let mut raw_id: u32 = 0; - - raw_id |= (priority as u32 & 0x07) << 26; - raw_id |= source_address.0 as u32; - - if Address::GLOBAL == destination_address { - if (parameter_group_number.raw() & 0xF000) >= 0xF000 { - raw_id |= (parameter_group_number.raw() & 0x3FFFF) << 8; - } else { - raw_id |= (destination_address.0 as u32) << 8; - raw_id |= (parameter_group_number.raw() & 0x3FF00) << 8; - } - } else if (parameter_group_number.raw() & 0xF000) < 0xF000 { - raw_id |= (destination_address.0 as u32) << 8; - raw_id |= (parameter_group_number.raw() & 0x3FF00) << 8; - } - CanId::new(raw_id & CAN_EFF_MASK, Type::Extended) - } - - /// Get the raw value of the CAN ID - #[inline] - pub fn raw(&self) -> u32 { - match self.type_() { - Type::Extended => self.0 & CAN_EFF_MASK, - Type::Standard => self.0 & CAN_SFF_MASK, - } - } - - /// Get the type of the ID (standard or extended) - #[inline] - pub fn type_(&self) -> Type { - if self.0 & CAN_EFF_FLAG != 0 { - Type::Extended - } else { - Type::Standard - } - } - - /// Get the priority of the ID - #[inline] - pub fn priority(&self) -> Priority { - match self.type_() { - Type::Standard => Priority::Highest, - Type::Extended => { - let raw = ((self.raw() & 0x1C000000) >> 26) as u8; - raw.into() - } - } - } - - /// Get the source address of the ID - #[inline] - pub fn source_address(&self) -> Address { - match self.type_() { - Type::Standard => Address::GLOBAL, - Type::Extended => Address((self.raw() & 0xFF) as u8), - } - } - - /// Get the ID's PGN - /// - /// In the case the the ID is a standard 11-bit ID, a NULL PGN will be returned. - #[inline] - pub fn pgn(&self) -> Pgn { - match self.type_() { - Type::Standard => Pgn::NULL, - Type::Extended => Pgn::from_id(self.raw()), - } - } - - /// Get the destination address for this CAN ID, if it's a destination-specific PGN - #[inline] - pub fn destination_address(&self) -> Address { - let pgn = self.pgn(); - if pgn == Pgn::NULL || pgn.is_broadcast() { - return Address::GLOBAL; - } - - let raw_pdu_s = ((self.raw() & 0xFF00) >> 8) as u8; - Address(raw_pdu_s) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_priority() { - let can_id = CanId::new(0x18EF1CF5, Type::Extended); - assert_eq!(can_id.priority(), Priority::Default); - } - - #[test] - fn test_source_address() { - let can_id = CanId::new(0x0705, Type::Standard); - assert_eq!(can_id.type_(), Type::Standard); - // TODO: Is this right? Do 11-bit IDs always have a global address? - assert_eq!(can_id.source_address(), Address::GLOBAL); - - let can_id = CanId::new(0x18EF1CF5, Type::Extended); - assert_eq!(can_id.source_address(), Address(0xF5)); - } - - #[test] - fn test_destination_address() { - let can_id = CanId::new(0x0705, Type::Standard); - assert_eq!(can_id.destination_address(), Address::GLOBAL); - - let can_id = CanId::new(0x18EEFF1C, Type::Extended); - assert_eq!(can_id.destination_address(), Address::GLOBAL); - - let can_id = CanId::new(0x09F8031C, Type::Extended); - assert_eq!(can_id.destination_address(), Address::GLOBAL); - - let can_id = CanId::new(0x0CAC1C13, Type::Extended); - assert_eq!(can_id.destination_address(), Address(0x1C)); - } - - #[test] - fn test_pgn() { - let can_id = CanId::new(0x07FF, Type::Standard); - assert_eq!(can_id.pgn(), Pgn::NULL); - - let can_id = CanId::new(0x0CAC1C13, Type::Extended); - assert_eq!(can_id.pgn(), Pgn::from_raw(0x0AC00)); - - let can_id = CanId::new(0x18FF3F13, Type::Extended); - assert_eq!(can_id.pgn(), Pgn::from_raw(0x0FF3F)); - - let can_id = CanId::new(0x18EF1CF5, Type::Extended); - assert_eq!(can_id.pgn(), Pgn::from_raw(0x0EF00)); - - let can_id = CanId::new(0x18EEFF1C, Type::Extended); - assert_eq!(can_id.pgn(), Pgn::from_raw(0x0EE00)); - } - - #[test] - fn test_encode() { - let encode_result = CanId::try_encode( - Pgn::from_raw(0x00EF00), - Address(0x81), - Address(0xF9), - Priority::Six, - ); - let can_id = encode_result.expect("EF00 Message was not encodable"); - assert_eq!(can_id.pgn(), Pgn::from_raw(0xEF00)); - assert_eq!(can_id.destination_address(), Address(0xF9)); - assert_eq!(can_id.source_address(), Address(0x81)); - assert_eq!(can_id.priority(), Priority::Six); - - let encode_result = CanId::try_encode( - Pgn::from_raw(0x00FF40), - Address(0x81), - Address(0xFF), - Priority::Six, - ); - let can_id = encode_result.expect("FF40 Message was not encodable"); - assert_eq!(can_id.pgn(), Pgn::from_raw(0xFF40)); - assert_eq!(can_id.destination_address(), Address(0xFF)); - assert_eq!(can_id.source_address(), Address(0x81)); - assert_eq!(can_id.priority(), Priority::Six); - - let encode_result = CanId::try_encode( - Pgn::from_raw(0x00FF40), - Address(0x81), - Address(0x0F), - Priority::Six, - ); - assert!(encode_result.is_err()); - - let error_contents: EncodingError = encode_result.unwrap_err(); - assert_eq!(error_contents.priority, Priority::Six); - assert_eq!(error_contents.source_address, Address(0x81)); - assert_eq!(error_contents.destination_address, Address(0x0F)); - assert_eq!(error_contents.parameter_group_number, Pgn::from_raw(0xFF40)); - } -} diff --git a/src/driver/frame.rs b/src/driver/frame.rs deleted file mode 100644 index 6a4744b..0000000 --- a/src/driver/frame.rs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2023 Raven Industries inc. -use crate::driver::CanId; - -#[derive(Debug, Default)] -#[repr(transparent)] -pub struct Channel(u8); - -#[derive(Debug, Default)] -pub struct Frame { - // TODO: Is a Duration too large (64 + 32 bits) for an object that will be created so often? - // Would it be better to use a u64 for microseconds? - // TODO: Is this just a monotonically increasing number, or is it a unix timestamp? - pub timestamp: std::time::Duration, - pub id: CanId, - pub channel: Channel, - pub data: [u8; 8], - pub data_length: u8, - pub extended: bool, -} diff --git a/src/driver/mod.rs b/src/driver/mod.rs deleted file mode 100644 index 0e39c02..0000000 --- a/src/driver/mod.rs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2023 Raven Industries inc. - -//! CAN Driver layer -//! -//! This module defines: -//! 1. An abstract `Driver` trait for different CAN drivers to implement -//! 2. `Frame`, `Pgn`, `Address`, et al types - -mod address; -mod can_id; -mod driver; -mod frame; -mod pgn; - -#[cfg(feature = "socketcan")] -mod socketcan; - -pub use address::Address; -pub use can_id::{CanId, Priority, Type}; -pub use driver::{Driver, DriverCloseError, DriverOpenError, DriverReadError, DriverWriteError}; -pub use frame::{Channel, Frame}; -pub use pgn::Pgn; - -#[cfg(feature = "socketcan")] -pub use self::socketcan::SocketcanDriver; diff --git a/src/driver/pgn.rs b/src/driver/pgn.rs deleted file mode 100644 index f79c426..0000000 --- a/src/driver/pgn.rs +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright 2023 Raven Industries inc. - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[repr(transparent)] -pub struct Pgn(u32); - -impl Pgn { - /// A fake PGN used to denote a PGN that does not exist - pub const NULL: Pgn = Pgn(0xFFFFFFFF); - - pub fn from_id(can_id: u32) -> Self { - const PDU2_FORMAT_MASK: u32 = 0x00F00000; - let raw_pgn = if (can_id & PDU2_FORMAT_MASK) < PDU2_FORMAT_MASK { - // point-to-point - (can_id >> 8) & 0x03FF00 - } else { - // broadcast - (can_id >> 8) & 0x03FFFF - }; - Pgn(raw_pgn) - } - - pub fn from_raw(pgn: u32) -> Self { - Pgn(pgn) - } - - #[inline] - pub fn is_broadcast(&self) -> bool { - !self.is_destination_specific() - } - - #[inline] - pub fn is_destination_specific(&self) -> bool { - // PDU1 / destination specific PGNs have a PDU Format 0x00 - 0xEF - // PDU2 / broadcast PGNs have a PDU Format 0xF0 - 0xFF - self.pdu_format() <= 0xEF - } - - #[inline] - pub fn is_proprietary(&self) -> bool { - self.pdu_format() == 0xEF - } - - #[inline] - pub fn raw(&self) -> u32 { - self.0 - } - - #[inline] - pub fn pdu_specific(&self) -> u8 { - (self.raw() & 0x00FF) as u8 - } - - #[inline] - pub fn pdu_format(&self) -> u8 { - ((self.raw() & 0xFF00) >> 8) as u8 - } - - #[inline] - pub fn data_page(&self) -> u8 { - ((self.raw() & 0x10000) >> 16) as u8 - } - - #[inline] - pub fn extended_data_page(&self) -> u8 { - ((self.raw() & 0x20000) >> 17) as u8 - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_from_id() { - let pgn = Pgn::from_id(0x18EF1CF5); - let expected = Pgn::from_raw(0x0EF00); - assert_eq!(pgn, expected); - - let pgn = Pgn::from_id(0x18FF3F13); - let expected = Pgn::from_raw(0x0FF3F); - assert_eq!(pgn, expected); - } - - #[test] - fn test_bitmath() { - let pgn = Pgn::from_raw(0x30000); - assert_eq!(pgn.data_page(), 0x01); - assert_eq!(pgn.extended_data_page(), 0x01); - - let pgn = Pgn::from_raw(0x0FF00); - assert_eq!(pgn.pdu_format(), 0xFF); - assert_eq!(pgn.pdu_specific(), 0x00); - - let pgn = Pgn::from_raw(0x000FF); - assert_eq!(pgn.pdu_format(), 0x00); - assert_eq!(pgn.pdu_specific(), 0xFF); - } - - #[test] - fn test_p2p() { - let pgn = Pgn::from_raw(0x0EE00); - assert_eq!(pgn.is_destination_specific(), true); - let pgn = Pgn::from_raw(0x0EF00); - assert_eq!(pgn.is_destination_specific(), true); - let pgn = Pgn::from_raw(0x0F000); - assert_eq!(pgn.is_destination_specific(), false); - let pgn = Pgn::from_raw(0x0FEFF); - assert_eq!(pgn.is_destination_specific(), false); - let pgn = Pgn::from_raw(0x0FF00); - assert_eq!(pgn.is_destination_specific(), false); - let pgn = Pgn::from_raw(0x0FFFF); - assert_eq!(pgn.is_destination_specific(), false); - let pgn = Pgn::from_raw(0x10000); - assert_eq!(pgn.is_destination_specific(), true); - let pgn = Pgn::from_raw(0x1EE00); - assert_eq!(pgn.is_destination_specific(), true); - let pgn = Pgn::from_raw(0x1EF00); - assert_eq!(pgn.is_destination_specific(), true); - let pgn = Pgn::from_raw(0x1F000); - assert_eq!(pgn.is_destination_specific(), false); - let pgn = Pgn::from_raw(0x1FEFF); - assert_eq!(pgn.is_destination_specific(), false); - let pgn = Pgn::from_raw(0x1FF00); - assert_eq!(pgn.is_destination_specific(), false); - let pgn = Pgn::from_raw(0x1FFFF); - assert_eq!(pgn.is_destination_specific(), false); - } -} diff --git a/src/driver/socketcan.rs b/src/driver/socketcan.rs deleted file mode 100644 index 09b0e35..0000000 --- a/src/driver/socketcan.rs +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2023 Raven Industries inc. -use std::time::Instant; - -use socketcan::frame::{CanDataFrame, CanFrame, Frame}; -use socketcan::{CanSocket, EmbeddedFrame, ExtendedId, Id, Socket, StandardId}; - -use crate::driver::{ - CanId, Channel, Driver, DriverCloseError, DriverOpenError, DriverReadError, DriverWriteError, - Frame as InternalFrame, Type, -}; - -impl From for DriverReadError { - fn from(e: socketcan::Error) -> DriverReadError { - match e { - socketcan::Error::Can(_) => DriverReadError::ErrorFrame(), - socketcan::Error::Io(e) => DriverReadError::IoError(e), - } - } -} - -impl From for DriverWriteError { - fn from(e: socketcan::Error) -> DriverWriteError { - match e { - socketcan::Error::Can(_) => DriverWriteError::BusError(), - socketcan::Error::Io(e) => DriverWriteError::IoError(e), - } - } -} - -impl From<&InternalFrame> for socketcan::frame::CanDataFrame { - fn from(f: &InternalFrame) -> socketcan::frame::CanDataFrame { - let id = match f.id.type_() { - Type::Standard => Id::Standard(unsafe { StandardId::new_unchecked(f.id.raw() as u16) }), - Type::Extended => Id::Extended(unsafe { ExtendedId::new_unchecked(f.id.raw()) }), - }; - CanDataFrame::new(id, &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") - } -} - -enum SocketcanIface { - Name(String), - Index(u32), -} - -/// A Linux socketcan [Driver] -/// -/// Enabled with the optional `socketcan` feature -pub struct SocketcanDriver { - iface: SocketcanIface, - sock: Option, - opened_timestamp: Instant, -} - -impl SocketcanDriver { - /// Create a socketcan driver with the given interface name. E.g., `can0`, or `vcan1` - pub fn new_by_name(if_name: &str) -> Self { - Self { - iface: SocketcanIface::Name(if_name.to_string()), - sock: None, - opened_timestamp: Instant::now(), - } - } - - /// Create a socketcan driver with the given interface index - pub fn new_by_index(if_index: u32) -> Self { - Self { - iface: SocketcanIface::Index(if_index), - sock: None, - opened_timestamp: Instant::now(), - } - } - - fn to_frame(&self, f: CanFrame) -> InternalFrame { - match f { - CanFrame::Remote(_r) => todo!("Remote frames unsupported yet"), - CanFrame::Error(_e) => todo!("Error frames unsupported yet"), - CanFrame::Data(f) => { - let timestamp = self.opened_timestamp.elapsed(); - let raw_id = f.raw_id(); - let extended = f.is_extended(); - 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 = f.dlc().min(8); - data[..data_length].copy_from_slice(f.data()); - let data_length = data_length as u8; - - InternalFrame { - timestamp, - id, - channel, - data, - data_length, - extended, - } - } - } - } -} - -impl Driver for SocketcanDriver { - fn is_valid(&self) -> bool { - self.sock.is_some() - } - fn open(&mut self) -> Result<(), DriverOpenError> { - match &self.iface { - SocketcanIface::Name(s) => self.sock = Some(CanSocket::open(s)?), - SocketcanIface::Index(i) => self.sock = Some(CanSocket::open_iface(*i)?), - } - self.opened_timestamp = Instant::now(); - - // NOTE: unwrap() is safe, because we return a DriverOpenError if we fail to create it. - self.sock.as_ref().unwrap().set_nonblocking(true)?; - Ok(()) - } - fn close(&mut self) -> Result<(), DriverCloseError> { - self.sock = 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.sock.as_mut() else { - return Err(DriverReadError::DriverClosed); - }; - let socketcan_frame = sock.read_frame()?; - *frame = self.to_frame(socketcan_frame); - Ok(()) - } - fn write_nonblocking(&mut self, frame: &InternalFrame) -> Result<(), DriverWriteError> { - let Some(sock) = self.sock.as_mut() else { - return Err(DriverWriteError::DriverClosed); - }; - let socketcan_frame: socketcan::frame::CanDataFrame = frame.into(); - sock.write_frame(&socketcan_frame)?; - Ok(()) - } -} diff --git a/src/j1939/address.rs b/src/j1939/address.rs new file mode 100644 index 0000000..2131fff --- /dev/null +++ b/src/j1939/address.rs @@ -0,0 +1,123 @@ +/* +Copyright 2023 Raven Industries inc. + +@author Jannes Brands +@date 2024-02-22 +*/ + +use crate::j1939::byte_field::ByteField; + +/// J1939 address (8-bits) used to identify control applications on the network +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(transparent)] +pub struct Address(u8); + +impl Address { + /// The number of bits in the address + pub const BIT_LENGTH: u8 = 8; + + /// Global Preferred Addresses + /// + /// only to be used by control applications that handles the given function and + /// function instance, if applicable, that is assigned to that address by SAE J1939. + /// + /// For more information see SAE J1939 4.6.1 + pub const GLOBAL_PREFERRED_ADDRESSES: (std::ops::Range, std::ops::Range) = + (0x00..0x7F, 0xF8..0xFD); + + /// Dynamic addresses + /// + /// any control application executing any system function can claim and use it. + /// The supplier of a control application can employ any strategy + /// to select the initial address within the range of 128 to 247. + /// + /// For more information see SAE J1939 4.6.2 + pub const DYNAMIC_ADDRESSES: std::ops::Range = 0x80..0xF7; + + /// Global Address + /// + /// The SAE J1939 source address 255 serves as the global destination address. + /// This global destination address is exclusively utilized as the destination + /// address in a D_PDU1 data frame to signify that the SAE J1939 data frame is + /// intended for all Control Applications (CAs) on the network. + /// + /// For more information see SAE J1939 4.6.3 + pub const GLOBAL: Address = Self::BROADCAST; + /// Alias for the [Address::GLOBAL] + pub const BROADCAST: Address = Address(0xFF); + + /// Null Address + /// + /// The SAE J1939 source address 254 is designated as the Null address. + /// This Null address is specifically employed as the source (transmitter) address + /// within a D_PDU1 or D_PDU2 data frame. + /// + /// There are only two approved applications for the Null address: + /// + /// 1. The Null address can be utilized with an Address Claimed Parameter Group (PG) + /// when a Control Application (CA) reports its inability to claim an SAE J1939 Address. + /// + /// 2. the Null address can be employed with a Request PG soliciting + /// the Address Claimed PG when the Request PG is transmitted by a CA + /// prior to claiming a source address. + /// + /// For more information see SAE J1939 4.6.4 + pub const NULL: Address = Address(0xFE); + + /// Create a new address + pub fn new(raw_address: u8) -> Self { + Self(raw_address) + } + + /// Returns if the address is the [Address::GLOBAL] (same as [Address::is_broadcast]) + #[inline] + pub fn is_global(&self) -> bool { + self.is_broadcast() + } + + /// Returns if the address is the [Address::BROADCAST] (same as [Address::is_global]) + #[inline] + pub fn is_broadcast(self) -> bool { + self == Self::BROADCAST + } + + /// Returns if the address is the [Address::NULL] + #[inline] + pub fn is_null(self) -> bool { + self == Self::NULL + } +} + +impl Default for Address { + fn default() -> Self { + Self::NULL + } +} + +impl ByteField for Address { + fn raw(self) -> u8 { + self.0 + } +} + +#[cfg(test)] +mod tests { + use crate::j1939::Address; + + #[test] + fn test_address() { + let address = Address::new(0b1010_1010); + assert!(!address.is_global()); + assert!(!address.is_broadcast()); + assert!(!address.is_null()); + + let address = Address::BROADCAST; + assert!(address.is_global()); + + let address = Address::NULL; + assert!(address.is_null()); + + let address = Address::default(); + assert!(address.is_null()); + } +} diff --git a/src/j1939/byte_field.rs b/src/j1939/byte_field.rs new file mode 100644 index 0000000..5caac1e --- /dev/null +++ b/src/j1939/byte_field.rs @@ -0,0 +1,61 @@ +/* +Copyright 2023 Raven Industries inc. + +@author Jannes Brands +@date 2024-02-22 +*/ + +use bitvec::order::Msb0; +use bitvec::view::BitView; + +/// A byte field +pub trait ByteField: Sized { + /// Get the raw value of the field + fn raw(self) -> u8; + + /// Get the raw bits of the field + fn raw_bits(self) -> [bool; 8] { + let raw = self.raw(); + let field_bits = raw.view_bits::(); + [ + field_bits[0], + field_bits[1], + field_bits[2], + field_bits[3], + field_bits[4], + field_bits[5], + field_bits[6], + field_bits[7], + ] + } +} + +#[cfg(test)] +mod tests { + use crate::j1939::byte_field::ByteField; + use crate::j1939::PduFormat; + + #[test] + fn test_byte_field() { + let byte_field = PduFormat::new(0b1010_1010); + assert_eq!(byte_field.raw(), 0b1010_1010); + assert_eq!( + byte_field.raw_bits(), + [true, false, true, false, true, false, true, false] + ); + + let byte_field = PduFormat::new(0xFF); + assert_eq!(byte_field.raw(), 0xFF); + assert_eq!( + byte_field.raw_bits(), + [true, true, true, true, true, true, true, true] + ); + + let byte_field = PduFormat::new(0x00); + assert_eq!(byte_field.raw(), 0x00); + assert_eq!( + byte_field.raw_bits(), + [false, false, false, false, false, false, false, false] + ); + } +} diff --git a/src/driver/driver.rs b/src/j1939/driver.rs similarity index 80% rename from src/driver/driver.rs rename to src/j1939/driver.rs index fa7d0dc..75d1b37 100644 --- a/src/driver/driver.rs +++ b/src/j1939/driver.rs @@ -1,16 +1,16 @@ // Copyright 2023 Raven Industries inc. -use crate::driver::Frame; +use crate::j1939::Frame; #[derive(Debug)] #[non_exhaustive] pub enum DriverOpenError { - /// The driver failed to open with filesystem semantics + /// The j1939 failed to open with filesystem semantics IoError(std::io::Error), } impl std::fmt::Display for DriverOpenError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Failed to open driver: {:?}", self) + write!(f, "Failed to open j1939: {:?}", self) } } impl std::error::Error for DriverOpenError { @@ -43,11 +43,11 @@ impl std::error::Error for DriverCloseError {} pub enum DriverReadError { /// There is no frame ready to be read NoFrameReady, - /// The driver has been closed + /// The j1939 has been closed DriverClosed, - /// The driver received an error frame + /// The j1939 received an error frame ErrorFrame(), - /// The driver failed to read with filesystem semantics + /// The j1939 failed to read with filesystem semantics IoError(std::io::Error), } @@ -71,9 +71,9 @@ impl From for DriverReadError { #[derive(Debug)] #[non_exhaustive] pub enum DriverWriteError { - /// The driver's internal buffer is full, or the driver is otherwise busy + /// The j1939's internal buffer is full, or the j1939 is otherwise busy NotReady, - /// The driver has been closed + /// The j1939 has been closed DriverClosed, /// Some fault with the CAN bus BusError(), @@ -102,23 +102,23 @@ impl From for DriverWriteError { /// /// This layer is meant to abstract the hardware, and should not do its own queuing/buffering. /// -/// This trait does _not_ define how to construct and configure a driver, as the details are likely -/// to differ from driver to driver. +/// This trait does _not_ define how to construct and configure a j1939, as the details are likely +/// to differ from j1939 to j1939. pub trait Driver { - /// Determine whether the driver is connected and healthy + /// Determine whether the j1939 is connected and healthy fn is_valid(&self) -> bool; - /// Open the driver + /// Open the j1939 /// - /// It is expected you must open the driver after creating it + /// It is expected you must open the j1939 after creating it fn open(&mut self) -> Result<(), DriverOpenError>; - /// Close the driver + /// Close the j1939 /// - /// It is not necessary to close the driver before dropping it + /// It is not necessary to close the j1939 before dropping it fn close(&mut self) -> Result<(), DriverCloseError>; - /// Read a [Frame] from the driver, if possible + /// Read a [Frame] from the j1939, if possible /// /// This is a non-blocking read. If there is no frame ready to read, this function will return /// [DriverReadError::NoFrameReady]. @@ -127,9 +127,9 @@ pub trait Driver { /// each call, or to re-use memory. fn read_nonblocking(&mut self, frame: &mut Frame) -> Result<(), DriverReadError>; - /// Write a [Frame] to the driver, if possible + /// Write a [Frame] to the j1939, if possible /// - /// This is a non-blocking write. If the frame cannot be written because the driver's + /// This is a non-blocking write. If the frame cannot be written because the j1939's /// queue/buffer is full (for drivers like `socketcan` that do internal buffering), or if /// it's otherwise busy, this function will return [DriverWriteError::NotReady]. /// diff --git a/src/j1939/extended_id.rs b/src/j1939/extended_id.rs new file mode 100644 index 0000000..ab57518 --- /dev/null +++ b/src/j1939/extended_id.rs @@ -0,0 +1,253 @@ +/* +Copyright 2023 Raven Industries inc. + +@author Jannes Brands +@date 2024-02-22 +*/ +use crate::j1939::byte_field::ByteField; +use crate::j1939::id::{Id, ParseIdError}; +use crate::j1939::priority::Priority; +use crate::j1939::standard_id::StandardId; +use crate::j1939::{Address, Pgn}; +use bitvec::field::BitField; +use bitvec::order::Msb0; +use bitvec::vec::BitVec; +use bitvec::view::BitView; +use embedded_can::{ExtendedId as EmbeddedExtendedId, Id as EmbeddedId}; + +/// Extended 29-bit J1939 identifier +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub struct ExtendedId { + standard_id: StandardId, + pgn: Pgn, +} + +impl ExtendedId { + /// The number of bits in the extended identifier + pub const BIT_LENGTH: u8 = 29; + const PRIORITY_START: usize = 0; + const PRIORITY_END: usize = 3; + const PGN_START: usize = 3; + const PGN_END: usize = 21; + const SOURCE_ADDRESS_START: usize = 21; + const SOURCE_ADDRESS_END: usize = 29; + + pub fn new(standard_id: StandardId, pgn: Pgn) -> Self { + Self { standard_id, pgn } + } + + /// Raw value of the extended identifier + pub fn raw(&self) -> u32 { + let mut raw_id: BitVec = BitVec::new(); + raw_id.extend(self.standard_id.priority().raw_bits()); + raw_id.extend(self.pgn.raw_bits()); + raw_id.extend(self.standard_id.source_address().raw_bits()); + raw_id.reverse(); + raw_id.load::() + } + + /// Raw bits of the extended identifier + pub fn raw_bits(&self) -> [bool; 29] { + let mut raw_id: BitVec = BitVec::new(); + raw_id.extend(self.standard_id.priority().raw_bits()); + raw_id.extend(self.pgn.raw_bits()); + raw_id.extend(self.standard_id.source_address().raw_bits()); + [ + raw_id[0], raw_id[1], raw_id[2], raw_id[3], raw_id[4], raw_id[5], raw_id[6], raw_id[7], + raw_id[8], raw_id[9], raw_id[10], raw_id[11], raw_id[12], raw_id[13], raw_id[14], + raw_id[15], raw_id[16], raw_id[17], raw_id[18], raw_id[19], raw_id[20], raw_id[21], + raw_id[22], raw_id[23], raw_id[24], raw_id[25], raw_id[26], raw_id[27], raw_id[28], + ] + } + + /// PGN of the identifier + #[inline] + pub fn pgn(&self) -> Pgn { + self.pgn + } +} + +impl From for EmbeddedId { + fn from(id: ExtendedId) -> Self { + EmbeddedId::Extended(EmbeddedExtendedId::new(id.raw()).unwrap()) + } +} + +impl TryFrom<[bool; 29]> for ExtendedId { + type Error = ParseIdError; + + fn try_from(raw_bits: [bool; 29]) -> Result { + let mut bit_data: BitVec = BitVec::new(); + bit_data.extend(raw_bits.iter()); + let mut priority_bits = + bit_data[ExtendedId::PRIORITY_START..ExtendedId::PRIORITY_END].to_bitvec(); + let mut pgn_bits = bit_data[ExtendedId::PGN_START..ExtendedId::PGN_END].to_bitvec(); + let mut source_address_bits = + bit_data[ExtendedId::SOURCE_ADDRESS_START..ExtendedId::SOURCE_ADDRESS_END].to_bitvec(); + + priority_bits.reverse(); + pgn_bits.reverse(); + source_address_bits.reverse(); + + let priority = Priority::try_from(priority_bits.load::()); + let pgn = Pgn::try_from(pgn_bits.load::()); + let source_address = Address::new(source_address_bits.load::()); + + if priority.is_err() { + return Err(ParseIdError::Priority); + } + + if pgn.is_err() { + return Err(ParseIdError::Pgn); + } + + Ok(ExtendedId::new( + StandardId::new(priority.unwrap(), source_address), + pgn.unwrap(), + )) + } +} + +impl TryFrom for ExtendedId { + type Error = ParseIdError; + + fn try_from(raw_id: u32) -> Result { + let raw_id_bits = raw_id.view_bits::().to_bitvec(); + Self::try_from([ + raw_id_bits[3], + raw_id_bits[4], + raw_id_bits[5], + raw_id_bits[6], + raw_id_bits[7], + raw_id_bits[8], + raw_id_bits[9], + raw_id_bits[10], + raw_id_bits[11], + raw_id_bits[12], + raw_id_bits[13], + raw_id_bits[14], + raw_id_bits[15], + raw_id_bits[16], + raw_id_bits[17], + raw_id_bits[18], + raw_id_bits[19], + raw_id_bits[20], + raw_id_bits[21], + raw_id_bits[22], + raw_id_bits[23], + raw_id_bits[24], + raw_id_bits[25], + raw_id_bits[26], + raw_id_bits[27], + raw_id_bits[28], + raw_id_bits[29], + raw_id_bits[30], + raw_id_bits[31], + ]) + } +} + +impl From for Id { + fn from(id: ExtendedId) -> Self { + Id::Extended(id) + } +} + +#[cfg(test)] +mod tests { + use crate::j1939::{Address, ExtendedId, PduFormat, PduSpecific, Pgn, Priority, StandardId}; + + #[test] + fn test_raw() { + let id = ExtendedId::new( + StandardId::new(Priority::Zero, Address::new(0x25)), + Pgn::new(false, true, PduFormat::new(0x8A), PduSpecific::new(0x0F)), + ); + assert_eq!(id.raw(), 0x18A0F25); + + let id = ExtendedId::new( + StandardId::new(Priority::Seven, Address::new(0xAF)), + Pgn::new(false, true, PduFormat::new(0x8A), PduSpecific::new(0x2F)), + ); + assert_eq!(id.raw(), 0x1D8A2FAF); + + let id = ExtendedId::new( + StandardId::new(Priority::Zero, Address::new(0x00)), + Pgn::new(true, false, PduFormat::new(0x4C), PduSpecific::new(0x12)), + ); + assert_eq!(id.raw(), 0x24C1200); + } + + #[test] + fn test_raw_bits() { + let id = ExtendedId::new( + StandardId::new(Priority::Zero, Address::new(0x25)), + Pgn::new(false, true, PduFormat::new(0x8A), PduSpecific::new(0x0F)), + ); + assert_eq!( + id.raw_bits(), + [ + false, false, false, false, true, true, false, false, false, true, false, true, + false, false, false, false, false, true, true, true, true, false, false, true, + false, false, true, false, true + ] + ); + + let id = ExtendedId::new( + StandardId::new(Priority::Seven, Address::new(0xAF)), + Pgn::new(false, true, PduFormat::new(0x8A), PduSpecific::new(0x2F)), + ); + + assert_eq!( + id.raw_bits(), + [ + true, true, true, false, true, true, false, false, false, true, false, true, false, + false, false, true, false, true, true, true, true, true, false, true, false, true, + true, true, true + ] + ); + } + + #[test] + fn test_from_extended_id_for_embedded_id() { + let id = ExtendedId::new( + StandardId::new(Priority::Zero, Address::new(0x25)), + Pgn::new(false, true, PduFormat::new(0x8A), PduSpecific::new(0x0F)), + ); + let embedded_id: embedded_can::Id = id.into(); + assert_eq!( + embedded_id, + embedded_can::Id::Extended(embedded_can::ExtendedId::new(0x18A0F25).unwrap()) + ); + } + + #[test] + fn test_try_from_u32_for_extended_id() { + let id = ExtendedId::try_from(0x18A0F25).unwrap(); + assert_eq!( + id, + ExtendedId::new( + StandardId::new(Priority::Zero, Address::new(0x25)), + Pgn::new(false, true, PduFormat::new(0x8A), PduSpecific::new(0x0F)), + ) + ); + + let id = ExtendedId::try_from(0x1D8A2FAF).unwrap(); + assert_eq!( + id, + ExtendedId::new( + StandardId::new(Priority::Seven, Address::new(0xAF)), + Pgn::new(false, true, PduFormat::new(0x8A), PduSpecific::new(0x2F)), + ) + ); + + let id = ExtendedId::try_from(0x24C1200).unwrap(); + assert_eq!( + id, + ExtendedId::new( + StandardId::new(Priority::Zero, Address::new(0x00)), + Pgn::new(true, false, PduFormat::new(0x4C), PduSpecific::new(0x12)), + ) + ); + } +} diff --git a/src/j1939/frame.rs b/src/j1939/frame.rs new file mode 100644 index 0000000..d7b6658 --- /dev/null +++ b/src/j1939/frame.rs @@ -0,0 +1,98 @@ +/* +Copyright 2023 Raven Industries inc. + +@author Jannes Brands +@date 2024-02-22 +*/ + +use crate::j1939::id::Id; +use embedded_can::{Frame as EmbeddedFrame, Id as EmbeddedId}; + +#[derive(Debug, Default)] +#[repr(transparent)] +pub struct Channel(u8); + +/// J1939 frame +#[derive(Debug, Clone, Default)] +pub struct Frame { + id: Id, + data: Vec, +} + +impl Frame { + /// Creates a new J1939 data frame + pub fn new(id: impl Into, data: Vec) -> Option { + Some(Self { + id: Id::try_from(id.into()).expect("Invalid J1939 ID"), + data, + }) + } + + /// Identifier of the frame + #[inline] + pub fn id(&self) -> Id { + self.id + } + + /// Data of the frame + #[inline] + pub fn data(self) -> Vec { + self.data + } +} + +impl EmbeddedFrame for Frame { + /// Creates a new J1939 data frame from a standard CAN data frame + fn new(id: impl Into, data: &[u8]) -> Option { + Frame::new(id, data.to_vec()) + } + + /// Creates a new remote frame (only to satisfy the trait) + ///
+ /// This will always return `None` as J1939 does not support remote frames + ///
+ fn new_remote(_id: impl Into, _dlc: usize) -> Option { + None + } + + /// Returns `true` if the frame is an extended frame + fn is_extended(&self) -> bool { + match self.id { + Id::Standard(_) => false, + Id::Extended(_) => true, + } + } + + /// Returns `true` if the frame is a standard frame + fn is_standard(&self) -> bool { + match self.id { + Id::Standard(_) => true, + Id::Extended(_) => false, + } + } + + /// returns always `false` as J1939 does not support remote frames + fn is_remote_frame(&self) -> bool { + false + } + + /// returns always `true` as J1939 only supports data frames + fn is_data_frame(&self) -> bool { + true + } + + /// Identifier of the frame + fn id(&self) -> EmbeddedId { + self.id.into() + } + + /// Data length code of the frame + fn dlc(&self) -> usize { + self.data.len() + } + + /// Data of the frame + fn data(&self) -> &[u8] { + self.data.as_slice() + } +} diff --git a/src/j1939/id.rs b/src/j1939/id.rs new file mode 100644 index 0000000..c69bd51 --- /dev/null +++ b/src/j1939/id.rs @@ -0,0 +1,84 @@ +/* +Copyright 2023 Raven Industries inc. + +@author Jannes Brands +@date 2024-02-22 +*/ +use crate::j1939::standard_id::StandardId; +use crate::j1939::{Address, ExtendedId, Pgn, Priority}; +use bitvec::field::BitField; +use bitvec::order::Msb0; +use bitvec::view::BitView; +use embedded_can::Id as EmbeddedId; + +#[derive(Debug)] +pub enum ParseIdError { + Priority, + Pgn, + SourceAddress, + StandardId, + ExtendedId, +} + +/// Identifier for a J1939 message (standard or extended) +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Id { + Standard(StandardId), + Extended(ExtendedId), +} + +impl Default for Id { + fn default() -> Self { + Id::Extended(ExtendedId::default()) + } +} + +impl From for EmbeddedId { + fn from(id: Id) -> Self { + match id { + Id::Standard(id) => id.into(), + Id::Extended(id) => id.into(), + } + } +} + +impl TryFrom for Id { + type Error = ParseIdError; + + fn try_from(value: EmbeddedId) -> Result { + match value { + EmbeddedId::Standard(id) => { + let bit_data = id.as_raw().view_bits::().to_bitvec(); + let priority_bits = + bit_data[StandardId::PRIORITY_START..StandardId::PRIORITY_END].to_bitvec(); + let source_address_bits = bit_data + [StandardId::SOURCE_ADDRESS_START..StandardId::SOURCE_ADDRESS_END] + .to_bitvec(); + let priority = + Priority::from([priority_bits[0], priority_bits[1], priority_bits[2]]); + let source_address = Address::new(source_address_bits.load()); + + Ok(Id::Standard(StandardId::new(priority, source_address))) + } + EmbeddedId::Extended(id) => { + let bit_data = id.as_raw().view_bits::().to_bitvec(); + let priority = Priority::try_from(bit_data.load::()); + let pgn = Pgn::try_from(bit_data.load::()); + let source_address = Address::new(bit_data.load::()); + + if priority.is_err() { + return Err(ParseIdError::Priority); + } + + if pgn.is_err() { + return Err(ParseIdError::Pgn); + } + + Ok(Id::Extended(ExtendedId::new( + StandardId::new(priority.unwrap(), source_address), + pgn.unwrap(), + ))) + } + } + } +} diff --git a/src/j1939/mod.rs b/src/j1939/mod.rs new file mode 100644 index 0000000..60b8f67 --- /dev/null +++ b/src/j1939/mod.rs @@ -0,0 +1,35 @@ +// Copyright 2023 Raven Industries inc. +//! J1939 layer +//! +//! This module defines: +//! - The J1939 protocol data unit (PDU) format +//! - The J1939 protocol data unit (PDU) specific +//! - The J1939 parameter group number (PGN) +//! - The J1939 priority +//! - The J1939 standard and extended identifier + +mod address; +mod byte_field; +mod driver; +mod extended_id; +mod frame; +mod id; +mod page; +mod pdu_format; +mod pdu_specific; +mod pgn; +mod priority; +mod standard_id; + +pub use address::Address; +pub use byte_field::ByteField; +pub use driver::{Driver, DriverCloseError, DriverOpenError, DriverReadError, DriverWriteError}; +pub use extended_id::ExtendedId; +pub use frame::{Channel, Frame}; +pub use id::{Id, ParseIdError}; +pub use page::{Page, ParsePageError}; +pub use pdu_format::PduFormat; +pub use pdu_specific::PduSpecific; +pub use pgn::Pgn; +pub use priority::Priority; +pub use standard_id::StandardId; diff --git a/src/j1939/page.rs b/src/j1939/page.rs new file mode 100644 index 0000000..4731adf --- /dev/null +++ b/src/j1939/page.rs @@ -0,0 +1,91 @@ +/* +Copyright 2023 Raven Industries inc. + +@author Jannes Brands +@date 2024-02-22 +*/ +use bitvec::field::BitField; +use bitvec::order::Msb0; +use bitvec::prelude::BitVec; + +#[derive(Debug, PartialEq)] +pub enum ParsePageError { + InvalidPage(u8), +} + +/// Page definition (EDP & DP combination) +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(u8)] +pub enum Page { + /// J1939 page 0 + J1939Page0 = 0, + /// J1939 page 1 + J1939Page1 = 1, + /// J1939 page reserved + J1939PageReserved = 2, + /// ISO 11992-4 defined + ///
+ /// In this case the rest of the identifier is not defined by J1939! + /// Please refer to the ISO 11992-4 standard for more information. + ///
+ ISO11992_4Defined = 3, +} + +impl Page { + pub const BIT_LENGTH: usize = 2; + + pub fn try_from_raw(raw_page: u8) -> Result { + match raw_page { + 0x0 => Ok(Page::J1939Page0), + 0x1 => Ok(Page::J1939Page1), + 0x2 => Ok(Page::J1939PageReserved), + 0x3 => Ok(Page::ISO11992_4Defined), + _ => Err(ParsePageError::InvalidPage(raw_page)), + } + } + + pub fn from_raw_bits(raw_page: [bool; 2]) -> Self { + let mut page_vec: BitVec = BitVec::new(); + page_vec.extend(raw_page.iter()); + Page::try_from_raw(page_vec.load::()).unwrap() + } +} + +impl TryFrom for Page { + type Error = ParsePageError; + + fn try_from(raw_page: u8) -> Result { + Page::try_from_raw(raw_page) + } +} + +impl From<[bool; 2]> for Page { + fn from(value: [bool; 2]) -> Self { + Page::from_raw_bits(value) + } +} + +#[cfg(test)] +mod tests { + use crate::j1939::{Page, ParsePageError}; + + #[test] + fn test_try_from_u8_for_page() { + assert_eq!(Page::try_from_raw(0x0).unwrap(), Page::J1939Page0); + assert_eq!(Page::try_from_raw(0x1).unwrap(), Page::J1939Page1); + assert_eq!(Page::try_from_raw(0x2).unwrap(), Page::J1939PageReserved); + assert_eq!(Page::try_from_raw(0x3).unwrap(), Page::ISO11992_4Defined); + assert_eq!( + Page::try_from_raw(0x4).unwrap_err(), + ParsePageError::InvalidPage(4) + ); + } + + #[test] + fn test_from_bool_array_for_page() { + assert_eq!(Page::from_raw_bits([false, false]), Page::J1939Page0); + assert_eq!(Page::from_raw_bits([false, true]), Page::J1939Page1); + assert_eq!(Page::from_raw_bits([true, false]), Page::J1939PageReserved); + assert_eq!(Page::from_raw_bits([true, true]), Page::ISO11992_4Defined); + } +} diff --git a/src/j1939/pdu_format.rs b/src/j1939/pdu_format.rs new file mode 100644 index 0000000..134c7dc --- /dev/null +++ b/src/j1939/pdu_format.rs @@ -0,0 +1,62 @@ +/* +Copyright 2023 Raven Industries inc. + +@author Jannes Brands +@date 2024-02-22 +*/ +use crate::j1939::byte_field::ByteField; + +/// PDU format field defined in the PGN +#[derive(Debug, Default, PartialEq, Clone, Copy, Eq, Hash)] +#[repr(transparent)] +pub struct PduFormat(u8); + +impl PduFormat { + pub const PDU_1_START: u8 = 0x00; + pub const PDU_1_END: u8 = 0xEF; + pub const PDU_2_START: u8 = 0xF0; + pub const PDU_2_END: u8 = 0xFF; + + pub fn new(raw_pdu_format: u8) -> Self { + Self(raw_pdu_format) + } + + #[inline] + pub fn is_destination_specific(&self) -> bool { + self.0 <= Self::PDU_1_END + } + + #[inline] + pub fn is_group_extension(&self) -> bool { + self.0 >= Self::PDU_2_START + } +} + +impl ByteField for PduFormat { + fn raw(self) -> u8 { + self.0 + } +} + +#[cfg(test)] +mod tests { + use crate::j1939::pdu_format::PduFormat; + + #[test] + fn test_pdu_format() { + let pdu_format = PduFormat::new(0x00); + assert_eq!(pdu_format.is_destination_specific(), true); + assert_eq!(pdu_format.is_group_extension(), false); + + let pdu_format = PduFormat::new(0xEF); + assert_eq!(pdu_format.is_destination_specific(), true); + assert_eq!(pdu_format.is_group_extension(), false); + + let pdu_format = PduFormat::new(0xF0); + assert_eq!(pdu_format.is_destination_specific(), false); + assert_eq!(pdu_format.is_group_extension(), true); + + let pdu_format = PduFormat::new(0xFF); + assert_eq!(pdu_format.is_destination_specific(), false); + } +} diff --git a/src/j1939/pdu_specific.rs b/src/j1939/pdu_specific.rs new file mode 100644 index 0000000..c0e771b --- /dev/null +++ b/src/j1939/pdu_specific.rs @@ -0,0 +1,24 @@ +/* +Copyright 2023 Raven Industries inc. + +@author Jannes Brands +@date 2024-02-22 +*/ +use crate::j1939::byte_field::ByteField; + +/// PDU specific field defined in the PGN +#[derive(Debug, Default, PartialEq, Clone, Copy, Eq, Hash)] +#[repr(transparent)] +pub struct PduSpecific(u8); + +impl PduSpecific { + pub fn new(raw_pdu_specific: u8) -> Self { + Self(raw_pdu_specific) + } +} + +impl ByteField for PduSpecific { + fn raw(self) -> u8 { + self.0 + } +} diff --git a/src/j1939/pgn.rs b/src/j1939/pgn.rs new file mode 100644 index 0000000..b996d49 --- /dev/null +++ b/src/j1939/pgn.rs @@ -0,0 +1,388 @@ +/* +Copyright 2023 Raven Industries inc. + +@author Jannes Brands +@date 2024-02-22 +*/ + +use crate::j1939::byte_field::ByteField; +use crate::j1939::page::Page; +use crate::j1939::pdu_format::PduFormat; +use crate::j1939::pdu_specific::PduSpecific; +use crate::j1939::Address; +use bitvec::field::BitField; +use bitvec::order::Lsb0; +use bitvec::vec::BitVec; +use bitvec::view::BitView; + +#[derive(Debug)] +pub enum ParsePgnError { + InvalidPgnLength(u32), +} + +impl std::fmt::Display for ParsePgnError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ParsePgnError::InvalidPgnLength(value) => write!( + f, + "Parse '{:?}' failed because the permitted PGN value is between 0 and 0x3FFFF!", + value + ), + } + } +} + +/// Parameter Group Number (PGN) +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Pgn { + extended_data_page: bool, + data_page: bool, + pdu_format: PduFormat, + pdu_specific: PduSpecific, +} + +impl Pgn { + /// The number of bits used to represent the PGN + pub const BIT_LENGTH: usize = 18; + /// The maximum value of the PGN + pub const MAX_VALUE: u32 = 0x3FFFF; + const EDP_START: u8 = 0; + const DP_START: u8 = 1; + const PDU_FORMAT_START: usize = 2; + const PDU_FORMAT_END: usize = 10; + const PDU_SPECIFIC_START: usize = 10; + pub const PDU_SPECIFIC_END: usize = 18; + /// PDU 1 proprietary A parameter group + /// format: destination specific (not global) + /// data length: 0 - 1785 bytes (multi-packet support) + /// default priority: [`Priority::DEFAULT`](`crate::j1939::priority::Priority::DEFAULT) + pub const PDU_1_FORMAT_PROPRIETARY_A: u32 = 0xEF00; + /// PDU 1 proprietary A2 parameter group + /// format: destination specific (not global) + /// data length: 0 - 1785 bytes (multi-packet support) + /// default priority: [`DEFAULT`](`crate::j1939::priority::Priority::DEFAULT) + pub const PDU_1_FORMAT_PROPRIETARY_A2: u32 = 0x1EF00; + /// PDU 2 proprietary B parameter group + /// format: group extension + /// data length: 0 - 1785 bytes (multi-packet support) + /// default priority: [`Priority::DEFAULT`](`crate::j1939::priority::Priority::DEFAULT) + pub const PDU_2_FORMAT_PROPRIETARY_B: (std::ops::Range, std::ops::Range) = + (0x00FF00..0x00FFFF, 0x01FF00..0x01FFFF); + + /// Create a new [Pgn] instance + pub fn new( + extended_data_page: bool, + data_page: bool, + pdu_format: PduFormat, + pdu_specific: PduSpecific, + ) -> Self { + Self { + extended_data_page, + data_page, + pdu_format, + pdu_specific, + } + } + + /// Get the raw bits of the [Pgn] + pub fn raw_bits(self) -> [bool; 18] { + let mut raw_pgn: BitVec = BitVec::new(); + raw_pgn.push(self.extended_data_page); + raw_pgn.push(self.data_page); + raw_pgn.extend(self.pdu_format.raw_bits()); + raw_pgn.extend(self.pdu_specific.raw_bits()); + [ + raw_pgn[0], + raw_pgn[1], + raw_pgn[2], + raw_pgn[3], + raw_pgn[4], + raw_pgn[5], + raw_pgn[6], + raw_pgn[7], + raw_pgn[8], + raw_pgn[9], + raw_pgn[10], + raw_pgn[11], + raw_pgn[12], + raw_pgn[13], + raw_pgn[14], + raw_pgn[15], + raw_pgn[16], + raw_pgn[17], + ] + } + + /// Get the raw value of the [Pgn] as a 32-bit integer + pub fn raw(self) -> u32 { + let mut raw_pgn: BitVec = BitVec::new(); + raw_pgn.extend(self.raw_bits()); + raw_pgn.reverse(); + raw_pgn.load() + } + + /// Get the destination address of the [Pgn] if it is destination specific + pub fn get_destination_address(&self) -> Option
{ + if self.pdu_format().is_destination_specific() { + Some(Address::new(self.pdu_specific.raw())) + } else { + None + } + } + + /// Set the destination [Address] of the [Pgn] if it is destination specific + pub fn set_destination_address(&mut self, address: Address) { + if self.pdu_format.is_destination_specific() { + self.pdu_specific = PduSpecific::new(address.raw()); + } + } + + /// Get the group extension of the [Pgn] if it is a group extension + pub fn get_group_extension(&self) -> Option { + if self.pdu_format().is_group_extension() { + Some(self.pdu_specific.raw()) + } else { + None + } + } + + /// Set the group extension of the [Pgn] if it is a group extension + pub fn set_group_extension(&mut self, group_extension: u8) { + if self.pdu_format.is_group_extension() { + self.pdu_specific = PduSpecific::new(group_extension); + } + } + + /// Returns if the [Pgn] is proprietary + pub fn is_proprietary(&self) -> bool { + self.raw() == Self::PDU_1_FORMAT_PROPRIETARY_A + || Self::PDU_2_FORMAT_PROPRIETARY_B.0.contains(&self.raw()) + || Self::PDU_2_FORMAT_PROPRIETARY_B.1.contains(&self.raw()) + || self.raw() == Self::PDU_1_FORMAT_PROPRIETARY_A2 + } + + /// Get the extended data page of the [Pgn] + #[inline] + pub fn extended_data_page(&self) -> bool { + self.extended_data_page + } + + /// Get the data page of the [Pgn] + #[inline] + pub fn data_page(&self) -> bool { + self.data_page + } + + /// Get the [Page] of the [Pgn] resulting from the extended data page and data page + #[inline] + pub fn page(&self) -> Page { + Page::from([self.extended_data_page, self.data_page]) + } + + /// Get the [PduFormat] of the [Pgn] + #[inline] + pub fn pdu_format(&self) -> PduFormat { + self.pdu_format + } + + /// Get the [PduSpecific] of the [Pgn] + #[inline] + pub fn pdu_specific(&self) -> PduSpecific { + self.pdu_specific + } +} + +impl TryFrom for Pgn { + type Error = ParsePgnError; + + fn try_from(raw_pgn: u32) -> Result { + if raw_pgn > Self::MAX_VALUE { + // raw value is too large to fit in PGN with 18 bits + return Err(ParsePgnError::InvalidPgnLength(raw_pgn)); + } + + let raw_pgn_be_bytes = raw_pgn.to_le_bytes(); + let mut bit_data = raw_pgn_be_bytes.view_bits::().to_bitvec(); + bit_data.truncate(Pgn::BIT_LENGTH); + bit_data.reverse(); + + let mut pdu_format = bit_data[Self::PDU_FORMAT_START..Self::PDU_FORMAT_END].to_bitvec(); + pdu_format.reverse(); + + let mut pdu_specific = + bit_data[Self::PDU_SPECIFIC_START..Self::PDU_SPECIFIC_END].to_bitvec(); + pdu_specific.reverse(); + + let pgn = Self { + extended_data_page: bit_data[Self::EDP_START as usize], + data_page: bit_data[Self::DP_START as usize], + pdu_format: PduFormat::new(pdu_format.load()), + pdu_specific: PduSpecific::new(pdu_specific.load()), + }; + + Ok(pgn) + } +} + +#[cfg(test)] +mod tests { + use crate::j1939::pdu_format::PduFormat; + use crate::j1939::pdu_specific::PduSpecific; + use crate::j1939::{Address, Page, Pgn}; + + #[test] + fn test_raw_bits() { + let pgn = Pgn::new(true, false, PduFormat::new(0xE6), PduSpecific::new(0xBA)); + let raw_bits = pgn.raw_bits(); + assert_eq!( + raw_bits, + [ + true, false, true, true, true, false, false, true, true, false, true, false, true, + true, true, false, true, false + ] + ); + + let pgn = Pgn::new(false, true, PduFormat::new(0x00), PduSpecific::new(0xFF)); + let raw_bits = pgn.raw_bits(); + assert_eq!( + raw_bits, + [ + false, true, false, false, false, false, false, false, false, false, true, true, + true, true, true, true, true, true + ] + ); + + let pgn = Pgn::new(false, true, PduFormat::new(0x12), PduSpecific::new(0x34)); + let raw_bits = pgn.raw_bits(); + assert_eq!( + raw_bits, + [ + false, true, false, false, false, true, false, false, true, false, false, false, + true, true, false, true, false, false + ] + ); + } + + #[test] + fn test_raw() { + let pgn = Pgn::new(true, false, PduFormat::new(0xE6), PduSpecific::new(0xBA)); + assert_eq!(pgn.raw(), 0x2E6BA); + + let pgn = Pgn::new(false, true, PduFormat::new(0x00), PduSpecific::new(0xFF)); + assert_eq!(pgn.raw(), 0x100FF); + + let pgn = Pgn::new(false, true, PduFormat::new(0x12), PduSpecific::new(0x34)); + assert_eq!(pgn.raw(), 0x11234); + } + + #[test] + fn test_get_destination_address() { + let pgn = Pgn::new(true, false, PduFormat::new(0xF3), PduSpecific::new(0xBA)); + assert_eq!(pgn.get_destination_address(), None); + + let pgn = Pgn::new(false, true, PduFormat::new(0x00), PduSpecific::new(0xFF)); + assert_eq!(pgn.get_destination_address(), Some(Address::BROADCAST)); + + let pgn = Pgn::new(false, true, PduFormat::new(0x12), PduSpecific::new(0x34)); + assert_eq!(pgn.get_destination_address(), Some(Address::new(0x34))); + } + + #[test] + fn test_set_destination_address() { + let mut pgn = Pgn::new(true, false, PduFormat::new(0xF3), PduSpecific::new(0xBA)); + pgn.set_destination_address(Address::new(0x34)); + assert_eq!(pgn.get_destination_address(), None); + + let mut pgn = Pgn::new(false, true, PduFormat::new(0x00), PduSpecific::new(0xFF)); + pgn.set_destination_address(Address::new(0x34)); + assert_eq!(pgn.get_destination_address(), Some(Address::new(0x34))); + + let mut pgn = Pgn::new(false, true, PduFormat::new(0x12), PduSpecific::new(0x34)); + pgn.set_destination_address(Address::new(0x56)); + assert_eq!(pgn.get_destination_address(), Some(Address::new(0x56))); + } + + #[test] + fn test_get_group_extension() { + let pgn = Pgn::new(true, false, PduFormat::new(0xF3), PduSpecific::new(0xBA)); + assert_eq!(pgn.get_group_extension(), Some(0xBA)); + + let pgn = Pgn::new(false, true, PduFormat::new(0x00), PduSpecific::new(0xFF)); + assert_eq!(pgn.get_group_extension(), None); + + let pgn = Pgn::new(false, true, PduFormat::new(0x12), PduSpecific::new(0x34)); + assert_eq!(pgn.get_group_extension(), None); + } + + #[test] + fn test_set_group_extension() { + let mut pgn = Pgn::new(true, false, PduFormat::new(0xF3), PduSpecific::new(0xBA)); + pgn.set_group_extension(0x34); + assert_eq!(pgn.get_group_extension(), Some(0x34)); + + let mut pgn = Pgn::new(false, true, PduFormat::new(0x00), PduSpecific::new(0xFF)); + pgn.set_group_extension(0x34); + assert_eq!(pgn.get_group_extension(), None); + + let mut pgn = Pgn::new(false, true, PduFormat::new(0x12), PduSpecific::new(0x34)); + pgn.set_group_extension(0x56); + assert_eq!(pgn.get_group_extension(), None); + } + + #[test] + fn test_is_proprietary() { + let pgn = Pgn::new(true, false, PduFormat::new(0xF3), PduSpecific::new(0xBA)); + assert!(!pgn.is_proprietary()); + + let pgn = Pgn::new(false, true, PduFormat::new(0x00), PduSpecific::new(0xFF)); + assert!(!pgn.is_proprietary()); + + let pgn = Pgn::new(false, true, PduFormat::new(0x12), PduSpecific::new(0x34)); + assert!(!pgn.is_proprietary()); + + let pgn = Pgn::new(false, true, PduFormat::new(0xEF), PduSpecific::new(0x00)); + assert!(pgn.is_proprietary()); + + let pgn = Pgn::new(false, true, PduFormat::new(0xFF), PduSpecific::new(0x00)); + assert!(pgn.is_proprietary()); + + let pgn = Pgn::new(false, true, PduFormat::new(0x1E), PduSpecific::new(0x00)); + assert!(!pgn.is_proprietary()); + + let pgn = Pgn::new(false, true, PduFormat::new(0x1F), PduSpecific::new(0x00)); + assert!(!pgn.is_proprietary()); + } + + #[test] + fn test_page() { + let pgn = Pgn::new(false, false, PduFormat::new(0xF3), PduSpecific::new(0xBA)); + assert_eq!(pgn.page(), Page::J1939Page0); + + let pgn = Pgn::new(false, true, PduFormat::new(0x00), PduSpecific::new(0xFF)); + assert_eq!(pgn.page(), Page::J1939Page1); + + let pgn = Pgn::new(true, false, PduFormat::new(0x12), PduSpecific::new(0x34)); + assert_eq!(pgn.page(), Page::J1939PageReserved); + + let pgn = Pgn::new(true, true, PduFormat::new(0x12), PduSpecific::new(0x34)); + assert_eq!(pgn.page(), Page::ISO11992_4Defined); + } + + #[test] + fn test_try_from() { + let pgn_parsed = Pgn::try_from(0x2E6BA).expect("Failed to parse PGN"); + + let pgn = Pgn::new(true, false, PduFormat::new(0xE6), PduSpecific::new(0xBA)); + assert_eq!(pgn, pgn_parsed); + + let pgn_parsed = Pgn::try_from(0x100FF).expect("Failed to parse PGN"); + + let pgn = Pgn::new(false, true, PduFormat::new(0x00), PduSpecific::new(0xFF)); + assert_eq!(pgn, pgn_parsed); + + let pgn_parsed = Pgn::try_from(0x11234).expect("Failed to parse PGN"); + + let pgn = Pgn::new(false, true, PduFormat::new(0x12), PduSpecific::new(0x34)); + assert_eq!(pgn, pgn_parsed); + } +} diff --git a/src/j1939/priority.rs b/src/j1939/priority.rs new file mode 100644 index 0000000..0f7c42c --- /dev/null +++ b/src/j1939/priority.rs @@ -0,0 +1,204 @@ +/* +Copyright 2023 Raven Industries inc. + +@author Jannes Brands +@date 2024-02-22 +*/ +use bitvec::field::BitField; +use bitvec::order::Lsb0; +use bitvec::vec::BitVec; +use bitvec::view::BitView; + +#[derive(Debug, PartialEq, Eq)] +pub struct ParsePriorityError(u8); + +impl std::fmt::Display for ParsePriorityError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Parse '{:?}' failed because the permitted priority value is between 0 and 7!", + self + ) + } +} + +/// The priority of a J1939 message +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(u8)] +pub enum Priority { + /// You may also use [`Priority::HIGHEST`] as an alias + Zero = 0, + One = 1, + Two = 2, + Three = 3, + Four = 4, + Five = 5, + /// You may also use [`Priority::DEFAULT`] as an alias + Six = 6, + /// You may also use [`Priority::LOWEST`] as an alias + Seven = 7, +} + +impl Priority { + /// The number of bits used to represent the priority + pub const BIT_LENGTH: u8 = 3; + /// The highest priority + pub const HIGHEST: Priority = Priority::Zero; + /// The default priority + pub const DEFAULT: Priority = Priority::Six; + /// The lowest priority + pub const LOWEST: Priority = Priority::Seven; + + /// Returns if the priority is the [Priority::HIGHEST] / [Priority::Zero] priority + #[inline] + pub fn is_highest(self) -> bool { + self == Self::HIGHEST + } + + /// Returns if the priority is the [Priority::DEFAULT] / [Priority::Six] priority + #[inline] + pub fn is_default(self) -> bool { + self == Self::DEFAULT + } + + /// Returns if the priority is the [Priority::LOWEST] / [Priority::Seven] priority + #[inline] + pub fn is_lowest(self) -> bool { + self == Self::LOWEST + } + + /// Get the raw value of the priority + #[inline] + pub fn raw(self) -> u8 { + self as u8 + } + + /// Get the raw bits of the priority + pub fn raw_bits(&self) -> [bool; 3] { + let priority_raw = self.raw(); + let mut priority_bits = priority_raw.view_bits::().to_bitvec(); + priority_bits.truncate(3); + priority_bits.reverse(); + [priority_bits[0], priority_bits[1], priority_bits[2]] + } +} + +impl Default for Priority { + fn default() -> Self { + Priority::DEFAULT + } +} + +impl From for u8 { + fn from(priority: Priority) -> Self { + priority.raw() + } +} + +impl From for [bool; 3] { + fn from(priority: Priority) -> Self { + priority.raw_bits() + } +} + +impl From<[bool; 3]> for Priority { + fn from(raw_priority: [bool; 3]) -> Self { + let mut priority_bits: BitVec = BitVec::new(); + priority_bits.extend(raw_priority.iter()); + priority_bits.reverse(); + let priority_raw = priority_bits.load_be::(); + match priority_raw { + 0x0 => Priority::Zero, + 0x1 => Priority::One, + 0x2 => Priority::Two, + 0x3 => Priority::Three, + 0x4 => Priority::Four, + 0x5 => Priority::Five, + 0x6 => Priority::Six, + 0x7 => Priority::Seven, + _ => unreachable!(), + } + } +} + +impl TryFrom for Priority { + type Error = ParsePriorityError; + + fn try_from(raw_priority: u8) -> Result { + match raw_priority { + 0x0 => Ok(Priority::Zero), + 0x1 => Ok(Priority::One), + 0x2 => Ok(Priority::Two), + 0x3 => Ok(Priority::Three), + 0x4 => Ok(Priority::Four), + 0x5 => Ok(Priority::Five), + 0x6 => Ok(Priority::Six), + 0x7 => Ok(Priority::Seven), + _ => Err(ParsePriorityError(raw_priority)), + } + } +} + +#[cfg(test)] +mod tests { + use crate::j1939::priority::ParsePriorityError; + use crate::j1939::Priority; + + #[test] + fn test_try_from_u8_for_priority() { + assert_eq!(Priority::try_from(0x0).unwrap(), Priority::Zero); + assert_eq!(Priority::try_from(0x1).unwrap(), Priority::One); + assert_eq!(Priority::try_from(0x2).unwrap(), Priority::Two); + assert_eq!(Priority::try_from(0x3).unwrap(), Priority::Three); + assert_eq!(Priority::try_from(0x4).unwrap(), Priority::Four); + assert_eq!(Priority::try_from(0x5).unwrap(), Priority::Five); + assert_eq!(Priority::try_from(0x6).unwrap(), Priority::Six); + assert_eq!(Priority::try_from(0x7).unwrap(), Priority::Seven); + assert_eq!(Priority::try_from(0x8).unwrap_err(), ParsePriorityError(8)); + } + + #[test] + fn test_from_bool_array_for_priority() { + assert_eq!(Priority::from([false, false, false]), Priority::Zero); + assert_eq!(Priority::from([false, false, true]), Priority::One); + assert_eq!(Priority::from([false, true, false]), Priority::Two); + assert_eq!(Priority::from([false, true, true]), Priority::Three); + assert_eq!(Priority::from([true, false, false]), Priority::Four); + assert_eq!(Priority::from([true, false, true]), Priority::Five); + assert_eq!(Priority::from([true, true, false]), Priority::Six); + assert_eq!(Priority::from([true, true, true]), Priority::Seven); + } + + #[test] + fn test_priority() { + assert_eq!(Priority::HIGHEST, Priority::Zero); + assert_eq!(Priority::DEFAULT, Priority::Six); + assert_eq!(Priority::LOWEST, Priority::Seven); + + assert_eq!(Priority::HIGHEST.is_highest(), true); + assert_eq!(Priority::HIGHEST.is_default(), false); + assert_eq!(Priority::HIGHEST.is_lowest(), false); + + let priority = Priority::try_from(0x0).unwrap(); + assert_eq!(priority.raw(), 0x0); + assert_eq!(priority.raw_bits(), [false, false, false]); + } + + #[test] + fn test_raw_priority() { + let priority = Priority::Five; + assert_eq!(u8::from(priority), 0x5); + assert_eq!(priority.raw(), 0x5); + + let priority = Priority::One; + assert_eq!(u8::from(priority), 0x1); + assert_eq!(priority.raw(), 0x1); + } + + #[test] + fn test_raw_bits_priority() { + assert_eq!(Priority::Four.raw_bits(), [true, false, false]); + assert_eq!(Priority::Six.raw_bits(), [true, true, false]); + assert_eq!(Priority::One.raw_bits(), [false, false, true]); + } +} diff --git a/src/j1939/standard_id.rs b/src/j1939/standard_id.rs new file mode 100644 index 0000000..f0d1325 --- /dev/null +++ b/src/j1939/standard_id.rs @@ -0,0 +1,148 @@ +/* +Copyright 2023 Raven Industries inc. + +@author Jannes Brands +@date 2024-02-22 +*/ +use crate::j1939::byte_field::ByteField; +use crate::j1939::id::Id; +use crate::j1939::{Address, Priority}; +use bitvec::field::BitField; +use bitvec::order::Msb0; +use bitvec::vec::BitVec; +use embedded_can::{Id as EmbeddedId, StandardId as EmbeddedStandardId}; + +/// Standard 11-bit J1939 identifier +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub struct StandardId { + priority: Priority, + source_address: Address, +} + +impl StandardId { + pub const SOURCE_ADDRESS_START: usize = 0; + pub const SOURCE_ADDRESS_END: usize = 8; + pub const PRIORITY_START: usize = 8; + pub const PRIORITY_END: usize = 11; + + /// Creates a new standard identifier out of a priority and source address + pub fn new(priority: Priority, source_address: Address) -> Self { + Self { + priority, + source_address, + } + } + + /// Get the priority of the identifier + #[inline] + pub fn priority(&self) -> Priority { + self.priority + } + + /// Get the source address of the identifier + #[inline] + pub fn source_address(&self) -> Address { + self.source_address + } + + /// Get the raw value of the standard identifier + pub fn raw(&self) -> u16 { + let priority_bits: [bool; 3] = self.priority.into(); + let raw_source_address: [bool; 8] = self.source_address.raw_bits(); + let mut raw_id = BitVec::::new(); + raw_id.extend(priority_bits); + raw_id.extend(raw_source_address); + raw_id.load() + } + + /// Get the raw bits of the standard identifier + pub fn raw_bits(&self) -> [bool; 11] { + let priority_bits: [bool; 3] = self.priority.into(); + let raw_source_address: [bool; 8] = self.source_address.raw_bits(); + [ + priority_bits[0], + priority_bits[1], + priority_bits[2], + raw_source_address[0], + raw_source_address[1], + raw_source_address[2], + raw_source_address[3], + raw_source_address[4], + raw_source_address[5], + raw_source_address[6], + raw_source_address[7], + ] + } +} + +impl From for Id { + fn from(id: StandardId) -> Self { + Id::Standard(id) + } +} + +impl From for EmbeddedId { + fn from(id: StandardId) -> Self { + EmbeddedId::Standard(EmbeddedStandardId::new(id.raw()).unwrap()) + } +} + +impl From for [bool; 11] { + fn from(id: StandardId) -> Self { + id.raw_bits() + } +} + +#[cfg(test)] +mod tests { + use crate::j1939::{Address, Priority, StandardId}; + use embedded_can::{Id as EmbeddedId, StandardId as EmbeddedStandardId}; + + #[test] + fn test_raw() { + let id = StandardId::new(Priority::Three, Address::new(0x0A)); + assert_eq!(id.raw(), 0x30A); + + let id = StandardId::new(Priority::Seven, Address::new(0x0F)); + assert_eq!(id.raw(), 0x70F); + + let id = StandardId::new(Priority::Zero, Address::new(0x00)); + assert_eq!(id.raw(), 0x000); + } + + #[test] + fn test_raw_bits() { + let id = StandardId::new(Priority::Three, Address::new(0x0A)); + assert_eq!( + id.raw_bits(), + [false, true, true, false, false, false, false, true, false, true, false] + ); + + let id = StandardId::new(Priority::Seven, Address::new(0x0F)); + assert_eq!( + id.raw_bits(), + [true, true, true, false, false, false, false, true, true, true, true] + ); + + let id = StandardId::new(Priority::Zero, Address::new(0x00)); + assert_eq!( + id.raw_bits(), + [false, false, false, false, false, false, false, false, false, false, false] + ); + } + + #[test] + fn test_from_standard_id_for_id() { + let id = StandardId::new(Priority::Three, Address::new(0x0A)); + assert_eq!( + EmbeddedId::from(id), + EmbeddedId::Standard(EmbeddedStandardId::new(0x30A).unwrap()) + ); + + let id = StandardId::new(Priority::Seven, Address::new(0x0F)); + assert_eq!( + EmbeddedId::from(id), + EmbeddedId::Standard(EmbeddedStandardId::new(0x70F).unwrap()) + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index b3b85af..77dd717 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,6 @@ #![allow(clippy::needless_return)] #![allow(clippy::module_inception)] -pub mod driver; +pub mod j1939; pub mod network_management; pub mod object_pool; diff --git a/src/network_management/can_message.rs b/src/network_management/can_message.rs deleted file mode 100644 index 0355e7e..0000000 --- a/src/network_management/can_message.rs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2023 Raven Industries inc. -use super::name::NAME; -use crate::driver::CanId; - -pub struct CANMessage { - data: Vec, - identifier: CanId, - source_name: NAME, - destination_name: NAME, -} - -impl CANMessage { - pub(super) fn new(data: Vec, identifier: CanId) -> CANMessage { - CANMessage { - data, - identifier, - source_name: NAME::default(), - destination_name: NAME::default(), - } - } - - pub fn get_data(&self) -> &[u8] { - self.data.as_slice() - } - - pub fn get_identifier(&self) -> CanId { - self.identifier - } - - pub fn get_source_name(&self) -> NAME { - self.source_name - } - - pub fn get_destination_name(&self) -> NAME { - self.destination_name - } -} diff --git a/src/network_management/common_parameter_group_numbers.rs b/src/network_management/common_parameter_group_numbers.rs index c83a71b..05a6ec4 100644 --- a/src/network_management/common_parameter_group_numbers.rs +++ b/src/network_management/common_parameter_group_numbers.rs @@ -1,62 +1,22 @@ // Copyright 2023 Raven Industries inc. +use crate::j1939::{PduFormat, PduSpecific, Pgn}; + #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum CommonParameterGroupNumbers { - TractorImplementManagementServerToTimClient = 0x002300, - TractorImplementManagementClientToTimServer = 0x002400, - AuthenticationClientToAuthenticationServer = 0x006F00, - AuthenticationServerToAuthenticationClient = 0x007000, - NameManagement = 0x009300, - GuidanceMachineStatus = 0x00AC00, - GuidanceSystemCommand = 0x00AD00, - ExtendedTransportProtocolData = 0x00C700, - ExtendedTransportProtocolCommand = 0x00C800, - RequestForRepetitionRate = 0x00CC00, - BinaryDataTransfer = 0x00D700, - MemoryAccessResponse = 0x00D800, - MemoryAccessRequest = 0x00D900, - StopStartBroadcast = 0x00DF00, - VirtualTerminalToNode = 0x00E600, - NodeToVirtualTerminal = 0x00E700, - Acknowledgement = 0x00E800, ParameterGroupNumberRequest = 0x00EA00, - TransportProtocolData = 0x00EB00, - TransportProtocolCommand = 0x00EC00, AddressClaim = 0x00EE00, - ProprietaryA = 0x00EF00, - ElectronicEngineController2 = 0x00F003, - ElectronicEngineController1 = 0x00F004, - HeartbeatMessage = 0x00F0E4, - ProductIdentification = 0x00FC8D, - ControlFunctionFunctionalities = 0x00FC8E, - DiagnosticProtocol = 0x00FD32, - IsobusComplianceCertificationMessage = 0x00FD42, - EcuIdentificationInformation = 0x00FDC5, - WorkingSetMaster = 0x00FE0D, - ResponseForRepetitionRate = 0x00FE0E, - MaintainPower = 0x00FE47, - WheelBasedSpeedAndDistance = 0x00FE48, - GroundBasedSpeedAndDistance = 0x00FE49, - ActiveDiagnosticTroubleCodes = 0x00FECA, - PreviouslyActiveDiagnosticTroubleCodes = 0x00FECB, - DiagnosticDataClearResetOfPreviouslyActiveDtcs = 0x00FECC, - FreezeFrameParameters = 0x00FECD, - DiagnosticDataClearResetForActiveDtcs = 0x00FED3, - CommandedAddress = 0x00FED8, - SoftwareIdentification = 0x00FEDA, - TimeDate = 0x00FEE6, - EngineTemperature1 = 0x00FEEE, - CruiseControlVehicleSpeed1 = 0x00FEF1, - IntakeExhaustConditions1 = 0x00FEF6, - NmeaAttitude = 0x01F119, - NmeaCogSogRapidUpdate = 0x01F802, - NmeaPositionDeltaHighPrecisionRapidUpdate = 0x01F803, - NmeaAltitudeDeltaHighPrecisionRapidUpdate = 0x01F804, - NmeaGnssPositionData = 0x01F805, - NmeaTimeDate = 0x01F809, - NmeaGnssDops = 0x01FA03, - NmeaGnssSatsInView = 0x01FA04, - NmeaGnssPseudoRangeNoiseStatistics = 0x01FA06, - NmeaGnssPseudoRangeErrorStatistics = 0x01FA0B, - AllowAll = 0xFFFFFF, +} + +impl CommonParameterGroupNumbers { + pub fn get_pgn(&self) -> Pgn { + match self { + CommonParameterGroupNumbers::AddressClaim => { + Pgn::new(false, false, PduFormat::new(0xEE), PduSpecific::new(0x00)) + } + CommonParameterGroupNumbers::ParameterGroupNumberRequest => { + Pgn::new(false, false, PduFormat::new(0xEA), PduSpecific::new(0x00)) + } + } + } } diff --git a/src/network_management/control_function.rs b/src/network_management/control_function.rs index 34999a6..2bb57ed 100644 --- a/src/network_management/control_function.rs +++ b/src/network_management/control_function.rs @@ -1,5 +1,5 @@ // Copyright 2023 Raven Industries inc. -use crate::driver::Address; +use crate::j1939::Address; use crate::network_management::name::NAME; use rand::Rng; use std::cell::RefCell; diff --git a/src/network_management/mod.rs b/src/network_management/mod.rs index 99d372c..a8adf36 100644 --- a/src/network_management/mod.rs +++ b/src/network_management/mod.rs @@ -1,5 +1,4 @@ // Copyright 2023 Raven Industries inc. -pub mod can_message; pub mod common_parameter_group_numbers; pub mod control_function; pub mod name; diff --git a/src/network_management/network_manager.rs b/src/network_management/network_manager.rs index 7314bd1..a43f47d 100644 --- a/src/network_management/network_manager.rs +++ b/src/network_management/network_manager.rs @@ -2,8 +2,7 @@ use std::time::Instant; use super::control_function::{AddressClaimingState, ControlFunction}; -use crate::driver::{Address, CanId, Pgn, Priority}; -use crate::network_management::can_message::CANMessage; +use crate::j1939::{Address, ByteField, ExtendedId, Frame, Id, Pgn, Priority, StandardId}; use crate::network_management::common_parameter_group_numbers::CommonParameterGroupNumbers; use crate::network_management::name::NAME; use std::cell::RefCell; @@ -12,9 +11,9 @@ use std::rc::Rc; #[derive(Debug, Clone, Copy)] pub(super) enum MessageQueuePriority { - /// High priority messages are always sent to the driver before normal ones + /// High priority messages are always sent to the j1939 before normal ones High, - /// Normal messages are sent to the driver when no high priority messages are in the queue (todo) + /// Normal messages are sent to the j1939 when no high priority messages are in the queue (todo) Normal, } @@ -30,9 +29,9 @@ pub struct NetworkManager { control_function_table: [Option>>; 253], inactive_control_functions: Vec>>, address_claim_state_machines: Vec>>, - high_priority_can_message_tx_queue: VecDeque, - normal_priority_can_message_tx_queue: VecDeque, - receive_message_queue: VecDeque, + high_priority_can_message_tx_queue: VecDeque, + normal_priority_can_message_tx_queue: VecDeque, + receive_message_queue: VecDeque, } impl NetworkManager { @@ -51,14 +50,14 @@ impl NetworkManager { &self, address: Address, ) -> &Option>> { - &self.control_function_table[address.0 as usize] + &self.control_function_table[address.raw() as usize] } pub fn get_control_function_address_by_name(&self, name: NAME) -> Address { for (i, cf) in self.control_function_table.iter().enumerate() { if let Some(extant_cf) = cf { if extant_cf.borrow().get_name() == name { - return Address(i as u8); + return Address::new(i as u8); } } } @@ -75,11 +74,11 @@ impl NetworkManager { pub(super) fn get_next_free_arbitrary_address(&self) -> Address { for address in 129..247 { - let is_device_at_address = self.get_control_function_by_address(Address(address)); + let is_device_at_address = self.get_control_function_by_address(Address::new(address)); let is_valid_device: bool = is_device_at_address.is_some(); if !is_valid_device { - return Address(address); + return Address::new(address); } else { let device_at_our_address = is_device_at_address.as_ref().unwrap().borrow(); @@ -91,40 +90,35 @@ impl NetworkManager { }; if >::into(NAME::default()) == preferred_address_name { - return Address(address); + return Address::new(address); } } } Address::NULL } - pub(super) fn construct_address_claim(source_address: Address, name: NAME) -> CANMessage { + pub(super) fn construct_address_claim(source_address: Address, name: NAME) -> Frame { let address_claim = >::into(name).to_le_bytes().to_vec(); - let request_id = CanId::try_encode( - Pgn::from_raw(CommonParameterGroupNumbers::AddressClaim as u32), - source_address, - Address::BROADCAST, - Priority::Default, + let request_id = ExtendedId::new( + StandardId::new(Priority::Three, source_address), + CommonParameterGroupNumbers::AddressClaim.get_pgn(), ); - CANMessage::new(address_claim, request_id.unwrap()) + Frame::new(request_id, address_claim).unwrap() } - pub(super) fn construct_request_for_address_claim() -> CANMessage { + pub(super) fn construct_request_for_address_claim() -> Frame { let pgn_to_request: u32 = CommonParameterGroupNumbers::AddressClaim as u32; let request = pgn_to_request.to_le_bytes().to_vec(); - let request_id = CanId::try_encode( - Pgn::from_raw(CommonParameterGroupNumbers::ParameterGroupNumberRequest as u32), - Address::NULL, - Address::BROADCAST, - Priority::Three, - ); - CANMessage::new(request, request_id.unwrap()) + let mut pgn = CommonParameterGroupNumbers::ParameterGroupNumberRequest.get_pgn(); + pgn.set_destination_address(Address::BROADCAST); + let request_id = ExtendedId::new(StandardId::new(Priority::Three, Address::NULL), pgn); + Frame::new(request_id, request).unwrap() } pub(super) fn enqueue_can_message( &mut self, - message: CANMessage, + message: Frame, queue_priority: MessageQueuePriority, ) { // Todo, max queue depth? @@ -152,21 +146,23 @@ impl NetworkManager { if data.len() <= 8 { let source = source.borrow(); let destination = destination.borrow(); - let message_id = CanId::try_encode( - parameter_group_number, - self.get_control_function_address_by_name(source.get_name()), + let mut pgn = parameter_group_number; + pgn.set_destination_address( self.get_control_function_address_by_name(destination.get_name()), - priority, - ) - .unwrap_or(CanId::default()); - - if message_id.raw() != CanId::default().raw() { - self.enqueue_can_message( - CANMessage::new(data.to_vec(), message_id), - MessageQueuePriority::Normal, - ); - return CANTransmitState::Success; - } + ); + let message_id = ExtendedId::new( + StandardId::new( + priority, + self.get_control_function_address_by_name(source.get_name()), + ), + pgn, + ); + + self.enqueue_can_message( + Frame::new(message_id, data.to_vec()).unwrap(), + MessageQueuePriority::Normal, + ); + return CANTransmitState::Success; } } CANTransmitState::Fail @@ -249,64 +245,74 @@ impl NetworkManager { // Todo receive messages, need to generalize message handling let current_message = self.receive_message_queue.front().unwrap(); - // Process address claims and requests to claim - if NAME::default() == current_message.get_destination_name() { - // Broadcast Message - if current_message.get_identifier().pgn() - == Pgn::from_raw(CommonParameterGroupNumbers::AddressClaim as u32) - { - // Todo - } else if current_message.get_identifier().pgn() - == Pgn::from_raw( - CommonParameterGroupNumbers::ParameterGroupNumberRequest as u32, - ) - && current_message.get_data().len() >= 3 - { - let message_data = current_message.get_data(); - let requested_pgn: u32 = (message_data[0] as u32) - | ((message_data[1] as u32) << 8) - | ((message_data[2] as u32) << 16); - - if requested_pgn - == CommonParameterGroupNumbers::ParameterGroupNumberRequest as u32 + match current_message.id() { + Id::Standard(_) => {} + Id::Extended(id) => { + // Process address claims and requests to claim + if NAME::default() == NAME::default() + /*TODO!: Replaced following code line by upper NAME::default(). There needs to be another abstraction of ISO 11783 specific frames which includes the NAME*/ + /*current_message.get_destination_name()*/ { - for internal_cf in &mut self.address_claim_state_machines { - let mut address_claimer = internal_cf.borrow_mut(); - match *address_claimer { - ControlFunction::Internal { - ref mut address_claim_data, - } => { - if address_claim_data.get_state() - == AddressClaimingState::AddressClaimingComplete - { - address_claim_data.set_state( - AddressClaimingState::SendReclaimAddressOnRequest, - ); + // Broadcast Message + if id.pgn().pdu_format() + == CommonParameterGroupNumbers::AddressClaim + .get_pgn() + .pdu_format() + { + // Todo + } else if id.pgn().pdu_format() + == CommonParameterGroupNumbers::ParameterGroupNumberRequest + .get_pgn() + .pdu_format() + && current_message.clone().data().len() >= 3 + { + let message_data = current_message.clone().data(); + let requested_pgn: u32 = (message_data[0] as u32) + | ((message_data[1] as u32) << 8) + | ((message_data[2] as u32) << 16); + + if requested_pgn + == CommonParameterGroupNumbers::ParameterGroupNumberRequest as u32 + { + for internal_cf in &mut self.address_claim_state_machines { + let mut address_claimer = internal_cf.borrow_mut(); + match *address_claimer { + ControlFunction::Internal { + ref mut address_claim_data, + } => { + if address_claim_data.get_state() + == AddressClaimingState::AddressClaimingComplete + { + address_claim_data.set_state( + AddressClaimingState::SendReclaimAddressOnRequest, + ); + } + } + ControlFunction::External { name: _ } => {} } } - ControlFunction::External { name: _ } => {} } + } else { + // Destination specific } + + self.receive_message_queue.pop_front(); } - } else { - // Destination specific } - - self.receive_message_queue.pop_front(); } } } fn update_transmit_messages(&mut self) { - let should_continue_sending: bool = true; // Todo, check driver return values. + let should_continue_sending: bool = true; // Todo, check j1939 return values. while !self.high_priority_can_message_tx_queue.is_empty() { - // todo hand off to driver + // todo hand off to j1939 self.high_priority_can_message_tx_queue.pop_front(); } while should_continue_sending && !self.normal_priority_can_message_tx_queue.is_empty() { - // todo hand off to driver + // todo hand off to j1939 self.normal_priority_can_message_tx_queue.pop_front(); } } @@ -350,7 +356,7 @@ mod tests { let new_cf = ControlFunction::new_internal_control_function( test_name, - Address(0x81), + Address::new(0x81), true, &mut network, ); diff --git a/src/object_pool/object.rs b/src/object_pool/object.rs index 39bdb66..0dd6fa3 100644 --- a/src/object_pool/object.rs +++ b/src/object_pool/object.rs @@ -12,6 +12,7 @@ use crate::object_pool::object_id::ObjectId; use crate::object_pool::{Colour, ObjectType}; #[derive(Debug)] +#[allow(clippy::enum_variant_names)] pub enum Object { WorkingSet(WorkingSet), DataMask(DataMask),