saelient/
id.rs

1/// PDU format.
2///
3/// See J1939™-21 section 5.3 for more details.
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5#[cfg_attr(feature = "defmt-1", derive(defmt::Format))]
6pub enum PduFormat {
7    /// PS = DA (destination address)
8    Pdu1(u8),
9    /// PS = GE (global extension)
10    Pdu2(u8),
11}
12
13impl From<u8> for PduFormat {
14    fn from(value: u8) -> Self {
15        match value {
16            ..=239 => PduFormat::Pdu1(value),
17            240.. => PduFormat::Pdu2(value),
18        }
19    }
20}
21
22impl From<&Pgn> for PduFormat {
23    fn from(pgn: &Pgn) -> Self {
24        let byte = u32::from(pgn) >> 8 & 0xff;
25        Self::from(byte as u8)
26    }
27}
28
29impl From<Pgn> for PduFormat {
30    fn from(pgn: Pgn) -> Self {
31        Self::from(&pgn)
32    }
33}
34
35/// J1939 identifier.
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37#[cfg_attr(feature = "defmt-1", derive(defmt::Format))]
38pub struct Id(u32);
39
40impl Id {
41    /// Create a new [`Id`] from a raw identifier value.
42    ///
43    /// Masked to 29 bits to ensure the id is valid.
44    pub fn new(raw: u32) -> Self {
45        Self(raw & embedded_can::ExtendedId::MAX.as_raw())
46    }
47
48    pub fn builder() -> IdBuilder {
49        IdBuilder::new()
50    }
51
52    /// Get the inner 29-bit value.
53    pub fn as_raw(&self) -> u32 {
54        self.0
55    }
56
57    /// Priority (P)
58    pub fn priority(&self) -> u8 {
59        (self.0 >> 26) as u8
60    }
61
62    /// Data page (DP)
63    pub fn dp(&self) -> bool {
64        (self.0 >> 24 & 1) != 0
65    }
66
67    /// Extended data page (EDP)
68    pub fn edp(&self) -> bool {
69        (self.0 >> 25 & 1) != 0
70    }
71
72    /// Parameter group number (PGN)
73    pub fn pgn(&self) -> Pgn {
74        let raw = self.0 >> 8;
75        let raw = match self.pf() {
76            PduFormat::Pdu1(_) => raw & 0xFF00,
77            PduFormat::Pdu2(_) => raw & 0xFFFF,
78        };
79        Pgn::from(raw)
80    }
81
82    /// PDU format (PF)
83    pub fn pf(&self) -> PduFormat {
84        let format = ((self.0 >> 16) & 0xFF) as u8;
85        PduFormat::from(format)
86    }
87
88    /// PDU specific (PS)
89    pub fn ps(&self) -> u8 {
90        ((self.0 >> 8) & 0xff) as u8
91    }
92
93    /// PDU specific destination address (DA)
94    pub fn da(&self) -> Option<u8> {
95        if self.ps() <= 239 {
96            Some(self.ps())
97        } else {
98            None
99        }
100    }
101
102    /// PDU specific group extension (GE)
103    pub fn ge(&self) -> Option<u8> {
104        if self.ps() >= 240 {
105            Some(self.ps())
106        } else {
107            None
108        }
109    }
110
111    /// Source address (SA)
112    pub fn sa(&self) -> u8 {
113        (self.0 & 0xff) as u8
114    }
115}
116
117impl From<embedded_can::ExtendedId> for Id {
118    fn from(id: embedded_can::ExtendedId) -> Self {
119        Self(id.as_raw())
120    }
121}
122
123#[allow(clippy::unwrap_used)]
124impl From<Id> for embedded_can::ExtendedId {
125    fn from(id: Id) -> Self {
126        embedded_can::ExtendedId::new(id.0).unwrap()
127    }
128}
129
130impl From<Id> for embedded_can::Id {
131    fn from(id: Id) -> Self {
132        embedded_can::Id::Extended(id.into())
133    }
134}
135
136#[derive(Debug, Clone, Copy)]
137#[cfg_attr(feature = "defmt-1", derive(defmt::Format))]
138pub struct IdBuilder {
139    priority: Option<u8>,
140    pgn: Option<Pgn>,
141    sa: Option<u8>,
142    da: Option<u8>,
143}
144
145impl IdBuilder {
146    /// Creates a new [`IdBuilder`]
147    ///
148    /// A source address and PGN must be provided. If a PDU1 PF is selected, a
149    /// destination address must also be provided.
150    pub fn new() -> Self {
151        Self {
152            priority: None,
153            pgn: None,
154            sa: None,
155            da: None,
156        }
157    }
158
159    /// Priority.
160    ///
161    /// Default is 6 if not set.
162    pub fn priority(mut self, p: u8) -> Self {
163        assert!(p <= 7);
164        self.priority = Some(p);
165        self
166    }
167
168    /// Parameter group number.
169    ///
170    /// Must be set or `.build()` will panic.
171    pub fn pgn(mut self, pgn: Pgn) -> Self {
172        self.pgn = Some(pgn);
173        self
174    }
175
176    /// Source address.
177    pub fn sa(mut self, sa: u8) -> Self {
178        self.sa = Some(sa);
179        self
180    }
181
182    /// Destination address.
183    ///
184    /// Required for PDU1 messages or `.build()` will panic.
185    pub fn da(mut self, da: u8) -> Self {
186        self.da = Some(da);
187        self
188    }
189
190    pub fn build(self) -> Option<Id> {
191        let mut id = ((self.priority.unwrap_or(6) as u32) << 26)
192            | (u32::from(self.pgn?) << 8)
193            | (self.sa? as u32);
194
195        if let PduFormat::Pdu1(_) = Id::new(id).pf() {
196            id |= (self.da? as u32) << 8;
197        }
198
199        Some(Id(id))
200    }
201}
202
203impl Default for IdBuilder {
204    fn default() -> Self {
205        Self::new()
206    }
207}
208
209/// Parameter group number (PGN)
210#[derive(Debug, Clone, Copy, PartialEq, Eq)]
211#[cfg_attr(feature = "defmt-1", derive(defmt::Format))]
212pub enum Pgn {
213    /// RQST2 - Request 2
214    Request2,
215    /// XFER - Transfer
216    Transfer,
217    /// DM17 - Boot Load Data
218    BootLoadData,
219    /// DM16 - Binary Data Transfer
220    BinaryDataTransfer,
221    /// DM15 - Memory Access Response
222    MemoryAccessResponse,
223    /// DM14 - Memory Access Request
224    MemoryAccessRequest,
225    /// RQST - Request
226    Request,
227    /// ACKM - Acknowledgement
228    Acknowledgement,
229    /// TP.DT - Transport Protocol - Data Transfer
230    TransportProtocolDataTransfer,
231    /// TP.CM - Transport Protocol - Connection Mgmt
232    TransportProtocolConnectionManagement,
233    /// PropA - Proprietary A
234    ProprietaryA,
235    /// PropA2 - Proprietary A2
236    ProprietaryA2,
237    /// PropB - Proprietary B
238    ProprietaryB(u8),
239    /// PropB2 - Proprietary B2
240    ProprietaryB2(u8),
241    /// Unknown PGN
242    Other(u32),
243}
244
245impl Pgn {
246    pub fn pf(&self) -> PduFormat {
247        PduFormat::from(*self)
248    }
249}
250
251impl From<u32> for Pgn {
252    fn from(value: u32) -> Self {
253        match value {
254            51456 => Self::Request2,
255            51712 => Self::Transfer,
256            54784 => Self::BootLoadData,
257            55040 => Self::BinaryDataTransfer,
258            55296 => Self::MemoryAccessResponse,
259            55552 => Self::MemoryAccessRequest,
260            59904 => Self::Request,
261            59392 => Self::Acknowledgement,
262            60160 => Self::TransportProtocolDataTransfer,
263            60416 => Self::TransportProtocolConnectionManagement,
264            61184 => Self::ProprietaryA,
265            126720 => Self::ProprietaryA2,
266            65280..=65535 => Self::ProprietaryB((value & 0xFF) as u8),
267            130816..=131071 => Self::ProprietaryB2((value & 0xFF) as u8),
268            _ => Self::Other(value),
269        }
270    }
271}
272
273impl From<&Pgn> for u32 {
274    fn from(value: &Pgn) -> Self {
275        match value {
276            Pgn::Request2 => 51456,
277            Pgn::Transfer => 51712,
278            Pgn::BootLoadData => 54784,
279            Pgn::BinaryDataTransfer => 55040,
280            Pgn::MemoryAccessResponse => 55296,
281            Pgn::MemoryAccessRequest => 55552,
282            Pgn::Request => 59904,
283            Pgn::Acknowledgement => 59392,
284            Pgn::TransportProtocolDataTransfer => 60160,
285            Pgn::TransportProtocolConnectionManagement => 60416,
286            Pgn::ProprietaryA => 61184,
287            Pgn::ProprietaryA2 => 126720,
288            Pgn::ProprietaryB(pgn) => (*pgn as u32) | 0xFF00,
289            Pgn::ProprietaryB2(pgn) => (*pgn as u32) | 0x1FF00,
290            Pgn::Other(pgn) => *pgn,
291        }
292    }
293}
294
295impl From<Pgn> for u32 {
296    fn from(value: Pgn) -> Self {
297        u32::from(&value)
298    }
299}
300
301#[cfg(test)]
302mod tests {
303    use super::*;
304
305    #[test]
306    fn proprietary_id() {
307        // example id taken from a DBC file.
308        // Note: it has the CAN_EFF_FLAG set but this will be masked.
309        let id = Id::new(2565821696);
310
311        assert_eq!(id.sa(), 0x00);
312        assert_eq!(id.da(), Some(0x55));
313        assert_eq!(id.pgn(), Pgn::ProprietaryA);
314        assert_eq!(id.pf(), PduFormat::Pdu1(0xEF));
315        assert_eq!(id.dp(), false);
316        assert_eq!(id.edp(), false);
317        assert_eq!(id.priority(), 6);
318    }
319
320    #[test]
321    fn builder() {
322        let id = IdBuilder::new()
323            .sa(0x00)
324            .da(0x55)
325            .pgn(Pgn::ProprietaryA)
326            .priority(6)
327            .build()
328            .unwrap();
329
330        assert_eq!(id, Id::new(2565821696));
331        assert_eq!(id.pf(), PduFormat::Pdu1(0xEF));
332    }
333
334    #[test]
335    fn pgn_pf() {
336        assert_eq!(PduFormat::from(Pgn::ProprietaryA), PduFormat::Pdu1(239));
337        assert_eq!(PduFormat::from(Pgn::ProprietaryB(0)), PduFormat::Pdu2(255));
338    }
339}