uf_crsf/packets/
link_statistics.rs

1use crate::packets::CrsfPacket;
2use crate::packets::PacketType;
3use crate::CrsfParsingError;
4
5/// Represents a Link Statistics packet.
6///
7/// This packet provides statistics about the connection quality.
8/// Uplink is the connection from the ground to the UAV and downlink the opposite direction.
9#[derive(Clone, Debug, PartialEq)]
10#[cfg_attr(feature = "defmt", derive(defmt::Format))]
11pub struct LinkStatistics {
12    /// Uplink RSSI Antenna 1 (dBm * -1).
13    pub uplink_rssi_1: u8,
14    /// Uplink RSSI Antenna 2 (dBm * -1).
15    pub uplink_rssi_2: u8,
16    /// Uplink package success rate / link quality (%).
17    pub uplink_link_quality: u8,
18    /// Uplink SNR (dB).
19    pub uplink_snr: i8,
20    /// The currently active antenna.
21    pub active_antenna: u8,
22    /// RF profile, e.g., 4fps = 0, 50fps, 150fps.
23    pub rf_mode: u8,
24    /// Uplink TX power enum {0mW = 0, 10mW, 25mW, 100mW, 500mW, 1000mW, 2000mW, 250mW, 50mW}.
25    pub uplink_tx_power: u8,
26    /// Downlink RSSI (dBm * -1).
27    pub downlink_rssi: u8,
28    /// Downlink package success rate / link quality (%).
29    pub downlink_link_quality: u8,
30    /// Downlink SNR (dB).
31    pub downlink_snr: i8,
32}
33
34impl LinkStatistics {
35    #[allow(clippy::too_many_arguments)]
36    pub fn new(
37        uplink_rssi_1: u8,
38        uplink_rssi_2: u8,
39        uplink_link_quality: u8,
40        uplink_snr: i8,
41        active_antenna: u8,
42        rf_mode: u8,
43        uplink_tx_power: u8,
44        downlink_rssi: u8,
45        downlink_link_quality: u8,
46        downlink_snr: i8,
47    ) -> Result<Self, CrsfParsingError> {
48        Ok(Self {
49            uplink_rssi_1,
50            uplink_rssi_2,
51            uplink_link_quality,
52            uplink_snr,
53            active_antenna,
54            rf_mode,
55            uplink_tx_power,
56            downlink_rssi,
57            downlink_link_quality,
58            downlink_snr,
59        })
60    }
61}
62
63impl CrsfPacket for LinkStatistics {
64    const PACKET_TYPE: PacketType = PacketType::LinkStatistics;
65    // there are 8 fields for u8 linter has false positive that code tries
66    // to get number of bits.
67    const MIN_PAYLOAD_SIZE: usize = (3 + 5) * size_of::<u8>() + 2 * size_of::<i8>();
68
69    fn to_bytes(&self, buffer: &mut [u8]) -> Result<usize, CrsfParsingError> {
70        self.validate_buffer_size(buffer)?;
71        buffer[0] = self.uplink_rssi_1;
72        buffer[1] = self.uplink_rssi_2;
73        buffer[2] = self.uplink_link_quality;
74        buffer[3] = self.uplink_snr as u8;
75        buffer[4] = self.active_antenna;
76        buffer[5] = self.rf_mode;
77        buffer[6] = self.uplink_tx_power;
78        buffer[7] = self.downlink_rssi;
79        buffer[8] = self.downlink_link_quality;
80        buffer[9] = self.downlink_snr as u8;
81        Ok(Self::MIN_PAYLOAD_SIZE)
82    }
83
84    fn from_bytes(data: &[u8]) -> Result<Self, CrsfParsingError> {
85        if data.len() == Self::MIN_PAYLOAD_SIZE {
86            Ok(Self {
87                uplink_rssi_1: data[0],
88                uplink_rssi_2: data[1],
89                uplink_link_quality: data[2],
90                uplink_snr: data[3] as i8,
91                active_antenna: data[4],
92                rf_mode: data[5],
93                uplink_tx_power: data[6],
94                downlink_rssi: data[7],
95                downlink_link_quality: data[8],
96                downlink_snr: data[9] as i8,
97            })
98        } else {
99            Err(CrsfParsingError::InvalidPayloadLength)
100        }
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn test_link_statistics_to_bytes() {
110        assert_eq!(LinkStatistics::MIN_PAYLOAD_SIZE, 10);
111        let link_statistics = LinkStatistics {
112            uplink_rssi_1: 100,
113            uplink_rssi_2: 75,
114            uplink_link_quality: 90,
115            uplink_snr: -10,
116            active_antenna: 1,
117            rf_mode: 2,
118            uplink_tx_power: 20,
119            downlink_rssi: 110,
120            downlink_link_quality: 80,
121            downlink_snr: -5,
122        };
123
124        let mut buffer = [0u8; LinkStatistics::MIN_PAYLOAD_SIZE];
125        let _ = link_statistics.to_bytes(&mut buffer);
126
127        let expected_bytes: [u8; LinkStatistics::MIN_PAYLOAD_SIZE] =
128            [100, 75, 90, 246, 1, 2, 20, 110, 80, 251];
129
130        assert_eq!(buffer, expected_bytes);
131    }
132
133    #[test]
134    fn test_link_statistics_from_bytes() {
135        let data: [u8; LinkStatistics::MIN_PAYLOAD_SIZE] =
136            [100, 75, 90, 246, 1, 2, 20, 110, 80, 251];
137
138        let link_statistics = LinkStatistics::from_bytes(&data).unwrap();
139
140        assert_eq!(
141            link_statistics,
142            LinkStatistics {
143                uplink_rssi_1: 100,
144                uplink_rssi_2: 75,
145                uplink_link_quality: 90,
146                uplink_snr: -10,
147                active_antenna: 1,
148                rf_mode: 2,
149                uplink_tx_power: 20,
150                downlink_rssi: 110,
151                downlink_link_quality: 80,
152                downlink_snr: -5,
153            }
154        );
155    }
156
157    #[test]
158    fn test_link_statistics_round_trip() {
159        let link_statistics = LinkStatistics {
160            uplink_rssi_1: 100,
161            uplink_rssi_2: 75,
162            uplink_link_quality: 90,
163            uplink_snr: -10,
164            active_antenna: 1,
165            rf_mode: 2,
166            uplink_tx_power: 20,
167            downlink_rssi: 110,
168            downlink_link_quality: 80,
169            downlink_snr: -5,
170        };
171
172        let mut buffer = [0u8; LinkStatistics::MIN_PAYLOAD_SIZE];
173        link_statistics.to_bytes(&mut buffer).unwrap();
174
175        let round_trip_link_statistics = LinkStatistics::from_bytes(&buffer).unwrap();
176
177        assert_eq!(link_statistics, round_trip_link_statistics);
178    }
179
180    #[test]
181    fn test_edge_cases() {
182        let link_statistics = LinkStatistics {
183            uplink_rssi_1: 255,
184            uplink_rssi_2: 100,
185            uplink_link_quality: 100,
186            uplink_snr: -128,
187            active_antenna: 3,
188            rf_mode: 4,
189            uplink_tx_power: 50,
190            downlink_rssi: 200,
191            downlink_link_quality: 90,
192            downlink_snr: 127,
193        };
194
195        let mut buffer = [0u8; LinkStatistics::MIN_PAYLOAD_SIZE];
196        link_statistics.to_bytes(&mut buffer).unwrap();
197        let round_trip_link_statistics = LinkStatistics::from_bytes(&buffer).unwrap();
198        assert_eq!(link_statistics, round_trip_link_statistics);
199    }
200}