1use crate::error::AprsError;
4
5#[derive(Debug, Clone, Default, PartialEq, Eq)]
15pub struct AprsTelemetry {
16 pub sequence: String,
18 pub analog: [Option<u16>; 5],
21 pub digital: u8,
23}
24
25pub fn parse_aprs_telemetry(info: &[u8]) -> Result<AprsTelemetry, AprsError> {
38 if info.first() != Some(&b'T') || info.get(1) != Some(&b'#') {
40 return Err(AprsError::InvalidFormat);
41 }
42
43 let body_bytes = info.get(2..).unwrap_or(&[]);
44 let body = String::from_utf8_lossy(body_bytes);
45 let parts: Vec<&str> = body.split(',').collect();
46 if parts.is_empty() || parts.len() > 7 {
48 return Err(AprsError::InvalidFormat);
49 }
50
51 let sequence = parts.first().ok_or(AprsError::InvalidFormat)?.to_string();
52
53 let mut analog: [Option<u16>; 5] = [None, None, None, None, None];
55 let analog_end = std::cmp::min(parts.len(), 6); let analog_parts = parts.get(1..analog_end).unwrap_or(&[]);
57 for (i, part) in analog_parts.iter().enumerate() {
58 let trimmed = part.trim();
59 if trimmed.is_empty() {
60 continue;
61 }
62 let val: u16 = trimmed.parse().map_err(|_| AprsError::InvalidFormat)?;
63 if let Some(slot) = analog.get_mut(i) {
64 *slot = Some(val);
65 }
66 }
67
68 let digital = if let Some(digi_raw) = parts.get(6) {
72 let digi_str = digi_raw.trim();
73 let digi_bits = digi_str.get(..8).unwrap_or(digi_str);
74 u8::from_str_radix(digi_bits, 2).map_err(|_| AprsError::InvalidFormat)?
75 } else {
76 0
77 };
78
79 Ok(AprsTelemetry {
80 sequence,
81 analog,
82 digital,
83 })
84}
85
86#[cfg(test)]
91mod tests {
92 use super::*;
93
94 type TestResult = Result<(), Box<dyn std::error::Error>>;
95
96 #[test]
97 fn parse_telemetry_full() -> TestResult {
98 let info = b"T#123,100,200,300,400,500,10101010";
99 let t = parse_aprs_telemetry(info)?;
100 assert_eq!(t.sequence, "123");
101 assert_eq!(
102 t.analog,
103 [Some(100), Some(200), Some(300), Some(400), Some(500)]
104 );
105 assert_eq!(t.digital, 0b1010_1010);
106 Ok(())
107 }
108
109 #[test]
110 fn parse_telemetry_mic_sequence() -> TestResult {
111 let info = b"T#MIC,001,002,003,004,005,11111111";
112 let t = parse_aprs_telemetry(info)?;
113 assert_eq!(t.sequence, "MIC");
114 assert_eq!(t.analog, [Some(1), Some(2), Some(3), Some(4), Some(5)]);
115 assert_eq!(t.digital, 0xFF);
116 Ok(())
117 }
118
119 #[test]
120 fn parse_telemetry_partial_analog() -> TestResult {
121 let info = b"T#001,10,20,30";
123 let t = parse_aprs_telemetry(info)?;
124 assert_eq!(t.sequence, "001");
125 assert_eq!(t.analog, [Some(10), Some(20), Some(30), None, None]);
126 assert_eq!(t.digital, 0);
127 Ok(())
128 }
129
130 #[test]
131 fn parse_telemetry_zero_values() -> TestResult {
132 let info = b"T#000,0,0,0,0,0,00000000";
133 let t = parse_aprs_telemetry(info)?;
134 assert_eq!(t.sequence, "000");
135 assert_eq!(t.analog, [Some(0), Some(0), Some(0), Some(0), Some(0)]);
136 assert_eq!(t.digital, 0);
137 Ok(())
138 }
139
140 #[test]
141 fn parse_telemetry_rejects_too_many_fields() {
142 let info = b"T#001,1,2,3,4,5,6,00000000";
144 assert!(
145 matches!(parse_aprs_telemetry(info), Err(AprsError::InvalidFormat)),
146 "expected InvalidFormat for 8-field input",
147 );
148 }
149
150 #[test]
151 fn parse_telemetry_invalid_no_hash() {
152 let info = b"T123,1,2,3,4,5,00000000";
153 assert!(parse_aprs_telemetry(info).is_err(), "missing # rejected");
154 }
155
156 #[test]
157 fn parse_telemetry_invalid_digital_field_is_error() {
158 let info = b"T#123,1,2,3,4,5,XXXXXXXX";
161 assert!(
162 matches!(parse_aprs_telemetry(info), Err(AprsError::InvalidFormat)),
163 "non-binary digital field must error",
164 );
165 }
166}