pamoja_radios/sx126x/status.rs
1//! What an SX126x reports back: its status byte, its errors, and a received packet.
2//!
3//! Each decoder follows section 13.5 and 13.6 of the SX1261/2 datasheet. Signal levels
4//! come out as [`Decibels`], held to a hundredth of a decibel, since the chip reports
5//! power in half decibels and SNR in quarters, so nothing is rounded away.
6
7use pamoja_lora::budget::Decibels;
8
9/// The operating mode in bits 6 to 4 of the status byte, from Table 13-76.
10#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
11pub enum ChipMode {
12 /// Standby on the 13 MHz RC oscillator (0x2).
13 StandbyRc,
14 /// Standby on the 32 MHz crystal oscillator (0x3).
15 StandbyXosc,
16 /// Frequency synthesis (0x4).
17 Fs,
18 /// Receive (0x5).
19 Rx,
20 /// Transmit (0x6).
21 Tx,
22 /// A value the datasheet leaves unused or reserved (0x0, 0x1, 0x7).
23 Other(u8),
24}
25
26/// The outcome of the last command, in bits 3 to 1 of the status byte, from Table 13-76.
27#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
28pub enum CommandStatus {
29 /// A packet was received and its data can be read (0x2).
30 DataAvailable,
31 /// A transaction took too long and tripped the internal watchdog (0x3).
32 Timeout,
33 /// The opcode was invalid or the parameters were the wrong length (0x4).
34 ProcessingError,
35 /// The command was understood but could not be carried out (0x5).
36 ExecutionFailure,
37 /// The transmission of the current packet ended (0x6).
38 TxDone,
39 /// A value the datasheet leaves reserved (0x0, 0x1, 0x7).
40 Other(u8),
41}
42
43/// The status byte an SX126x returns.
44///
45/// # Examples
46///
47/// ```
48/// use pamoja_radios::sx126x::status::{ChipMode, CommandStatus, Status};
49///
50/// // 0x2C: standby on the RC oscillator after a transmission ended.
51/// let status = Status::from_byte(0x2C);
52/// assert_eq!(status.chip_mode, ChipMode::StandbyRc);
53/// assert_eq!(status.command_status, CommandStatus::TxDone);
54/// ```
55#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
56pub struct Status {
57 /// The mode the chip is in.
58 pub chip_mode: ChipMode,
59 /// How the last command went.
60 pub command_status: CommandStatus,
61}
62
63impl Status {
64 /// Decodes a status byte.
65 ///
66 /// # Arguments
67 ///
68 /// * `byte` - the status byte.
69 ///
70 /// # Returns
71 ///
72 /// The chip mode and the command status it carries.
73 pub const fn from_byte(byte: u8) -> Status {
74 let chip_mode = match (byte >> 4) & 0x07 {
75 0x2 => ChipMode::StandbyRc,
76 0x3 => ChipMode::StandbyXosc,
77 0x4 => ChipMode::Fs,
78 0x5 => ChipMode::Rx,
79 0x6 => ChipMode::Tx,
80 other => ChipMode::Other(other),
81 };
82 let command_status = match (byte >> 1) & 0x07 {
83 0x2 => CommandStatus::DataAvailable,
84 0x3 => CommandStatus::Timeout,
85 0x4 => CommandStatus::ProcessingError,
86 0x5 => CommandStatus::ExecutionFailure,
87 0x6 => CommandStatus::TxDone,
88 other => CommandStatus::Other(other),
89 };
90 Status {
91 chip_mode,
92 command_status,
93 }
94 }
95
96 /// Reports whether the last command failed.
97 ///
98 /// # Returns
99 ///
100 /// `true` for a watchdog timeout, a processing error, or an execution failure.
101 pub const fn is_error(&self) -> bool {
102 matches!(
103 self.command_status,
104 CommandStatus::Timeout
105 | CommandStatus::ProcessingError
106 | CommandStatus::ExecutionFailure
107 )
108 }
109}
110
111/// Where a received payload sits in the data buffer, from GetRxBufferStatus.
112#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
113pub struct RxBufferStatus {
114 /// The length of the last received payload in bytes.
115 pub payload_len: u8,
116 /// The buffer offset of its first byte.
117 pub start: u8,
118}
119
120impl RxBufferStatus {
121 /// Decodes a GetRxBufferStatus answer.
122 ///
123 /// # Arguments
124 ///
125 /// * `bytes` - PayloadLengthRx and RxStartBufferPointer, in that order.
126 ///
127 /// # Returns
128 ///
129 /// The payload length and its offset.
130 pub const fn from_bytes(bytes: [u8; 2]) -> RxBufferStatus {
131 RxBufferStatus {
132 payload_len: bytes[0],
133 start: bytes[1],
134 }
135 }
136}
137
138/// The signal levels of the last LoRa packet received, from GetPacketStatus.
139///
140/// # Examples
141///
142/// ```
143/// use pamoja_radios::sx126x::status::PacketStatus;
144///
145/// // RssiPkt 0xDB, SnrPkt 0xF6, SignalRssiPkt 0xE0.
146/// let packet = PacketStatus::from_bytes([0xDB, 0xF6, 0xE0]);
147/// assert_eq!(packet.rssi_dbm.to_string(), "-109.50");
148/// assert_eq!(packet.snr_db.to_string(), "-2.50");
149/// assert_eq!(packet.signal_rssi_dbm.to_string(), "-112.00");
150/// ```
151#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
152pub struct PacketStatus {
153 /// The RSSI averaged over the packet, in dBm: -RssiPkt/2.
154 pub rssi_dbm: Decibels,
155 /// The estimated signal-to-noise ratio, in dB: SnrPkt/4, a two's complement byte.
156 pub snr_db: Decibels,
157 /// The estimated RSSI of the LoRa signal after despreading, in dBm: -SignalRssiPkt/2.
158 pub signal_rssi_dbm: Decibels,
159}
160
161impl PacketStatus {
162 /// Decodes a LoRa GetPacketStatus answer.
163 ///
164 /// # Arguments
165 ///
166 /// * `bytes` - RssiPkt, SnrPkt, and SignalRssiPkt, in that order.
167 ///
168 /// # Returns
169 ///
170 /// The three levels, exact to a hundredth of a decibel.
171 pub const fn from_bytes(bytes: [u8; 3]) -> PacketStatus {
172 PacketStatus {
173 rssi_dbm: Decibels::from_hundredths(-(bytes[0] as i32) * 50),
174 snr_db: Decibels::from_hundredths((bytes[1] as i8) as i32 * 25),
175 signal_rssi_dbm: Decibels::from_hundredths(-(bytes[2] as i32) * 50),
176 }
177 }
178}
179
180/// Decodes the instantaneous RSSI of a GetRssiInst answer, -RssiInst/2 in dBm.
181///
182/// # Arguments
183///
184/// * `byte` - the RssiInst byte.
185///
186/// # Returns
187///
188/// The received power in dBm.
189pub const fn rssi_inst_dbm(byte: u8) -> Decibels {
190 Decibels::from_hundredths(-(byte as i32) * 50)
191}
192
193/// The errors an SX126x has recorded, from GetDeviceErrors and Table 13-86.
194#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
195pub struct DeviceErrors(u16);
196
197impl DeviceErrors {
198 /// Bit 0: the RC64k calibration failed.
199 pub const RC64K_CALIBRATION: DeviceErrors = DeviceErrors(1 << 0);
200 /// Bit 1: the RC13M calibration failed.
201 pub const RC13M_CALIBRATION: DeviceErrors = DeviceErrors(1 << 1);
202 /// Bit 2: the PLL calibration failed.
203 pub const PLL_CALIBRATION: DeviceErrors = DeviceErrors(1 << 2);
204 /// Bit 3: the ADC calibration failed.
205 pub const ADC_CALIBRATION: DeviceErrors = DeviceErrors(1 << 3);
206 /// Bit 4: the image calibration failed.
207 pub const IMAGE_CALIBRATION: DeviceErrors = DeviceErrors(1 << 4);
208 /// Bit 5: the crystal oscillator failed to start. Expected at power on and after a
209 /// cold wake when a TCXO is fitted, and cleared with ClearDeviceErrors.
210 pub const XOSC_START: DeviceErrors = DeviceErrors(1 << 5);
211 /// Bit 6: the PLL failed to lock.
212 pub const PLL_LOCK: DeviceErrors = DeviceErrors(1 << 6);
213 /// Bit 8: the power amplifier failed to ramp.
214 pub const PA_RAMP: DeviceErrors = DeviceErrors(1 << 8);
215
216 /// Decodes a GetDeviceErrors answer.
217 ///
218 /// # Arguments
219 ///
220 /// * `bytes` - the OpError bytes, most significant first.
221 ///
222 /// # Returns
223 ///
224 /// The recorded errors.
225 pub const fn from_bytes(bytes: [u8; 2]) -> DeviceErrors {
226 DeviceErrors(u16::from_be_bytes(bytes))
227 }
228
229 /// Returns the raw error bits.
230 ///
231 /// # Returns
232 ///
233 /// The OpError value.
234 pub const fn bits(self) -> u16 {
235 self.0
236 }
237
238 /// Reports whether every error of another set was recorded.
239 ///
240 /// # Arguments
241 ///
242 /// * `other` - the errors to look for.
243 ///
244 /// # Returns
245 ///
246 /// `true` when all of them are set.
247 pub const fn contains(self, other: DeviceErrors) -> bool {
248 self.0 & other.0 == other.0
249 }
250
251 /// Reports whether no error was recorded.
252 ///
253 /// # Returns
254 ///
255 /// `true` when no bit is set.
256 pub const fn is_empty(self) -> bool {
257 self.0 == 0
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 #[test]
266 fn every_chip_mode_and_command_status_decodes() {
267 let modes = [
268 (0x2, ChipMode::StandbyRc),
269 (0x3, ChipMode::StandbyXosc),
270 (0x4, ChipMode::Fs),
271 (0x5, ChipMode::Rx),
272 (0x6, ChipMode::Tx),
273 (0x0, ChipMode::Other(0)),
274 ];
275 for (bits, mode) in modes {
276 assert_eq!(Status::from_byte(bits << 4).chip_mode, mode);
277 }
278 let outcomes = [
279 (0x2, CommandStatus::DataAvailable),
280 (0x3, CommandStatus::Timeout),
281 (0x4, CommandStatus::ProcessingError),
282 (0x5, CommandStatus::ExecutionFailure),
283 (0x6, CommandStatus::TxDone),
284 (0x1, CommandStatus::Other(1)),
285 ];
286 for (bits, outcome) in outcomes {
287 assert_eq!(Status::from_byte(bits << 1).command_status, outcome);
288 }
289 }
290
291 #[test]
292 fn the_failing_command_statuses_are_errors() {
293 assert!(Status::from_byte(0x3 << 1).is_error());
294 assert!(Status::from_byte(0x4 << 1).is_error());
295 assert!(Status::from_byte(0x5 << 1).is_error());
296 assert!(!Status::from_byte(0x6 << 1).is_error());
297 assert!(!Status::from_byte(0x2 << 1).is_error());
298 }
299
300 #[test]
301 fn the_packet_status_follows_the_datasheet_formulas() {
302 for raw in [0u8, 1, 0x7F, 0x80, 0xFF] {
303 let packet = PacketStatus::from_bytes([raw, raw, raw]);
304 assert_eq!(packet.rssi_dbm.hundredths(), -(i32::from(raw)) * 100 / 2);
305 assert_eq!(packet.snr_db.hundredths(), i32::from(raw as i8) * 100 / 4);
306 assert_eq!(packet.signal_rssi_dbm, packet.rssi_dbm);
307 }
308 assert_eq!(rssi_inst_dbm(0x6F), Decibels::from_hundredths(-5_550));
309 }
310
311 #[test]
312 fn the_buffer_status_and_device_errors_decode() {
313 assert_eq!(
314 RxBufferStatus::from_bytes([0x0A, 0x80]),
315 RxBufferStatus {
316 payload_len: 10,
317 start: 0x80
318 }
319 );
320 let errors = DeviceErrors::from_bytes([0x01, 0x20]);
321 assert!(errors.contains(DeviceErrors::PA_RAMP));
322 assert!(errors.contains(DeviceErrors::XOSC_START));
323 assert!(!errors.contains(DeviceErrors::PLL_LOCK));
324 assert!(DeviceErrors::default().is_empty());
325 }
326}