diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a3399d..92dc303 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,14 @@ All notable changes to the `Serial Monitor` crate will be documented in this file. -# Unreleased 0.4.x +## Unreleased 0.4.x +* Switched to `crossbeam-channel` for more efficient channel routing +* 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) -# 0.3.4 +## 0.3.4 * implement option to self-update the application diff --git a/Cargo.lock b/Cargo.lock index c94fd32..4780e53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1015,6 +1015,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -4292,6 +4301,7 @@ dependencies = [ name = "serial-monitor-rust" version = "0.3.5" dependencies = [ + "crossbeam-channel", "csv", "eframe", "egui-file-dialog", diff --git a/Cargo.toml b/Cargo.toml index 7a356aa..f717681 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ self_update = { git = "https://github.com/hacknus/self_update", features = ["arc tempfile = { version = "3.15", optional = true } reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls", "http2"], optional = true } semver = { version = "1.0.24", optional = true } +crossbeam-channel = "0.5.14" [features] self_update = ["dep:self_update", "tempfile", "reqwest", "semver"] @@ -51,4 +52,7 @@ dbg-build = false dbg-name = false name = "Serial Monitor" no-build = false -output = "target/wix/SerialMonitorInstaller.msi" \ No newline at end of file +output = "target/wix/SerialMonitorInstaller.msi" + +[profile.release] +debug = true diff --git a/src/data.rs b/src/data.rs index 20cffe4..52b4a7e 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1,3 +1,4 @@ +use egui_plot::PlotPoint; use std::fmt; use std::time::{SystemTime, UNIX_EPOCH}; @@ -62,3 +63,9 @@ impl Default for DataContainer { } } } + +#[derive(Clone, Debug, Default)] +pub struct GuiOutputDataContainer { + pub prints: Vec, + pub plots: Vec<(String, Vec)>, +} diff --git a/src/gui.rs b/src/gui.rs index 651b7ff..69155b9 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,14 +1,14 @@ use core::f32; +use crossbeam_channel::{Receiver, Sender}; use std::cmp::max; use std::ops::RangeInclusive; use std::path::PathBuf; -use std::sync::mpsc::{Receiver, Sender}; use std::sync::{Arc, RwLock}; use std::time::Duration; use crate::color_picker::{color_picker_widget, color_picker_window, COLORS}; use crate::custom_highlighter::highlight_impl; -use crate::data::{DataContainer, SerialDirection}; +use crate::data::GuiOutputDataContainer; use crate::serial::{clear_serial_settings, save_serial_settings, Device, SerialDevices}; use crate::settings_window::settings_window; use crate::toggle::toggle; @@ -24,7 +24,7 @@ use eframe::{egui, Storage}; use egui::ThemePreference; use egui_file_dialog::information_panel::InformationPanel; use egui_file_dialog::FileDialog; -use egui_plot::{log_grid_spacer, GridMark, Legend, Line, Plot, PlotPoint, PlotPoints}; +use egui_plot::{log_grid_spacer, GridMark, Legend, Line, Plot, PlotPoints}; use preferences::Preferences; #[cfg(feature = "self_update")] use self_update::update::Release; @@ -64,6 +64,13 @@ pub enum WindowFeedback { Cancel, } +#[derive(Clone)] +pub enum GuiCommand { + Clear, + ShowTimestamps(bool), + ShowSentTraffic(bool), +} + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] pub struct GuiSettingsContainer { pub device: String, @@ -118,7 +125,7 @@ pub struct MyApp { plot_serial_display_ratio: f32, picked_path: PathBuf, plot_location: Option, - data: DataContainer, + data: GuiOutputDataContainer, file_dialog_state: FileDialogState, file_dialog: FileDialog, information_panel: InformationPanel, @@ -129,12 +136,12 @@ pub struct MyApp { device_lock: Arc>, devices_lock: Arc>>, connected_lock: Arc>, - data_lock: Arc>, + data_lock: Arc>, save_tx: Sender, load_tx: Sender, load_names_rx: Receiver>, send_tx: Sender, - clear_tx: Sender, + gui_cmd_tx: Sender, history: Vec, index: usize, eol: String, @@ -156,7 +163,7 @@ pub struct MyApp { impl MyApp { pub fn new( cc: &eframe::CreationContext, - data_lock: Arc>, + data_lock: Arc>, device_lock: Arc>, devices_lock: Arc>>, devices: SerialDevices, @@ -166,7 +173,7 @@ impl MyApp { load_tx: Sender, load_names_rx: Receiver>, send_tx: Sender, - clear_tx: Sender, + gui_cmd_tx: Sender, ) -> Self { let mut file_dialog = FileDialog::default() //.initial_directory(PathBuf::from("/path/to/app")) @@ -203,7 +210,7 @@ impl MyApp { picked_path: PathBuf::new(), device: "".to_string(), old_device: "".to_string(), - data: DataContainer::default(), + data: GuiOutputDataContainer::default(), file_dialog_state: FileDialogState::None, file_dialog, information_panel: InformationPanel::default().add_file_preview("csv", |ui, item| { @@ -227,7 +234,7 @@ impl MyApp { load_tx, load_names_rx, send_tx, - clear_tx, + gui_cmd_tx, plotting_range: usize::MAX, plot_serial_display_ratio: 0.45, command: "".to_string(), @@ -283,25 +290,6 @@ impl MyApp { window_feedback } - fn console_text(&self, packet: &crate::data::Packet) -> Option { - match (self.show_sent_cmds, self.show_timestamps, &packet.direction) { - (true, true, _) => Some(format!( - "[{}] t + {:.3}s: {}\n", - packet.direction, - packet.relative_time as f32 / 1000.0, - packet.payload - )), - (true, false, _) => Some(format!("[{}]: {}\n", packet.direction, packet.payload)), - (false, true, SerialDirection::Receive) => Some(format!( - "t + {:.3}s: {}\n", - packet.relative_time as f32 / 1000.0, - packet.payload - )), - (false, false, SerialDirection::Receive) => Some(packet.payload.clone() + "\n"), - (_, _, _) => None, - } - } - fn draw_central_panel(&mut self, ctx: &egui::Context) { egui::CentralPanel::default().show(ctx, |ui| { let left_border = 10.0; @@ -327,11 +315,17 @@ impl MyApp { ui.horizontal(|ui| { ui.add_space(left_border); ui.vertical(|ui| { - if let Ok(read_guard) = self.data_lock.read() { - self.data = read_guard.clone(); + 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(); } - if self.data.loaded_from_file && self.file_opened { + // 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)) @@ -341,32 +335,40 @@ impl MyApp { } } if self.serial_devices.number_of_plots[self.device_idx] > 0 { - if self.data.dataset.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.dataset.len(), 1)) + 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.dataset.len(), 1)).map(|_| 0.0).collect(); + (0..max(self.data.plots.len(), 1)).map(|_| 0.0).collect(); } - 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 }); - } - } - } - } + // 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) @@ -388,13 +390,15 @@ impl MyApp { .x_axis_formatter(t_fmt); let plot_inner = signal_plot.show(ui, |signal_plot_ui| { - for (i, graph) in graphs.iter().enumerate() { + 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.to_vec())) - .name(&self.labels[i]) - .color(self.colors[i]), + Line::new(PlotPoints::Owned( + graph[window..].to_vec(), + )) + .name(&self.labels[i]) + .color(self.colors[i]), ); } } @@ -430,7 +434,7 @@ impl MyApp { let serial_height = panel_height - plot_ui_heigh - left_border * 2.0 - top_spacing; - let num_rows = self.data.raw_traffic.len(); + let num_rows = self.data.prints.len(); let row_height = ui.text_style_height(&egui::TextStyle::Body); let color = if self.gui_conf.dark_mode { @@ -453,10 +457,10 @@ impl MyApp { let content: String = row_range .into_iter() .flat_map(|i| { - if self.data.raw_traffic.is_empty() { + if self.data.prints.is_empty() { None } else { - self.console_text(&self.data.raw_traffic[i]) + Some(self.data.prints[i].clone()) } }) .collect(); @@ -578,7 +582,7 @@ 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.time.is_empty() { + if !self.data.prints.is_empty() { self.show_warning_window = WindowFeedback::Waiting; self.old_device = old_name; } else { @@ -619,11 +623,11 @@ impl MyApp { self.device_idx = self.serial_devices.devices.len() - 1; save_serial_settings(&self.serial_devices); } - self.clear_tx - .send(true) + self.gui_cmd_tx + .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 = DataContainer::default(); + self.data = GuiOutputDataContainer::default(); self.show_warning_window = WindowFeedback::None; } WindowFeedback::Cancel => { @@ -916,11 +920,11 @@ impl MyApp { || ui.input_mut(|i| i.consume_shortcut(&CLEAR_PLOT_SHORTCUT)) { log::info!("Cleared recorded Data"); - if let Err(err) = self.clear_tx.send(true) { + if let Err(err) = self.gui_cmd_tx.send(GuiCommand::Clear) { 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 = DataContainer::default(); + 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); @@ -937,14 +941,34 @@ impl MyApp { }); ui.add_space(5.0); ui.horizontal(|ui| { - ui.add(toggle(&mut self.show_sent_cmds)) - .on_hover_text("Show sent commands in console."); + if ui + .add(toggle(&mut self.show_sent_cmds)) + .on_hover_text("Show sent commands in console.") + .changed() + { + if let Err(err) = self + .gui_cmd_tx + .send(GuiCommand::ShowSentTraffic(self.show_sent_cmds)) + { + log::error!("clear_tx thread send failed: {:?}", err); + } + } ui.label("Show Sent Commands"); }); ui.add_space(5.0); ui.horizontal(|ui| { - ui.add(toggle(&mut self.show_timestamps)) - .on_hover_text("Show timestamp in console."); + if ui + .add(toggle(&mut self.show_timestamps)) + .on_hover_text("Show timestamp in console.") + .changed() + { + if let Err(err) = self + .gui_cmd_tx + .send(GuiCommand::ShowTimestamps(self.show_sent_cmds)) + { + log::error!("clear_tx thread send failed: {:?}", err); + } + } ui.label("Show Timestamp"); }); ui.add_space(5.0); diff --git a/src/io.rs b/src/io.rs index 46e25a9..c367334 100644 --- a/src/io.rs +++ b/src/io.rs @@ -17,7 +17,7 @@ pub struct FileOptions { pub fn open_from_csv( data: &mut DataContainer, csv_options: &mut FileOptions, -) -> Result<(), Box> { +) -> Result, Box> { let mut rdr = ReaderBuilder::new() .has_headers(true) .from_path(&csv_options.file_path)?; @@ -35,6 +35,8 @@ pub fn open_from_csv( data.time.clear(); data.dataset = vec![vec![]; csv_options.names.len()]; + let mut raw_data = vec![]; + // Read and parse each record in the CSV for result in rdr.records() { let record = result?; @@ -60,11 +62,14 @@ pub fn open_from_csv( return Err("Unexpected number of data columns in the CSV".into()); } } + // Join the row into a single string with ", " as delimiter and push to raw_data + let row = record.iter().collect::>().join(", "); + raw_data.push(row + "\n"); } data.loaded_from_file = true; - Ok(()) + Ok(raw_data) } pub fn save_to_csv(data: &DataContainer, csv_options: &FileOptions) -> Result<(), Box> { diff --git a/src/main.rs b/src/main.rs index 6c4fac8..17d8fd3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,19 +5,20 @@ extern crate csv; extern crate preferences; extern crate serde; -use std::cmp::max; -use std::path::PathBuf; -use std::sync::mpsc::{Receiver, Sender}; -use std::sync::{mpsc, Arc, RwLock}; -use std::{env, thread}; - -use crate::data::{DataContainer, Packet}; -use crate::gui::{load_gui_settings, MyApp, RIGHT_PANEL_WIDTH}; +use crate::data::{DataContainer, GuiOutputDataContainer, Packet, SerialDirection}; +use crate::gui::{load_gui_settings, GuiCommand, MyApp, RIGHT_PANEL_WIDTH}; use crate::io::{open_from_csv, save_to_csv, FileOptions}; use crate::serial::{load_serial_settings, serial_thread, Device}; +use crossbeam_channel::{select, Receiver, Sender}; use eframe::egui::{vec2, ViewportBuilder, Visuals}; use eframe::{egui, icon_data}; +use egui_plot::PlotPoint; use preferences::AppInfo; +use std::cmp::max; +use std::path::PathBuf; +use std::sync::{Arc, RwLock}; +use std::time::Duration; +use std::{env, thread}; mod color_picker; mod custom_highlighter; @@ -48,115 +49,217 @@ fn split(payload: &str) -> Vec { .collect() } +fn console_text(show_timestamps: bool, show_sent_cmds: bool, packet: &Packet) -> Option { + match (show_sent_cmds, show_timestamps, &packet.direction) { + (true, true, _) => Some(format!( + "[{}] t + {:.3}s: {}\n", + packet.direction, + packet.relative_time as f32 / 1000.0, + packet.payload + )), + (true, false, _) => Some(format!("[{}]: {}\n", packet.direction, packet.payload)), + (false, true, SerialDirection::Receive) => Some(format!( + "t + {:.3}s: {}\n", + packet.relative_time as f32 / 1000.0, + packet.payload + )), + (false, false, SerialDirection::Receive) => Some(packet.payload.clone() + "\n"), + (_, _, _) => None, + } +} + fn main_thread( sync_tx: Sender, - data_lock: Arc>, + data_lock: Arc>, raw_data_rx: Receiver, save_rx: Receiver, load_rx: Receiver, load_names_tx: Sender>, - clear_rx: Receiver, + gui_cmd_rx: Receiver, ) { // reads data from mutex, samples and saves if needed let mut data = DataContainer::default(); let mut failed_format_counter = 0; + let mut show_timestamps = true; + let mut show_sent_cmds = true; + let mut file_opened = false; loop { - if let Ok(cl) = clear_rx.try_recv() { - if cl { - data = DataContainer::default(); - failed_format_counter = 0; + select! { + recv(raw_data_rx) -> packet => { + if let Ok(packet) = packet { + if !file_opened { + data.loaded_from_file = false; + if !packet.payload.is_empty() { + sync_tx.send(true).expect("unable to send sync tx"); + data.raw_traffic.push(packet.clone()); + + if let Ok(mut gui_data) = data_lock.write() { + if let Some(text) = console_text(show_timestamps, show_sent_cmds, &packet) { + // append prints + gui_data.prints.push(text); + } + } + + 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]); + failed_format_counter = 0; + } + + 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 !file_opened { - if let Ok(packet) = raw_data_rx.try_recv() { - data.loaded_from_file = false; - if !packet.payload.is_empty() { - sync_tx.send(true).expect("unable to send sync tx"); - data.raw_traffic.push(packet.clone()); - let split_data = split(&packet.payload); - if data.dataset.is_empty() || failed_format_counter > 10 { - // resetting dataset - data.dataset = vec![vec![]; max(split_data.len(), 1)]; - 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]); + recv(gui_cmd_rx) -> msg => { + if let Ok(cmd) = msg { + match cmd { + GuiCommand::Clear => { + data = DataContainer::default(); failed_format_counter = 0; + if let Ok(mut gui_data) = data_lock.write() { + *gui_data = GuiOutputDataContainer::default(); + } } - data.time.push(packet.relative_time); - data.absolute_time.push(packet.absolute_time); - if data.time.len() != data.dataset[0].len() { - // resetting dataset - data.time = vec![]; - data.dataset = vec![vec![]; max(split_data.len(), 1)]; + GuiCommand::ShowTimestamps(val) => { + show_timestamps = val; + } + GuiCommand::ShowSentTraffic(val) => { + show_sent_cmds = val; } - } 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 Ok(fp) = load_rx.try_recv() { - if let Some(file_ending) = fp.extension() { - match file_ending.to_str().unwrap() { - "csv" => { - file_opened = true; - let mut file_options = FileOptions { - file_path: fp.clone(), - save_absolute_time: false, - save_raw_traffic: false, - names: vec![], - }; - match open_from_csv(&mut data, &mut file_options) { - Ok(_) => { - log::info!("opened {:?}", fp); - load_names_tx - .send(file_options.names) - .expect("unable to send names on channel after loading"); + recv(load_rx) -> msg => { + if let Ok(fp) = msg { + // load logic + if let Some(file_ending) = fp.extension() { + match file_ending.to_str().unwrap() { + "csv" => { + file_opened = true; + let mut file_options = FileOptions { + file_path: fp.clone(), + save_absolute_time: false, + save_raw_traffic: false, + names: vec![], + }; + match open_from_csv(&mut data, &mut file_options) { + Ok(raw_data) => { + log::info!("opened {:?}", fp); + if let Ok(mut gui_data) = data_lock.write() { + + gui_data.prints = raw_data; + + dbg!(&gui_data.prints); + + gui_data.plots = (0..data.dataset.len()) + .map(|i| (file_options.names[i].to_string(), vec![])) + .collect(); + // append plot-points + for ((_label, graph), data_i) in + gui_data.plots.iter_mut().zip(&data.dataset) + { + for (y,t) in data_i.iter().zip(data.time.iter()) { + graph.push(PlotPoint { + x: *t / 1000.0, + y: *y as f64 , + }); + } + } + + } + load_names_tx + .send(file_options.names) + .expect("unable to send names on channel after loading"); + } + Err(err) => { + file_opened = false; + log::error!("failed opening {:?}: {:?}", fp, err); + } + }; } - Err(err) => { + _ => { file_opened = false; - log::error!("failed opening {:?}: {:?}", fp, err); + log::error!("file not supported: {:?} \n Close the file to connect to a spectrometer or open another file.", fp); + continue; } - }; - } - _ => { + } + } else { file_opened = false; - log::error!("file not supported: {:?} \n Close the file to connect to a spectrometer or open another file.", fp); - continue; } + } else { + file_opened = false; } - } else { - file_opened = false; } - } else { - file_opened = false; - } - - if let Ok(mut write_guard) = data_lock.write() { - *write_guard = data.clone(); - } - - if let Ok(csv_options) = save_rx.try_recv() { - match save_to_csv(&data, &csv_options) { - Ok(_) => { - log::info!("saved data file to {:?} ", csv_options.file_path); - } - Err(e) => { - log::error!( - "failed to save file to {:?}: {:?}", - csv_options.file_path, - e - ); + recv(save_rx) -> msg => { + if let Ok(csv_options) = msg { + match save_to_csv(&data, &csv_options) { + Ok(_) => { + log::info!("saved data file to {:?} ", csv_options.file_path); + } + Err(e) => { + log::error!( + "failed to save file to {:?}: {:?}", + csv_options.file_path, + e + ); + } + } } } + default(Duration::from_millis(10)) => { + // occasionally push data to GUI + } } } } @@ -169,17 +272,20 @@ fn main() { let device_lock = Arc::new(RwLock::new(Device::default())); let devices_lock = Arc::new(RwLock::new(vec![gui_settings.device.clone()])); - let data_lock = Arc::new(RwLock::new(DataContainer::default())); + let data_lock = Arc::new(RwLock::new(GuiOutputDataContainer::default())); let connected_lock = Arc::new(RwLock::new(false)); - let (save_tx, save_rx): (Sender, Receiver) = mpsc::channel(); - let (load_tx, load_rx): (Sender, Receiver) = mpsc::channel(); + let (save_tx, save_rx): (Sender, Receiver) = + crossbeam_channel::unbounded(); + let (load_tx, load_rx): (Sender, Receiver) = crossbeam_channel::unbounded(); let (loaded_names_tx, loaded_names_rx): (Sender>, Receiver>) = - mpsc::channel(); - let (send_tx, send_rx): (Sender, Receiver) = mpsc::channel(); - let (clear_tx, clear_rx): (Sender, Receiver) = mpsc::channel(); - let (raw_data_tx, raw_data_rx): (Sender, Receiver) = mpsc::channel(); - let (sync_tx, sync_rx): (Sender, Receiver) = mpsc::channel(); + crossbeam_channel::unbounded(); + let (send_tx, send_rx): (Sender, Receiver) = crossbeam_channel::unbounded(); + let (gui_cmd_tx, gui_cmd_rx): (Sender, Receiver) = + crossbeam_channel::unbounded(); + let (raw_data_tx, raw_data_rx): (Sender, Receiver) = + crossbeam_channel::unbounded(); + let (sync_tx, sync_rx): (Sender, Receiver) = crossbeam_channel::unbounded(); let serial_device_lock = device_lock.clone(); let serial_devices_lock = devices_lock.clone(); @@ -205,7 +311,7 @@ fn main() { save_rx, load_rx, loaded_names_tx, - clear_rx, + gui_cmd_rx, ); }); @@ -261,7 +367,7 @@ fn main() { load_tx, loaded_names_rx, send_tx, - clear_tx, + gui_cmd_tx, ))) }), ) { diff --git a/src/serial.rs b/src/serial.rs index 4ebd3ec..0aa85cb 100644 --- a/src/serial.rs +++ b/src/serial.rs @@ -1,9 +1,9 @@ +use crossbeam_channel::{Receiver, Sender}; use eframe::egui::Color32; use preferences::Preferences; use serde::{Deserialize, Serialize}; use serialport::{DataBits, FlowControl, Parity, SerialPort, StopBits}; use std::io::{BufRead, BufReader}; -use std::sync::mpsc::{Receiver, Sender}; use std::sync::{Arc, RwLock}; use std::time::{Duration, Instant};