-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
Showing
6 changed files
with
394 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
// 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<u8> 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 { | ||
/// 29-bit CAN ID | ||
Extended, | ||
/// 11-bit CAN ID | ||
Standard, | ||
} | ||
|
||
#[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) | ||
} | ||
|
||
/// 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::Extended => { | ||
let raw = ((self.raw() & 0x1C000000) >> 26) as u8; | ||
raw.into() | ||
} | ||
Type::Standard => Priority::Highest, | ||
} | ||
} | ||
|
||
/// Get the source address of the ID | ||
#[inline] | ||
pub fn source_address(&self) -> Address { | ||
match self.type_() { | ||
Type::Extended => Address((self.raw() & 0xFF) as u8), | ||
Type::Standard => Address::GLOBAL, | ||
} | ||
} | ||
|
||
/// 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<Pgn> { | ||
match self.type_() { | ||
Type::Extended => Some(Pgn::from_id(self.raw())), | ||
Type::Standard => None, | ||
} | ||
} | ||
|
||
/// Get the destination address for this CAN ID, if it's a destination-specific PGN | ||
#[inline] | ||
pub fn destination_address(&self) -> Option<Address> { | ||
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_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(), None); | ||
|
||
let can_id = CanId::new(0x18EEFF1C, Type::Extended); | ||
assert_eq!(can_id.destination_address(), Some(Address::GLOBAL)); | ||
|
||
let can_id = CanId::new(0x09F8031C, Type::Extended); | ||
assert_eq!(can_id.destination_address(), None); | ||
|
||
let can_id = CanId::new(0x0CAC1C13, Type::Extended); | ||
assert_eq!(can_id.destination_address(), Some(Address(0x1C))); | ||
} | ||
|
||
#[test] | ||
fn test_pgn() { | ||
let can_id = CanId::new(0x07FF, Type::Standard); | ||
assert_eq!(can_id.pgn(), None); | ||
|
||
let can_id = CanId::new(0x0CAC1C13, Type::Extended); | ||
assert_eq!(can_id.pgn(), Some(Pgn::from_raw(0x0AC00))); | ||
|
||
let can_id = CanId::new(0x18FF3F13, Type::Extended); | ||
assert_eq!(can_id.pgn(), Some(Pgn::from_raw(0x0FF3F))); | ||
|
||
let can_id = CanId::new(0x18EF1CF5, Type::Extended); | ||
assert_eq!(can_id.pgn(), Some(Pgn::from_raw(0x0EF00))); | ||
|
||
let can_id = CanId::new(0x18EEFF1C, Type::Extended); | ||
assert_eq!(can_id.pgn(), Some(Pgn::from_raw(0x0EE00))); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
Oops, something went wrong.