use crate::id::CanId;
const PDU1_LIMIT: u8 = 240;
pub const BROADCAST_ADDRESS: u8 = 0xFF;
pub mod priority {
pub const CONTROL: u8 = 3;
pub const DEFAULT: u8 = 6;
pub const LOWEST: u8 = 7;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct J1939Id {
priority: u8,
pgn: u32,
source: u8,
pdu_specific: u8,
}
impl J1939Id {
pub fn from_id(id: CanId) -> Option<J1939Id> {
if !id.is_extended() {
return None;
}
let raw = id.raw();
let priority = ((raw >> 26) & 0x7) as u8;
let page = (raw >> 24) & 0x3; let pf = ((raw >> 16) & 0xFF) as u8;
let ps = ((raw >> 8) & 0xFF) as u8;
let source = (raw & 0xFF) as u8;
let mut pgn = (page << 16) | (u32::from(pf) << 8);
if pf >= PDU1_LIMIT {
pgn |= u32::from(ps);
}
Some(J1939Id {
priority,
pgn,
source,
pdu_specific: ps,
})
}
pub fn broadcast(priority: u8, pgn: u32, source: u8) -> J1939Id {
Self::from_parts(priority, pgn, source, BROADCAST_ADDRESS)
}
pub fn from_parts(priority: u8, pgn: u32, source: u8, destination: u8) -> J1939Id {
let pf = ((pgn >> 8) & 0xFF) as u8;
let pdu_specific = if pf < PDU1_LIMIT {
destination
} else {
(pgn & 0xFF) as u8
};
J1939Id {
priority: priority & 0x7,
pgn: pgn & 0x3_FFFF,
source,
pdu_specific,
}
}
pub fn priority(&self) -> u8 {
self.priority
}
pub fn pgn(&self) -> u32 {
self.pgn
}
pub fn source(&self) -> u8 {
self.source
}
pub fn pdu_format(&self) -> u8 {
((self.pgn >> 8) & 0xFF) as u8
}
pub fn destination(&self) -> Option<u8> {
if self.pdu_format() < PDU1_LIMIT {
Some(self.pdu_specific)
} else {
None
}
}
pub fn is_broadcast(&self) -> bool {
self.pdu_format() >= PDU1_LIMIT
}
pub fn to_id(&self) -> CanId {
let pf = self.pdu_format();
let ps = if pf < PDU1_LIMIT {
self.pdu_specific
} else {
(self.pgn & 0xFF) as u8
};
let page = (self.pgn >> 16) & 0x3;
let raw = (u32::from(self.priority) << 26)
| (page << 24)
| (u32::from(pf) << 16)
| (u32::from(ps) << 8)
| u32::from(self.source);
CanId::extended(raw)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_standard_identifier_is_not_j1939() {
assert_eq!(J1939Id::from_id(CanId::standard(0x100)), None);
}
#[test]
fn the_engine_speed_broadcast_decodes() {
let message = J1939Id::from_id(CanId::extended(0x0CF0_0400)).unwrap();
assert_eq!(message.priority(), 3);
assert_eq!(message.pgn(), 61_444);
assert_eq!(message.source(), 0x00);
assert_eq!(message.pdu_format(), 240);
assert!(message.is_broadcast());
assert_eq!(message.destination(), None);
}
#[test]
fn an_addressed_request_decodes_its_destination() {
let message = J1939Id::from_id(CanId::extended(0x18EA_2101)).unwrap();
assert_eq!(message.priority(), 6);
assert_eq!(message.pgn(), 59_904);
assert_eq!(message.source(), 0x01);
assert!(!message.is_broadcast());
assert_eq!(message.destination(), Some(0x21));
}
#[test]
fn a_broadcast_identifier_round_trips() {
let id = CanId::extended(0x0CF0_0400);
assert_eq!(J1939Id::from_id(id).unwrap().to_id(), id);
}
#[test]
fn an_addressed_identifier_round_trips() {
let id = CanId::extended(0x18EA_2101);
assert_eq!(J1939Id::from_id(id).unwrap().to_id(), id);
}
#[test]
fn composing_an_addressed_message_places_the_destination() {
let message = J1939Id::from_parts(6, 59_904, 0x01, 0x21);
assert_eq!(message.to_id(), CanId::extended(0x18EA_2101));
assert_eq!(message.destination(), Some(0x21));
}
#[test]
fn composing_a_broadcast_message_ignores_the_destination() {
let with_dest = J1939Id::from_parts(3, 61_444, 0x00, 0x55);
let without = J1939Id::from_parts(3, 61_444, 0x00, 0x00);
assert_eq!(with_dest.to_id(), without.to_id());
assert_eq!(with_dest.to_id(), CanId::extended(0x0CF0_0400));
}
#[test]
fn the_data_page_bits_round_trip() {
for raw in [0x0DF0_0400u32, 0x03F0_0400] {
let id = CanId::extended(raw);
assert_eq!(J1939Id::from_id(id).unwrap().to_id(), id);
}
}
#[test]
fn a_data_page_one_pgn_decodes_above_the_first_page() {
let message = J1939Id::from_id(CanId::extended(0x0DF0_0400)).unwrap();
assert_eq!(message.pgn(), 0x1_F004);
assert_eq!(message.priority(), 3);
assert!(message.is_broadcast());
}
}