Skip to main content

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}