use std::io::Write;
use base91;
use compression_type::NmeaSource;
use AprsCompressionType;
use DecodeError;
use EncodeError;
#[derive(PartialEq, Copy, Clone, Debug)]
pub enum AprsCompressedCs {
CourseSpeed(AprsCourseSpeed),
RadioRange(AprsRadioRange),
Altitude(AprsAltitude),
}
impl AprsCompressedCs {
pub(crate) fn parse(c: u8, s: u8, t: AprsCompressionType) -> Result<Self, DecodeError> {
let c_lwr = base91::digit_from_ascii(c).ok_or(DecodeError::InvalidCs([c, s]))?;
let s_lwr = base91::digit_from_ascii(s).ok_or(DecodeError::InvalidCs([c, s]))?;
if t.nmea_source == NmeaSource::Gga {
Ok(AprsCompressedCs::Altitude(AprsAltitude::from_cs(
c_lwr, s_lwr,
)))
} else {
let val = match c_lwr {
0..=89 => AprsCompressedCs::CourseSpeed(AprsCourseSpeed::from_cs(c_lwr, s_lwr)),
90 => AprsCompressedCs::RadioRange(AprsRadioRange::from_s(s_lwr)),
_ => return Err(DecodeError::InvalidCs([c, s])),
};
Ok(val)
}
}
pub(crate) fn encode<W: Write>(
self,
buf: &mut W,
t: AprsCompressionType,
) -> Result<(), EncodeError> {
match self {
AprsCompressedCs::CourseSpeed(cs) => {
let (c, s) = cs.to_cs();
buf.write_all(&[base91::digit_to_ascii(c), base91::digit_to_ascii(s)])?;
}
AprsCompressedCs::RadioRange(rr) => {
let s = rr.to_s();
buf.write_all(&[b'{', base91::digit_to_ascii(s)])?;
}
AprsCompressedCs::Altitude(a) => {
if t.nmea_source != NmeaSource::Gga {
return Err(EncodeError::NonGgaAltitude);
}
let (c, s) = a.to_cs();
buf.write_all(&[base91::digit_to_ascii(c), base91::digit_to_ascii(s)])?;
}
}
buf.write_all(&[base91::digit_to_ascii(u8::from(t))])?;
Ok(())
}
}
#[derive(PartialEq, Copy, Clone, Debug)]
pub struct AprsCourseSpeed {
course_degrees: u16,
speed_knots: f64,
}
impl AprsCourseSpeed {
pub fn new(course_degrees: u16, speed_knots: f64) -> Self {
assert!(course_degrees <= 360);
assert!(speed_knots < (1.08_f64).powi(255));
Self {
course_degrees,
speed_knots,
}
}
pub fn course_degrees(&self) -> u16 {
self.course_degrees
}
pub fn speed_knots(&self) -> f64 {
self.speed_knots
}
fn from_cs(c: u8, s: u8) -> Self {
let course_degrees = c as u16 * 4;
let speed_knots = (1.08_f64).powi(s as i32) - 1.0;
debug_assert!(course_degrees <= 360);
debug_assert!(speed_knots < (1.08_f64).powi(255));
Self {
course_degrees,
speed_knots,
}
}
fn to_cs(self) -> (u8, u8) {
let c = self.course_degrees / 4;
let s = ((self.speed_knots + 1.0).ln() / (1.08_f64).ln()).round();
(c as u8, s as u8)
}
}
#[derive(PartialEq, Copy, Clone, Debug)]
pub struct AprsRadioRange {
range_miles: f64,
}
impl AprsRadioRange {
pub fn new(range_miles: f64) -> Self {
assert!(range_miles < (1.08_f64).powi(255));
Self { range_miles }
}
pub fn range_miles(&self) -> f64 {
self.range_miles
}
fn from_s(s: u8) -> Self {
Self {
range_miles: 2.0 * (1.08_f64).powi(s as i32),
}
}
fn to_s(self) -> u8 {
let s = ((self.range_miles / 2.0).ln() / (1.08_f64).ln()).round();
s as u8
}
}
#[derive(PartialEq, Copy, Clone, Debug)]
pub struct AprsAltitude {
altitude_feet: f64,
}
impl AprsAltitude {
pub fn new(altitude_feet: f64) -> Self {
assert!(altitude_feet < (1.002_f64).powi(255 * 91 + 255));
Self { altitude_feet }
}
pub fn altitude_feet(&self) -> f64 {
self.altitude_feet
}
fn from_cs(c: u8, s: u8) -> Self {
Self {
altitude_feet: (1.002_f64).powi(c as i32 * 91 + s as i32),
}
}
fn to_cs(self) -> (u8, u8) {
let alt = (self.altitude_feet.ln() / (1.002_f64).ln()).round() as i32;
let c = alt / 91;
let s = alt % 91;
(c as u8, s as u8)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn course_speed_exhaustive() {
for c in 0..91 {
for s in 0..91 {
let val = AprsCourseSpeed::from_cs(c, s);
assert_eq!((c, s), val.to_cs());
}
}
}
#[test]
fn radio_range_exhaustive() {
for s in 0..91 {
let val = AprsRadioRange::from_s(s);
assert_eq!(s, val.to_s());
}
}
#[test]
fn altitude_exhaustive() {
for c in 0..91 {
for s in 0..91 {
let val = AprsAltitude::from_cs(c, s);
assert_eq!((c, s), val.to_cs());
}
}
}
}