can_types/protocol/j1939/
pgn.rs

1// Copyright (c) 2024 Nathan H. Keough
2//
3// This work is dual-licensed under MIT OR Apache 2.0 (or any later version).
4// You may choose between one of them if you use this work.
5//
6// For further detail, please refer to the individual licenses located at the root of this crate.
7
8//! # J1939 Parameter Group Number (PGN)
9//!
10//! **Description:**
11//! The Parameter Group Number (PGN) is a key component in the J1939 protocol that identifies a specific data group
12//! within the CAN bus network. Each PGN corresponds to a particular type of message or data set, facilitating structured
13//! communication between electronic control units (ECUs) in a vehicle or machine.
14//!
15//! - **Function:** PGNs categorize and standardize the types of messages transmitted over the J1939 network, allowing
16//!   different devices to understand and process the data correctly.
17//! - **Format:** PGNs are 18-bit identifiers, which are part of the 29-bit extended frame format. The structure
18//!   of a PGN includes fields such as the priority, data page, and parameter group number itself.
19//! - **Usage:** Each PGN represents a different parameter group, such as engine parameters, vehicle diagnostics,
20//!   or environmental data. For example, a specific PGN might be used to report engine temperature, while another
21//!   could be used for transmission status.
22//!
23//! **Examples of PGNs:**
24//! - *PGN 61444:* Provides information on the engine temperature.
25//! - *PGN 65265:* Transmits data related to the vehicle's diagnostic information.
26//!
27//! **Source Documents:**
28//! - *SAE J1939-21*
29//! - *SAE J1939-71*
30
31if_alloc! {
32    use crate::alloc::{string::String, fmt::format};
33}
34
35use bitfield_struct::bitfield;
36
37use crate::{conversion::Conversion, identifier::Id, protocol::j1939::identifier::J1939};
38
39use super::address::DestinationAddr;
40
41/// Represents the assignment type of a Protocol Data Unit (PDU).
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum PduAssignment {
44    /// Society of Automotive Engineers (SAE) assigned PDU.  
45    /// Contains the PDU value.
46    Sae(u32),
47    /// Manufacturer/proprietary assigned PDU.  
48    /// Contains the PDU value.
49    Manufacturer(u32),
50    /// Unknown or unrecognized PDU assignment.
51    /// Contains the PDU value.
52    Unknown(u32),
53}
54
55/// Represents the format of a Protocol Data Unit (PDU).
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub enum PduFormat {
58    /// PDU format 1.  
59    /// Contains PDU format value.
60    Pdu1(u8),
61    /// PDU format 2.  
62    /// Contains PDU format value.
63    Pdu2(u8),
64}
65
66/// Represents the communication mode.
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum CommunicationMode {
69    /// Point-to-Point communication mode.  
70    /// This PDU communication variant may contain a destination address.
71    P2P,
72    /// Broadcast communication mode.  
73    Broadcast,
74}
75
76/// Represents the group extension.
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum GroupExtension {
79    /// No group extension.
80    None,
81    /// Group extension with a specific value.
82    Some(u8),
83}
84
85/// Bitfield representation of 18-bit Parameter Group Number (PGN).
86///
87/// ### Repr: `u32`
88///
89/// | Field        | Size (bits) |
90/// |--------------|-------------|
91/// | Padding      | 14          |
92/// | Reserved     | 1           |
93/// | Data Page    | 1           |
94/// | PDU Format   | 8           |
95/// | PDU Specific | 8           |
96#[bitfield(u32, order = Msb, conversion = false)]
97#[derive(PartialEq, Eq)]
98pub struct Pgn {
99    #[bits(14)]
100    __: u16,
101    #[bits(1)]
102    reserved_bits: bool,
103    #[bits(1)]
104    data_page_bits: bool,
105    #[bits(8)]
106    pdu_format_bits: u8,
107    #[bits(8)]
108    pdu_specific_bits: u8,
109}
110
111impl Conversion<u32> for Pgn {
112    type Error = anyhow::Error;
113
114    /// Creates a new [`Pgn`] bitfield from a 32-bit integer.
115    #[inline]
116    fn from_bits(bits: u32) -> Self {
117        Self(bits)
118    }
119
120    /// Creates a new [`Pgn`] bitfield from a base-16 (hex) string slice.
121    #[inline]
122    fn from_hex(hex_str: &str) -> Self {
123        let bits = u32::from_str_radix(hex_str, 16).unwrap_or_default();
124
125        Self(bits)
126    }
127
128    /// Creates a new [`Pgn`] bitfield from a 32-bit integer.
129    /// # Errors
130    /// - Never (conversion is trivial)
131    #[inline]
132    fn try_from_bits(bits: u32) -> Result<Self, Self::Error> {
133        if bits > 0x3FFFF {
134            return Err(anyhow::anyhow!(
135                "PGN bits out of range! Valid range is 0x0000..0xFFFF - got {bits:#04X}"
136            ));
137        }
138        Ok(Self(bits))
139    }
140
141    /// Creates a new [`Pgn`] bitfield from a base-16 (hex) string slice.
142    /// # Errors
143    /// - If failed to parse input hexadecimal string slice.
144    /// - If value out of range for valid 18-bit PGNs.
145    #[inline]
146    fn try_from_hex(hex_str: &str) -> Result<Self, Self::Error> {
147        let bits = u32::from_str_radix(hex_str, 16).map_err(anyhow::Error::msg)?;
148        if bits > 0x3FFFF {
149            return Err(anyhow::anyhow!(
150                "PGN bits out of range! Valid range is 0x0000..0xFFFF - got {bits:#04X}"
151            ));
152        }
153        Ok(Self(bits))
154    }
155
156    /// Creates a new 32-bit integer from the [`Pgn`] bitfield.
157    #[inline]
158    fn into_bits(self) -> u32 {
159        self.0
160    }
161
162    /// Creates a new base-16 (hex) `String` from the [`Pgn`] bitfield.
163    /// # Requires
164    /// - `alloc`
165    #[inline]
166    #[cfg(feature = "alloc")]
167    fn into_hex(self) -> String {
168        format(format_args!("{:05X}", self.into_bits()))
169    }
170}
171
172impl Pgn {
173    /// Returns the PDU format based on the parsed bits.
174    ///
175    /// # Returns
176    /// - `PduFormat::Pdu1(bits)` if the PDU format value is less than 240.
177    /// - `PduFormat::Pdu2(bits)` otherwise.
178    #[inline]
179    #[must_use]
180    pub const fn pdu_format(&self) -> PduFormat {
181        match (self.pdu_format_bits() < 240, self.pdu_format_bits()) {
182            (true, a) => PduFormat::Pdu1(a),
183            (false, b) => PduFormat::Pdu2(b),
184        }
185    }
186
187    /// Returns the group extension based on the parsed bits.
188    ///
189    /// # Returns
190    /// - `GroupExtension::None` if the PDU format is `Pdu1`.
191    /// - `GroupExtension::Some(bits)` if the PDU format is `Pdu2`.
192    #[inline]
193    #[must_use]
194    pub const fn group_extension(&self) -> GroupExtension {
195        match self.pdu_format() {
196            PduFormat::Pdu1(_) => GroupExtension::None,
197            PduFormat::Pdu2(_) => GroupExtension::Some(self.pdu_specific_bits()),
198        }
199    }
200
201    /// Returns the destination address based on the parsed PDU format.
202    ///
203    /// # Returns
204    /// - `DestinationAddress::Some(bits)` if the PDU format is `Pdu1`.
205    /// - `DestinationAddress::None` if the PDU format is `Pdu2`.
206    #[inline]
207    #[must_use]
208    pub const fn destination_address(&self) -> DestinationAddr {
209        match self.pdu_format() {
210            PduFormat::Pdu1(_) => DestinationAddr::Some(self.pdu_specific_bits()),
211            PduFormat::Pdu2(_) => DestinationAddr::None,
212        }
213    }
214
215    /// Returns the communication mode based on the parsed PDU format.
216    ///
217    /// # Returns
218    /// - `CommunicationMode::P2P` if the PDU format is `Pdu1`.
219    /// - `CommunicationMode::Broadcast` if the PDU format is `Pdu2`.
220    #[inline]
221    #[must_use]
222    pub const fn communication_mode(&self) -> CommunicationMode {
223        match self.pdu_format() {
224            PduFormat::Pdu1(_) => CommunicationMode::P2P,
225            PduFormat::Pdu2(_) => CommunicationMode::Broadcast,
226        }
227    }
228
229    /// Checks if the communication mode is point-to-point (P2P).
230    ///
231    /// # Returns
232    /// - `true` if the communication mode is `P2P`.
233    /// - `false` if the communication mode is `Broadcast`.
234    #[inline]
235    #[must_use]
236    pub const fn is_p2p(&self) -> bool {
237        match self.communication_mode() {
238            CommunicationMode::P2P => true,
239            CommunicationMode::Broadcast => false,
240        }
241    }
242
243    /// Checks if the communication mode is broadcast.
244    ///
245    /// # Returns
246    /// - `true` if the communication mode is `Broadcast`.
247    /// - `false` if the communication mode is `P2P`.
248    #[inline]
249    #[must_use]
250    pub const fn is_broadcast(&self) -> bool {
251        match self.communication_mode() {
252            CommunicationMode::P2P => false,
253            CommunicationMode::Broadcast => true,
254        }
255    }
256
257    /// Determines the PDU assignment based on the parsed bits.
258    ///
259    /// # Returns
260    /// - `PduAssignment::Sae(bits)` for known SAE-defined PDU assignments.
261    /// - `PduAssignment::Manufacturer(bits)` for manufacturer-defined PDU assignments.
262    /// - `PduAssignment::Unknown(bits)` for unrecognized PDU assignments.
263    #[must_use]
264    pub fn pdu_assignment(&self) -> PduAssignment {
265        match self.into_bits() {
266            0x0000_0000..=0x0000_EE00
267            | 0x0000_F000..=0x0000_FEFF
268            | 0x0001_0000..=0x0001_EE00
269            | 0x0001_F000..=0x0001_FEFF => PduAssignment::Sae(self.into_bits()),
270
271            0x0000_EF00 | 0x0000_FF00..=0x0000_FFFF | 0x0001_EF00 | 0x0001_FF00..=0x0001_FFFF => {
272                PduAssignment::Manufacturer(self.into_bits())
273            }
274            p => PduAssignment::Unknown(p),
275        }
276    }
277}
278
279impl Id<J1939> {
280    /// Computes the PGN bitfield value based on the 29-bit identifier fields.
281    ///
282    /// # Returns
283    /// The combined PGN bitfield value.
284    #[inline]
285    #[must_use]
286    pub const fn pgn_bits(&self) -> u32 {
287        let pgn_bitfield = Pgn::new()
288            .with_reserved_bits(self.reserved())
289            .with_data_page_bits(self.data_page())
290            .with_pdu_format_bits(self.pdu_format())
291            .with_pdu_specific_bits(self.pdu_specific());
292
293        pgn_bitfield.0
294    }
295
296    /// Constructs and returns a [`Pgn`] struct based on the 29-bit identifier fields.
297    ///
298    /// # Returns
299    /// A [`Pgn`] bitfield initialized with the 29-bit identifier fields.
300    #[inline]
301    #[must_use]
302    pub const fn pgn(&self) -> Pgn {
303        Pgn::new()
304            .with_reserved_bits(self.reserved())
305            .with_data_page_bits(self.data_page())
306            .with_pdu_format_bits(self.pdu_format())
307            .with_pdu_specific_bits(self.pdu_specific())
308    }
309}
310
311#[cfg(test)]
312mod pgn_tests {
313    // use crate::{
314    //     conversion::Conversion,
315    //     identifier::IdExtended,
316    //     pgn::{CommunicationMode, DestinationAddress, GroupExtension, PduAssignment, PduFormat},
317    // };
318
319    use super::*;
320    use crate::protocol::j1939::address::Addr;
321
322    #[test]
323    fn test_pdu_assignment() -> Result<(), anyhow::Error> {
324        let id_a = Id::<J1939>::try_from_hex("18FEF200")?;
325        let id_b = Id::<J1939>::try_from_hex("1CFE9201")?;
326        let id_c = Id::<J1939>::try_from_hex("10FF2121")?;
327        let id_d = Id::<J1939>::try_from_hex("0C00290B")?;
328
329        let assignment_a = id_a.pgn().pdu_assignment();
330        let assignment_b = id_b.pgn().pdu_assignment();
331        let assignment_c = id_c.pgn().pdu_assignment();
332        let assignment_d = id_d.pgn().pdu_assignment();
333
334        assert_eq!(PduAssignment::Sae(65266), assignment_a);
335        assert_eq!(PduAssignment::Sae(65170), assignment_b);
336        assert_eq!(PduAssignment::Manufacturer(65313), assignment_c);
337        assert_eq!(PduAssignment::Sae(41), assignment_d);
338
339        Ok(())
340    }
341
342    #[test]
343    fn test_communication_mode() -> Result<(), anyhow::Error> {
344        let id_a = Id::<J1939>::try_from_hex("18FEF200")?;
345        let id_b = Id::<J1939>::try_from_hex("1CFE9201")?;
346        let id_c = Id::<J1939>::try_from_hex("10FF2121")?;
347        let id_d = Id::<J1939>::try_from_hex("0C00290B")?;
348
349        let comms_mode_a = id_a.pgn().communication_mode();
350        let comms_mode_b = id_b.pgn().communication_mode();
351        let comms_mode_c = id_c.pgn().communication_mode();
352        let comms_mode_d = id_d.pgn().communication_mode();
353
354        assert_eq!(CommunicationMode::Broadcast, comms_mode_a);
355        assert_eq!(CommunicationMode::Broadcast, comms_mode_b);
356        assert_eq!(CommunicationMode::Broadcast, comms_mode_c);
357        assert_eq!(CommunicationMode::P2P, comms_mode_d);
358
359        Ok(())
360    }
361
362    #[test]
363    fn test_destination_address() -> Result<(), anyhow::Error> {
364        let id_a = Id::<J1939>::try_from_hex("18FEF200")?;
365        let id_b = Id::<J1939>::try_from_hex("1CFE9201")?;
366        let id_c = Id::<J1939>::try_from_hex("10FF2121")?;
367        let id_d = Id::<J1939>::try_from_hex("0C00290B")?;
368
369        let dest_addr_a = id_a.pgn().destination_address();
370        let dest_addr_b = id_b.pgn().destination_address();
371        let dest_addr_c = id_c.pgn().destination_address();
372        let dest_addr_d = id_d.pgn().destination_address();
373
374        assert_eq!(DestinationAddr::None, dest_addr_a);
375        assert_eq!(DestinationAddr::None, dest_addr_b);
376        assert_eq!(DestinationAddr::None, dest_addr_c);
377        assert_eq!(DestinationAddr::Some(41), dest_addr_d);
378        assert_eq!(Some(Addr::RetarderExhaustEngine1), dest_addr_d.lookup());
379
380        Ok(())
381    }
382
383    #[test]
384    fn test_group_extension() -> Result<(), anyhow::Error> {
385        let id_a = Id::<J1939>::try_from_hex("18FEF200")?;
386        let id_b = Id::<J1939>::try_from_hex("1CFE9201")?;
387        let id_c = Id::<J1939>::try_from_hex("10FF2121")?;
388        let id_d = Id::<J1939>::try_from_hex("0C00290B")?;
389
390        let group_ext_a = id_a.pgn().group_extension();
391        let group_ext_b = id_b.pgn().group_extension();
392        let group_ext_c = id_c.pgn().group_extension();
393        let group_ext_d = id_d.pgn().group_extension();
394
395        assert_eq!(GroupExtension::Some(242), group_ext_a);
396        assert_eq!(GroupExtension::Some(146), group_ext_b);
397        assert_eq!(GroupExtension::Some(33), group_ext_c);
398        assert_eq!(GroupExtension::None, group_ext_d);
399
400        Ok(())
401    }
402
403    #[test]
404    fn test_pdu_format() -> Result<(), anyhow::Error> {
405        let id_a = Id::<J1939>::try_from_hex("18FEF200")?;
406        let id_b = Id::<J1939>::try_from_hex("1CFE9201")?;
407        let id_c = Id::<J1939>::try_from_hex("10FF2121")?;
408        let id_d = Id::<J1939>::try_from_hex("0C00290B")?;
409
410        let pdu_format_a = id_a.pgn().pdu_format();
411        let pdu_format_b = id_b.pgn().pdu_format();
412        let pdu_format_c = id_c.pgn().pdu_format();
413        let pdu_format_d = id_d.pgn().pdu_format();
414
415        assert_eq!(PduFormat::Pdu2(254), pdu_format_a);
416        assert_eq!(PduFormat::Pdu2(254), pdu_format_b);
417        assert_eq!(PduFormat::Pdu2(255), pdu_format_c);
418        assert_eq!(PduFormat::Pdu1(0), pdu_format_d);
419
420        Ok(())
421    }
422
423    #[test]
424    fn test_pgn_bits() -> Result<(), anyhow::Error> {
425        let id_a = Id::<J1939>::try_from_hex("18FEF200")?;
426        let id_b = Id::<J1939>::try_from_hex("1CFE9201")?;
427        let id_c = Id::<J1939>::try_from_hex("10FF2121")?;
428        let id_d = Id::<J1939>::try_from_hex("0C00290B")?;
429
430        let pgn_bits_a = id_a.pgn();
431        let pgn_bits_b = id_b.pgn();
432        let pgn_bits_c = id_c.pgn();
433        let pgn_bits_d = id_d.pgn();
434
435        assert_eq!(65266, pgn_bits_a.into_bits());
436        assert_eq!(65170, pgn_bits_b.into_bits());
437        assert_eq!(65313, pgn_bits_c.into_bits());
438        assert_eq!(41, pgn_bits_d.into_bits());
439
440        Ok(())
441    }
442}