pamoja_can/signals.rs
1//! The eight data bytes of a J1939 frame, addressed by the signals inside them.
2//!
3//! A parameter group places each signal at a fixed byte offset, little-endian, with a
4//! scale and an offset the standard publishes. Reading or writing one by hand means
5//! slicing the payload and calling `from_le_bytes`, which is where an off-by-one goes
6//! unnoticed. This module does that addressing, and starts a payload filled with the byte
7//! the standard reserves for a signal the sender is not reporting, so a controller only
8//! writes the signals it actually has.
9
10/// The byte a J1939 sender writes for a signal it is not reporting.
11///
12/// A receiver reads this as "not available" rather than as a measurement, which is why a
13/// payload starts filled with it instead of with zeros.
14pub const NOT_AVAILABLE: u8 = 0xFF;
15
16/// The eight data bytes of a J1939 frame.
17///
18/// # Examples
19///
20/// ```
21/// use pamoja_can::Signals;
22///
23/// // Engine speed sits in bytes 4 and 5 of EEC1, at 0.125 rpm per bit.
24/// let mut payload = Signals::new();
25/// payload.set_u16(3, (1000.0 / 0.125) as u16);
26///
27/// assert_eq!(payload.u16(3), Some(8000));
28/// assert_eq!(payload.u8(0), Some(pamoja_can::NOT_AVAILABLE));
29/// ```
30#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31pub struct Signals {
32 bytes: [u8; 8],
33}
34
35impl Default for Signals {
36 fn default() -> Self {
37 Self::new()
38 }
39}
40
41impl Signals {
42 /// Builds a payload with every signal marked not available.
43 ///
44 /// # Returns
45 ///
46 /// Eight bytes of [`NOT_AVAILABLE`], ready for a sender to write only what it has.
47 pub fn new() -> Signals {
48 Signals {
49 bytes: [NOT_AVAILABLE; 8],
50 }
51 }
52
53 /// Reads a payload received off the bus.
54 ///
55 /// # Arguments
56 ///
57 /// * `bytes` - the eight data bytes of a received frame.
58 ///
59 /// # Returns
60 ///
61 /// The payload, ready for its signals to be read out.
62 pub fn from_bytes(bytes: [u8; 8]) -> Signals {
63 Signals { bytes }
64 }
65
66 /// Returns the eight data bytes, ready to put in a frame.
67 ///
68 /// # Returns
69 ///
70 /// The payload in wire order.
71 pub fn as_bytes(&self) -> &[u8; 8] {
72 &self.bytes
73 }
74
75 /// Writes a one-byte signal.
76 ///
77 /// # Arguments
78 ///
79 /// * `at` - the byte offset the parameter group places the signal at, `0..=7`.
80 /// * `value` - the raw value, already scaled as the group defines.
81 ///
82 /// # Returns
83 ///
84 /// The payload, so writes chain.
85 pub fn set_u8(&mut self, at: usize, value: u8) -> &mut Signals {
86 if at < 8 {
87 self.bytes[at] = value;
88 }
89 self
90 }
91
92 /// Writes a two-byte little-endian signal.
93 ///
94 /// # Arguments
95 ///
96 /// * `at` - the offset of the signal's first byte, `0..=6`.
97 /// * `value` - the raw value, already scaled as the group defines.
98 ///
99 /// # Returns
100 ///
101 /// The payload, so writes chain.
102 pub fn set_u16(&mut self, at: usize, value: u16) -> &mut Signals {
103 if at + 1 < 8 {
104 self.bytes[at..at + 2].copy_from_slice(&value.to_le_bytes());
105 }
106 self
107 }
108
109 /// Reads a one-byte signal.
110 ///
111 /// # Arguments
112 ///
113 /// * `at` - the byte offset the parameter group places the signal at.
114 ///
115 /// # Returns
116 ///
117 /// The raw value, or `None` if `at` is past the payload.
118 pub fn u8(&self, at: usize) -> Option<u8> {
119 self.bytes.get(at).copied()
120 }
121
122 /// Reads a two-byte little-endian signal.
123 ///
124 /// # Arguments
125 ///
126 /// * `at` - the offset of the signal's first byte.
127 ///
128 /// # Returns
129 ///
130 /// The raw value, or `None` if the signal would run past the payload.
131 pub fn u16(&self, at: usize) -> Option<u16> {
132 if at + 1 < 8 {
133 Some(u16::from_le_bytes([self.bytes[at], self.bytes[at + 1]]))
134 } else {
135 None
136 }
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn a_new_payload_reports_nothing() {
146 let payload = Signals::new();
147 assert_eq!(payload.as_bytes(), &[NOT_AVAILABLE; 8]);
148 assert_eq!(payload.u8(0), Some(NOT_AVAILABLE));
149 assert_eq!(payload.u16(0), Some(0xFFFF));
150 }
151
152 #[test]
153 fn a_signal_reads_back_from_where_it_was_written() {
154 let mut payload = Signals::new();
155 payload.set_u16(3, 8_000).set_u8(2, 125);
156
157 assert_eq!(payload.u16(3), Some(8_000));
158 assert_eq!(payload.u8(2), Some(125));
159 assert_eq!(payload.u8(0), Some(NOT_AVAILABLE));
160 assert_eq!(Signals::from_bytes(*payload.as_bytes()), payload);
161 }
162
163 #[test]
164 fn a_signal_past_the_payload_is_refused_rather_than_wrapped() {
165 let mut payload = Signals::new();
166 payload.set_u8(8, 1);
167 payload.set_u16(7, 1);
168
169 assert_eq!(payload.as_bytes(), &[NOT_AVAILABLE; 8]);
170 assert_eq!(payload.u8(8), None);
171 assert_eq!(payload.u16(7), None);
172 }
173}