Skip to content

Commit

Permalink
Initial Frame, Address, and PGN types
Browse files Browse the repository at this point in the history
I suspect these types will need some work, but I'd rather commit
something imperfect than delay waiting on perfection.
  • Loading branch information
Notgnoshi committed Aug 8, 2023
1 parent 1a358aa commit 1a56183
Show file tree
Hide file tree
Showing 6 changed files with 394 additions and 13 deletions.
12 changes: 12 additions & 0 deletions src/driver/address.rs
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
213 changes: 213 additions & 0 deletions src/driver/can_id.rs
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)));
}
}
19 changes: 19 additions & 0 deletions src/driver/frame.rs
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,
}
17 changes: 17 additions & 0 deletions src/driver/mod.rs
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;
129 changes: 129 additions & 0 deletions src/driver/pgn.rs
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);
}
}
Loading

0 comments on commit 1a56183

Please sign in to comment.