semtech_udp/packet/push_data/
mod.rs

1/*
2### 3.2. PUSH_DATA packet ###
3
4That packet type is used by the gateway mainly to forward the RF packets
5received, and associated metadata, to the server.
6
7Bytes  | Function
8:------:|---------------------------------------------------------------------
90      | protocol version = 2
101-2    | random token
113      | PUSH_DATA identifier 0x00
124-11   | Gateway unique identifier (MAC address)
1312-end | JSON object, starting with {, ending with }, see section 4
14 */
15mod rxpk;
16pub use rxpk::*;
17
18use super::{
19    push_ack, types, write_preamble, Error as PktError, Identifier, MacAddress, SerializablePacket,
20};
21use serde::{Deserialize, Serialize};
22use serde_repr::{Deserialize_repr, Serialize_repr};
23use std::io::{Cursor, Write};
24use types::{DataRate, Modulation};
25
26#[derive(Debug, Clone)]
27pub struct Packet {
28    pub random_token: u16,
29    pub gateway_mac: MacAddress,
30    pub data: Data,
31}
32
33impl Packet {
34    pub fn from_rxpk(gateway_mac: MacAddress, rxpk: RxPk) -> Packet {
35        let rxpk = vec![rxpk];
36        Packet {
37            random_token: 0,
38            gateway_mac,
39            data: Data {
40                rxpk: Some(rxpk),
41                stat: None,
42            },
43        }
44    }
45
46    pub fn from_stat(gateway_mac: MacAddress, stat: Stat) -> Packet {
47        Packet {
48            random_token: 0,
49            gateway_mac,
50            data: Data {
51                rxpk: None,
52                stat: Some(stat),
53            },
54        }
55    }
56
57    pub fn random() -> Packet {
58        let rxpk = vec![RxPk::V1(RxPkV1 {
59            chan: 0,
60            codr: Some(lora_modulation::CodingRate::_4_5),
61            data: vec![0, 0],
62            datr: DataRate::default(),
63            freq: 902.800_000,
64            lsnr: -15.0,
65            modu: Modulation::LORA,
66            rfch: 0,
67            rssi: -80,
68            rssis: Some(-80),
69            size: 12,
70            stat: CRC::OK,
71            tmst: 12,
72            time: None,
73        })];
74
75        Packet {
76            random_token: rand::random(),
77            gateway_mac: MacAddress::from([0; 8]),
78            data: Data {
79                rxpk: Some(rxpk),
80                stat: None,
81            },
82        }
83    }
84}
85
86#[derive(Debug, Serialize, Deserialize, Clone)]
87pub struct Data {
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub rxpk: Option<Vec<RxPk>>,
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub stat: Option<Stat>,
92}
93
94#[derive(Debug, Serialize_repr, Deserialize_repr, Copy, Clone, PartialEq, Eq)]
95#[repr(i8)]
96pub enum CRC {
97    Disabled = 0,
98    OK = 1,
99    Fail = -1,
100}
101
102use std::fmt;
103impl fmt::Display for RxPk {
104    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
105        write!(
106            f,
107            "@{} us, {:.2} MHz, {:?}, {}, snr: {}, len: {}",
108            self.timestamp(),
109            self.frequency(),
110            self.datarate(),
111            if let Some(rssis) = self.signal_rssi() {
112                format!("rssis: {rssis}")
113            } else {
114                format!("rssic: {}", self.channel_rssi())
115            },
116            self.snr(),
117            self.data().len()
118        )
119    }
120}
121
122macro_rules! get_field_ref {
123    ($self:expr, $field:ident) => {
124        match $self {
125            RxPk::V1(pk) => &pk.$field,
126            RxPk::V2(pk) => &pk.$field,
127        }
128    };
129}
130macro_rules! get_field {
131    ($self:expr, $field:ident) => {
132        match $self {
133            RxPk::V1(pk) => pk.$field,
134            RxPk::V2(pk) => pk.$field,
135        }
136    };
137}
138use std::cmp;
139
140impl RxPk {
141    pub fn snr(&self) -> f32 {
142        match self {
143            RxPk::V1(pk) => pk.lsnr,
144            RxPk::V2(pk) => pk
145                .rsig
146                .iter()
147                // truncate the decimal when choosing best LSNR value
148                .fold(-150.0, |max, x| {
149                    if (max as i32) < (x.lsnr as i32) {
150                        x.lsnr
151                    } else {
152                        max
153                    }
154                }),
155        }
156    }
157
158    pub fn channel_rssi(&self) -> i32 {
159        match self {
160            RxPk::V1(pk) => pk.rssi,
161            RxPk::V2(pk) => pk.rsig.iter().fold(-150, |max, x| cmp::max(max, x.rssic)),
162        }
163    }
164
165    pub fn signal_rssi(&self) -> Option<i32> {
166        match self {
167            RxPk::V1(pk) => pk.rssis,
168            RxPk::V2(pk) => pk.rsig.iter().fold(None, |max, x| {
169                if let Some(rssis) = x.rssis {
170                    Some(if let Some(current_max) = max {
171                        cmp::max(current_max, rssis)
172                    } else {
173                        rssis
174                    })
175                } else {
176                    max
177                }
178            }),
179        }
180    }
181
182    pub fn frequency(&self) -> f64 {
183        get_field!(self, freq)
184    }
185
186    pub fn data(&self) -> &Vec<u8> {
187        get_field_ref!(self, data)
188    }
189
190    pub fn timestamp(&self) -> u32 {
191        get_field!(self, tmst)
192    }
193
194    pub fn time(&self) -> &Option<String> {
195        get_field_ref!(self, time)
196    }
197
198    pub fn datarate(&self) -> DataRate {
199        get_field!(self, datr)
200    }
201
202    pub fn crc_status(&self) -> CRC {
203        get_field!(self, stat)
204    }
205
206    pub fn coding_rate(&self) -> Option<lora_modulation::CodingRate> {
207        get_field!(self, codr)
208    }
209}
210
211/*
212Name |  Type  | Function
213:----:|:------:|--------------------------------------------------------------
214time | string | UTC 'system' time of the gateway, ISO 8601 'expanded' format
215lati | number | GPS latitude of the gateway in degree (float, N is +)
216long | number | GPS latitude of the gateway in degree (float, E is +)
217alti | number | GPS altitude of the gateway in meter RX (integer)
218rxnb | number | Number of radio packets received (unsigned integer)
219rxok | number | Number of radio packets received with a valid PHY CRC
220rxfw | number | Number of radio packets forwarded (unsigned integer)
221ackr | number | Percentage of upstream datagrams that were acknowledged
222dwnb | number | Number of downlink datagrams received (unsigned integer)
223txnb | number | Number of packets emitted (unsigned integer)
224temp | number | Current temperature in degree celcius (float)
225*/
226
227// the order of this is important as it makes us identical to Semtech
228#[derive(Debug, Serialize, Deserialize, Clone)]
229pub struct Stat {
230    pub time: String,
231    #[serde(skip_serializing_if = "Option::is_none")]
232    pub lati: Option<f64>,
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub long: Option<f64>,
235    #[serde(skip_serializing_if = "Option::is_none")]
236    pub alti: Option<i64>,
237    pub rxnb: u64,
238    pub rxok: u64,
239    pub rxfw: u64,
240    // if there were no upstream datagrams, this field can be null
241    pub ackr: Option<f64>,
242    pub dwnb: u64,
243    pub txnb: u64,
244    pub temp: Option<f64>,
245}
246
247impl SerializablePacket for Packet {
248    fn serialize(&self, buffer: &mut [u8]) -> std::result::Result<u64, PktError> {
249        let mut w = Cursor::new(buffer);
250        write_preamble(&mut w, self.random_token)?;
251        w.write_all(&[Identifier::PushData as u8])?;
252        w.write_all(self.gateway_mac.as_bytes())?;
253        w.write_all(serde_json::to_string(&self.data)?.as_bytes())?;
254        Ok(w.position())
255    }
256}
257
258impl From<Packet> for super::Packet {
259    fn from(packet: Packet) -> super::Packet {
260        super::Packet::Up(super::Up::PushData(packet))
261    }
262}
263
264impl Packet {
265    pub fn into_ack(self) -> push_ack::Packet {
266        push_ack::Packet {
267            random_token: self.random_token,
268        }
269    }
270}
271
272#[cfg(test)]
273mod test {
274    use super::*;
275
276    fn check_given_snr(data: Data, expected_snr: f32) {
277        if let Some(mut rxpk) = data.rxpk {
278            assert_eq!(rxpk.len(), 1);
279            if let Some(rxpk) = rxpk.pop() {
280                assert_eq!(rxpk.snr(), expected_snr)
281            } else {
282                // rxpk is empty vector
283                assert!(false)
284            }
285        } else {
286            // rxpk is None
287            assert!(false)
288        }
289    }
290
291    #[test]
292    fn rxpk_positive_lsnr() {
293        let json = "{\"rxpk\":[{\"aesk\":0,\"brd\":263,\"codr\":\"4/5\",\"data\":\"QC65rwEA4w8CaH7LyGf/3+dxzrXkkfEsRCcXbFM=\",\"datr\":\"SF12BW125\",\"freq\":868.5,\"jver\":2,\"modu\":\"LORA\",\"rsig\":[{\"ant\":0,\"chan\":7,\"lsnr\":7.8,\"rssic\":-103}],\"size\":29,\"stat\":1,\"time\":\"2022-03-31T07:51:15.709338Z\",\"tmst\":445296860}]}";
294        let parsed: Data = serde_json::from_str(json).expect("Error parsing push_data::Data");
295        check_given_snr(parsed, 7.8);
296    }
297
298    #[test]
299    fn rxpk_negative_lsnr() {
300        let json = "{\"rxpk\":[{\"aesk\":0,\"brd\":261,\"codr\":\"4/5\",\"data\":\"QI8cACQA6iAD3TTei0kPKKyxBA==\",\"datr\":\"SF11BW125\",\"freq\":868.1,\"jver\":2,\"modu\":\"LORA\",\"rsig\":[{\"ant\":0,\"chan\":5,\"lsnr\":-3.5,\"rssic\":-120}],\"size\":19,\"stat\":1,\"time\":\"2022-03-31T07:51:12.631018Z\",\"tmst\":442218540}]}";
301        let parsed: Data = serde_json::from_str(json).expect("Error parsing push_data::Data");
302        check_given_snr(parsed, -3.5);
303    }
304
305    #[test]
306    fn snr_roundtrip() {
307        let json = "{\"rxpk\":[{\"jver\":1,\"tmst\":682631918,\"chan\":0,\"rfch\":0,\"freq\":865.062500,\"mid\": 0,\"stat\":1,\"modu\":\"LORA\",\"datr\":\"SF12BW125\",\"codr\":\"4/5\",\"rssis\":-95,\"lsnr\":6.8,\"foff\":-1300,\"rssi\":-94,\"size\":20,\"data\":\"QNbPNwABAQANyqD8ngiq26Hk4gs=\"}]}";
308        let parsed: Data = serde_json::from_str(json).expect("Error parsing push_data::Data");
309        check_given_snr(parsed.clone(), 6.8);
310        let serialized = serde_json::to_string(&parsed).expect("Error serializing push_data::Data");
311        let reparsed: Data =
312            serde_json::from_str(&serialized).expect("Error parsing push_data::Data");
313        check_given_snr(reparsed, 6.8);
314    }
315}