use crate::flight_modes::MAV_AUTOPILOT_PX4;
pub const MAV_PARAM_TYPE_UINT8: u8 = 1;
pub const MAV_PARAM_TYPE_INT8: u8 = 2;
pub const MAV_PARAM_TYPE_UINT16: u8 = 3;
pub const MAV_PARAM_TYPE_INT16: u8 = 4;
pub const MAV_PARAM_TYPE_UINT32: u8 = 5;
pub const MAV_PARAM_TYPE_INT32: u8 = 6;
pub const MAV_PARAM_TYPE_UINT64: u8 = 7;
pub const MAV_PARAM_TYPE_INT64: u8 = 8;
pub const MAV_PARAM_TYPE_REAL32: u8 = 9;
pub const MAV_PARAM_TYPE_REAL64: u8 = 10;
#[must_use]
pub fn uses_bytewise(autopilot: i64) -> bool {
autopilot == MAV_AUTOPILOT_PX4
}
#[must_use]
pub fn is_known_type(param_type: u8) -> bool {
(MAV_PARAM_TYPE_UINT8..=MAV_PARAM_TYPE_REAL64).contains(¶m_type)
}
fn integer_range(param_type: u8) -> Option<(f64, f64)> {
const EXACT: f64 = 9_007_199_254_740_992.0;
Some(match param_type {
MAV_PARAM_TYPE_UINT8 => (0.0, f64::from(u8::MAX)),
MAV_PARAM_TYPE_INT8 => (f64::from(i8::MIN), f64::from(i8::MAX)),
MAV_PARAM_TYPE_UINT16 => (0.0, f64::from(u16::MAX)),
MAV_PARAM_TYPE_INT16 => (f64::from(i16::MIN), f64::from(i16::MAX)),
MAV_PARAM_TYPE_UINT32 => (0.0, f64::from(u32::MAX)),
MAV_PARAM_TYPE_INT32 => (f64::from(i32::MIN), f64::from(i32::MAX)),
MAV_PARAM_TYPE_UINT64 => (0.0, EXACT),
MAV_PARAM_TYPE_INT64 => (-EXACT, EXACT),
_ => return None,
})
}
#[must_use]
pub fn decode(raw: f32, param_type: u8, bytewise: bool) -> f64 {
if bytewise {
let b = raw.to_le_bytes();
match param_type {
MAV_PARAM_TYPE_UINT8 => return f64::from(b[0]),
MAV_PARAM_TYPE_INT8 => return f64::from(i8::from_le_bytes([b[0]])),
MAV_PARAM_TYPE_UINT16 => return f64::from(u16::from_le_bytes([b[0], b[1]])),
MAV_PARAM_TYPE_INT16 => return f64::from(i16::from_le_bytes([b[0], b[1]])),
MAV_PARAM_TYPE_UINT32 => return f64::from(u32::from_le_bytes(b)),
MAV_PARAM_TYPE_INT32 => return f64::from(i32::from_le_bytes(b)),
_ => {}
}
}
f64::from(raw)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Encoded {
pub wire: f32,
pub stored: f64,
}
pub fn encode(value: f64, param_type: u8, bytewise: bool) -> Result<Encoded, String> {
if !value.is_finite() {
return Err("value must be a finite number".to_string());
}
if let Some((min, max)) = integer_range(param_type) {
let n = value.round();
if n < min || n > max {
return Err(format!(
"value {value} is outside the range of this parameter's type ({min} to {max})"
));
}
if bytewise && param_type <= MAV_PARAM_TYPE_INT32 {
let mut b = [0u8; 4];
match param_type {
MAV_PARAM_TYPE_UINT8 => b[0] = n as u8,
MAV_PARAM_TYPE_INT8 => b[0] = (n as i8).to_le_bytes()[0],
MAV_PARAM_TYPE_UINT16 => b[..2].copy_from_slice(&(n as u16).to_le_bytes()),
MAV_PARAM_TYPE_INT16 => b[..2].copy_from_slice(&(n as i16).to_le_bytes()),
MAV_PARAM_TYPE_UINT32 => b = (n as u32).to_le_bytes(),
_ => b = (n as i32).to_le_bytes(),
}
return Ok(Encoded {
wire: f32::from_le_bytes(b),
stored: n,
});
}
let wire = n as f32;
return Ok(Encoded {
wire,
stored: f64::from(wire),
});
}
let wire = value as f32;
if !wire.is_finite() {
return Err(format!("value {value} does not fit a 32-bit float"));
}
Ok(Encoded {
wire,
stored: f64::from(wire),
})
}
#[must_use]
pub fn echo_matches(cached: f64, stored: f64) -> bool {
(cached - stored).abs() <= stored.abs().max(1.0) * f64::from(f32::EPSILON)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_px4_integer_travels_as_its_bytes_not_as_a_float() {
let e = encode(5.0, MAV_PARAM_TYPE_INT32, true).unwrap();
assert_eq!(e.wire.to_le_bytes(), 5i32.to_le_bytes());
assert_eq!(e.stored, 5.0);
assert_eq!(decode(e.wire, MAV_PARAM_TYPE_INT32, true), 5.0);
assert_ne!(f64::from(e.wire), 5.0);
}
#[test]
fn narrow_and_signed_px4_integers_fill_only_their_low_bytes() {
let e = encode(-1.0, MAV_PARAM_TYPE_INT8, true).unwrap();
assert_eq!(e.wire.to_le_bytes(), [0xFF, 0, 0, 0]);
assert_eq!(decode(e.wire, MAV_PARAM_TYPE_INT8, true), -1.0);
let e = encode(-300.0, MAV_PARAM_TYPE_INT16, true).unwrap();
assert_eq!(&e.wire.to_le_bytes()[..2], &(-300i16).to_le_bytes());
assert_eq!(&e.wire.to_le_bytes()[2..], &[0, 0]);
assert_eq!(decode(e.wire, MAV_PARAM_TYPE_INT16, true), -300.0);
let e = encode(4_000_000_000.0, MAV_PARAM_TYPE_UINT32, true).unwrap();
assert_eq!(decode(e.wire, MAV_PARAM_TYPE_UINT32, true), 4_000_000_000.0);
}
#[test]
fn an_ardupilot_integer_is_the_float_of_the_rounded_value() {
let e = encode(4.6, MAV_PARAM_TYPE_INT32, false).unwrap();
assert_eq!(e.wire, 5.0);
assert_eq!(e.stored, 5.0);
assert_eq!(decode(e.wire, MAV_PARAM_TYPE_INT32, false), 5.0);
}
#[test]
fn a_float_param_is_the_same_on_either_firmware() {
for bytewise in [false, true] {
let e = encode(0.135, MAV_PARAM_TYPE_REAL32, bytewise).unwrap();
assert_eq!(e.wire, 0.135f32);
assert_eq!(
decode(e.wire, MAV_PARAM_TYPE_REAL32, bytewise),
f64::from(0.135f32)
);
}
}
#[test]
fn a_value_outside_the_type_is_refused_not_wrapped() {
assert!(encode(256.0, MAV_PARAM_TYPE_UINT8, true).is_err());
assert!(encode(-1.0, MAV_PARAM_TYPE_UINT8, false).is_err());
assert!(encode(128.0, MAV_PARAM_TYPE_INT8, true).is_err());
assert!(encode(2_147_483_648.0, MAV_PARAM_TYPE_INT32, true).is_err());
assert!(encode(1e39, MAV_PARAM_TYPE_REAL32, false).is_err());
assert!(encode(f64::NAN, MAV_PARAM_TYPE_REAL32, false).is_err());
assert!(encode(255.0, MAV_PARAM_TYPE_UINT8, true).is_ok());
assert!(encode(-128.0, MAV_PARAM_TYPE_INT8, true).is_ok());
}
#[test]
fn a_landed_float_write_matches_its_echo_at_f32_precision() {
let e = encode(100.1, MAV_PARAM_TYPE_REAL32, false).unwrap();
assert!(echo_matches(f64::from(100.1f32), e.stored));
assert!(!echo_matches(100.2, e.stored));
assert!(echo_matches(
0.0,
encode(0.0, MAV_PARAM_TYPE_REAL32, false).unwrap().stored
));
assert!(!echo_matches(1.0, 2.0));
}
#[test]
fn only_px4_packs_bytewise() {
assert!(uses_bytewise(12));
assert!(!uses_bytewise(3));
assert!(!uses_bytewise(0));
assert!(is_known_type(6) && is_known_type(9) && !is_known_type(0) && !is_known_type(11));
}
}