use ados_protocol::mavlink::{
ardupilotmega::{MavFrame, ODOMETRY_DATA, VISION_POSITION_ESTIMATE_DATA},
serialize_v2, MavHeader, MavMessage, MavlinkError,
};
pub const VIO_COMPONENT_ID: i64 = 197;
pub const POSE_COVARIANCE_LEN: usize = 21;
#[derive(Debug, Clone, PartialEq)]
pub struct Pose {
pub position: (f32, f32, f32),
pub orientation: (f32, f32, f32, f32),
pub timestamp_us: u64,
pub covariance: Option<[f32; POSE_COVARIANCE_LEN]>,
}
impl Pose {
pub fn identity(timestamp_us: u64) -> Self {
Self {
position: (0.0, 0.0, 0.0),
orientation: (1.0, 0.0, 0.0, 0.0),
timestamp_us,
covariance: None,
}
}
fn covariance_field(&self) -> [f32; POSE_COVARIANCE_LEN] {
match self.covariance {
Some(c) => c,
None => {
let mut c = [0.0f32; POSE_COVARIANCE_LEN];
c[0] = f32::NAN;
c
}
}
}
fn euler_rpy(&self) -> (f32, f32, f32) {
let (w, x, y, z) = self.orientation;
let sinr_cosp = 2.0 * (w * x + y * z);
let cosr_cosp = 1.0 - 2.0 * (x * x + y * y);
let roll = sinr_cosp.atan2(cosr_cosp);
let sinp = 2.0 * (w * y - z * x);
let pitch = if sinp.abs() >= 1.0 {
(std::f32::consts::FRAC_PI_2).copysign(sinp)
} else {
sinp.asin()
};
let siny_cosp = 2.0 * (w * z + x * y);
let cosy_cosp = 1.0 - 2.0 * (y * y + z * z);
let yaw = siny_cosp.atan2(cosy_cosp);
(roll, pitch, yaw)
}
pub fn to_vision_position_estimate(&self) -> MavMessage {
let (roll, pitch, yaw) = self.euler_rpy();
MavMessage::VISION_POSITION_ESTIMATE(VISION_POSITION_ESTIMATE_DATA {
usec: self.timestamp_us,
x: self.position.0,
y: self.position.1,
z: self.position.2,
roll,
pitch,
yaw,
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Odometry {
pub pose: Pose,
pub linear_velocity: (f32, f32, f32),
pub angular_velocity: (f32, f32, f32),
pub velocity_covariance: Option<[f32; POSE_COVARIANCE_LEN]>,
}
impl Odometry {
#[allow(deprecated)]
pub fn to_odometry(&self) -> MavMessage {
let (w, x, y, z) = self.pose.orientation;
let vel_cov = match self.velocity_covariance {
Some(c) => c,
None => {
let mut c = [0.0f32; POSE_COVARIANCE_LEN];
c[0] = f32::NAN;
c
}
};
MavMessage::ODOMETRY(ODOMETRY_DATA {
time_usec: self.pose.timestamp_us,
x: self.pose.position.0,
y: self.pose.position.1,
z: self.pose.position.2,
q: [w, x, y, z],
vx: self.linear_velocity.0,
vy: self.linear_velocity.1,
vz: self.linear_velocity.2,
rollspeed: self.angular_velocity.0,
pitchspeed: self.angular_velocity.1,
yawspeed: self.angular_velocity.2,
pose_covariance: self.pose.covariance_field(),
velocity_covariance: vel_cov,
frame_id: MavFrame::MAV_FRAME_LOCAL_NED,
child_frame_id: MavFrame::MAV_FRAME_BODY_NED,
})
}
}
pub(crate) fn frame_for(msg: &MavMessage) -> Result<Vec<u8>, MavlinkError> {
let header = MavHeader {
system_id: 1,
component_id: VIO_COMPONENT_ID as u8,
sequence: 0,
};
serialize_v2(header, msg)
}
#[cfg(test)]
mod tests {
use super::*;
use ados_protocol::mavlink::parse_v2;
#[test]
fn identity_pose_builds_a_vision_position_estimate_frame() {
let pose = Pose::identity(1_700_000_000_000);
let frame = frame_for(&pose.to_vision_position_estimate()).unwrap();
assert_eq!(frame[0], 0xFD); let (header, msg) = parse_v2(&frame).unwrap();
assert_eq!(header.component_id, VIO_COMPONENT_ID as u8);
match msg {
MavMessage::VISION_POSITION_ESTIMATE(d) => {
assert_eq!(d.usec, 1_700_000_000_000);
assert_eq!((d.x, d.y, d.z), (0.0, 0.0, 0.0));
assert!(d.roll.abs() < 1e-6);
assert!(d.pitch.abs() < 1e-6);
assert!(d.yaw.abs() < 1e-6);
}
other => panic!("expected VISION_POSITION_ESTIMATE, got {other:?}"),
}
}
#[test]
fn quaternion_yaw_maps_to_euler_yaw() {
let s = std::f32::consts::FRAC_1_SQRT_2;
let pose = Pose {
position: (1.0, 2.0, -3.0),
orientation: (s, 0.0, 0.0, s),
timestamp_us: 42,
covariance: None,
};
match pose.to_vision_position_estimate() {
MavMessage::VISION_POSITION_ESTIMATE(d) => {
assert!((d.yaw - std::f32::consts::FRAC_PI_2).abs() < 1e-5);
assert!(d.roll.abs() < 1e-5);
assert!(d.pitch.abs() < 1e-5);
assert_eq!((d.x, d.y, d.z), (1.0, 2.0, -3.0));
}
other => panic!("expected VISION_POSITION_ESTIMATE, got {other:?}"),
}
}
#[test]
fn odometry_carries_quaternion_and_twist() {
let odo = Odometry {
pose: Pose {
position: (5.0, 6.0, 7.0),
orientation: (1.0, 0.0, 0.0, 0.0),
timestamp_us: 99,
covariance: Some([0.1; POSE_COVARIANCE_LEN]),
},
linear_velocity: (0.5, -0.5, 0.0),
angular_velocity: (0.01, 0.02, 0.03),
velocity_covariance: None,
};
let frame = frame_for(&odo.to_odometry()).unwrap();
let (header, msg) = parse_v2(&frame).unwrap();
assert_eq!(header.component_id, VIO_COMPONENT_ID as u8);
match msg {
MavMessage::ODOMETRY(d) => {
assert_eq!(d.time_usec, 99);
assert_eq!(d.q, [1.0, 0.0, 0.0, 0.0]);
assert_eq!((d.vx, d.vy, d.vz), (0.5, -0.5, 0.0));
assert_eq!((d.rollspeed, d.pitchspeed, d.yawspeed), (0.01, 0.02, 0.03));
assert!((d.pose_covariance[0] - 0.1).abs() < 1e-6);
assert!(d.velocity_covariance[0].is_nan());
}
other => panic!("expected ODOMETRY, got {other:?}"),
}
}
}