Skip to main content

pamoja_can/
j1939.rs

1//! J1939: the meaning packed into a 29-bit CAN identifier.
2//!
3//! J1939 is the protocol trucks, tractors, marine engines, and generators speak over CAN.
4//! It carries most of its addressing in the extended identifier itself: a priority, a
5//! parameter group number that names what the message is, and the source (and sometimes
6//! destination) address. This decodes that identifier and composes one.
7
8use crate::id::CanId;
9
10// PDU formats below this value carry a destination address in the PS field (PDU1);
11// formats at or above it are broadcast, with PS a group extension (PDU2).
12const PDU1_LIMIT: u8 = 240;
13
14/// The destination address that means every node on the bus.
15///
16/// A broadcast parameter group carries this in place of a destination, so a receiver
17/// knows the message is not addressed to it in particular.
18pub const BROADCAST_ADDRESS: u8 = 0xFF;
19
20/// The priorities J1939 conventionally assigns.
21///
22/// A priority is three bits, `0` highest and `7` lowest. The standard leaves the choice to
23/// the application, but publishes these as the usual ones, so naming them keeps a bare
24/// number out of a call site.
25pub mod priority {
26    /// The priority a control message such as engine speed is normally sent at.
27    pub const CONTROL: u8 = 3;
28    /// The default for everything that is not time critical, including requests.
29    pub const DEFAULT: u8 = 6;
30    /// The lowest priority, for traffic that may wait behind anything else.
31    pub const LOWEST: u8 = 7;
32}
33
34/// The fields a J1939 message packs into its 29-bit identifier.
35///
36/// # Examples
37///
38/// ```
39/// use pamoja_can::{CanId, J1939Id};
40///
41/// // The standard engine-speed broadcast, identifier 0x0CF00400.
42/// let message = J1939Id::from_id(CanId::extended(0x0CF0_0400)).unwrap();
43/// assert_eq!(message.priority(), 3);
44/// assert_eq!(message.pgn(), 61_444);
45/// assert_eq!(message.source(), 0x00);
46/// assert!(message.is_broadcast());
47/// assert_eq!(message.destination(), None);
48/// ```
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50pub struct J1939Id {
51    priority: u8,
52    pgn: u32,
53    source: u8,
54    pdu_specific: u8,
55}
56
57impl J1939Id {
58    /// Decodes a J1939 identifier from an extended CAN identifier.
59    ///
60    /// # Arguments
61    ///
62    /// * `id` - the CAN identifier to decode.
63    ///
64    /// # Returns
65    ///
66    /// The decoded fields, or [`None`] if `id` is a standard identifier, which J1939 does
67    /// not use.
68    pub fn from_id(id: CanId) -> Option<J1939Id> {
69        if !id.is_extended() {
70            return None;
71        }
72        let raw = id.raw();
73        let priority = ((raw >> 26) & 0x7) as u8;
74        let page = (raw >> 24) & 0x3; // the EDP and DP bits together
75        let pf = ((raw >> 16) & 0xFF) as u8;
76        let ps = ((raw >> 8) & 0xFF) as u8;
77        let source = (raw & 0xFF) as u8;
78        let mut pgn = (page << 16) | (u32::from(pf) << 8);
79        if pf >= PDU1_LIMIT {
80            pgn |= u32::from(ps);
81        }
82        Some(J1939Id {
83            priority,
84            pgn,
85            source,
86            pdu_specific: ps,
87        })
88    }
89
90    /// Builds the identifier for a message addressed to every node.
91    ///
92    /// A broadcast parameter group has no destination, so this fills in
93    /// [`BROADCAST_ADDRESS`] rather than making a caller remember it.
94    ///
95    /// # Arguments
96    ///
97    /// * `priority` - the three-bit priority; [`priority`] names the usual ones.
98    /// * `pgn` - the parameter group number the message belongs to.
99    /// * `source` - the address of the node sending it.
100    ///
101    /// # Returns
102    ///
103    /// The identifier fields.
104    pub fn broadcast(priority: u8, pgn: u32, source: u8) -> J1939Id {
105        Self::from_parts(priority, pgn, source, BROADCAST_ADDRESS)
106    }
107
108    /// Composes a J1939 identifier from its fields.
109    ///
110    /// # Arguments
111    ///
112    /// * `priority` - the message priority, masked to its low three bits.
113    /// * `pgn` - the parameter group number.
114    /// * `source` - the source address.
115    /// * `destination` - the destination address, used only for an addressed (PDU1)
116    ///   parameter group and ignored for a broadcast (PDU2) one.
117    ///
118    /// # Returns
119    ///
120    /// The identifier fields.
121    pub fn from_parts(priority: u8, pgn: u32, source: u8, destination: u8) -> J1939Id {
122        let pf = ((pgn >> 8) & 0xFF) as u8;
123        let pdu_specific = if pf < PDU1_LIMIT {
124            destination
125        } else {
126            (pgn & 0xFF) as u8
127        };
128        J1939Id {
129            priority: priority & 0x7,
130            pgn: pgn & 0x3_FFFF,
131            source,
132            pdu_specific,
133        }
134    }
135
136    /// Returns the message priority, 0 (highest) to 7.
137    ///
138    /// # Returns
139    ///
140    /// The priority.
141    pub fn priority(&self) -> u8 {
142        self.priority
143    }
144
145    /// Returns the parameter group number, which names what the message carries.
146    ///
147    /// # Returns
148    ///
149    /// The PGN.
150    pub fn pgn(&self) -> u32 {
151        self.pgn
152    }
153
154    /// Returns the source address: the node that sent the message.
155    ///
156    /// # Returns
157    ///
158    /// The source address.
159    pub fn source(&self) -> u8 {
160        self.source
161    }
162
163    /// Returns the PDU format byte of the parameter group.
164    ///
165    /// # Returns
166    ///
167    /// The PDU format.
168    pub fn pdu_format(&self) -> u8 {
169        ((self.pgn >> 8) & 0xFF) as u8
170    }
171
172    /// Returns the destination address, for an addressed message.
173    ///
174    /// # Returns
175    ///
176    /// The destination address for a PDU1 message, or [`None`] for a broadcast PDU2 one.
177    pub fn destination(&self) -> Option<u8> {
178        if self.pdu_format() < PDU1_LIMIT {
179            Some(self.pdu_specific)
180        } else {
181            None
182        }
183    }
184
185    /// Reports whether the message is a broadcast.
186    ///
187    /// # Returns
188    ///
189    /// `true` for a broadcast (PDU2) message, `false` for an addressed (PDU1) one.
190    pub fn is_broadcast(&self) -> bool {
191        self.pdu_format() >= PDU1_LIMIT
192    }
193
194    /// Composes the extended CAN identifier these fields describe.
195    ///
196    /// # Returns
197    ///
198    /// The extended [`CanId`].
199    pub fn to_id(&self) -> CanId {
200        let pf = self.pdu_format();
201        let ps = if pf < PDU1_LIMIT {
202            self.pdu_specific
203        } else {
204            (self.pgn & 0xFF) as u8
205        };
206        let page = (self.pgn >> 16) & 0x3;
207        let raw = (u32::from(self.priority) << 26)
208            | (page << 24)
209            | (u32::from(pf) << 16)
210            | (u32::from(ps) << 8)
211            | u32::from(self.source);
212        CanId::extended(raw)
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219
220    #[test]
221    fn a_standard_identifier_is_not_j1939() {
222        assert_eq!(J1939Id::from_id(CanId::standard(0x100)), None);
223    }
224
225    #[test]
226    fn the_engine_speed_broadcast_decodes() {
227        let message = J1939Id::from_id(CanId::extended(0x0CF0_0400)).unwrap();
228        assert_eq!(message.priority(), 3);
229        assert_eq!(message.pgn(), 61_444);
230        assert_eq!(message.source(), 0x00);
231        assert_eq!(message.pdu_format(), 240);
232        assert!(message.is_broadcast());
233        assert_eq!(message.destination(), None);
234    }
235
236    #[test]
237    fn an_addressed_request_decodes_its_destination() {
238        // A request (PGN 59904, PDU format 0xEA) to address 0x21 from 0x01, priority 6.
239        let message = J1939Id::from_id(CanId::extended(0x18EA_2101)).unwrap();
240        assert_eq!(message.priority(), 6);
241        assert_eq!(message.pgn(), 59_904);
242        assert_eq!(message.source(), 0x01);
243        assert!(!message.is_broadcast());
244        assert_eq!(message.destination(), Some(0x21));
245    }
246
247    #[test]
248    fn a_broadcast_identifier_round_trips() {
249        let id = CanId::extended(0x0CF0_0400);
250        assert_eq!(J1939Id::from_id(id).unwrap().to_id(), id);
251    }
252
253    #[test]
254    fn an_addressed_identifier_round_trips() {
255        let id = CanId::extended(0x18EA_2101);
256        assert_eq!(J1939Id::from_id(id).unwrap().to_id(), id);
257    }
258
259    #[test]
260    fn composing_an_addressed_message_places_the_destination() {
261        let message = J1939Id::from_parts(6, 59_904, 0x01, 0x21);
262        assert_eq!(message.to_id(), CanId::extended(0x18EA_2101));
263        assert_eq!(message.destination(), Some(0x21));
264    }
265
266    #[test]
267    fn composing_a_broadcast_message_ignores_the_destination() {
268        // PGN 61444 is broadcast, so the destination argument has no effect.
269        let with_dest = J1939Id::from_parts(3, 61_444, 0x00, 0x55);
270        let without = J1939Id::from_parts(3, 61_444, 0x00, 0x00);
271        assert_eq!(with_dest.to_id(), without.to_id());
272        assert_eq!(with_dest.to_id(), CanId::extended(0x0CF0_0400));
273    }
274
275    #[test]
276    fn the_data_page_bits_round_trip() {
277        // Identifiers that set the data-page bit (0x0DF00400) and both the extended and
278        // data-page bits (0x03F00400) must survive decode and re-encode unchanged.
279        for raw in [0x0DF0_0400u32, 0x03F0_0400] {
280            let id = CanId::extended(raw);
281            assert_eq!(J1939Id::from_id(id).unwrap().to_id(), id);
282        }
283    }
284
285    #[test]
286    fn a_data_page_one_pgn_decodes_above_the_first_page() {
287        let message = J1939Id::from_id(CanId::extended(0x0DF0_0400)).unwrap();
288        assert_eq!(message.pgn(), 0x1_F004);
289        assert_eq!(message.priority(), 3);
290        assert!(message.is_broadcast());
291    }
292}