From 50e4363c32a150785e8c5cad776e059d1e00721a Mon Sep 17 00:00:00 2001
From: Federico Lolli <federico.lolli@skywarder.eu>
Date: Mon, 11 Mar 2024 15:31:19 +0100
Subject: [PATCH] Added initial version of mavlink message parsing

---
 src/ffi.rs    | 141 --------------------------------------------------
 src/main.rs   | 131 ++++++++++++----------------------------------
 src/packet.rs | 100 +++++++++++++++++++++++++++++++++++
 src/utils.rs  |  32 ++++++++++++
 4 files changed, 166 insertions(+), 238 deletions(-)
 delete mode 100644 src/ffi.rs
 create mode 100644 src/packet.rs
 create mode 100644 src/utils.rs

diff --git a/src/ffi.rs b/src/ffi.rs
deleted file mode 100644
index 77dc33a..0000000
--- a/src/ffi.rs
+++ /dev/null
@@ -1,141 +0,0 @@
-use serde::{Deserialize, Serialize};
-
-#[derive(Debug, Clone, Default, Serialize, Deserialize)]
-pub struct MavlinkPayloadFlightTM {
-    // [us] Timestamp in microseconds
-    pub timestamp: u64,
-    // [Pa] Pressure from digital sensor
-    pub pressure_digi: f32,
-    // [Pa] Pressure from static port
-    pub pressure_static: f32,
-    // [m/s] Pitot airspeed
-    pub airspeed_pitot: f32,
-    // [m] Altitude above ground level
-    pub altitude_agl: f32,
-    // [m/s^2] Acceleration on X axis (body)
-    pub acc_x: f32,
-    // [m/s^2] Acceleration on Y axis (body)
-    pub acc_y: f32,
-    // [m/s^2] Acceleration on Z axis (body)
-    pub acc_z: f32,
-    // [rad/s] Angular speed on X axis (body)
-    pub gyro_x: f32,
-    // [rad/s] Angular speed on Y axis (body)
-    pub gyro_y: f32,
-    // [rad/s] Angular speed on Z axis (body)
-    pub gyro_z: f32,
-    // [uT] Magnetic field on X axis (body)
-    pub mag_x: f32,
-    // [uT] Magnetic field on Y axis (body)
-    pub mag_y: f32,
-    // [uT] Magnetic field on Z axis (body)
-    pub mag_z: f32,
-    // [deg] Latitude
-    pub gps_lat: f32,
-    // [deg] Longitude
-    pub gps_lon: f32,
-    // [m] GPS Altitude
-    pub gps_alt: f32,
-    // [deg] Left servo motor angle
-    pub left_servo_angle: f32,
-    // [deg] Right servo motor angle
-    pub right_servo_angle: f32,
-    // [deg] Navigation system estimated noth position
-    pub nas_n: f32,
-    // [deg] Navigation system estimated east position
-    pub nas_e: f32,
-    // [m] Navigation system estimated down position
-    pub nas_d: f32,
-    // [m/s] Navigation system estimated north velocity
-    pub nas_vn: f32,
-    // [m/s] Navigation system estimated east velocity
-    pub nas_ve: f32,
-    // [m/s] Navigation system estimated down velocity
-    pub nas_vd: f32,
-    // [deg] Navigation system estimated attitude (qx)
-    pub nas_qx: f32,
-    // [deg] Navigation system estimated attitude (qy)
-    pub nas_qy: f32,
-    // [deg] Navigation system estimated attitude (qz)
-    pub nas_qz: f32,
-    // [deg] Navigation system estimated attitude (qw)
-    pub nas_qw: f32,
-    //  Navigation system gyroscope bias on x axis
-    pub nas_bias_x: f32,
-    //  Navigation system gyroscope bias on y axis
-    pub nas_bias_y: f32,
-    //  Navigation system gyroscope bias on z axis
-    pub nas_bias_z: f32,
-    // [m/s] Wind estimated north velocity
-    pub wes_n: f32,
-    // [m/s] Wind estimated east velocity
-    pub wes_e: f32,
-    // [V] Battery voltage
-    pub vbat: f32,
-    // [V] Power supply 5V
-    pub vsupply_5v: f32,
-    // [degC] Temperature
-    pub temperature: f32,
-    //  Flight Mode Manager State
-    pub fmm_state: u8,
-    //  Navigation System FSM State
-    pub nas_state: u8,
-    //  Wind Estimation System FSM State
-    pub wes_state: u8,
-    //  GPS fix (1 = fix, 0 = no fix)
-    pub gps_fix: u8,
-    //  Nosecone pin status (1 = connected, 0 = disconnected)
-    pub pin_nosecone: u8,
-    //  Logger error (0 = no error)
-    pub logger_error: i8,
-}
-
-impl MavlinkPayloadFlightTM {
-    pub fn into_le_bytes(self) -> [u8; 158] {
-        let mut buffer = [0u8; 158];
-        buffer[0..8].copy_from_slice(&self.timestamp.to_le_bytes());
-        buffer[8..12].copy_from_slice(&self.pressure_digi.to_le_bytes());
-        buffer[12..16].copy_from_slice(&self.pressure_static.to_le_bytes());
-        buffer[16..20].copy_from_slice(&self.airspeed_pitot.to_le_bytes());
-        buffer[20..24].copy_from_slice(&self.altitude_agl.to_le_bytes());
-        buffer[24..28].copy_from_slice(&self.acc_x.to_le_bytes());
-        buffer[28..32].copy_from_slice(&self.acc_y.to_le_bytes());
-        buffer[32..36].copy_from_slice(&self.acc_z.to_le_bytes());
-        buffer[36..40].copy_from_slice(&self.gyro_x.to_le_bytes());
-        buffer[40..44].copy_from_slice(&self.gyro_y.to_le_bytes());
-        buffer[44..48].copy_from_slice(&self.gyro_z.to_le_bytes());
-        buffer[48..52].copy_from_slice(&self.mag_x.to_le_bytes());
-        buffer[52..56].copy_from_slice(&self.mag_y.to_le_bytes());
-        buffer[56..60].copy_from_slice(&self.mag_z.to_le_bytes());
-        buffer[60..64].copy_from_slice(&self.gps_lat.to_le_bytes());
-        buffer[64..68].copy_from_slice(&self.gps_lon.to_le_bytes());
-        buffer[68..72].copy_from_slice(&self.gps_alt.to_le_bytes());
-        buffer[72..76].copy_from_slice(&self.left_servo_angle.to_le_bytes());
-        buffer[76..80].copy_from_slice(&self.right_servo_angle.to_le_bytes());
-        buffer[80..84].copy_from_slice(&self.nas_n.to_le_bytes());
-        buffer[84..88].copy_from_slice(&self.nas_e.to_le_bytes());
-        buffer[88..92].copy_from_slice(&self.nas_d.to_le_bytes());
-        buffer[92..96].copy_from_slice(&self.nas_vn.to_le_bytes());
-        buffer[96..100].copy_from_slice(&self.nas_ve.to_le_bytes());
-        buffer[100..104].copy_from_slice(&self.nas_vd.to_le_bytes());
-        buffer[104..108].copy_from_slice(&self.nas_qx.to_le_bytes());
-        buffer[108..112].copy_from_slice(&self.nas_qy.to_le_bytes());
-        buffer[112..116].copy_from_slice(&self.nas_qz.to_le_bytes());
-        buffer[116..120].copy_from_slice(&self.nas_qw.to_le_bytes());
-        buffer[120..124].copy_from_slice(&self.nas_bias_x.to_le_bytes());
-        buffer[124..128].copy_from_slice(&self.nas_bias_y.to_le_bytes());
-        buffer[128..132].copy_from_slice(&self.nas_bias_z.to_le_bytes());
-        buffer[132..136].copy_from_slice(&self.wes_n.to_le_bytes());
-        buffer[136..140].copy_from_slice(&self.wes_e.to_le_bytes());
-        buffer[140..144].copy_from_slice(&self.vbat.to_le_bytes());
-        buffer[144..148].copy_from_slice(&self.vsupply_5v.to_le_bytes());
-        buffer[148..152].copy_from_slice(&self.temperature.to_le_bytes());
-        buffer[152..153].copy_from_slice(&self.fmm_state.to_le_bytes());
-        buffer[153..154].copy_from_slice(&self.nas_state.to_le_bytes());
-        buffer[154..155].copy_from_slice(&self.wes_state.to_le_bytes());
-        buffer[155..156].copy_from_slice(&self.gps_fix.to_le_bytes());
-        buffer[156..157].copy_from_slice(&self.pin_nosecone.to_le_bytes());
-        buffer[157..158].copy_from_slice(&self.logger_error.to_le_bytes());
-        buffer
-    }
-}
diff --git a/src/main.rs b/src/main.rs
index bf6c009..a994985 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,16 +1,25 @@
-mod ffi;
+mod packet;
+mod utils;
 
-use std::fs::File;
-use std::io;
-use std::io::Read;
-use std::path::PathBuf;
-use std::time::{Duration, Instant};
+use std::{
+    fs::File,
+    io::{self, Read},
+    path::PathBuf,
+    time::{Duration, Instant},
+};
 
 use clap::Parser;
 use log::{debug, error, info, warn};
 use serialport::SerialPort;
+use skyward_mavlink::{
+    lyra::MavMessage,
+    mavlink::{write_v1_msg, MavHeader},
+};
 
-use ffi::MavlinkPayloadFlightTM;
+use crate::{
+    packet::PacketSequence,
+    utils::{get_first_stm32_serial_port, print_stats},
+};
 
 const ACK: u8 = 0x06;
 
@@ -42,8 +51,7 @@ fn main() {
     info!("reading from {}", args.csv_input.display());
     let mut rdr = csv::Reader::from_reader(File::open(args.csv_input).unwrap());
     for result in rdr.deserialize() {
-        let record: MavlinkPayloadFlightTM = result.unwrap();
-        packets.push(record);
+        packets.push(MavMessage::PAYLOAD_FLIGHT_TM(result.unwrap()));
     }
     debug!("collected {} packets", packets.len());
 
@@ -51,11 +59,11 @@ fn main() {
     let mut packets = PacketSequence::from(packets);
 
     // Open the first serialport available.
-    let port_name = if let Some(port_name) = args.port {
-        port_name.to_owned()
-    } else if let Some(p) = get_first_stm32_serial_port() {
-        debug!("found STM32 device on {}", p);
-        p
+    let port_name = if let Some(name) = args.port {
+        name
+    } else if let Some(name) = get_first_stm32_serial_port() {
+        debug!("found STM32 device on {}", name);
+        name
     } else {
         error!("FATAL: no STM32 serial port found, your device may not be connected properly");
         std::process::exit(1);
@@ -80,19 +88,23 @@ fn main() {
         }
         start = Some(Instant::now());
         write_port.write_packet(packet.to_owned());
-        debug!("sent packet ({}/{})", packets.index, packets.packets.len());
+        debug!(
+            "sent packet ({}/{})",
+            packets.sent_packets(),
+            packets.total_packets()
+        );
 
         // wait ACK
         loop {
             match port.read(&mut buffer) {
                 Ok(t) => {
+                    eprint!("{}", String::from_utf8_lossy(&buffer[..t]));
                     // if didn't get the signal then print the buffer and exit with error
                     if buffer[..t].contains(&ACK) {
                         break;
                     } else {
                         warn!("didn't get the signal");
                         std::thread::sleep(Duration::from_millis(100));
-                        // eprintln!("{}", String::from_utf8_lossy(&buffer[..t]));
                     }
                 }
                 Err(ref e) if e.kind() == io::ErrorKind::TimedOut => (),
@@ -102,95 +114,20 @@ fn main() {
                 }
             }
         }
+        eprintln!();
     }
 
-    info!("sent {} packets", packets.index);
+    info!("sent {} packets", packets.sent_packets());
     print_stats(times);
 }
 
-struct PacketSequence {
-    packets: Vec<MavlinkPayloadFlightTM>,
-    index: usize,
-    delta_from_start: u64,
-    start_instant: Option<Instant>,
-}
-
-impl From<Vec<MavlinkPayloadFlightTM>> for PacketSequence {
-    fn from(value: Vec<MavlinkPayloadFlightTM>) -> Self {
-        let delta_from_start = value[0].timestamp;
-        Self {
-            packets: value,
-            index: 0,
-            delta_from_start,
-            start_instant: None,
-        }
-    }
-}
-
-impl PacketSequence {
-    fn wait_next(&mut self) -> Option<&MavlinkPayloadFlightTM> {
-        // return None if we reached the end of the sequence
-        if self.index == self.packets.len() {
-            return None;
-        }
-
-        // get time delta from the start
-        let next_timestamp = self.packets[self.index].timestamp;
-        let time_delta = next_timestamp - self.delta_from_start;
-
-        // get the instant the first packet was sent or set it if it's the first packet
-        if let Some(start_inst) = self.start_instant {
-            // get the instant to wait until
-            let till = start_inst + Duration::from_micros(time_delta);
-
-            // sleep for the difference between the last timestamp and the current one
-            std::thread::sleep(till - Instant::now());
-        } else {
-            // set the start instant
-            self.start_instant = Some(Instant::now());
-        }
-        self.index += 1;
-        self.packets.get(self.index - 1)
-    }
-}
-
 trait MavLinkPort {
-    fn write_packet(&mut self, packet: MavlinkPayloadFlightTM);
+    fn write_packet(&mut self, packet: MavMessage);
 }
 
 impl<T: AsRef<dyn SerialPort> + std::io::Write> MavLinkPort for T {
-    fn write_packet(&mut self, packet: MavlinkPayloadFlightTM) {
-        self.write_all(&packet.into_le_bytes())
-            .expect("Failed to write to serial port");
+    fn write_packet(&mut self, packet: MavMessage) {
+        let header = MavHeader::default();
+        write_v1_msg(self, header, &packet).expect("Failed to write to serial port");
     }
 }
-
-fn get_first_stm32_serial_port() -> Option<String> {
-    let ports = serialport::available_ports().expect("No serial ports found!");
-    for port in ports {
-        if let serialport::SerialPortType::UsbPort(info) = port.port_type {
-            if let Some(p) = info.product {
-                if p.contains("STM32") || p.contains("ST-LINK") {
-                    return Some(port.port_name);
-                }
-            }
-        }
-    }
-    None
-}
-
-/// print stats (mean, median, std, etc)
-fn print_stats(times: Vec<u128>) {
-    let mean = times.iter().sum::<u128>() as f64 / times.len() as f64;
-    let median = times[times.len() / 2] as f64;
-    let std = times
-        .iter()
-        .map(|&x| (x as f64 - mean).powi(2))
-        .sum::<f64>()
-        .sqrt()
-        / times.len() as f64;
-    info!("Stats:");
-    info!("Mean: {}ms", mean);
-    info!("Median: {}ms", median);
-    info!("Std: {}ms", std);
-}
diff --git a/src/packet.rs b/src/packet.rs
new file mode 100644
index 0000000..91406d2
--- /dev/null
+++ b/src/packet.rs
@@ -0,0 +1,100 @@
+use std::time::{Duration, Instant};
+
+use serde::{Deserialize, Serialize};
+use skyward_mavlink::{
+    lyra::{MavMessage, ACK_TM_DATA, PAYLOAD_FLIGHT_TM_DATA},
+    mavlink::Message,
+};
+
+pub trait TimedMessage: Message {
+    fn timestamp(&self) -> Option<u64>;
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(untagged)]
+pub enum PacketKind {
+    Dummy(ACK_TM_DATA),
+    PayloadFlightTm(PAYLOAD_FLIGHT_TM_DATA),
+}
+
+impl From<PacketKind> for MavMessage {
+    fn from(value: PacketKind) -> Self {
+        match value {
+            PacketKind::Dummy(d) => MavMessage::ACK_TM(d),
+            PacketKind::PayloadFlightTm(p) => MavMessage::PAYLOAD_FLIGHT_TM(p),
+        }
+    }
+}
+
+impl Default for PacketKind {
+    fn default() -> Self {
+        PacketKind::Dummy(ACK_TM_DATA {
+            recv_msgid: 0,
+            seq_ack: 0,
+        })
+    }
+}
+
+impl TimedMessage for MavMessage {
+    fn timestamp(&self) -> Option<u64> {
+        match self {
+            MavMessage::PAYLOAD_FLIGHT_TM(data) => Some(data.timestamp),
+            MavMessage::ACK_TM(_) => None,
+            _ => None,
+        }
+    }
+}
+
+pub struct PacketSequence {
+    packets: Vec<MavMessage>,
+    index: usize,
+    delta_from_start: u64,
+    start_instant: Option<Instant>,
+}
+
+impl From<Vec<MavMessage>> for PacketSequence {
+    fn from(value: Vec<MavMessage>) -> Self {
+        let delta_from_start = value[0].timestamp().unwrap();
+        Self {
+            packets: value,
+            index: 0,
+            delta_from_start,
+            start_instant: None,
+        }
+    }
+}
+
+impl PacketSequence {
+    pub fn sent_packets(&self) -> usize {
+        self.index
+    }
+
+    pub fn total_packets(&self) -> usize {
+        self.packets.len()
+    }
+
+    pub fn wait_next(&mut self) -> Option<&MavMessage> {
+        // return None if we reached the end of the sequence
+        if self.index == self.packets.len() {
+            return None;
+        }
+
+        // get time delta from the start
+        let next_timestamp = self.packets[self.index].timestamp().unwrap();
+        let time_delta = next_timestamp - self.delta_from_start;
+
+        // get the instant the first packet was sent or set it if it's the first packet
+        if let Some(start_inst) = self.start_instant {
+            // get the instant to wait until
+            let till = start_inst + Duration::from_micros(time_delta);
+
+            // sleep for the difference between the last timestamp and the current one
+            std::thread::sleep(till - Instant::now());
+        } else {
+            // set the start instant
+            self.start_instant = Some(Instant::now());
+        }
+        self.index += 1;
+        self.packets.get(self.index - 1)
+    }
+}
diff --git a/src/utils.rs b/src/utils.rs
new file mode 100644
index 0000000..1c76055
--- /dev/null
+++ b/src/utils.rs
@@ -0,0 +1,32 @@
+use log::info;
+
+/// Get the first serial port that contains "STM32" or "ST-LINK" in its product name
+pub fn get_first_stm32_serial_port() -> Option<String> {
+    let ports = serialport::available_ports().expect("No serial ports found!");
+    for port in ports {
+        if let serialport::SerialPortType::UsbPort(info) = port.port_type {
+            if let Some(p) = info.product {
+                if p.contains("STM32") || p.contains("ST-LINK") {
+                    return Some(port.port_name);
+                }
+            }
+        }
+    }
+    None
+}
+
+/// print stats (mean, median, std, etc)
+pub fn print_stats(times: Vec<u128>) {
+    let mean = times.iter().sum::<u128>() as f64 / times.len() as f64;
+    let median = times[times.len() / 2] as f64;
+    let std = times
+        .iter()
+        .map(|&x| (x as f64 - mean).powi(2))
+        .sum::<f64>()
+        .sqrt()
+        / times.len() as f64;
+    info!("Stats:");
+    info!("Mean: {}ms", mean);
+    info!("Median: {}ms", median);
+    info!("Std: {}ms", std);
+}
-- 
GitLab