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}