use arrayvec::{ArrayString, ArrayVec}; use half::f16; pub enum Whisker { Identification(Identification), Timestamp(Timestamp), Gps(Gps), Comment(Comment), Route(Route), Destination(Destination), } #[derive(Debug, PartialEq, Eq)] pub struct Identification { pub callsign: ArrayString<254>, pub ssid: u8, } impl Identification { pub fn new(call: &str, ssid: u8) -> Option<Self> { let callsign = ArrayString::from(call).ok()?; Some(Self { callsign, ssid }) } /// Returns none if there is not enough space in the buffer pub fn encode<'a>(&self, buf: &'a mut [u8]) -> Option<&'a [u8]> { // safe to unwrap because we know the length is <= 254 let len: u8 = self.callsign.as_bytes().len().try_into().unwrap(); *buf.get_mut(0)? = len + 1; let mut len = 1; for (i, b) in self.callsign.as_bytes().iter().enumerate() { *buf.get_mut(i + 1)? = *b; len += 1 } *buf.get_mut(len)? = self.ssid; len += 1; Some(&buf[0..len]) } pub fn decode(data: &[u8]) -> Option<Self> { let len = (*data.first()?).into(); let callsign = core::str::from_utf8(data.get(1..len)?).ok()?; let callsign = ArrayString::from(callsign).ok()?; let ssid = *data.get(len)?; Some(Self { callsign, ssid }) } } #[derive(Debug, PartialEq, Eq)] pub struct Timestamp(u64); impl Timestamp { /// Returns none if the given unix timestamp doesn't fit in 5 bytes pub fn new(unix_time: u64) -> Option<Self> { // must fit in 5 bytes if unix_time > (1 << (5 * 8)) - 1 { return None; } Some(Self(unix_time)) } pub fn unix_time(&self) -> u64 { self.0 } /// Returns none if there is not enough space in the buffer pub fn encode<'a>(&self, buf: &'a mut [u8]) -> Option<&'a [u8]> { if buf.len() < 6 { return None; } buf[0] = 5; buf[1..6].copy_from_slice(&self.0.to_le_bytes()[0..5]); Some(&buf[0..6]) } pub fn decode(data: &[u8]) -> Option<Self> { if data.len() < 6 { return None; } if data[0] != 5 { return None; } let mut extended_data = [0; 8]; extended_data[0..5].copy_from_slice(&data[1..6]); Some(Self(u64::from_le_bytes(extended_data))) } } #[derive(Debug, PartialEq)] pub struct Gps { latitude: i32, longitude: i32, pub altitude: f16, pub max_error: u8, heading: u8, pub speed: f16, } impl Gps { /// Constructs a new GPS whisker. /// /// latitude: degrees /// longitude: degrees /// altitude: meters /// max_error: maximum error distance from specified lat/lon/alt, meters /// heading: direction relative to north, degrees /// speed: meters per second pub fn new( latitude: f64, longitude: f64, altitude: f16, max_error: u8, heading: f64, speed: f16, ) -> Self { let latitude = latitude.clamp(-89.999, 89.999); let latitude = (latitude * ((1u32 << 31) as f64) / 90.0) as i32; let longitude = longitude.clamp(-179.999, 179.999); let longitude = (longitude * ((1u32 << 31) as f64) / 180.0) as i32; let heading = heading.clamp(-359.999, 359.999); let heading = (heading * 128.0 / 360.0) as u8; Self { latitude, longitude, altitude, max_error, heading, speed, } } pub fn latitude(&self) -> f64 { (self.latitude as f64) / (1u32 << 31) as f64 * 90.0 } pub fn longitude(&self) -> f64 { (self.longitude as f64) / (1u32 << 31) as f64 * 180.0 } pub fn heading(&self) -> f64 { self.heading as f64 / 128.0 * 360.0 } pub fn encode<'a>(&self, buf: &'a mut [u8]) -> Option<&'a [u8]> { let buf = buf.get_mut(0..15)?; buf[0] = 14; buf[1..5].copy_from_slice(&self.latitude.to_le_bytes()); buf[5..9].copy_from_slice(&self.longitude.to_le_bytes()); buf[9..11].copy_from_slice(&self.altitude.to_le_bytes()); buf[11] = self.max_error; buf[12] = self.heading; buf[13..15].copy_from_slice(&self.speed.to_le_bytes()); Some(buf) } pub fn decode(data: &[u8]) -> Option<Self> { let data = data.get(0..15)?; if data[0] != 14 { return None; } let latitude = i32::from_le_bytes(data[1..5].try_into().unwrap()); let longitude = i32::from_le_bytes(data[5..9].try_into().unwrap()); let altitude = f16::from_le_bytes(data[9..11].try_into().unwrap()); let max_error = data[11]; let heading = data[12]; let speed = f16::from_le_bytes(data[13..15].try_into().unwrap()); Some(Self { latitude, longitude, altitude, max_error, heading, speed, }) } } pub struct Comment(ArrayVec<u8, 255>); impl Comment { pub fn new(data: &[u8]) -> Option<Self> { let mut av = ArrayVec::new(); av.try_extend_from_slice(data).ok()?; Some(Self(av)) } pub fn encode<'a>(&self, buf: &'a mut [u8]) -> Option<&'a [u8]> { let len = self.0.len(); // length must be <= 255, so this is safe *buf.get_mut(0)? = len.try_into().unwrap(); buf.get_mut(1..(len + 1))?.copy_from_slice(&self.0); Some(&buf[0..(len + 1)]) } pub fn decode(data: &[u8]) -> Option<Self> { let len: usize = (*data.first()?).into(); let content = data.get(1..(len + 1))?; Self::new(content) } } pub struct Route { max_hops: u8, path: ArrayVec<u8, 255>, } pub struct Destination { ack: u8, callsign: ArrayString<255>, ssid: u8, } #[cfg(test)] mod tests { use super::*; #[test] fn ident_e2e() { let call = "VE9ABCDEFGZZ4839-???"; let ssid = 17; let ident = Identification::new(call, ssid).unwrap(); let mut buf = [0; 256]; let encoded = ident.encode(&mut buf).unwrap(); let decoded = Identification::decode(encoded).unwrap(); assert_eq!(ident, decoded); assert_eq!(call, &decoded.callsign); assert_eq!(ssid, decoded.ssid); } #[test] fn new_timestamp() { let t = 34894; assert_eq!(t, Timestamp::new(t).unwrap().unix_time()); let t = 1 << (5 * 8); assert_eq!(None, Timestamp::new(t)); } #[test] fn timestamp_e2e() { let t = 34894; let timestamp = Timestamp::new(t).unwrap(); let mut buf = [0; 256]; let encoded = timestamp.encode(&mut buf).unwrap(); let decoded = Timestamp::decode(encoded).unwrap(); assert_eq!(timestamp, decoded); assert_eq!(t, timestamp.unix_time()); } #[test] fn new_gps() { let gps = Gps::new( 90.0, -180.0, f16::from_f32(45.02), 24, 123.45, f16::from_f32(12.3), ); assert_eq!(89.99899999704212, gps.latitude()); assert_eq!(-179.9989999551326, gps.longitude()); assert_eq!(120.9375, gps.heading()); } #[test] fn gps_e2e() { let gps = Gps::new( 90.0, -180.0, f16::from_f32(45.02), 24, 123.45, f16::from_f32(12.3), ); let mut buf = [0; 256]; let encoded = gps.encode(&mut buf).unwrap(); let decoded = Gps::decode(encoded).unwrap(); assert_eq!(gps, decoded); } #[test] fn comment_e2e() { let data = b"Hello world! This is an example comment"; let comment = Comment::new(data).unwrap(); let mut buf = [0; 256]; let encoded = comment.encode(&mut buf).unwrap(); let decoded = Comment::decode(encoded).unwrap(); let decoded2 = Comment::decode(&buf).unwrap(); assert_eq!(data[..], decoded.0[..]); assert_eq!(data[..], decoded2.0[..]); } }