ogn_parser/
status.rs

1//! A Status Report announces the station's current mission or any other single
2//! line status to everyone. The report starts with the '>' APRS Data Type Identifier.
3//! The report may optionally contain a timestamp.
4//!
5//! Examples:
6//! - ">12.6V 0.2A 22degC"              (report without timestamp)
7//! - ">120503hFatal error"             (report with timestamp in HMS format)
8//! - ">281205zSystem will shutdown"    (report with timestamp in DHM format)
9
10use std::fmt::{Display, Formatter};
11use std::str::FromStr;
12
13use serde::Serialize;
14
15use crate::AprsError;
16use crate::Timestamp;
17use crate::status_comment::StatusComment;
18
19#[derive(PartialEq, Debug, Clone, Serialize)]
20pub struct AprsStatus {
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub timestamp: Option<Timestamp>,
23    #[serde(flatten)]
24    pub comment: StatusComment,
25}
26
27impl FromStr for AprsStatus {
28    type Err = AprsError;
29
30    fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
31        // Interpret the first 7 bytes as a timestamp, if valid.
32        // Otherwise the whole field is the comment.
33        let timestamp = if s.len() >= 7 {
34            s[0..7].parse::<Timestamp>().ok()
35        } else {
36            None
37        };
38        let comment = if timestamp.is_some() { &s[7..] } else { s };
39
40        Ok(AprsStatus {
41            timestamp,
42            comment: comment.parse::<StatusComment>().unwrap(),
43        })
44    }
45}
46
47impl Display for AprsStatus {
48    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
49        write!(f, ">")?;
50
51        if let Some(ts) = &self.timestamp {
52            write!(f, "{}", ts)?;
53        }
54        write!(f, "{:#?}", self.comment)?;
55
56        Ok(())
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use csv::WriterBuilder;
63    use std::io::stdout;
64
65    use super::*;
66
67    #[test]
68    fn parse_without_timestamp_or_comment() {
69        let result = "".parse::<AprsStatus>().unwrap();
70        assert_eq!(result.timestamp, None);
71        assert_eq!(result.comment, StatusComment::default());
72    }
73
74    #[test]
75    fn parse_with_timestamp_without_comment() {
76        let result = "312359z".parse::<AprsStatus>().unwrap();
77        assert_eq!(result.timestamp, Some(Timestamp::DDHHMM(31, 23, 59)));
78        assert_eq!(result.comment, StatusComment::default());
79    }
80
81    #[test]
82    fn parse_without_timestamp_with_comment() {
83        let result = "Hi there!".parse::<AprsStatus>().unwrap();
84        assert_eq!(result.timestamp, None);
85        assert_eq!(result.comment.unparsed.unwrap(), "Hi there!");
86    }
87
88    #[test]
89    fn parse_with_timestamp_and_comment() {
90        let result = "235959hHi there!".parse::<AprsStatus>().unwrap();
91        assert_eq!(result.timestamp, Some(Timestamp::HHMMSS(23, 59, 59)));
92        assert_eq!(result.comment.unparsed.unwrap(), "Hi there!");
93    }
94
95    #[ignore = "status_comment serialization not implemented"]
96    #[test]
97    fn test_serialize() {
98        let aprs_position = "235959hHi there!".parse::<AprsStatus>().unwrap();
99        let mut wtr = WriterBuilder::new().from_writer(stdout());
100        wtr.serialize(aprs_position).unwrap();
101        wtr.flush().unwrap();
102    }
103}