ogn_parser/
status_comment.rs

1use rust_decimal::prelude::*;
2use serde::Serialize;
3use std::{convert::Infallible, str::FromStr};
4
5use crate::utils::{extract_values, split_value_unit};
6
7#[derive(Debug, PartialEq, Default, Clone, Serialize)]
8pub struct StatusComment {
9    #[serde(skip_serializing_if = "Option::is_none")]
10    pub version: Option<String>,
11    #[serde(skip_serializing_if = "Option::is_none")]
12    pub platform: Option<String>,
13    #[serde(skip_serializing_if = "Option::is_none")]
14    pub cpu_load: Option<Decimal>,
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub ram_free: Option<Decimal>,
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub ram_total: Option<Decimal>,
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub ntp_offset: Option<Decimal>,
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub ntp_correction: Option<Decimal>,
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub voltage: Option<Decimal>,
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub amperage: Option<Decimal>,
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub cpu_temperature: Option<Decimal>,
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub visible_senders: Option<u16>,
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub latency: Option<Decimal>,
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub senders: Option<u16>,
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub rf_correction_manual: Option<i16>,
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub rf_correction_automatic: Option<Decimal>,
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub noise: Option<Decimal>,
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub senders_signal_quality: Option<Decimal>,
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub senders_messages: Option<u32>,
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub good_senders_signal_quality: Option<Decimal>,
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub good_senders: Option<u16>,
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub good_and_bad_senders: Option<u16>,
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub unparsed: Option<String>,
53}
54
55impl FromStr for StatusComment {
56    type Err = Infallible;
57    fn from_str(s: &str) -> Result<Self, Self::Err> {
58        let mut status_comment = StatusComment {
59            ..Default::default()
60        };
61        let mut unparsed: Vec<_> = vec![];
62        for part in s.split_whitespace() {
63            // receiver software version: vX.Y.Z
64            // X (major)
65            // Y (minor)
66            // Z (bugfix)
67            if &part[0..1] == "v"
68                && part.matches('.').count() == 3
69                && status_comment.version.is_none()
70            {
71                let (first, second) = part
72                    .match_indices('.')
73                    .nth(2)
74                    .map(|(idx, _)| part.split_at(idx))
75                    .unwrap();
76                status_comment.version = Some(first[1..].into());
77                status_comment.platform = Some(second[1..].into());
78
79            // cpu load: CPU:x.x
80            // x.x: cpu load as percentage
81            } else if part.len() > 4
82                && part.starts_with("CPU:")
83                && status_comment.cpu_load.is_none()
84            {
85                if let Ok(cpu_load) = part[4..].parse::<f32>() {
86                    status_comment.cpu_load = Decimal::from_f32(cpu_load);
87                } else {
88                    unparsed.push(part);
89                }
90
91            // RAM usage: RAM:x.x/y.yMB
92            // x.x: free RAM in MB
93            // y.y: total RAM in MB
94            } else if part.len() > 6
95                && part.starts_with("RAM:")
96                && part.ends_with("MB")
97                && part.find('/').is_some()
98                && status_comment.ram_free.is_none()
99            {
100                let subpart = &part[4..part.len() - 2];
101                let split_point = subpart.find('/').unwrap();
102                let (first, second) = subpart.split_at(split_point);
103                let ram_free = first.parse::<f32>().ok();
104                let ram_total = second[1..].parse::<f32>().ok();
105                if ram_free.is_some() && ram_total.is_some() {
106                    status_comment.ram_free = ram_free.and_then(Decimal::from_f32);
107                    status_comment.ram_total = ram_total.and_then(Decimal::from_f32);
108                } else {
109                    unparsed.push(part);
110                }
111
112            // time synchronisation: NTP:x.xms/y.yppm
113            // x.x: NTP offset in [ms]
114            // y.y: NTP correction in [ppm]
115            } else if part.len() > 6
116                && part.starts_with("NTP:")
117                && part.find('/').is_some()
118                && status_comment.ntp_offset.is_none()
119            {
120                let subpart = &part[4..part.len() - 3];
121                let split_point = subpart.find('/').unwrap();
122                let (first, second) = subpart.split_at(split_point);
123                let ntp_offset = first[0..first.len() - 2].parse::<f32>().ok();
124                let ntp_correction = second[1..].parse::<f32>().ok();
125                if ntp_offset.is_some() && ntp_correction.is_some() {
126                    status_comment.ntp_offset = ntp_offset.and_then(Decimal::from_f32);
127                    status_comment.ntp_correction = ntp_correction.and_then(Decimal::from_f32);
128                } else {
129                    unparsed.push(part);
130                }
131
132            // senders count: x/yAcfts[1h]
133            // x: visible senders in the last hour
134            // y: total senders in the last hour
135            } else if part.len() >= 11
136                && part.ends_with("Acfts[1h]")
137                && part.find('/').is_some()
138                && status_comment.visible_senders.is_none()
139            {
140                let subpart = &part[0..part.len() - 9];
141                let split_point = subpart.find('/').unwrap();
142                let (first, second) = subpart.split_at(split_point);
143                let visible_senders = first.parse::<u16>().ok();
144                let senders = second[1..].parse::<u16>().ok();
145                if visible_senders.is_some() && senders.is_some() {
146                    status_comment.visible_senders = visible_senders;
147                    status_comment.senders = senders;
148                } else {
149                    unparsed.push(part);
150                }
151
152            // latency: Lat:x.xs
153            // x.x: latency in [s]
154            } else if part.len() > 5
155                && part.starts_with("Lat:")
156                && part.ends_with("s")
157                && status_comment.latency.is_none()
158            {
159                let latency = part[4..part.len() - 1].parse::<f32>().ok();
160                if latency.is_some() {
161                    status_comment.latency = latency.and_then(Decimal::from_f32);
162                } else {
163                    unparsed.push(part);
164                }
165
166            // radio frequency informations start with "RF:"
167            } else if part.len() >= 11
168                && part.starts_with("RF:")
169                && status_comment.rf_correction_manual.is_none()
170            {
171                let values = extract_values(part);
172                // short RF format: RF:+x.x/y.yppm/+z.zdB
173                // x.x: manual correction in [ppm]
174                // y.y: automatic correction in [ppm]
175                // z.z: background noise in [dB]
176                if values.len() == 3 {
177                    let rf_correction_manual = values[0].parse::<i16>().ok();
178                    let rf_correction_automatic = values[1].parse::<f32>().ok();
179                    let noise = values[2].parse::<f32>().ok();
180
181                    if rf_correction_manual.is_some()
182                        && rf_correction_automatic.is_some()
183                        && noise.is_some()
184                    {
185                        status_comment.rf_correction_manual = rf_correction_manual;
186                        status_comment.rf_correction_automatic =
187                            rf_correction_automatic.and_then(Decimal::from_f32);
188                        status_comment.noise = noise.and_then(Decimal::from_f32)
189                    } else {
190                        unparsed.push(part);
191                        continue;
192                    }
193                // medium RF format: RF:+x.x/y.yppm/+z.zdB/+a.adB@10km[b]
194                // a.a: sender signal quality [dB]
195                // b: number of messages
196                } else if values.len() == 6 {
197                    let rf_correction_manual = values[0].parse::<i16>().ok();
198                    let rf_correction_automatic = values[1].parse::<f32>().ok();
199                    let noise = values[2].parse::<f32>().ok();
200                    let senders_signal_quality = values[3].parse::<f32>().ok();
201                    let senders_messages = values[5].parse::<u32>().ok();
202                    if rf_correction_manual.is_some()
203                        && rf_correction_automatic.is_some()
204                        && noise.is_some()
205                        && senders_signal_quality.is_some()
206                        && senders_messages.is_some()
207                    {
208                        status_comment.rf_correction_manual = rf_correction_manual;
209                        status_comment.rf_correction_automatic =
210                            rf_correction_automatic.and_then(Decimal::from_f32);
211                        status_comment.noise = noise.and_then(Decimal::from_f32);
212                        status_comment.senders_signal_quality =
213                            senders_signal_quality.and_then(Decimal::from_f32);
214                        status_comment.senders_messages = senders_messages;
215                    } else {
216                        unparsed.push(part);
217                        continue;
218                    }
219                // long RF format: RF:+x.x/y.yppm/+z.zdB/+a.adB@10km[b]/+c.cdB@10km[d/e]
220                // c.c: good senders signal quality [dB]
221                // d: number of good senders
222                // e: number of good and bad senders
223                } else if values.len() == 10 {
224                    let rf_correction_manual = values[0].parse::<i16>().ok();
225                    let rf_correction_automatic = values[1].parse::<f32>().ok();
226                    let noise = values[2].parse::<f32>().ok();
227                    let senders_signal_quality = values[3].parse::<f32>().ok();
228                    let senders_messages = values[5].parse::<u32>().ok();
229                    let good_senders_signal_quality = values[6].parse::<f32>().ok();
230                    let good_senders = values[8].parse::<u16>().ok();
231                    let good_and_bad_senders = values[9].parse::<u16>().ok();
232                    if rf_correction_manual.is_some()
233                        && rf_correction_automatic.is_some()
234                        && noise.is_some()
235                        && senders_signal_quality.is_some()
236                        && senders_messages.is_some()
237                        && good_senders_signal_quality.is_some()
238                        && good_senders.is_some()
239                        && good_and_bad_senders.is_some()
240                    {
241                        status_comment.rf_correction_manual = rf_correction_manual;
242                        status_comment.rf_correction_automatic =
243                            rf_correction_automatic.and_then(Decimal::from_f32);
244                        status_comment.noise = noise.and_then(Decimal::from_f32);
245                        status_comment.senders_signal_quality =
246                            senders_signal_quality.and_then(Decimal::from_f32);
247                        status_comment.senders_messages = senders_messages;
248                        status_comment.good_senders_signal_quality =
249                            good_senders_signal_quality.and_then(Decimal::from_f32);
250                        status_comment.good_senders = good_senders;
251                        status_comment.good_and_bad_senders = good_and_bad_senders;
252                    } else {
253                        unparsed.push(part);
254                        continue;
255                    }
256                } else {
257                    unparsed.push(part);
258                    continue;
259                }
260            } else if let Some((value, unit)) = split_value_unit(part) {
261                // cpu temperature: +x.xC
262                // x.x: cpu temperature in [°C]
263                if unit == "C" && status_comment.cpu_temperature.is_none() {
264                    status_comment.cpu_temperature =
265                        value.parse::<f32>().ok().and_then(Decimal::from_f32);
266                // voltage: +x.xV
267                // x.x: voltage in [V]
268                } else if unit == "V" && status_comment.voltage.is_none() {
269                    status_comment.voltage = value.parse::<f32>().ok().and_then(Decimal::from_f32);
270                // currency: +x.xA
271                // x.x: currency in [A]
272                } else if unit == "A" && status_comment.amperage.is_none() {
273                    status_comment.amperage = value.parse::<f32>().ok().and_then(Decimal::from_f32);
274                } else {
275                    unparsed.push(part);
276                }
277            } else {
278                unparsed.push(part);
279            }
280        }
281        status_comment.unparsed = if !unparsed.is_empty() {
282            Some(unparsed.join(" "))
283        } else {
284            None
285        };
286
287        Ok(status_comment)
288    }
289}
290
291#[cfg(test)]
292mod tests {
293    use super::*;
294
295    #[test]
296    fn test_sdr() {
297        let result = "v0.2.7.RPI-GPU CPU:0.7 RAM:770.2/968.2MB NTP:1.8ms/-3.3ppm +55.7C 7/8Acfts[1h] RF:+54-1.1ppm/-0.16dB/+7.1dB@10km[19481]/+16.8dB@10km[7/13]".parse::<StatusComment>().unwrap();
298        assert_eq!(
299            result,
300            StatusComment {
301                version: Some("0.2.7".into()),
302                platform: Some("RPI-GPU".into()),
303                cpu_load: Decimal::from_f32(0.7),
304                ram_free: Decimal::from_f32(770.2),
305                ram_total: Decimal::from_f32(968.2),
306                ntp_offset: Decimal::from_f32(1.8),
307                ntp_correction: Decimal::from_f32(-3.3),
308                voltage: None,
309                amperage: None,
310                cpu_temperature: Decimal::from_f32(55.7),
311                visible_senders: Some(7),
312                senders: Some(8),
313                rf_correction_manual: Some(54),
314                rf_correction_automatic: Decimal::from_f32(-1.1),
315                noise: Decimal::from_f32(-0.16),
316                senders_signal_quality: Decimal::from_f32(7.1),
317                senders_messages: Some(19481),
318                good_senders_signal_quality: Decimal::from_f32(16.8),
319                good_senders: Some(7),
320                good_and_bad_senders: Some(13),
321                ..Default::default()
322            }
323        );
324    }
325
326    #[test]
327    fn test_sdr_different_order() {
328        let result = "NTP:1.8ms/-3.3ppm +55.7C CPU:0.7 RAM:770.2/968.2MB 7/8Acfts[1h] RF:+54-1.1ppm/-0.16dB/+7.1dB@10km[19481]/+16.8dB@10km[7/13] v0.2.7.RPI-GPU".parse::<StatusComment>().unwrap();
329        assert_eq!(
330            result,
331            StatusComment {
332                version: Some("0.2.7".into()),
333                platform: Some("RPI-GPU".into()),
334                cpu_load: Decimal::from_f32(0.7),
335                ram_free: Decimal::from_f32(770.2),
336                ram_total: Decimal::from_f32(968.2),
337                ntp_offset: Decimal::from_f32(1.8),
338                ntp_correction: Decimal::from_f32(-3.3),
339                voltage: None,
340                amperage: None,
341                cpu_temperature: Decimal::from_f32(55.7),
342                visible_senders: Some(7),
343                senders: Some(8),
344                rf_correction_manual: Some(54),
345                rf_correction_automatic: Decimal::from_f32(-1.1),
346                noise: Decimal::from_f32(-0.16),
347                senders_signal_quality: Decimal::from_f32(7.1),
348                senders_messages: Some(19481),
349                good_senders_signal_quality: Decimal::from_f32(16.8),
350                good_senders: Some(7),
351                good_and_bad_senders: Some(13),
352                ..Default::default()
353            }
354        );
355    }
356
357    #[test]
358    fn test_rf_3() {
359        let result = "RF:+29+0.0ppm/+35.22dB".parse::<StatusComment>().unwrap();
360        assert_eq!(
361            result,
362            StatusComment {
363                rf_correction_manual: Some(29),
364                rf_correction_automatic: Decimal::from_f32(0.0),
365                noise: Decimal::from_f32(35.22),
366                ..Default::default()
367            }
368        )
369    }
370
371    #[test]
372    fn test_rf_6() {
373        let result = "RF:+41+56.0ppm/-1.87dB/+0.1dB@10km[1928]"
374            .parse::<StatusComment>()
375            .unwrap();
376        assert_eq!(
377            result,
378            StatusComment {
379                rf_correction_manual: Some(41),
380                rf_correction_automatic: Decimal::from_f32(56.0),
381                noise: Decimal::from_f32(-1.87),
382                senders_signal_quality: Decimal::from_f32(0.1),
383                senders_messages: Some(1928),
384                ..Default::default()
385            }
386        )
387    }
388
389    #[test]
390    fn test_rf_10() {
391        let result = "RF:+54-1.1ppm/-0.16dB/+7.1dB@10km[19481]/+16.8dB@10km[7/13]"
392            .parse::<StatusComment>()
393            .unwrap();
394        assert_eq!(
395            result,
396            StatusComment {
397                rf_correction_manual: Some(54),
398                rf_correction_automatic: Decimal::from_f32(-1.1),
399                noise: Decimal::from_f32(-0.16),
400                senders_signal_quality: Decimal::from_f32(7.1),
401                senders_messages: Some(19481),
402                good_senders_signal_quality: Decimal::from_f32(16.8),
403                good_senders: Some(7),
404                good_and_bad_senders: Some(13),
405                ..Default::default()
406            }
407        )
408    }
409}