diff --git a/src/mavlink/reflection.rs b/src/mavlink/reflection.rs index c9f8a7fc585cee4d3fae97cdd3124ee9fc85a22f..c7665364fda6ffb74bf642523e3ace510c722c64 100644 --- a/src/mavlink/reflection.rs +++ b/src/mavlink/reflection.rs @@ -7,6 +7,7 @@ use std::collections::HashMap; use mavlink_bindgen::parser::{MavProfile, MavType}; +use skyward_mavlink::mavlink::Message; use crate::error::ErrInstrument; @@ -39,12 +40,12 @@ impl ReflectionContext { } /// Get the name of a message by its ID. - pub fn get_msg(&self, msg: impl MessageLike) -> Option<&MavMessage> { + pub fn get_msg(&'static self, msg: impl MessageLike) -> Option<&'static MavMessage> { msg.to_mav_message(self).ok() } /// Get all field names for a message by its ID. - pub fn get_fields(&self, message_id: impl MessageLike) -> Option<Vec<IndexedField<'_>>> { + pub fn get_fields(&'static self, message_id: impl MessageLike) -> Option<Vec<IndexedField>> { message_id.to_mav_message(self).ok().map(|msg| { msg.fields .iter() @@ -72,9 +73,9 @@ impl ReflectionContext { /// Get all plottable field names for a message by its ID. pub fn get_plottable_fields( - &self, + &'static self, message_id: impl MessageLike, - ) -> Option<Vec<IndexedField<'_>>> { + ) -> Option<Vec<IndexedField>> { let msg = message_id.to_mav_message(self).ok()?; msg.fields .iter() @@ -98,14 +99,36 @@ impl ReflectionContext { } } -#[derive(Clone)] -pub struct IndexedField<'a> { +#[derive(Debug, Clone)] +pub struct IndexedField { id: usize, - msg: &'a MavMessage, - field: &'a MavField, + msg: &'static MavMessage, + field: &'static MavField, } -impl IndexedField<'_> { +macro_rules! extract_as_type { + ($as_type: ty, $func: ident, $($mav_ty: ident, $rust_ty: ty),+) => { + pub fn $func(&self, message: &impl Message) -> Result<$as_type, String> { + macro_rules! downcast { + ($value: expr, $type: ty) => { + Ok(*$value + .downcast::<$type>() + .map_err(|_| "Type mismatch".to_string())? as $as_type) + }; + } + + let value = message + .get_field(self.id) + .ok_or("Field not found".to_string())?; + match self.field.mavtype { + $(MavType::$mav_ty => downcast!(value, $rust_ty),)+ + _ => Err("Field type not supported".to_string()), + } + } + }; +} + +impl IndexedField { pub fn msg(&self) -> &MavMessage { self.msg } @@ -127,16 +150,136 @@ impl IndexedField<'_> { } } +/// ### Extractors +/// These methods allow to extract the value of a field from a message, casting +/// it to the desired type. +impl IndexedField { + #[rustfmt::skip] + extract_as_type!(f32, extract_as_f32, + UInt8, u8, + UInt16, u16, + UInt32, u32, + UInt64, u64, + Int8, i8, + Int16, i16, + Int32, i32, + Int64, i64, + Float, f32, + Double, f64 + ); + + #[rustfmt::skip] + extract_as_type!(f64, extract_as_f64, + UInt8, u8, + UInt16, u16, + UInt32, u32, + UInt64, u64, + Int8, i8, + Int16, i16, + Int32, i32, + Int64, i64, + Float, f32, + Double, f64 + ); + + #[rustfmt::skip] + extract_as_type!(u8, extract_as_u8, + UInt8, u8, + Char, char + ); + + #[rustfmt::skip] + extract_as_type!(u16, extract_as_u16, + UInt8, u8, + Int8, i8, + UInt16, u16 + ); + + #[rustfmt::skip] + extract_as_type!(u32, extract_as_u32, + UInt8, u8, + Int8, i8, + UInt16, u16, + Int16, i16, + UInt32, u32 + ); + + #[rustfmt::skip] + extract_as_type!(u64, extract_as_u64, + UInt8, u8, + Int8, i8, + UInt16, u16, + Int16, i16, + UInt32, u32, + Int32, i32, + UInt64, u64 + ); + + #[rustfmt::skip] + extract_as_type!(i8, extract_as_i8, + Int8, i8 + ); + + #[rustfmt::skip] + extract_as_type!(i16, extract_as_i16, + UInt8, u8, + Int8, i8, + Int16, i16 + ); + + #[rustfmt::skip] + extract_as_type!(i32, extract_as_i32, + UInt8, u8, + Int8, i8, + UInt16, u16, + Int16, i16, + Int32, i32 + ); + + #[rustfmt::skip] + extract_as_type!(i64, extract_as_i64, + UInt8, u8, + Int8, i8, + UInt16, u16, + Int16, i16, + UInt32, u32, + Int32, i32, + Int64, i64 + ); + + #[rustfmt::skip] + extract_as_type!(char, extract_as_char, + UInt8, u8, + Char, char + ); +} + +impl std::hash::Hash for IndexedField { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + self.id.hash(state); + self.msg.id.hash(state); + } +} + +impl PartialEq for IndexedField { + fn eq(&self, other: &Self) -> bool { + self.id == other.id && self.msg.id == other.msg.id + } +} + pub trait MessageLike { - fn to_mav_message<'b>(&self, ctx: &'b ReflectionContext) -> Result<&'b MavMessage, String>; + fn to_mav_message( + &self, + ctx: &'static ReflectionContext, + ) -> Result<&'static MavMessage, String>; } -pub trait FieldLike<'a, 'b> { +pub trait FieldLike { fn to_mav_field( - &'a self, + &self, msg_id: u32, - ctx: &'b ReflectionContext, - ) -> Result<IndexedField<'b>, String>; + ctx: &'static ReflectionContext, + ) -> Result<IndexedField, String>; } impl MessageLike for u32 { @@ -158,12 +301,12 @@ impl MessageLike for &str { } } -impl<'b> FieldLike<'_, 'b> for &MavField { +impl FieldLike for &MavField { fn to_mav_field( &self, msg_id: u32, - ctx: &'b ReflectionContext, - ) -> Result<IndexedField<'b>, String> { + ctx: &'static ReflectionContext, + ) -> Result<IndexedField, String> { ctx.id_msg_map .get(&msg_id) .and_then(|msg| { @@ -181,12 +324,8 @@ impl<'b> FieldLike<'_, 'b> for &MavField { } } -impl<'b> FieldLike<'b, 'b> for IndexedField<'b> { - fn to_mav_field( - &self, - _msg_id: u32, - _ctx: &ReflectionContext, - ) -> Result<IndexedField<'_>, String> { +impl FieldLike for IndexedField { + fn to_mav_field(&self, _msg_id: u32, _ctx: &ReflectionContext) -> Result<IndexedField, String> { Ok(IndexedField { id: self.id, msg: self.msg, @@ -195,12 +334,12 @@ impl<'b> FieldLike<'b, 'b> for IndexedField<'b> { } } -impl<'b> FieldLike<'_, 'b> for usize { +impl FieldLike for usize { fn to_mav_field( &self, msg_id: u32, - ctx: &'b ReflectionContext, - ) -> Result<IndexedField<'b>, String> { + ctx: &'static ReflectionContext, + ) -> Result<IndexedField, String> { ctx.id_msg_map .get(&msg_id) .and_then(|msg| { @@ -214,12 +353,12 @@ impl<'b> FieldLike<'_, 'b> for usize { } } -impl<'b> FieldLike<'_, 'b> for &str { +impl FieldLike for &str { fn to_mav_field( &self, msg_id: u32, - ctx: &'b ReflectionContext, - ) -> Result<IndexedField<'b>, String> { + ctx: &'static ReflectionContext, + ) -> Result<IndexedField, String> { ctx.id_msg_map .get(&msg_id) .and_then(|msg| { diff --git a/src/ui/cache.rs b/src/ui/cache.rs index 9835924607803fe30d82dc326ae6d8541970abb2..ec089b2e776d8574c08bfc2537ec7ec9739d242f 100644 --- a/src/ui/cache.rs +++ b/src/ui/cache.rs @@ -7,10 +7,7 @@ use std::{ time::{Duration, Instant}, }; -use egui::Context; -use serialport::SerialPortInfo; - -use crate::{communication, error::ErrInstrument}; +use crate::error::ErrInstrument; const SERIAL_PORT_REFRESH_INTERVAL: Duration = Duration::from_millis(500); const SHORT_REFRESH_INTERVAL: Duration = Duration::from_millis(500); diff --git a/src/ui/panes/plot.rs b/src/ui/panes/plot.rs index 9d9ad095dedb335b8a35c6ad7c3c6a7532c037bf..01b58496e6829c9424f90bef3eb6bb8081bbca3d 100644 --- a/src/ui/panes/plot.rs +++ b/src/ui/panes/plot.rs @@ -5,16 +5,15 @@ use crate::{ MAVLINK_PROFILE, error::ErrInstrument, mavlink::{ - Message, MessageData, ROCKET_FLIGHT_TM_DATA, TimedMessage, - reflection::{self, FieldLike}, + MessageData, ROCKET_FLIGHT_TM_DATA, TimedMessage, + reflection::{FieldLike, IndexedField}, }, ui::{app::PaneResponse, cache::ChangeTracker}, }; use egui::{Color32, Vec2b}; use egui_plot::{Legend, Line, PlotPoint, PlotPoints}; use egui_tiles::TileId; -use mavlink_bindgen::parser::MavType; -use serde::{Deserialize, Serialize}; +use serde::{self, Deserialize, Serialize, ser::SerializeStruct}; use source_window::sources_window; use std::{hash::Hash, iter::zip}; @@ -65,7 +64,7 @@ impl PaneBehavior for Plot2DPane { )) .color(settings.color) .width(settings.width) - .name(&field.field.name), + .name(&field.field().name), ); } plot_ui @@ -104,10 +103,10 @@ impl PaneBehavior for Plot2DPane { } = &self.settings; for msg in messages { - let x: f64 = x_field.extract_from_message(&msg.message).log_unwrap(); + let x: f64 = x_field.extract_as_f64(&msg.message).log_unwrap(); let ys: Vec<f64> = y_fields .iter() - .map(|(field, _)| field.extract_from_message(&msg.message).log_unwrap()) + .map(|(field, _)| field.extract_as_f64(&msg.message).log_unwrap()) .collect(); if self.line_data.len() < ys.len() { @@ -115,7 +114,7 @@ impl PaneBehavior for Plot2DPane { } for (line, y) in zip(&mut self.line_data, ys) { - let point = if x_field.field.name == "timestamp" { + let point = if x_field.field().name == "timestamp" { PlotPoint::new(x / 1e6, y) } else { PlotPoint::new(x, y) @@ -146,15 +145,15 @@ fn show_menu(ui: &mut egui::Ui, settings_visible: &mut bool) { } } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, PartialEq)] struct PlotSettings { plot_message_id: u32, - x_field: FieldWithID, - y_fields: Vec<(FieldWithID, LineSettings)>, + x_field: IndexedField, + y_fields: Vec<(IndexedField, LineSettings)>, } impl PlotSettings { - fn plot_lines(&self) -> &[(FieldWithID, LineSettings)] { + fn plot_lines(&self) -> &[(IndexedField, LineSettings)] { &self.y_fields } @@ -166,19 +165,19 @@ impl PlotSettings { self.plot_message_id } - fn get_x_field(&self) -> &FieldWithID { + fn get_x_field(&self) -> &IndexedField { &self.x_field } - fn get_mut_x_field(&mut self) -> &mut FieldWithID { + fn get_mut_x_field(&mut self) -> &mut IndexedField { &mut self.x_field } - fn get_mut_y_fields(&mut self) -> &mut [(FieldWithID, LineSettings)] { + fn get_mut_y_fields(&mut self) -> &mut [(IndexedField, LineSettings)] { &mut self.y_fields[..] } - fn set_x_field(&mut self, field: FieldWithID) { + fn set_x_field(&mut self, field: IndexedField) { self.x_field = field; } @@ -186,11 +185,11 @@ impl PlotSettings { self.y_fields.len() } - fn contains_field(&self, field: &FieldWithID) -> bool { + fn contains_field(&self, field: &IndexedField) -> bool { self.y_fields.iter().any(|(f, _)| f == field) } - fn add_field(&mut self, field: FieldWithID) { + fn add_field(&mut self, field: IndexedField) { let line_settings = LineSettings::default(); self.y_fields.push((field, line_settings)); } @@ -198,8 +197,7 @@ impl PlotSettings { fn clear_fields(&mut self) { self.x_field = 0 .to_mav_field(self.plot_message_id, &MAVLINK_PROFILE) - .log_unwrap() - .into(); + .log_unwrap(); self.y_fields.clear(); } } @@ -207,9 +205,9 @@ impl PlotSettings { impl Default for PlotSettings { fn default() -> Self { let msg_id = ROCKET_FLIGHT_TM_DATA::ID; - let x_field = FieldWithID::new(msg_id, 0).log_unwrap(); + let x_field = 0.to_mav_field(msg_id, &MAVLINK_PROFILE).log_unwrap(); let y_fields = vec![( - FieldWithID::new(msg_id, 1).log_unwrap(), + 1.to_mav_field(msg_id, &MAVLINK_PROFILE).log_unwrap(), LineSettings::default(), )]; Self { @@ -250,65 +248,53 @@ impl Hash for LineSettings { } } -/// A struct to hold a field and its ID in a message -/// We use this and not `reflection::IndexedField` because we need to serialize it -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -struct FieldWithID { - id: usize, - field: reflection::MavField, -} - -impl FieldWithID { - fn new(msg_id: u32, field_id: usize) -> Option<Self> { - Some(Self { - id: field_id, - field: field_id - .to_mav_field(msg_id, &MAVLINK_PROFILE) - .ok()? - .field() - .clone(), - }) +impl Serialize for PlotSettings { + fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + let mut state = serializer.serialize_struct("PlotSettings", 3)?; + let y_fields: Vec<_> = self.y_fields.iter().map(|(f, s)| (f.id(), s)).collect(); + state.serialize_field("msg_id", &self.plot_message_id)?; + state.serialize_field("x_field", &self.x_field.id())?; + state.serialize_field("y_fields", &y_fields)?; + state.end() } +} - fn extract_from_message(&self, message: &impl Message) -> Result<f64, String> { - macro_rules! downcast { - ($value: expr, $type: ty) => { - Ok(*$value - .downcast::<$type>() - .map_err(|_| "Type mismatch".to_string())? as f64) - }; +impl<'de> Deserialize<'de> for PlotSettings { + fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { + #[derive(Deserialize)] + struct FieldSettings { + field: usize, + settings: LineSettings, } - let value = message - .get_field(self.id) - .ok_or("Field not found".to_string())?; - match self.field.mavtype { - MavType::UInt8 => downcast!(value, u8), - MavType::UInt16 => downcast!(value, u16), - MavType::UInt32 => downcast!(value, u32), - MavType::UInt64 => downcast!(value, u64), - MavType::Int8 => downcast!(value, i8), - MavType::Int16 => downcast!(value, i16), - MavType::Int32 => downcast!(value, i32), - MavType::Int64 => downcast!(value, i64), - MavType::Float => downcast!(value, f32), - MavType::Double => downcast!(value, f64), - _ => Err("Field type not supported".to_string()), + #[derive(Deserialize)] + struct PlotSettingsData { + msg_id: u32, + x_field: usize, + y_fields: Vec<FieldSettings>, } - } -} -impl Hash for FieldWithID { - fn hash<H: std::hash::Hasher>(&self, state: &mut H) { - self.id.hash(state); - } -} - -impl From<reflection::IndexedField<'_>> for FieldWithID { - fn from(indexed_field: reflection::IndexedField<'_>) -> Self { - Self { - id: indexed_field.id(), - field: indexed_field.field().clone(), - } + let data = PlotSettingsData::deserialize(deserializer)?; + let x_field = data + .x_field + .to_mav_field(data.msg_id, &MAVLINK_PROFILE) + .log_unwrap(); + let y_fields = data + .y_fields + .into_iter() + .map(|FieldSettings { field, settings }| { + ( + field + .to_mav_field(data.msg_id, &MAVLINK_PROFILE) + .log_unwrap(), + settings, + ) + }) + .collect(); + Ok(Self { + plot_message_id: data.msg_id, + x_field, + y_fields, + }) } } diff --git a/src/ui/panes/plot/source_window.rs b/src/ui/panes/plot/source_window.rs index e49df739c1d240ee93186a1c1fbd11ced4a389ec..bf636a781c86fabd5b02aebd589d870eb24db89d 100644 --- a/src/ui/panes/plot/source_window.rs +++ b/src/ui/panes/plot/source_window.rs @@ -1,7 +1,4 @@ -use crate::{ - MAVLINK_PROFILE, - ui::{cache::ChangeTracker, panes::plot::FieldWithID}, -}; +use crate::{MAVLINK_PROFILE, ui::cache::ChangeTracker}; use crate::error::ErrInstrument; @@ -32,12 +29,9 @@ pub fn sources_window(ui: &mut egui::Ui, plot_settings: &mut PlotSettings) { } // check fields and assign a default field_x and field_y once the msg is changed - let fields: Vec<FieldWithID> = MAVLINK_PROFILE + let fields = MAVLINK_PROFILE .get_plottable_fields(plot_settings.get_msg_id()) - .log_expect("Invalid message id") - .into_iter() - .map(|f| f.into()) - .collect::<Vec<_>>(); + .log_expect("Invalid message id"); // get the first field that is in the list of fields or the previous if valid let x_field = plot_settings.get_x_field(); let new_field_x = fields @@ -57,10 +51,10 @@ pub fn sources_window(ui: &mut egui::Ui, plot_settings: &mut PlotSettings) { // if fields are valid, show the combo boxes for the x_axis egui::ComboBox::from_label("X Axis") - .selected_text(&x_field.field.name) + .selected_text(&x_field.field().name) .show_ui(ui, |ui| { for msg in fields.iter() { - ui.selectable_value(x_field, msg.to_owned(), &msg.field.name); + ui.selectable_value(x_field, msg.to_owned(), &msg.field().name); } }); @@ -85,10 +79,10 @@ pub fn sources_window(ui: &mut egui::Ui, plot_settings: &mut PlotSettings) { "Y Axis".to_owned() }; egui::ComboBox::from_label(widget_label) - .selected_text(&field.field.name) + .selected_text(&field.field().name) .show_ui(ui, |ui| { for msg in fields.iter() { - ui.selectable_value(field, msg.to_owned(), &msg.field.name); + ui.selectable_value(field, msg.to_owned(), &msg.field().name); } }); ui.color_edit_button_srgba(color);