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