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 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 } 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 } 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 } 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 } 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 } 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 } 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 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 } 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 } 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 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 } else if unit == "V" && status_comment.voltage.is_none() {
269 status_comment.voltage = value.parse::<f32>().ok().and_then(Decimal::from_f32);
270 } 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}