From 3c5275c3fa937cb6def3edf605f45aa300175a8b Mon Sep 17 00:00:00 2001 From: Austin Gill Date: Mon, 7 Aug 2023 14:43:30 -0500 Subject: [PATCH] Initial Frame, Address, and PGN types I suspect these types will need some work, but I'd rather commit something imperfect than delay waiting on perfection. --- src/driver/address.rs | 12 +++ src/driver/can_id.rs | 206 ++++++++++++++++++++++++++++++++++++++++++ src/driver/frame.rs | 19 ++++ src/driver/mod.rs | 17 ++++ src/driver/pgn.rs | 129 ++++++++++++++++++++++++++ src/lib.rs | 3 +- 6 files changed, 385 insertions(+), 1 deletion(-) create mode 100644 src/driver/address.rs create mode 100644 src/driver/can_id.rs create mode 100644 src/driver/frame.rs create mode 100644 src/driver/mod.rs create mode 100644 src/driver/pgn.rs diff --git a/src/driver/address.rs b/src/driver/address.rs new file mode 100644 index 0000000..5191171 --- /dev/null +++ b/src/driver/address.rs @@ -0,0 +1,12 @@ +// Copyright 2023 Raven Industries inc. + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(transparent)] +pub struct Address(pub u8); + +impl Address { + pub const GLOBAL: Address = Address(0xFF); + 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 new file mode 100644 index 0000000..022a96b --- /dev/null +++ b/src/driver/can_id.rs @@ -0,0 +1,206 @@ +// 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, Default, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(transparent)] +pub struct CanId(u32); + +impl CanId { + pub fn new(raw: u32) -> Self { + Self(raw) + } + + /// Get the raw value of the CAN ID + #[inline] + pub fn raw(&self) -> u32 { + self.0 + } + + /// Get the type of the ID (standard or extended) + #[inline] + pub fn type_(&self) -> Type { + if self.raw() <= 0x7FF { + Type::Standard + } else { + Type::Extended + } + } + + /// 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 + /// + /// # Default + /// If the CAN ID is a Standard 11-bit ID, then a NULL PGN will be returned. + /// See [`pgn()`](Self::pgn) for safe checked alternative. + #[inline] + pub fn pgn_default_null(&self) -> Pgn { + self.pgn().unwrap_or(Pgn::NULL) + } + + /// Get the ID's PGN + /// + /// In the case the the ID is a standard 11-bit ID, no PGN will be returned + #[inline] + pub fn pgn(&self) -> Option { + match self.type_() { + Type::Standard => None, + Type::Extended => Some(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) -> Option
{ + let Some(pgn) = self.pgn() else { + return None; + }; + + if pgn.is_broadcast() { + return None; + } + + let raw_pdu_s = ((self.raw() & 0xFF00) >> 8) as u8; + Some(Address(raw_pdu_s)) + } + + /// Get the destination address for this CAN ID + /// + /// # Default + /// If it's a broadcast PGN, the returned address will be `0xFF` + #[inline] + pub fn destination_address_default_global(&self) -> Address { + self.destination_address().unwrap_or(Address::GLOBAL) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_standard_extended() { + let can_id = CanId::new(0x07FF); + assert_eq!(can_id.type_(), Type::Standard); + + let can_id = CanId::new(0x08FF); + assert_eq!(can_id.type_(), Type::Extended); + } + + #[test] + fn test_priority() { + let can_id = CanId::new(0x18EF1CF5); + assert_eq!(can_id.priority(), Priority::Default); + } + + #[test] + fn test_source_address() { + let can_id = CanId::new(0x0705); + 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); + assert_eq!(can_id.source_address(), Address(0xF5)); + } + + #[test] + fn test_destination_address() { + let can_id = CanId::new(0x0705); + assert_eq!(can_id.destination_address(), None); + + let can_id = CanId::new(0x18EEFF1C); + assert_eq!(can_id.destination_address(), Some(Address::GLOBAL)); + + let can_id = CanId::new(0x09F8031C); + assert_eq!(can_id.destination_address(), None); + + let can_id = CanId::new(0x0CAC1C13); + assert_eq!(can_id.destination_address(), Some(Address(0x1C))); + } + + #[test] + fn test_pgn() { + let can_id = CanId::new(0x07FF); + assert_eq!(can_id.pgn(), None); + + let can_id = CanId::new(0x0CAC1C13); + assert_eq!(can_id.pgn(), Some(Pgn::from_raw(0x0AC00))); + + let can_id = CanId::new(0x18FF3F13); + assert_eq!(can_id.pgn(), Some(Pgn::from_raw(0x0FF3F))); + + let can_id = CanId::new(0x18EF1CF5); + assert_eq!(can_id.pgn(), Some(Pgn::from_raw(0x0EF00))); + + let can_id = CanId::new(0x18EEFF1C); + assert_eq!(can_id.pgn(), Some(Pgn::from_raw(0x0EE00))); + } +} diff --git a/src/driver/frame.rs b/src/driver/frame.rs new file mode 100644 index 0000000..6a4744b --- /dev/null +++ b/src/driver/frame.rs @@ -0,0 +1,19 @@ +// 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 new file mode 100644 index 0000000..86b636a --- /dev/null +++ b/src/driver/mod.rs @@ -0,0 +1,17 @@ +// Copyright 2023 Raven Industries inc. + +//! CAN Driver layer +//! +//! This module defines: +//! 1. **TODO:** An abstract `Driver` trait for different CAN drivers to implement +//! 2. `Frame`, `Pgn`, `Address`, et al types + +mod address; +mod can_id; +mod frame; +mod pgn; + +pub use address::Address; +pub use can_id::{CanId, Priority, Type}; +pub use frame::Frame; +pub use pgn::Pgn; diff --git a/src/driver/pgn.rs b/src/driver/pgn.rs new file mode 100644 index 0000000..f79c426 --- /dev/null +++ b/src/driver/pgn.rs @@ -0,0 +1,129 @@ +// 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/lib.rs b/src/lib.rs index 08cfc1c..18da879 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ // Copyright 2023 Raven Industries inc. -#![allow(clippy::module_inception)] +#![allow(clippy::bool_assert_comparison)] +pub mod driver; pub mod network_management;