aprs_parser/
compressed_cs.rs1use std::io::Write;
2
3use base91;
4use compression_type::NmeaSource;
5use AprsCompressionType;
6use DecodeError;
7use EncodeError;
8
9#[derive(PartialEq, Copy, Clone, Debug)]
10pub enum AprsCompressedCs {
11 CourseSpeed(AprsCourseSpeed),
12 RadioRange(AprsRadioRange),
13 Altitude(AprsAltitude),
14}
15
16impl AprsCompressedCs {
17 pub(crate) fn parse(c: u8, s: u8, t: AprsCompressionType) -> Result<Self, DecodeError> {
18 let c_lwr = base91::digit_from_ascii(c).ok_or(DecodeError::InvalidCs([c, s]))?;
19 let s_lwr = base91::digit_from_ascii(s).ok_or(DecodeError::InvalidCs([c, s]))?;
20
21 if t.nmea_source == NmeaSource::Gga {
22 Ok(AprsCompressedCs::Altitude(AprsAltitude::from_cs(
23 c_lwr, s_lwr,
24 )))
25 } else {
26 let val = match c_lwr {
27 0..=89 => AprsCompressedCs::CourseSpeed(AprsCourseSpeed::from_cs(c_lwr, s_lwr)),
28 90 => AprsCompressedCs::RadioRange(AprsRadioRange::from_s(s_lwr)),
29 _ => return Err(DecodeError::InvalidCs([c, s])),
30 };
31
32 Ok(val)
33 }
34 }
35
36 pub(crate) fn encode<W: Write>(
37 self,
38 buf: &mut W,
39 t: AprsCompressionType,
40 ) -> Result<(), EncodeError> {
41 match self {
42 AprsCompressedCs::CourseSpeed(cs) => {
43 let (c, s) = cs.to_cs();
44 buf.write_all(&[base91::digit_to_ascii(c), base91::digit_to_ascii(s)])?;
45 }
46 AprsCompressedCs::RadioRange(rr) => {
47 let s = rr.to_s();
48 buf.write_all(&[b'{', base91::digit_to_ascii(s)])?;
49 }
50 AprsCompressedCs::Altitude(a) => {
51 if t.nmea_source != NmeaSource::Gga {
52 return Err(EncodeError::NonGgaAltitude);
53 }
54
55 let (c, s) = a.to_cs();
56 buf.write_all(&[base91::digit_to_ascii(c), base91::digit_to_ascii(s)])?;
57 }
58 }
59
60 buf.write_all(&[base91::digit_to_ascii(u8::from(t))])?;
61
62 Ok(())
63 }
64}
65
66#[derive(PartialEq, Copy, Clone, Debug)]
67pub struct AprsCourseSpeed {
68 course_degrees: u16,
69 speed_knots: f64,
70}
71
72impl AprsCourseSpeed {
73 pub fn new(course_degrees: u16, speed_knots: f64) -> Self {
74 assert!(course_degrees <= 360);
75 assert!(speed_knots < (1.08_f64).powi(255));
76
77 Self {
78 course_degrees,
79 speed_knots,
80 }
81 }
82
83 pub fn course_degrees(&self) -> u16 {
84 self.course_degrees
85 }
86
87 pub fn speed_knots(&self) -> f64 {
88 self.speed_knots
89 }
90
91 fn from_cs(c: u8, s: u8) -> Self {
92 let course_degrees = c as u16 * 4;
93 let speed_knots = (1.08_f64).powi(s as i32) - 1.0;
94
95 debug_assert!(course_degrees <= 360);
96 debug_assert!(speed_knots < (1.08_f64).powi(255));
97
98 Self {
99 course_degrees,
100 speed_knots,
101 }
102 }
103
104 fn to_cs(self) -> (u8, u8) {
105 let c = self.course_degrees / 4;
106 let s = ((self.speed_knots + 1.0).ln() / (1.08_f64).ln()).round();
107
108 (c as u8, s as u8)
109 }
110}
111
112#[derive(PartialEq, Copy, Clone, Debug)]
113pub struct AprsRadioRange {
114 range_miles: f64,
115}
116
117impl AprsRadioRange {
118 pub fn new(range_miles: f64) -> Self {
119 assert!(range_miles < (1.08_f64).powi(255));
120
121 Self { range_miles }
122 }
123
124 pub fn range_miles(&self) -> f64 {
125 self.range_miles
126 }
127
128 fn from_s(s: u8) -> Self {
129 Self {
130 range_miles: 2.0 * (1.08_f64).powi(s as i32),
131 }
132 }
133
134 fn to_s(self) -> u8 {
135 let s = ((self.range_miles / 2.0).ln() / (1.08_f64).ln()).round();
136
137 s as u8
138 }
139}
140
141#[derive(PartialEq, Copy, Clone, Debug)]
142pub struct AprsAltitude {
143 altitude_feet: f64,
144}
145
146impl AprsAltitude {
147 pub fn new(altitude_feet: f64) -> Self {
148 assert!(altitude_feet < (1.002_f64).powi(255 * 91 + 255));
149
150 Self { altitude_feet }
151 }
152
153 pub fn altitude_feet(&self) -> f64 {
154 self.altitude_feet
155 }
156
157 fn from_cs(c: u8, s: u8) -> Self {
158 Self {
159 altitude_feet: (1.002_f64).powi(c as i32 * 91 + s as i32),
160 }
161 }
162
163 fn to_cs(self) -> (u8, u8) {
164 let alt = (self.altitude_feet.ln() / (1.002_f64).ln()).round() as i32;
165
166 let c = alt / 91;
167 let s = alt % 91;
168
169 (c as u8, s as u8)
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn course_speed_exhaustive() {
179 for c in 0..91 {
180 for s in 0..91 {
181 let val = AprsCourseSpeed::from_cs(c, s);
182
183 assert_eq!((c, s), val.to_cs());
184 }
185 }
186 }
187
188 #[test]
189 fn radio_range_exhaustive() {
190 for s in 0..91 {
191 let val = AprsRadioRange::from_s(s);
192
193 assert_eq!(s, val.to_s());
194 }
195 }
196
197 #[test]
198 fn altitude_exhaustive() {
199 for c in 0..91 {
200 for s in 0..91 {
201 let val = AprsAltitude::from_cs(c, s);
202
203 assert_eq!((c, s), val.to_cs());
204 }
205 }
206 }
207}