diff --git a/CHANGELOG.md b/CHANGELOG.md index 92dc303..bde12f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ All notable changes to the `Serial Monitor` crate will be documented in this fil * removed many `.clone()` calls to reduce CPU load * Fixed sample rate / disconnect issue * Releases are now linked to libssl 3.4.1 on linux (built on Ubuntu 22.04) +* Implement smarter data parser that also can read and assign multi-line data-packets +* ... ## 0.3.4 diff --git a/README.md b/README.md index f2dc6fa..a451229 100644 --- a/README.md +++ b/README.md @@ -79,8 +79,14 @@ cargo wix - [X] Automatic reconnect after device has been unplugged - [X] Color-picker for curves - [X] Open a CSV file and display data in plot +- [X] Smart data parser, multiline packets will be correctly assigned: + + ```DATA1: 0, 1, 2, 3``` + ```DATA2: 1, 2, 4, 9``` + + However, when one saves the data as a CSV, only the timestamp of the fist data-packet will be saved. +- [ ] Allow timestamp selection for CSV saving (in case of multiline data-packets) - [ ] Allow to select (and copy) more than just the displayed raw traffic (also implement ctrl + A) -- [ ] Smarter data parser - [ ] make serial print selectable and show corresponding datapoint in plot - [ ] COM-Port names on Windows (display manufacturer, name, pid or vid of device?) - [ ] current command entered is lost when navigating through the history diff --git a/src/data.rs b/src/data.rs index 52b4a7e..282ff0a 100644 --- a/src/data.rs +++ b/src/data.rs @@ -43,27 +43,15 @@ impl Default for Packet { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct DataContainer { - pub time: Vec, + pub time: Vec>, pub absolute_time: Vec, pub dataset: Vec>, pub raw_traffic: Vec, pub loaded_from_file: bool, } -impl Default for DataContainer { - fn default() -> DataContainer { - DataContainer { - time: vec![], - absolute_time: vec![], - dataset: vec![vec![]], - raw_traffic: vec![], - loaded_from_file: false, - } - } -} - #[derive(Clone, Debug, Default)] pub struct GuiOutputDataContainer { pub prints: Vec, diff --git a/src/gui.rs b/src/gui.rs index 69155b9..dec06fb 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -4,7 +4,7 @@ use std::cmp::max; use std::ops::RangeInclusive; use std::path::PathBuf; use std::sync::{Arc, RwLock}; -use std::time::Duration; +use std::time::{Duration, Instant}; use crate::color_picker::{color_picker_widget, color_picker_window, COLORS}; use crate::custom_highlighter::highlight_impl; @@ -125,7 +125,6 @@ pub struct MyApp { plot_serial_display_ratio: f32, picked_path: PathBuf, plot_location: Option, - data: GuiOutputDataContainer, file_dialog_state: FileDialogState, file_dialog: FileDialog, information_panel: InformationPanel, @@ -210,7 +209,6 @@ impl MyApp { picked_path: PathBuf::new(), device: "".to_string(), old_device: "".to_string(), - data: GuiOutputDataContainer::default(), file_dialog_state: FileDialogState::None, file_dialog, information_panel: InformationPanel::default().add_file_preview("csv", |ui, item| { @@ -316,208 +314,210 @@ impl MyApp { ui.add_space(left_border); ui.vertical(|ui| { if let Ok(gui_data) = self.data_lock.read() { - self.data = gui_data.clone(); self.labels = gui_data.plots.iter().map(|d| d.0.clone()).collect(); self.colors = (0..max(self.labels.len(), 1)) .map(|i| COLORS[i % COLORS.len()]) .collect(); - self.color_vals = (0..max(self.data.plots.len(), 1)).map(|_| 0.0).collect(); - } - - // TODO what about self.data.loaded_from_file - if self.file_opened { - if let Ok(labels) = self.load_names_rx.try_recv() { - self.labels = labels; - self.colors = (0..max(self.labels.len(), 1)) - .map(|i| COLORS[i % COLORS.len()]) - .collect(); - self.color_vals = (0..max(self.labels.len(), 1)).map(|_| 0.0).collect(); - } - } - if self.serial_devices.number_of_plots[self.device_idx] > 0 { - if self.data.plots.len() != self.labels.len() && !self.file_opened { - // self.labels = (0..max(self.data.dataset.len(), 1)) - // .map(|i| format!("Column {i}")) - // .collect(); - self.colors = (0..max(self.data.plots.len(), 1)) - .map(|i| COLORS[i % COLORS.len()]) - .collect(); - self.color_vals = - (0..max(self.data.plots.len(), 1)).map(|_| 0.0).collect(); + self.color_vals = (0..max(gui_data.plots.len(), 1)).map(|_| 0.0).collect(); + + // TODO what about self.data.loaded_from_file + if self.file_opened { + if let Ok(labels) = self.load_names_rx.try_recv() { + self.labels = labels; + self.colors = (0..max(self.labels.len(), 1)) + .map(|i| COLORS[i % COLORS.len()]) + .collect(); + self.color_vals = + (0..max(self.labels.len(), 1)).map(|_| 0.0).collect(); + } } + if self.serial_devices.number_of_plots[self.device_idx] > 0 { + if gui_data.plots.len() != self.labels.len() && !self.file_opened { + // self.labels = (0..max(self.data.dataset.len(), 1)) + // .map(|i| format!("Column {i}")) + // .collect(); + self.colors = (0..max(gui_data.plots.len(), 1)) + .map(|i| COLORS[i % COLORS.len()]) + .collect(); + self.color_vals = + (0..max(gui_data.plots.len(), 1)).map(|_| 0.0).collect(); + } - // offloaded to main thread - - // let mut graphs: Vec> = vec![vec![]; self.data.dataset.len()]; - // let window = self.data.dataset[0] - // .len() - // .saturating_sub(self.plotting_range); - // - // for (i, time) in self.data.time[window..].iter().enumerate() { - // let x = *time / 1000.0; - // for (graph, data) in graphs.iter_mut().zip(&self.data.dataset) { - // if self.data.time.len() == data.len() { - // if let Some(y) = data.get(i + window) { - // graph.push(PlotPoint { x, y: *y as f64 }); - // } - // } - // } - // } - - let window = if let Some(first_entry) = self.data.plots.first() { - first_entry.1.len().saturating_sub(self.plotting_range) - } else { - 0 - }; - - let t_fmt = |x: GridMark, _range: &RangeInclusive| { - format!("{:4.2} s", x.value) - }; + // offloaded to main thread + + // for (graph, (timepoints, datapoints)) in graphs + // .iter_mut() + // .zip(self.data.time.iter().zip(self.data.dataset.iter())) + // { + // for (time, data) in timepoints + // .iter() + // .skip(window) + // .zip(datapoints.iter().skip(window)) + // { + // graph.push(PlotPoint { + // x: *time / 1000.0, + // y: *data as f64, + // }); + // } + // } + + let window = if let Some(first_entry) = gui_data.plots.first() { + first_entry.1.len().saturating_sub(self.plotting_range) + } else { + 0 + }; - let plots_ui = ui.vertical(|ui| { - for graph_idx in 0..self.serial_devices.number_of_plots[self.device_idx] - { - if graph_idx != 0 { - ui.separator(); - } + let t_fmt = |x: GridMark, _range: &RangeInclusive| { + format!("{:4.2} s", x.value) + }; - let signal_plot = Plot::new(format!("data-{graph_idx}")) - .height(plot_height) - .width(width) - .legend(Legend::default()) - .x_grid_spacer(log_grid_spacer(10)) - .y_grid_spacer(log_grid_spacer(10)) - .x_axis_formatter(t_fmt); - - let plot_inner = signal_plot.show(ui, |signal_plot_ui| { - for (i, (_label, graph)) in self.data.plots.iter().enumerate() { - // this check needs to be here for when we change devices (not very elegant) - if i < self.labels.len() { - signal_plot_ui.line( - Line::new(PlotPoints::Owned( - graph[window..].to_vec(), - )) - .name(&self.labels[i]) - .color(self.colors[i]), - ); - } + let plots_ui = ui.vertical(|ui| { + for graph_idx in + 0..self.serial_devices.number_of_plots[self.device_idx] + { + if graph_idx != 0 { + ui.separator(); } - }); - self.plot_location = Some(plot_inner.response.rect); - } - let separator_response = ui.separator(); - let separator = ui - .interact( - separator_response.rect, - separator_response.id, - Sense::click_and_drag(), - ) - .on_hover_cursor(egui::CursorIcon::ResizeVertical); - - let resize_y = separator.drag_delta().y; - - if separator.double_clicked() { - self.plot_serial_display_ratio = 0.45; - } - self.plot_serial_display_ratio = (self.plot_serial_display_ratio - + resize_y / panel_height) - .clamp(0.1, 0.9); + let signal_plot = Plot::new(format!("data-{graph_idx}")) + .height(plot_height) + .width(width) + .legend(Legend::default()) + .x_grid_spacer(log_grid_spacer(10)) + .y_grid_spacer(log_grid_spacer(10)) + .x_axis_formatter(t_fmt); + + let plot_inner = signal_plot.show(ui, |signal_plot_ui| { + for (i, (_label, graph)) in + gui_data.plots.iter().enumerate() + { + // this check needs to be here for when we change devices (not very elegant) + if i < self.labels.len() { + signal_plot_ui.line( + Line::new(PlotPoints::Owned( + graph[window..].to_vec(), + )) + .name(&self.labels[i]) + .color(self.colors[i]), + ); + } + } + }); - ui.add_space(top_spacing); - }); - plot_ui_heigh = plots_ui.response.rect.height(); - } else { - plot_ui_heigh = 0.0; - } + self.plot_location = Some(plot_inner.response.rect); + } + let separator_response = ui.separator(); + let separator = ui + .interact( + separator_response.rect, + separator_response.id, + Sense::click_and_drag(), + ) + .on_hover_cursor(egui::CursorIcon::ResizeVertical); + + let resize_y = separator.drag_delta().y; + + if separator.double_clicked() { + self.plot_serial_display_ratio = 0.45; + } + self.plot_serial_display_ratio = (self.plot_serial_display_ratio + + resize_y / panel_height) + .clamp(0.1, 0.9); - let serial_height = - panel_height - plot_ui_heigh - left_border * 2.0 - top_spacing; + ui.add_space(top_spacing); + }); + plot_ui_heigh = plots_ui.response.rect.height(); + } else { + plot_ui_heigh = 0.0; + } - let num_rows = self.data.prints.len(); - let row_height = ui.text_style_height(&egui::TextStyle::Body); + let serial_height = + panel_height - plot_ui_heigh - left_border * 2.0 - top_spacing; - let color = if self.gui_conf.dark_mode { - Color32::WHITE - } else { - Color32::BLACK - }; + let num_rows = gui_data.prints.len(); + let row_height = ui.text_style_height(&egui::TextStyle::Body); - let mut text_edit_size = ui.available_size(); - text_edit_size.x = width; - egui::ScrollArea::vertical() - .id_salt("serial_output") - .auto_shrink([false; 2]) - .stick_to_bottom(true) - .enable_scrolling(true) - .max_height(serial_height - top_spacing) - .min_scrolled_height(serial_height - top_spacing) - .max_width(width) - .show_rows(ui, row_height, num_rows, |ui, row_range| { - let content: String = row_range - .into_iter() - .flat_map(|i| { - if self.data.prints.is_empty() { - None - } else { - Some(self.data.prints[i].clone()) - } - }) - .collect(); - - let mut layouter = |ui: &egui::Ui, string: &str, wrap_width: f32| { - let mut layout_job = highlight_impl( - ui.ctx(), - string, - self.serial_devices.highlight_labels[self.device_idx].clone(), - Color32::from_rgb(155, 164, 167), - ) - .unwrap(); - layout_job.wrap.max_width = wrap_width; - ui.fonts(|f| f.layout_job(layout_job)) - }; + let color = if self.gui_conf.dark_mode { + Color32::WHITE + } else { + Color32::BLACK + }; - ui.add( - egui::TextEdit::multiline(&mut content.as_str()) - .font(DEFAULT_FONT_ID) // for cursor height + let mut text_edit_size = ui.available_size(); + text_edit_size.x = width; + egui::ScrollArea::vertical() + .id_salt("serial_output") + .auto_shrink([false; 2]) + .stick_to_bottom(true) + .enable_scrolling(true) + .max_height(serial_height - top_spacing) + .min_scrolled_height(serial_height - top_spacing) + .max_width(width) + .show_rows(ui, row_height, num_rows, |ui, row_range| { + let content: String = gui_data + .prints + .clone() + .into_iter() + .skip(row_range.start) + .take(row_range.count()) + .collect(); + + let mut layouter = + |ui: &egui::Ui, string: &str, wrap_width: f32| { + let mut layout_job = highlight_impl( + ui.ctx(), + string, + self.serial_devices.highlight_labels[self.device_idx] + .clone(), + Color32::from_rgb(155, 164, 167), + ) + .unwrap(); + layout_job.wrap.max_width = wrap_width; + ui.fonts(|f| f.layout_job(layout_job)) + }; + + ui.add( + egui::TextEdit::multiline(&mut content.as_str()) + .font(DEFAULT_FONT_ID) // for cursor height + .lock_focus(true) + .text_color(color) + .desired_width(width) + .layouter(&mut layouter), + ); + }); + ui.horizontal(|ui| { + let cmd_line = ui.add( + egui::TextEdit::singleline(&mut self.command) + .desired_width(width - 50.0) .lock_focus(true) - .text_color(color) - .desired_width(width) - .layouter(&mut layouter), + .code_editor(), ); - }); - ui.horizontal(|ui| { - let cmd_line = ui.add( - egui::TextEdit::singleline(&mut self.command) - .desired_width(width - 50.0) - .lock_focus(true) - .code_editor(), - ); - let cmd_has_lost_focus = cmd_line.lost_focus(); - let key_pressed = ui.input(|i| i.key_pressed(egui::Key::Enter)); - if (key_pressed && cmd_has_lost_focus) || ui.button("Send").clicked() { - // send command - self.history.push(self.command.clone()); - self.index = self.history.len() - 1; - let eol = self.eol.replace("\\r", "\r").replace("\\n", "\n"); - if let Err(err) = self.send_tx.send(self.command.clone() + &eol) { - log::error!("send_tx thread send failed: {:?}", err); + let cmd_has_lost_focus = cmd_line.lost_focus(); + let key_pressed = ui.input(|i| i.key_pressed(egui::Key::Enter)); + if (key_pressed && cmd_has_lost_focus) || ui.button("Send").clicked() { + // send command + self.history.push(self.command.clone()); + self.index = self.history.len() - 1; + let eol = self.eol.replace("\\r", "\r").replace("\\n", "\n"); + if let Err(err) = self.send_tx.send(self.command.clone() + &eol) { + log::error!("send_tx thread send failed: {:?}", err); + } + // stay in focus! + cmd_line.request_focus(); } - // stay in focus! - cmd_line.request_focus(); - } - }); + }); - if ui.input(|i| i.key_pressed(egui::Key::ArrowUp)) { - self.index = self.index.saturating_sub(1); - if !self.history.is_empty() { - self.command = self.history[self.index].clone(); + if ui.input(|i| i.key_pressed(egui::Key::ArrowUp)) { + self.index = self.index.saturating_sub(1); + if !self.history.is_empty() { + self.command = self.history[self.index].clone(); + } } - } - if ui.input(|i| i.key_pressed(egui::Key::ArrowDown)) { - self.index = std::cmp::min(self.index + 1, self.history.len() - 1); - if !self.history.is_empty() { - self.command = self.history[self.index].clone(); + if ui.input(|i| i.key_pressed(egui::Key::ArrowDown)) { + self.index = std::cmp::min(self.index + 1, self.history.len() - 1); + if !self.history.is_empty() { + self.command = self.history[self.index].clone(); + } } } }); @@ -582,12 +582,16 @@ impl MyApp { // let selected_new_device = response.changed(); //somehow this does not work // if selected_new_device { if old_name != self.device { - if !self.data.prints.is_empty() { - self.show_warning_window = WindowFeedback::Waiting; - self.old_device = old_name; - } else { - self.show_warning_window = WindowFeedback::Clear; - } + // TODO: check this here + + // if !gui_data.prints.is_empty() { + // self.show_warning_window = WindowFeedback::Waiting; + // self.old_device = old_name; + // } else { + // self.show_warning_window = WindowFeedback::Clear; + // } + + self.show_warning_window = WindowFeedback::Clear; } }); match self.show_warning_window { @@ -627,7 +631,6 @@ impl MyApp { .send(GuiCommand::Clear) .expect("failed to send clear after choosing new device"); // need to clear the data here such that we don't get errors in the gui (plot) - self.data = GuiOutputDataContainer::default(); self.show_warning_window = WindowFeedback::None; } WindowFeedback::Cancel => { @@ -924,7 +927,6 @@ impl MyApp { log::error!("clear_tx thread send failed: {:?}", err); } // need to clear the data here in order to prevent errors in the gui (plot) - self.data = GuiOutputDataContainer::default(); // self.names_tx.send(self.serial_devices.labels[self.device_idx].clone()).expect("Failed to send names"); } ui.add_space(5.0); diff --git a/src/io.rs b/src/io.rs index c367334..1f1e7be 100644 --- a/src/io.rs +++ b/src/io.rs @@ -50,12 +50,17 @@ pub fn open_from_csv( let time_value = record.get(0).unwrap(); if csv_options.save_absolute_time { data.absolute_time.push(time_value.parse()?); - } else { - data.time.push(time_value.parse()?); } // Parse the remaining columns and populate the dataset for (i, value) in record.iter().skip(1).enumerate() { + if !csv_options.save_absolute_time { + if let Some(time_column) = data.time.get_mut(i) { + time_column.push(time_value.parse()?); + } else { + return Err("Unexpected number of time data columns in the CSV".into()); + } + } if let Some(dataset_column) = data.dataset.get_mut(i) { dataset_column.push(value.parse()?); } else { @@ -84,7 +89,8 @@ pub fn save_to_csv(data: &DataContainer, csv_options: &FileOptions) -> Result<() let time = if csv_options.save_absolute_time { data.absolute_time[j].to_string() } else { - data.time[j].to_string() + // TODO: this currently just takes the time value of the first entry + data.time[0][j].to_string() }; let mut data_to_write = vec![time]; for value in data.dataset.iter() { @@ -119,7 +125,11 @@ pub fn save_raw(data: &DataContainer, path: &PathBuf) -> Result<(), Box Vec { +fn split(payload: &str) -> (Option, Vec) { let mut split_data: Vec<&str> = vec![]; for s in payload.split(':') { split_data.extend(s.split(',')); } - split_data - .iter() - .map(|x| x.trim()) - .flat_map(|x| x.parse::()) - .collect() + if split_data.is_empty() { + return (None, vec![]); + } + // Try to parse the first entry as a number + let first_entry = split_data[0]; + if first_entry.parse::().is_ok() { + // First entry is a number → No identifier, process normally + let values: Vec = split_data + .iter() + .map(|x| x.trim()) + .flat_map(|x| x.parse::()) + .collect(); + (None, values) + } else { + // First entry is a string identifier → Process with identifier + let identifier = first_entry.to_string(); + let values: Vec = split_data[1..] + .iter() + .filter_map(|x| match x.trim().parse::() { + Ok(val) => Some(val), + Err(_) => None, + }) + .collect(); + (Some(identifier), values) + } } fn console_text(show_timestamps: bool, show_sent_cmds: bool, packet: &Packet) -> Option { @@ -79,13 +99,19 @@ fn main_thread( ) { // reads data from mutex, samples and saves if needed let mut data = DataContainer::default(); + let mut identifier_map: HashMap = HashMap::new(); let mut failed_format_counter = 0; + let mut failed_key_counter = 0; let mut show_timestamps = true; let mut show_sent_cmds = true; let mut file_opened = false; + const MAX_FPS: u32 = 24; + let frame_duration = Duration::from_secs_f64(1.0 / MAX_FPS as f64); + let mut last_sent = Instant::now(); + loop { select! { recv(raw_data_rx) -> packet => { @@ -93,8 +119,12 @@ fn main_thread( if !file_opened { data.loaded_from_file = false; if !packet.payload.is_empty() { - sync_tx.send(true).expect("unable to send sync tx"); + if last_sent.elapsed() >= frame_duration { + sync_tx.send(true).expect("unable to send sync tx"); + last_sent = Instant::now(); + } data.raw_traffic.push(packet.clone()); + data.absolute_time.push(packet.absolute_time); if let Ok(mut gui_data) = data_lock.write() { if let Some(text) = console_text(show_timestamps, show_sent_cmds, &packet) { @@ -103,59 +133,148 @@ fn main_thread( } } - let split_data = split(&packet.payload); - if data.dataset.is_empty() || failed_format_counter > 10 { - // resetting dataset - data.time = vec![]; - data.dataset = vec![vec![]; max(split_data.len(), 1)]; - if let Ok(mut gui_data) = data_lock.write() { - gui_data.plots = (0..max(split_data.len(), 1)) - .map(|i| (format!("Column {i}"), vec![])) - .collect(); - } - failed_format_counter = 0; - // log::error!("resetting dataset. split length = {}, length data.dataset = {}", split_data.len(), data.dataset.len()); - } else if split_data.len() == data.dataset.len() { - // appending data - for (i, set) in data.dataset.iter_mut().enumerate() { - set.push(split_data[i]); + if packet.direction == SerialDirection::Receive { + + let (identifier_opt, values) = split(&packet.payload); + + if data.dataset.is_empty() || failed_format_counter > 10 { + // resetting dataset + println!("resetting dataset with values: {}", values.len()); + data.time = vec![vec![]; values.len()]; + data.dataset = vec![vec![]; values.len()]; + if let Ok(mut gui_data) = data_lock.write() { + gui_data.plots = (0..values.len()) + .map(|i| (format!("Column {i}"), vec![])) + .collect(); + } + if let Some(ref identifier) = identifier_opt { + identifier_map.insert(identifier.clone(), 0); + failed_key_counter = 0; + } + failed_format_counter = 0; + // log::error!("resetting dataset. split length = {}, length data.dataset = {}", split_data.len(), data.dataset.len()); } + // else if split_data.len() == data.dataset.len() { + // // appending data + // for (i, set) in data.dataset.iter_mut().enumerate() { + // set.push(split_data[i]); + // failed_format_counter = 0; + // identifier_map = HashMap::new(); + // } + // + // data.time.push(packet.relative_time); + // data.absolute_time.push(packet.absolute_time); + // + // // appending data for GUI thread + // if let Ok(mut gui_data) = data_lock.write() { + // // append plot-points + // for ((_label, graph), data_i) in + // gui_data.plots.iter_mut().zip(&data.dataset) + // { + // if data.time.len() == data_i.len() { + // if let Some(y) = data_i.last() { + // graph.push(PlotPoint { + // x: packet.relative_time / 1000.0, + // y: *y as f64, + // }); + // } + // } + // } + // } + // if data.time.len() != data.dataset[0].len() { + // // resetting dataset + // data.time = vec![]; + // data.dataset = vec![vec![]; max(split_data.len(), 1)]; + // if let Ok(mut gui_data) = data_lock.write() { + // gui_data.prints = vec!["".to_string(); max(split_data.len(), 1)]; + // gui_data.plots = (0..max(split_data.len(), 1)) + // .map(|i| (format!("Column {i}"), vec![])) + // .collect(); + // } + // } + // } else { + // // not same length + // failed_format_counter += 1; + // // log::error!("not same length in main! length split_data = {}, length data.dataset = {}", split_data.len(), data.dataset.len()) + // } + + if let Some(identifier) = identifier_opt { + if !identifier_map.contains_key(&identifier) { + failed_key_counter += 1; + if failed_key_counter < 10 && !identifier_map.is_empty() { + continue; // skip outer loop iteration + } + + let new_index = data.dataset.len(); + for _ in 0..values.len() { + data.dataset.push(vec![]); + data.time.push(vec![]); + } - data.time.push(packet.relative_time); - data.absolute_time.push(packet.absolute_time); - - // appending data for GUI thread - if let Ok(mut gui_data) = data_lock.write() { - // append plot-points - for ((_label, graph), data_i) in - gui_data.plots.iter_mut().zip(&data.dataset) - { - if data.time.len() == data_i.len() { - if let Some(y) = data_i.last() { + if let Ok(mut gui_data) = data_lock.write() { + gui_data.plots.push((format!("Column {new_index}"), vec![])); + } + + println!("pushing new index: {}", new_index); + + identifier_map.insert(identifier.clone(), new_index); + } else { + failed_key_counter = 0; + } + + let index = identifier_map[&identifier]; + + // // Ensure dataset and time vectors have enough columns + // while data.dataset.len() <= index { + // data.dataset.push(vec![]); + // data.time.push(vec![]); + // } + + // Append values to corresponding dataset entries + for (i, &value) in values.iter().enumerate() { + data.dataset[index + i].push(value); + data.time[index + i].push(packet.relative_time); + } + + if let Ok(mut gui_data) = data_lock.write() { + for( ((_label, graph), data_i), time_i) in + gui_data.plots.iter_mut().zip(&data.dataset).zip(&data.time) + { + if let (Some(y), Some(t)) = (data_i.last(), time_i.last() ){ graph.push(PlotPoint { - x: packet.relative_time / 1000.0, + x: *t / 1000.0, y: *y as f64, }); } + } } - } - if data.time.len() != data.dataset[0].len() { - // resetting dataset - data.time = vec![]; - data.dataset = vec![vec![]; max(split_data.len(), 1)]; - if let Ok(mut gui_data) = data_lock.write() { - gui_data.prints = vec!["".to_string(); max(split_data.len(), 1)]; - gui_data.plots = (0..max(split_data.len(), 1)) - .map(|i| (format!("Column {i}"), vec![])) - .collect(); + } else { + // Handle unnamed datasets (pure numerical data) + if values.len() == data.dataset.len() { + for (i, &value) in values.iter().enumerate() { + data.dataset[i].push(value); + data.time[i].push(packet.relative_time); + } + if let Ok(mut gui_data) = data_lock.write() { + for ((_label, graph), data_i) in + gui_data.plots.iter_mut().zip(&data.dataset) + { + if data.time.len() == data_i.len() { + if let Some(y) = data_i.last() { + graph.push(PlotPoint { + x: packet.relative_time / 1000.0, + y: *y as f64, + }); + } + } + } + } + } else { + failed_format_counter += 1; } } - } else { - // not same length - failed_format_counter += 1; - // log::error!("not same length in main! length split_data = {}, length data.dataset = {}", split_data.len(), data.dataset.len()) } } } @@ -167,6 +286,7 @@ fn main_thread( GuiCommand::Clear => { data = DataContainer::default(); failed_format_counter = 0; + identifier_map = HashMap::new(); if let Ok(mut gui_data) = data_lock.write() { *gui_data = GuiOutputDataContainer::default(); } @@ -211,7 +331,8 @@ fn main_thread( { for (y,t) in data_i.iter().zip(data.time.iter()) { graph.push(PlotPoint { - x: *t / 1000.0, + // TODO: this always takes the first time value + x: t[0] / 1000.0, y: *y as f64 , }); }