Skip to main content

aprs_decode/
status.rs

1use crate::error::AprsError;
2use crate::types::Timestamp;
3
4/// An APRS Status Report.
5///
6/// DTI: `>`
7///
8/// Announces the station's current mission or status as a single free-text line.
9/// APRS101 restricts the timestamp to DDHHMM format, but HHMMSS is also seen in practice.
10#[derive(Debug, Clone, PartialEq, Eq)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12pub struct AprsStatus {
13    /// Optional timestamp. The spec allows only `DDHHMM`; `HHMMSS` is a common extension.
14    pub timestamp: Option<Timestamp>,
15    pub comment: Vec<u8>,
16}
17
18impl AprsStatus {
19    /// Decode from the information field (including the leading `>` DTI byte).
20    pub(crate) fn parse(info: &[u8]) -> Result<Self, AprsError> {
21        // Strip the leading `>` DTI
22        let b = info.get(1..).unwrap_or_default();
23
24        // Opportunistically try to parse the first 7 bytes as a timestamp.
25        // On failure (invalid or too short) the whole field is the comment.
26        let timestamp = b.get(..7).and_then(|ts| Timestamp::parse(ts).ok());
27        let comment = if timestamp.is_some() {
28            b.get(7..).unwrap_or_default().to_vec()
29        } else {
30            b.to_vec()
31        };
32
33        Ok(Self { timestamp, comment })
34    }
35
36    pub fn encode(&self) -> Vec<u8> {
37        let mut out = vec![b'>'];
38        if let Some(ref ts) = self.timestamp {
39            ts.encode(&mut out);
40        }
41        out.extend_from_slice(&self.comment);
42        out
43    }
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49
50    #[test]
51    fn no_timestamp_no_comment() {
52        let s = AprsStatus::parse(b">").unwrap();
53        assert!(s.timestamp.is_none());
54        assert!(s.comment.is_empty());
55    }
56
57    #[test]
58    fn with_ddhhmm_timestamp() {
59        let s = AprsStatus::parse(b">312359zSystem online").unwrap();
60        assert_eq!(s.timestamp, Some(Timestamp::Ddhhmm(31, 23, 59)));
61        assert_eq!(s.comment, b"System online");
62    }
63
64    #[test]
65    fn with_hhmmss_timestamp() {
66        let s = AprsStatus::parse(b">235959hHi there!").unwrap();
67        assert_eq!(s.timestamp, Some(Timestamp::Hhmmss(23, 59, 59)));
68        assert_eq!(s.comment, b"Hi there!");
69    }
70
71    #[test]
72    fn no_timestamp_with_comment() {
73        let s = AprsStatus::parse(b">12.6V 0.2A 22degC").unwrap();
74        assert!(s.timestamp.is_none());
75        assert_eq!(s.comment, b"12.6V 0.2A 22degC");
76    }
77
78    #[test]
79    fn encode_round_trip() {
80        for raw in [
81            b">312359zSystem online".as_slice(),
82            b">Hi there!".as_slice(),
83            b">".as_slice(),
84        ] {
85            let s = AprsStatus::parse(raw).unwrap();
86            assert_eq!(s.encode(), raw);
87        }
88    }
89}