Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • cats/ham-cats
  • sam/ham-cats
  • Quantum_P/ham-cats
  • Reed/ham-cats
4 results
Show changes
Commits on Source (9)
[package]
name = "ham-cats"
version = "0.2.0"
version = "0.2.2"
edition = "2021"
license = "MIT"
description = "Reference implementations for CATS, the ham radio protocol"
......@@ -14,6 +14,6 @@ bitvec = { version = "1.0.1", default-features = false }
crc = "3.0.1"
encoding_rs = { version = "0.8.33", default-features = false }
half = { version = "2.3.1", default-features = false }
labrador-ldpc = { git = "https://github.com/adamgreig/labrador-ldpc" }
labrador-ldpc = "1.2.1"
paste = "1.0.14"
snafu = { version = "0.7.5", default-features = false }
......@@ -107,7 +107,7 @@ impl<'a, const N: usize, T> From<&'a mut [T; N]> for Buffer<'a, N, T> {
}
}
impl<'a, const N: usize, T> Deref for Buffer<'a, N, T> {
impl<const N: usize, T> Deref for Buffer<'_, N, T> {
type Target = [T];
fn deref(&self) -> &Self::Target {
......@@ -115,7 +115,7 @@ impl<'a, const N: usize, T> Deref for Buffer<'a, N, T> {
}
}
impl<'a, const N: usize, T> DerefMut for Buffer<'a, N, T> {
impl<const N: usize, T> DerefMut for Buffer<'_, N, T> {
fn deref_mut(&mut self) -> &mut [T] {
&mut self.data[..self.i]
}
......
#![no_std]
#![cfg_attr(not(test), no_std)]
pub mod buffer;
pub mod error;
......
......@@ -169,7 +169,7 @@ impl<'a, const N: usize> Packet<'a, N> {
/// Encodes packet for transmission on the air.
/// Includes the data length L, but does not include the preamble or sync word.
pub fn fully_encode(self, out: &mut Buffer<N>) -> Result<(), EncodeError> {
pub fn fully_encode<const M: usize>(self, out: &mut Buffer<M>) -> Result<(), EncodeError> {
let mut data = self.semi_encode().map_err(|(err, _)| err)?;
whitener::whiten(&mut data);
ldpc::encode(&mut data)?;
......@@ -379,7 +379,7 @@ impl<'a, const N: usize> Packet<'a, N> {
}
}
impl<'a, const N: usize> Debug for Packet<'a, N> {
impl<const N: usize> Debug for Packet<'_, N> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_list()
.entries(ValidatedWhiskerIter::new(&self.buf))
......
......@@ -36,7 +36,7 @@ impl Destination {
}
pub fn ack_num(&self) -> u8 {
self.ack
self.ack & !(1 << 7)
}
pub fn callsign(&self) -> &str {
......
use core::fmt::{Debug, Display};
use half::f16;
#[derive(Debug, PartialEq, Clone)]
#[derive(PartialEq, Clone)]
pub struct Gps {
latitude: i32,
longitude: i32,
......@@ -100,6 +101,27 @@ impl Gps {
}
}
impl Debug for Gps {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
f.debug_struct("Gps")
.field("latitude", &DebugUnits(self.latitude(), "°"))
.field("longitude", &DebugUnits(self.longitude(), "°"))
.field("altitude", &DebugUnits(self.altitude, " m"))
.field("max_error", &DebugUnits(self.max_error, " m"))
.field("heading", &DebugUnits(self.heading(), "°"))
.field("speed", &DebugUnits(self.speed, " m/s"))
.finish()
}
}
struct DebugUnits<'a, T>(T, &'a str);
impl<T: Display> Debug for DebugUnits<'_, T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}{}", self.0, self.1)
}
}
// no-std and it's not worth bringing in a library for this
fn round(v: f64) -> u32 {
let floor = v as u32;
......@@ -135,4 +157,11 @@ mod tests {
let gps = Gps::new(0.0, 0.0, f16::from_f32(0.0), 0, 540.0, f16::from_f32(0.0));
assert_eq!(gps.heading, 128);
}
#[test]
fn debug_printing() {
let gps = Gps::new(0.0, 0.0, f16::from_f32(0.0), 0, 359.0, f16::from_f32(0.0));
let x = format!("{gps:?}");
assert_eq!(x,"Gps { latitude: 0°, longitude: 0°, altitude: 0 m, max_error: 0 m, heading: 358.59375°, speed: 0 m/s }")
}
}
......@@ -155,7 +155,7 @@ impl<'a> WhiskerIter<'a> {
}
}
impl<'a> Iterator for WhiskerIter<'a> {
impl Iterator for WhiskerIter<'_> {
type Item = Result<Whisker, DecodeError>;
fn next(&mut self) -> Option<Self::Item> {
......@@ -179,7 +179,7 @@ impl<'a> ValidatedWhiskerIter<'a> {
}
}
impl<'a> Iterator for ValidatedWhiskerIter<'a> {
impl Iterator for ValidatedWhiskerIter<'_> {
type Item = Whisker;
fn next(&mut self) -> Option<Self::Item> {
......@@ -496,6 +496,15 @@ mod tests {
assert_eq!(dest, decoded);
}
#[test]
fn dest_ack() {
let dest = Destination::new(true, 84, "abc", 17).unwrap();
assert_eq!(84, dest.ack_num());
assert!(dest.is_ack());
assert_eq!("abc", dest.callsign());
assert_eq!(17, dest.ssid());
}
#[test]
fn arbitrary_e2e() {
let data = b"Hello world! This is an example comment";
......
......@@ -9,6 +9,11 @@ pub struct NodeInfo {
voltage: Option<u8>,
xcvr_temperature: Option<i8>,
battery_charge: Option<u8>,
altitude: Option<f32>,
balloon: bool,
ambient_temperature: Option<i8>,
ambient_humidity: Option<u8>,
ambient_pressure: Option<u16>,
}
impl NodeInfo {
......@@ -59,6 +64,32 @@ impl NodeInfo {
self.battery_charge.map(|b| b as f64 / 2.55)
}
/// Higher precision altitude than what's available in the GPS whisker. Takes precedent if set. Useful for high altitude balloons.
/// Meters
pub fn altitude(&self) -> Option<f32> {
self.altitude
}
/// If true, this whisker was emitted by a high altitude balloon payload
pub fn is_balloon(&self) -> bool {
self.balloon
}
/// Degrees C
pub fn ambient_temperature(&self) -> Option<i8> {
self.ambient_temperature
}
/// Relative humidity, percent
pub fn ambient_humidity(&self) -> Option<f64> {
self.ambient_humidity.map(|x| x as f64 / 2.55)
}
/// Decapascal (daPa)
pub fn ambient_pressure(&self) -> Option<u16> {
self.ambient_pressure
}
pub fn encode<'a>(&self, buf: &'a mut [u8]) -> Option<&'a [u8]> {
let mut bitmask: u32 = 0;
......@@ -128,6 +159,38 @@ impl NodeInfo {
i += 1;
}
if let Some(x) = self.altitude {
bitmask |= 512;
buf.get_mut(i..(i + 4))?.copy_from_slice(&x.to_le_bytes());
i += 4;
}
if self.balloon {
bitmask |= 1024;
}
if let Some(x) = self.ambient_temperature {
bitmask |= 2048;
*buf.get_mut(i)? = x.to_le_bytes()[0];
i += 1;
}
if let Some(x) = self.ambient_humidity {
bitmask |= 4096;
*buf.get_mut(i)? = x;
i += 1;
}
if let Some(x) = self.ambient_pressure {
bitmask |= 8192;
buf.get_mut(i..(i + 2))?.copy_from_slice(&x.to_le_bytes());
i += 2;
}
buf[0] = (i - 1).try_into().ok()?;
buf[1..4].copy_from_slice(&bitmask.to_be_bytes()[1..]);
......@@ -141,17 +204,17 @@ impl NodeInfo {
let mut i = 4;
if bitmask & 1 > 0 {
builder = builder.hardware_id(u16::from_le_bytes([*data.get(i)?, *data.get(i + 1)?]));
builder.hardware_id = Some(u16::from_le_bytes([*data.get(i)?, *data.get(i + 1)?]));
i += 2;
}
if bitmask & 2 > 0 {
builder = builder.software_id(*data.get(i)?);
builder.software_id = Some(*data.get(i)?);
i += 1;
}
if bitmask & 4 > 0 {
builder = builder.uptime(u32::from_le_bytes([
builder.uptime = Some(u32::from_le_bytes([
*data.get(i)?,
*data.get(i + 1)?,
*data.get(i + 2)?,
......@@ -162,7 +225,7 @@ impl NodeInfo {
}
if bitmask & 8 > 0 {
builder = builder.antenna_height(*data.get(i)?);
builder.antenna_height = Some(*data.get(i)?);
i += 1;
}
......@@ -182,14 +245,44 @@ impl NodeInfo {
}
if bitmask & 128 > 0 {
builder = builder.xcvr_temperature(i8::from_le_bytes([*data.get(i)?]));
builder.xcvr_temperature = Some(i8::from_le_bytes([*data.get(i)?]));
i += 1;
}
if bitmask & 256 > 0 {
builder.battery_charge = Some(*data.get(i)?);
i += 1;
}
if bitmask & 512 > 0 {
builder.altitude = Some(f32::from_le_bytes(
data.get(i..(i + 4))?.try_into().unwrap(),
));
i += 4;
}
builder.balloon = bitmask & 1024 > 0;
if bitmask & 2048 > 0 {
builder.ambient_temperature = Some(i8::from_le_bytes([*data.get(i)?]));
i += 1;
}
if bitmask & 4096 > 0 {
builder.ambient_humidity = Some(*data.get(i)?);
i += 1;
}
if bitmask & 8192 > 0 {
builder.ambient_pressure = Some(u16::from_le_bytes(
data.get(i..(i + 2))?.try_into().unwrap(),
));
i += 2;
}
// prevent unused variable warning
let _ = i;
Some(builder.build())
}
}
......@@ -205,6 +298,11 @@ pub struct NodeInfoBuilder {
voltage: Option<u8>,
xcvr_temperature: Option<i8>,
battery_charge: Option<u8>,
altitude: Option<f32>,
balloon: bool,
ambient_temperature: Option<i8>,
ambient_humidity: Option<u8>,
ambient_pressure: Option<u16>,
}
impl NodeInfoBuilder {
......@@ -219,6 +317,11 @@ impl NodeInfoBuilder {
voltage,
xcvr_temperature,
battery_charge,
altitude,
balloon,
ambient_temperature,
ambient_humidity,
ambient_pressure,
} = self;
NodeInfo {
......@@ -231,6 +334,11 @@ impl NodeInfoBuilder {
voltage,
xcvr_temperature,
battery_charge,
altitude,
balloon,
ambient_temperature,
ambient_humidity,
ambient_pressure,
}
}
......@@ -294,6 +402,43 @@ impl NodeInfoBuilder {
self
}
/// Higher precision altitude than what's available in the GPS whisker. Takes precedent if set. Useful for high altitude balloons.
/// Meters
pub fn altitude(mut self, val: f32) -> Self {
self.altitude = Some(val);
self
}
/// If true, this whisker was emitted by a high altitude balloon payload
/// Please only set this on actual balloon payloads. Otherwise it can cause issues for downstream users!
pub fn set_balloon(mut self) -> Self {
self.balloon = true;
self
}
/// Degrees C
pub fn ambient_temperature(mut self, val: i8) -> Self {
self.ambient_temperature = Some(val);
self
}
/// Relative humidity, percent
pub fn ambient_humidity(mut self, val: f64) -> Self {
self.ambient_humidity = Some((val * 2.55).min(255.0) as u8);
self
}
/// Decapascal (daPa)
pub fn ambient_pressure(mut self, val: u16) -> Self {
self.ambient_pressure = Some(val);
self
}
}
#[cfg(test)]
......
......@@ -232,7 +232,7 @@ impl<'a> PastHop<'a> {
}
}
#[derive(Debug)]
#[derive(Debug, Clone)]
struct UntrustedRouteIter<'a> {
route: &'a Route,
i: usize,
......@@ -312,6 +312,7 @@ impl<'a> Iterator for UntrustedRouteIter<'a> {
}
}
#[derive(Clone)]
pub struct RouteIter<'a> {
iter: UntrustedRouteIter<'a>,
}
......