1use crate::error::AprsError;
13
14#[derive(Debug, Clone, PartialEq)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25pub struct AprsTelemetry {
26 pub sequence: Vec<u8>,
28 pub analog: [Option<f32>; 5],
30 pub digital: u8,
32 pub comment: Vec<u8>,
33}
34
35impl AprsTelemetry {
36 pub(crate) fn parse(info: &[u8]) -> Result<Self, AprsError> {
38 if info.len() < 2 || info[1] != b'#' {
40 return Err(AprsError::TruncatedPacket { expected: 2, got: info.len() });
41 }
42 let body = &info[2..]; let parts: Vec<&[u8]> = body.split(|&c| c == b',').collect();
46
47 let sequence = parts.first().unwrap_or(&b"".as_slice()).to_vec();
48
49 let mut analog = [None; 5];
50 for (i, slot) in analog.iter_mut().enumerate() {
51 if let Some(part) = parts.get(i + 1) {
52 *slot = std::str::from_utf8(part).ok()
53 .and_then(|s| s.trim().parse::<f32>().ok());
54 }
55 }
56
57 let digital = parts.get(6).and_then(|part| {
58 if part.len() >= 8 && part[..8].iter().all(|&c| c == b'0' || c == b'1') {
59 let mut val = 0u8;
60 for &bit in &part[..8] {
61 val = (val << 1) | (bit - b'0');
62 }
63 Some(val)
64 } else {
65 None
66 }
67 }).unwrap_or(0);
68
69 let comment = if parts.len() > 7 {
70 let mut c = Vec::new();
71 for (i, part) in parts[7..].iter().enumerate() {
72 if i > 0 { c.push(b','); }
73 c.extend_from_slice(part);
74 }
75 c
76 } else {
77 vec![]
78 };
79
80 Ok(Self { sequence, analog, digital, comment })
81 }
82
83 pub fn encode(&self) -> Vec<u8> {
84 let mut out = b"T#".to_vec();
85 out.extend_from_slice(&self.sequence);
86 for val in &self.analog {
87 out.push(b',');
88 if let Some(v) = val {
89 if *v == v.trunc() && v.is_finite() {
91 out.extend_from_slice(format!("{}", *v as i64).as_bytes());
92 } else {
93 out.extend_from_slice(format!("{}", v).as_bytes());
94 }
95 }
96 }
97 out.push(b',');
98 for i in (0..8).rev() {
99 out.push(b'0' + ((self.digital >> i) & 1));
100 }
101 if !self.comment.is_empty() {
102 out.push(b',');
103 out.extend_from_slice(&self.comment);
104 }
105 out
106 }
107}
108
109#[derive(Debug, Clone, PartialEq)]
113#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
114pub struct TelemetryEquation {
115 pub a: f32,
116 pub b: f32,
117 pub c: f32,
118}
119
120impl TelemetryEquation {
121 pub fn apply(&self, raw: f32) -> f32 {
123 self.a + self.b * raw + self.c * raw * raw
124 }
125}
126
127#[derive(Debug, Clone, PartialEq, Default)]
133#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
134pub struct TelemetryMetadata {
135 pub param_names: Vec<Option<Vec<u8>>>,
137 pub unit_labels: Vec<Option<Vec<u8>>>,
139 pub equations: Vec<TelemetryEquation>,
141 pub bit_sense: u8,
143 pub project_name: Vec<u8>,
145}
146
147impl TelemetryMetadata {
148 pub fn parse_parm(text: &[u8]) -> Vec<Option<Vec<u8>>> {
152 parse_csv_fields(text, 13)
153 }
154
155 pub fn parse_unit(text: &[u8]) -> Vec<Option<Vec<u8>>> {
157 parse_csv_fields(text, 13)
158 }
159
160 pub fn parse_eqns(text: &[u8]) -> Vec<TelemetryEquation> {
164 let parts: Vec<&[u8]> = text.split(|&c| c == b',').collect();
165 let mut result = Vec::with_capacity(5);
166 for i in 0..5 {
167 let a = parts.get(i * 3).and_then(|p| parse_f32(p)).unwrap_or(0.0);
168 let b = parts.get(i * 3 + 1).and_then(|p| parse_f32(p)).unwrap_or(1.0);
169 let c = parts.get(i * 3 + 2).and_then(|p| parse_f32(p)).unwrap_or(0.0);
170 result.push(TelemetryEquation { a, b, c });
171 }
172 result
173 }
174
175 pub fn parse_bits(text: &[u8]) -> (u8, Vec<u8>) {
179 let comma = text.iter().position(|&b| b == b',');
180 let sense_bytes = match comma {
181 Some(pos) => &text[..pos],
182 None => text,
183 };
184 let project = match comma {
185 Some(pos) => text.get(pos + 1..).unwrap_or_default().to_vec(),
186 None => vec![],
187 };
188 let mut sense = 0u8;
189 for (i, &b) in sense_bytes.iter().enumerate().take(8) {
190 if b == b'1' {
191 sense |= 0x80 >> i;
192 }
193 }
194 (sense, project)
195 }
196}
197
198fn parse_csv_fields(text: &[u8], max: usize) -> Vec<Option<Vec<u8>>> {
201 text.split(|&c| c == b',')
202 .take(max)
203 .map(|part| {
204 let trimmed: Vec<u8> = part.iter().copied()
205 .skip_while(|&b| b == b' ')
206 .collect();
207 if trimmed.is_empty() { None } else { Some(trimmed) }
208 })
209 .collect()
210}
211
212fn parse_f32(b: &[u8]) -> Option<f32> {
213 std::str::from_utf8(b).ok()?.trim().parse().ok()
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
221 fn parse_basic_telemetry() {
222 let t = AprsTelemetry::parse(b"T#001,100,200,300,400,500,10101010").unwrap();
223 assert_eq!(t.sequence, b"001");
224 assert_eq!(t.analog[0], Some(100.0));
225 assert_eq!(t.analog[4], Some(500.0));
226 assert_eq!(t.digital, 0b10101010);
227 assert!(t.comment.is_empty());
228 }
229
230 #[test]
231 fn parse_telemetry_with_comment() {
232 let t = AprsTelemetry::parse(b"T#001,100,200,300,400,500,11110000,Hello World").unwrap();
233 assert_eq!(t.digital, 0b11110000);
234 assert_eq!(t.comment, b"Hello World");
235 }
236
237 #[test]
238 fn encode_round_trip() {
239 let raw = b"T#001,100,200,300,400,500,10101010,Test";
240 let t = AprsTelemetry::parse(raw).unwrap();
241 assert_eq!(t.encode().as_slice(), raw.as_slice());
242 }
243
244 #[test]
245 fn parse_parm_names() {
246 let names = TelemetryMetadata::parse_parm(b"Bat1,Bat2,Temp,Hum,Pres,LED1,LED2");
247 assert_eq!(names[0].as_deref(), Some(b"Bat1".as_slice()));
248 assert_eq!(names[4].as_deref(), Some(b"Pres".as_slice()));
249 assert_eq!(names[5].as_deref(), Some(b"LED1".as_slice()));
250 }
251
252 #[test]
253 fn parse_eqns() {
254 let eqns = TelemetryMetadata::parse_eqns(b"0,0.01,0,0,0.01,0,0,1,0,0,1,0,0,1,0");
255 assert_eq!(eqns.len(), 5);
256 assert!((eqns[0].b - 0.01).abs() < 0.001);
257 assert!((eqns[0].c).abs() < 0.001);
258 }
259
260 #[test]
261 fn equation_apply() {
262 let eq = TelemetryEquation { a: 0.0, b: 0.01, c: 0.0 };
263 assert!((eq.apply(100.0) - 1.0).abs() < 0.001);
264 }
265
266 #[test]
267 fn parse_bits() {
268 let (sense, project) = TelemetryMetadata::parse_bits(b"11111111,My Station");
269 assert_eq!(sense, 0xFF);
270 assert_eq!(project, b"My Station");
271 }
272
273 #[test]
274 fn parse_bits_mixed() {
275 let (sense, _) = TelemetryMetadata::parse_bits(b"10110100,Test");
276 assert_eq!(sense, 0b10110100);
277 }
278
279 #[test]
280 fn missing_digital_bits_defaults_to_zero() {
281 let t = AprsTelemetry::parse(b"T#001,100,200,300,400,500").unwrap();
283 assert_eq!(t.digital, 0);
284 }
285}