diff --git a/src/app.rs b/src/app.rs index 70870f8..2963ba0 100644 --- a/src/app.rs +++ b/src/app.rs @@ -16,17 +16,13 @@ use tokio::sync::{mpsc, watch}; use eframe::egui::{self, Button, CollapsingHeader, Color32, ComboBox, DragValue, Id, Key, Label, Layout, Modal, Modifiers, RichText, TextEdit, Widget}; use egui_plot::{Corner, GridInput, GridMark, Legend, Line, Plot, PlotPoint, Points}; -use egui_dock::{DockArea, DockState, Style}; +use egui_dock::{DockArea, DockState, Style, TabViewer}; use egui_extras::{TableBuilder, Column}; use crate::state::{AppState, ControlCommand, HardwareConnected, HardwareState, MeasurementDataState, Mode}; - use crate::logging::LoggingStates; -use crate::plot::{TimeSeriesPlot, BodePlot}; - -use crate::signals::{LoggingSignal, StartStopSignal}; - -use crate::icd::{BioImpedanceLeadMode, IcdDftNum, SweepPoints, ElectrodeConfiguration, ElectrodeOptionsWithMultiplexer}; +use crate::signals::LoggingSignal; +use crate::icd::{BioImpedanceLeadMode, IcdDftNum, SweepPoints, ElectrodeOptionsWithMultiplexer}; const LEAD_MODES: [BioImpedanceLeadMode; 2] = [ BioImpedanceLeadMode::TwoLead, @@ -46,7 +42,7 @@ const SWEEP_POINTS_VARIANTS: [SweepPoints; 2] = [ ]; #[derive(Clone, Copy,Debug, PartialEq, Eq)] -enum TabActive { +pub enum TabActive { Single, Sweep, Shortcuts, @@ -54,11 +50,11 @@ enum TabActive { pub struct App { tree: DockState, - tab_viewer: TabViewer, - tab_active: TabActive, + tab_viewer: CustomTabs, + // tab_active: TabActive, log_tx: mpsc::Sender, pub gui_logging_state: Arc>, - log_filename: String, + log_filename: Arc>, log_marker_modal: bool, log_marker: String, measurement_data: Arc, @@ -67,9 +63,10 @@ pub struct App { hardware_state_rx: sync::watch::Receiver } -struct TabViewer { +struct CustomTabs { show_settings: bool, show_settings_toggle: Option, + log_filename: Arc>, measurement_data: Arc, control_tx: mpsc::Sender, app_state_rx: sync::watch::Receiver, @@ -139,113 +136,112 @@ fn render_four_lead( changed } -impl TabViewer { +impl CustomTabs { fn single_tab(&mut self, ui: &mut egui::Ui) { egui::Frame::default().inner_margin(5).show(ui, |ui| { let settings = CollapsingHeader::new("Settings") .open(self.show_settings_toggle) .show(ui, |ui| { - if let running = self.hardware_state_rx.borrow().running { - ui.add_enabled_ui(!running, |ui| { - ui.horizontal(|ui| { - ui.label("Lead Mode:"); + let running = self.hardware_state_rx.borrow().running; + ui.add_enabled_ui(!running, |ui| { + ui.horizontal(|ui| { + ui.label("Lead Mode:"); - let mut lead_mode = self.app_state_rx.borrow().lead_mode; + let mut lead_mode = self.app_state_rx.borrow().lead_mode; - // Map current lead mode to index - let mut index = LEAD_MODES - .iter() - .position(|&m| m == lead_mode) - .unwrap_or(0); + // Map current lead mode to index + let mut index = LEAD_MODES + .iter() + .position(|&m| m == lead_mode) + .unwrap_or(0); - ComboBox::from_id_salt("LeadMode") - .width(60.0) - .show_index(ui, &mut index, LEAD_MODES.len(), |i| { - match LEAD_MODES[i] { - BioImpedanceLeadMode::TwoLead => "2-Lead", - BioImpedanceLeadMode::FourLead => "4-Lead", - } - .to_string() - }); - - // Update lead mode if changed - if lead_mode != LEAD_MODES[index] { - lead_mode = LEAD_MODES[index]; - self.control_tx.try_send(ControlCommand::ChangeLeadMode(lead_mode)).unwrap(); - } - }); - ui.horizontal(|ui| { - // Show lead configuration - ui.label("Lead Configuration:"); - let hardware_connected = self.hardware_state_rx.borrow().hardware_connected; - let lead_mode = self.app_state_rx.borrow().lead_mode; - - let mut settings = self.app_state_rx.borrow().electrode_settings; - - match (hardware_connected, lead_mode) { - (HardwareConnected::WithoutMultiplexer, BioImpedanceLeadMode::TwoLead) => { - ui.label(format!("Drive/sense: Electrode+ (CE0), Electrode- (AIN1)")); + ComboBox::from_id_salt("LeadMode") + .width(60.0) + .show_index(ui, &mut index, LEAD_MODES.len(), |i| { + match LEAD_MODES[i] { + BioImpedanceLeadMode::TwoLead => "2-Lead", + BioImpedanceLeadMode::FourLead => "4-Lead", } - (HardwareConnected::WithoutMultiplexer, BioImpedanceLeadMode::FourLead) => { - ui.label(format!("Drive: I+ (CE0), I- (AIN1) | Sense: V+ (AIN2), V- (AIN3)")); - } - (HardwareConnected::WithMultiplexer, BioImpedanceLeadMode::TwoLead) => { - if render_two_lead(ui, &mut settings.with_multiplexer_2_lead) { - self.control_tx.try_send(ControlCommand::ChangeElectrodeSettings(settings)).unwrap(); - } - } - (HardwareConnected::WithMultiplexer, BioImpedanceLeadMode::FourLead) => { - if render_four_lead(ui, &mut settings.with_multiplexer_4_lead) { - self.control_tx.try_send(ControlCommand::ChangeElectrodeSettings(settings)).unwrap(); - } - } - (HardwareConnected::None, _) => {} - } - }); + .to_string() + }); + + // Update lead mode if changed + if lead_mode != LEAD_MODES[index] { + lead_mode = LEAD_MODES[index]; + self.control_tx.try_send(ControlCommand::ChangeLeadMode(lead_mode)).unwrap(); + } }); - ui.add_enabled_ui(!running, |ui| { - ui.horizontal(|ui| { - ui.label("Single Frequency:"); - let mut freq = self.app_state_rx.borrow().single_frequency; - let response = ui.add(DragValue::new(&mut freq).speed(0.1)); - if response.changed() { - self.control_tx.try_send(ControlCommand::SetFrequency(freq)).unwrap(); + ui.horizontal(|ui| { + // Show lead configuration + ui.label("Lead Configuration:"); + let hardware_connected = self.hardware_state_rx.borrow().hardware_connected; + let lead_mode = self.app_state_rx.borrow().lead_mode; + + let mut settings = self.app_state_rx.borrow().electrode_settings; + + match (hardware_connected, lead_mode) { + (HardwareConnected::WithoutMultiplexer, BioImpedanceLeadMode::TwoLead) => { + ui.label(format!("Drive/sense: Electrode+ (CE0), Electrode- (AIN1)")); } - ui.label("Hz"); - }); - ui.horizontal(|ui| { - ui.label("ADC samples per DFT:"); - let mut dft_num = self.app_state_rx.borrow().dft_num; - let mut index = DFTNUM_VARIANTS.iter().position(|&x| x == dft_num).unwrap_or(0); - ComboBox::from_id_salt("Dftnum") - .width(75.0) - .show_index(ui, &mut index, DFTNUM_VARIANTS.len(), |i| { - format!("{}", 1 << (2 + i)) // 2^2 = 4, 2^3 = 8, ..., 2^14 = 16384 - }); - let new_value = DFTNUM_VARIANTS[index]; - if dft_num != new_value { - dft_num = new_value; - self.control_tx.try_send(ControlCommand::ChangeDftNum(dft_num)).unwrap(); - }; - }); - }); - ui.add_enabled_ui(running, |ui| { - ui.horizontal(|ui| { - ui.label("Periods per DFT:"); - match (running, self.hardware_state_rx.borrow().periods_per_dft) { - (true, Some(periods)) => { - ui.add(Label::new(format!("{:.2}", periods))); - }, - (true, None) => { - ui.add(Label::new("N/A")); - }, - (false, _) => { - ui.add(Label::new("Start to determine!")); + (HardwareConnected::WithoutMultiplexer, BioImpedanceLeadMode::FourLead) => { + ui.label(format!("Drive: I+ (CE0), I- (AIN1) | Sense: V+ (AIN2), V- (AIN3)")); + } + (HardwareConnected::WithMultiplexer, BioImpedanceLeadMode::TwoLead) => { + if render_two_lead(ui, &mut settings.with_multiplexer_2_lead) { + self.control_tx.try_send(ControlCommand::ChangeElectrodeSettings(settings)).unwrap(); } } - }); + (HardwareConnected::WithMultiplexer, BioImpedanceLeadMode::FourLead) => { + if render_four_lead(ui, &mut settings.with_multiplexer_4_lead) { + self.control_tx.try_send(ControlCommand::ChangeElectrodeSettings(settings)).unwrap(); + } + } + (HardwareConnected::None, _) => {} + } }); - } + }); + ui.add_enabled_ui(!running, |ui| { + ui.horizontal(|ui| { + ui.label("Single Frequency:"); + let mut freq = self.app_state_rx.borrow().single_frequency; + let response = ui.add(DragValue::new(&mut freq).speed(0.1)); + if response.changed() { + self.control_tx.try_send(ControlCommand::SetFrequency(freq)).unwrap(); + } + ui.label("Hz"); + }); + ui.horizontal(|ui| { + ui.label("ADC samples per DFT:"); + let mut dft_num = self.app_state_rx.borrow().dft_num; + let mut index = DFTNUM_VARIANTS.iter().position(|&x| x == dft_num).unwrap_or(0); + ComboBox::from_id_salt("Dftnum") + .width(75.0) + .show_index(ui, &mut index, DFTNUM_VARIANTS.len(), |i| { + format!("{}", 1 << (2 + i)) // 2^2 = 4, 2^3 = 8, ..., 2^14 = 16384 + }); + let new_value = DFTNUM_VARIANTS[index]; + if dft_num != new_value { + dft_num = new_value; + self.control_tx.try_send(ControlCommand::ChangeDftNum(dft_num)).unwrap(); + }; + }); + }); + ui.add_enabled_ui(running, |ui| { + ui.horizontal(|ui| { + ui.label("Periods per DFT:"); + match (running, self.hardware_state_rx.borrow().periods_per_dft) { + (true, Some(periods)) => { + ui.add(Label::new(format!("{:.2}", periods))); + }, + (true, None) => { + ui.add(Label::new("N/A")); + }, + (false, _) => { + ui.add(Label::new("Start to determine!")); + } + } + }); + }); }); self.show_settings_toggle = None; @@ -303,137 +299,136 @@ impl TabViewer { let settings = CollapsingHeader::new("Settings") .open(self.show_settings_toggle) .show(ui, |ui| { - if let running = self.hardware_state_rx.borrow().running { - ui.add_enabled_ui(!running, |ui| { - ui.horizontal(|ui| { - ui.label("Lead Mode:"); + let running = self.hardware_state_rx.borrow().running; + ui.add_enabled_ui(!running, |ui| { + ui.horizontal(|ui| { + ui.label("Lead Mode:"); - let mut lead_mode = self.app_state_rx.borrow().lead_mode; + let mut lead_mode = self.app_state_rx.borrow().lead_mode; - // Map current lead mode to index - let mut index = LEAD_MODES - .iter() - .position(|&m| m == lead_mode) - .unwrap_or(0); + // Map current lead mode to index + let mut index = LEAD_MODES + .iter() + .position(|&m| m == lead_mode) + .unwrap_or(0); - ComboBox::from_id_salt("LeadMode") - .width(60.0) - .show_index(ui, &mut index, LEAD_MODES.len(), |i| { - match LEAD_MODES[i] { - BioImpedanceLeadMode::TwoLead => "2-Lead", - BioImpedanceLeadMode::FourLead => "4-Lead", - } - .to_string() - }); - - // Update lead mode if changed - if lead_mode != LEAD_MODES[index] { - lead_mode = LEAD_MODES[index]; - self.control_tx.try_send(ControlCommand::ChangeLeadMode(lead_mode)).unwrap(); - } - }); - ui.horizontal(|ui| { - // Show lead configuration - ui.label("Lead Configuration:"); - let hardware_connected = self.hardware_state_rx.borrow().hardware_connected; - let lead_mode = self.app_state_rx.borrow().lead_mode; - - let mut settings = self.app_state_rx.borrow().electrode_settings; - match (hardware_connected, lead_mode) { - (HardwareConnected::WithoutMultiplexer, BioImpedanceLeadMode::TwoLead) => { - ui.label(format!("Drive/sense: Electrode+ (CE0), Electrode- (AIN1)")); + ComboBox::from_id_salt("LeadMode") + .width(60.0) + .show_index(ui, &mut index, LEAD_MODES.len(), |i| { + match LEAD_MODES[i] { + BioImpedanceLeadMode::TwoLead => "2-Lead", + BioImpedanceLeadMode::FourLead => "4-Lead", } - (HardwareConnected::WithoutMultiplexer, BioImpedanceLeadMode::FourLead) => { - ui.label("Drive: I+ (CE0), I- (AIN1) | Sense: V+ (AIN2), V- (AIN3)"); - } - (HardwareConnected::WithMultiplexer, BioImpedanceLeadMode::TwoLead) => { - if render_two_lead(ui, &mut settings.with_multiplexer_2_lead) { - self.control_tx.try_send(ControlCommand::ChangeElectrodeSettings(settings)).unwrap(); - } - } - (HardwareConnected::WithMultiplexer, BioImpedanceLeadMode::FourLead) => { - if render_four_lead(ui, &mut settings.with_multiplexer_4_lead) { - self.control_tx.try_send(ControlCommand::ChangeElectrodeSettings(settings)).unwrap(); - } - } - (HardwareConnected::None, _) => {} - } - }); + .to_string() + }); + + // Update lead mode if changed + if lead_mode != LEAD_MODES[index] { + lead_mode = LEAD_MODES[index]; + self.control_tx.try_send(ControlCommand::ChangeLeadMode(lead_mode)).unwrap(); + } }); - ui.add_enabled_ui(!running, |ui| { - ui.horizontal(|ui| { - ui.label("Measurement Points:"); - let mut measurement_points = self.app_state_rx.borrow().sweep_points; - let mut index = SWEEP_POINTS_VARIANTS.iter().position(|&x| x == measurement_points).unwrap_or(0); - ComboBox::from_id_salt("MeasurementPoints") - .width(75.0) - .show_index(ui, &mut index, SWEEP_POINTS_VARIANTS.len(), |i| { - format!("{:?}", SWEEP_POINTS_VARIANTS[i].len()) - }); - let new_value = SWEEP_POINTS_VARIANTS[index]; - if measurement_points != new_value { - measurement_points = new_value; - self.control_tx.try_send(ControlCommand::ChangeSweepPoints(measurement_points)).unwrap(); - info!("Sweep Points setting changed!"); - } - }); - }); - ui.add_enabled_ui(running, |ui| { - let (freq, periods_per_dft_vec) = self.hardware_state_rx.borrow().periods_per_dft_sweep.clone(); + ui.horizontal(|ui| { + // Show lead configuration + ui.label("Lead Configuration:"); + let hardware_connected = self.hardware_state_rx.borrow().hardware_connected; + let lead_mode = self.app_state_rx.borrow().lead_mode; - fn format_frequency(freq: u32) -> String { - if freq >= 1_000 { - // kHz - if freq % 1_000 == 0 { - format!("{}k", (freq / 1_000) as u64) - } else { - format!("{:.1}k", freq as f32 / 1_000.0) + let mut settings = self.app_state_rx.borrow().electrode_settings; + match (hardware_connected, lead_mode) { + (HardwareConnected::WithoutMultiplexer, BioImpedanceLeadMode::TwoLead) => { + ui.label(format!("Drive/sense: Electrode+ (CE0), Electrode- (AIN1)")); + } + (HardwareConnected::WithoutMultiplexer, BioImpedanceLeadMode::FourLead) => { + ui.label("Drive: I+ (CE0), I- (AIN1) | Sense: V+ (AIN2), V- (AIN3)"); + } + (HardwareConnected::WithMultiplexer, BioImpedanceLeadMode::TwoLead) => { + if render_two_lead(ui, &mut settings.with_multiplexer_2_lead) { + self.control_tx.try_send(ControlCommand::ChangeElectrodeSettings(settings)).unwrap(); } + } + (HardwareConnected::WithMultiplexer, BioImpedanceLeadMode::FourLead) => { + if render_four_lead(ui, &mut settings.with_multiplexer_4_lead) { + self.control_tx.try_send(ControlCommand::ChangeElectrodeSettings(settings)).unwrap(); + } + } + (HardwareConnected::None, _) => {} + } + }); + }); + ui.add_enabled_ui(!running, |ui| { + ui.horizontal(|ui| { + ui.label("Measurement Points:"); + let mut measurement_points = self.app_state_rx.borrow().sweep_points; + let mut index = SWEEP_POINTS_VARIANTS.iter().position(|&x| x == measurement_points).unwrap_or(0); + ComboBox::from_id_salt("MeasurementPoints") + .width(75.0) + .show_index(ui, &mut index, SWEEP_POINTS_VARIANTS.len(), |i| { + format!("{:?}", SWEEP_POINTS_VARIANTS[i].len()) + }); + let new_value = SWEEP_POINTS_VARIANTS[index]; + if measurement_points != new_value { + measurement_points = new_value; + self.control_tx.try_send(ControlCommand::ChangeSweepPoints(measurement_points)).unwrap(); + info!("Sweep Points setting changed!"); + } + }); + }); + ui.add_enabled_ui(running, |ui| { + let (freq, periods_per_dft_vec) = self.hardware_state_rx.borrow().periods_per_dft_sweep.clone(); + + fn format_frequency(freq: u32) -> String { + if freq >= 1_000 { + // kHz + if freq % 1_000 == 0 { + format!("{}k", (freq / 1_000) as u64) } else { - // Hz - if freq % 1 == 0 { - format!("{}", freq as u64) - } else { - format!("{:.1}", freq) - } + format!("{:.1}k", freq as f32 / 1_000.0) + } + } else { + // Hz + if freq % 1 == 0 { + format!("{}", freq as u64) + } else { + format!("{:.1}", freq) } } + } - TableBuilder::new(ui) - .column(Column::auto()) - .columns(Column::remainder().at_most(30.0), freq.len()) - .header(10.0, |mut header| { - header.col(|ui| { - ui.label("Frequency [Hz]:"); - }); - for freq in &freq { - header.col(|ui| { - ui.label(format_frequency(*freq)); - }); - } - }) - .body(|mut body| { - body.row(5.0, |mut row| { - row.col(|ui| { - ui.label("Periods per DFT:"); - }); - if let Some(periods) = periods_per_dft_vec { - for period in &periods { - row.col(|ui| { - ui.label(format!("{:.1}", period)); - }); - } - } else { - for _ in &freq { - row.col(|ui| { - ui.label("N/A"); - }); - } - } - }); + TableBuilder::new(ui) + .column(Column::auto()) + .columns(Column::remainder().at_most(30.0), freq.len()) + .header(10.0, |mut header| { + header.col(|ui| { + ui.label("Frequency [Hz]:"); }); - }); - } + for freq in &freq { + header.col(|ui| { + ui.label(format_frequency(*freq)); + }); + } + }) + .body(|mut body| { + body.row(5.0, |mut row| { + row.col(|ui| { + ui.label("Periods per DFT:"); + }); + if let Some(periods) = periods_per_dft_vec { + for period in &periods { + row.col(|ui| { + ui.label(format!("{:.1}", period)); + }); + } + } else { + for _ in &freq { + row.col(|ui| { + ui.label("N/A"); + }); + } + } + }); + }); + }); }); self.show_settings_toggle = None; @@ -575,7 +570,7 @@ fn log10x_formatter(name: &str, value: &PlotPoint) -> String { ) } -impl egui_dock::TabViewer for TabViewer { +impl TabViewer for CustomTabs { type Tab = String; fn title(&mut self, tab: &mut Self::Tab) -> eframe::egui::WidgetText { @@ -592,6 +587,32 @@ impl egui_dock::TabViewer for TabViewer { } } } + + fn on_tab_button(&mut self, tab: &mut Self::Tab, _response: &egui::Response) { + if _response.clicked() { + let tab_active = match tab.as_str() { + "Single" => { + *self.log_filename.lock().unwrap() = format!("log_{}_single.csv", Local::now().format("%Y%m%d")); + TabActive::Single + }, + "Sweep" => { + *self.log_filename.lock().unwrap() = format!("log_{}_sweep.csv", Local::now().format("%Y%m%d")); + TabActive::Sweep + }, + "Shortcuts" => TabActive::Shortcuts, + _ => return, + }; + self.control_tx.try_send(ControlCommand::Stop).unwrap(); + self.control_tx.try_send(ControlCommand::ChangeActiveTab(tab_active)).unwrap(); + + // if *self.gui_logging_state.lock().unwrap() == LoggingStates::Logging { + // self.log_tx.try_send(LoggingSignal::StopFileLogging).unwrap_or_else(|e| { + // error!("Failed to send logging logging signal: {:?}", e); + // }); + // } + + } + } } impl App { @@ -601,28 +622,34 @@ impl App { app_state_rx: watch::Receiver, hardware_state_rx: watch::Receiver) -> Self { // Step 1: Initialize shared fields first - let tab_active = TabActive::Single; + // let tab_active = app_state_rx.borrow().tab_active; // Step 2: Now we can initialize tab_viewer let control_tx_clone = control_tx.clone(); let app_state_clone = app_state_rx.clone(); let hardware_state_clone = hardware_state_rx.clone(); - let tab_viewer = TabViewer { measurement_data: measurement_data.clone(), + let log_filename = Arc::new(Mutex::new(format!("log_{}_single.csv", Local::now().format("%Y%m%d")))); + let log_filename_clone = log_filename.clone(); + + let tab_viewer = CustomTabs { + measurement_data: measurement_data.clone(), show_settings: false, show_settings_toggle: None, + log_filename: log_filename_clone, control_tx: control_tx_clone, app_state_rx: app_state_clone, hardware_state_rx: hardware_state_clone }; + // tab_viewer.on_tab_button(&mut "Single".to_string(), &egui::Response::clicked()); // Step 3: Construct App let app = App { tree: DockState::new(vec!["Single".to_string(), "Sweep".to_string(), "Shortcuts".to_string()]), tab_viewer, - tab_active, + // tab_active, log_tx, gui_logging_state: Arc::new(Mutex::new(LoggingStates::Idle)), - log_filename: format!("log_{}_single.csv", Local::now().format("%Y%m%d")), + log_filename, log_marker_modal: false, log_marker: String::new(), measurement_data, @@ -654,7 +681,7 @@ impl App { } pub fn toggle_start_stop(&self) { - match (self.tab_active, self.hardware_state_rx.borrow().running) { + match (self.app_state_rx.borrow().tab_active, self.hardware_state_rx.borrow().running) { (TabActive::Single, false) => { self.control_tx.try_send(ControlCommand::Start(Mode::Single)).unwrap(); }, @@ -724,7 +751,7 @@ impl eframe::App for App { if ui.add_enabled(is_connected, custom_toggle_widget(&mut logging_enabled)).changed() { let signal = match gui_logging_state { - LoggingStates::Idle => LoggingSignal::StartFileLogging(self.log_filename.clone()), + LoggingStates::Idle => LoggingSignal::StartFileLogging(self.log_filename.lock().unwrap().clone()), LoggingStates::Starting | LoggingStates::Logging => LoggingSignal::StopFileLogging, }; @@ -792,7 +819,7 @@ impl eframe::App for App { ui.add_enabled_ui(gui_logging_state == LoggingStates::Idle, |ui| { ui.label("Log filename:"); - TextEdit::singleline(&mut self.log_filename).desired_width(150.0).id(Id::new("file_name_field")).ui(ui); + TextEdit::singleline(&mut *self.log_filename.lock().unwrap()).desired_width(150.0).id(Id::new("file_name_field")).ui(ui); }); // Spacer to push the LED to the right @@ -837,48 +864,6 @@ impl eframe::App for App { .show_inside(ui, &mut self.tab_viewer); }); - // Do something when a tab is changed - if let Some((_, tab)) = self.tree.find_active_focused() { - match tab.as_str() { - "Single" => { - if self.tab_active != TabActive::Single { - self.tab_active = TabActive::Single; - self.control_tx.try_send(ControlCommand::Stop).unwrap(); - if *self.gui_logging_state.lock().unwrap() == LoggingStates::Logging { - self.log_tx.try_send(LoggingSignal::StopFileLogging).unwrap_or_else(|e| { - error!("Failed to send logging logging signal: {:?}", e); - }); - } - self.log_filename = format!("log_{}_single.csv", Local::now().format("%Y%m%d")); - info!("Switched to Single tab"); - } - } - "Sweep" => { - if self.tab_active != TabActive::Sweep { - self.tab_active = TabActive::Sweep; - self.control_tx.try_send(ControlCommand::Stop).unwrap(); - if *self.gui_logging_state.lock().unwrap() == LoggingStates::Logging { - self.log_tx.try_send(LoggingSignal::StopFileLogging).unwrap_or_else(|e| { - error!("Failed to send logging logging signal: {:?}", e); - }); - } - self.log_filename = format!("log_{}_sweep.csv", Local::now().format("%Y%m%d")); - info!("Switched to Sweep tab"); - } - } - "Shortcuts" => { - // if self.tab_active != TabActive::Shortcuts { - // self.tab_active = TabActive::Shortcuts; - // *self.on.lock().unwrap() = false; - // self.update_start_stop(); - // info!("Switched to Shortcuts tab"); - // } - } - _ => { - } - } - } - // CMD- or control-W to close window if ctx.input(|i| i.modifiers.cmd_ctrl_matches(Modifiers::COMMAND)) && ctx.input(|i| i.key_pressed(Key::W)) @@ -927,7 +912,7 @@ impl eframe::App for App { } }, LoggingStates::Idle => { - if let Err(e) = self.log_tx.try_send(LoggingSignal::StartFileLogging(self.log_filename.clone())) { + if let Err(e) = self.log_tx.try_send(LoggingSignal::StartFileLogging(self.log_filename.lock().unwrap().clone())) { error!("Failed to send logging signal: {:?}", e); } }, diff --git a/src/bin/main_gui.rs b/src/bin/main_gui.rs index eff2ce3..a1470bc 100644 --- a/src/bin/main_gui.rs +++ b/src/bin/main_gui.rs @@ -64,12 +64,12 @@ async fn main() { tokio::spawn(async move { log_data(log_rx, gui_logging_state_1).await }); // TCP command server in separate thread - // let rt = Runtime::new().expect("Unable to create Runtime"); + let rt = Runtime::new().expect("Unable to create Runtime"); - // let tcp_run_impedancemeter_tx = hardware_control_tx.clone(); - // std::thread::spawn(move || { - // rt.block_on(bioz_host_rs::tcp::tcp_command_server(tcp_run_impedancemeter_tx)); - // }); + let app_control_tx_clone = app_control_tx.clone(); + std::thread::spawn(move || { + rt.block_on(bioz_host_rs::tcp::tcp_command_server(app_control_tx_clone)); + }); // Execute the runtime in its own thread. let rt = Runtime::new().expect("Unable to create Runtime"); diff --git a/src/control.rs b/src/control.rs index 7a383c4..dadfb0a 100644 --- a/src/control.rs +++ b/src/control.rs @@ -1,6 +1,6 @@ use log::info; use tokio::sync::{mpsc, watch}; -use crate::{signals::StartStopSignal, state::{AppState, ControlCommand, HardwareConnected, HardwareState}}; +use crate::{app::TabActive, signals::StartStopSignal, state::{AppState, ControlCommand, HardwareConnected, HardwareState}}; use crate::state::Mode; @@ -32,29 +32,41 @@ pub async fn app_control_loop(mut app_control_rx: mpsc::Receiver state.sweep_points = sweep_points; info!("Sweep points changed to {:?}!", sweep_points); } + ControlCommand::ChangeActiveTab(tab_active) => { + state.tab_active = tab_active; + info!("Active tab changed to {:?}!", tab_active); + } ControlCommand::Start(mode) => { + if hardware_state_rx.borrow().hardware_connected == HardwareConnected::None { + info!("Cannot start measurement: No hardware connected!"); + continue; + } state.mode = mode; info!("Starting impedance hardware with mode {:?}...", mode); match (mode, hardware_state_rx.borrow().hardware_connected) { (Mode::Single, HardwareConnected::WithoutMultiplexer) => { info!("Starting single measurement..."); + state.tab_active = TabActive::Single; hardware_control_tx.try_send(StartStopSignal::StartSingle( state.single_frequency, state.lead_mode, None, state.dft_num)).unwrap(); } (Mode::Single, HardwareConnected::WithMultiplexer) => { info!("Starting single measurement..."); + state.tab_active = TabActive::Single; let electrode_config = state.electrode_settings.to_electrode_config(hardware_state_rx.borrow().hardware_connected, state.lead_mode); hardware_control_tx.try_send(StartStopSignal::StartSingle( state.single_frequency, state.lead_mode, electrode_config, state.dft_num)).unwrap(); } (Mode::Sweep, HardwareConnected::WithoutMultiplexer) => { info!("Starting sweep measurement..."); + state.tab_active = TabActive::Sweep; hardware_control_tx.try_send(StartStopSignal::StartSweep( state.lead_mode, None, state.sweep_points)).unwrap(); } (Mode::Sweep, HardwareConnected::WithMultiplexer) => { info!("Starting sweep measurement..."); + state.tab_active = TabActive::Sweep; let electrode_config = state.electrode_settings.to_electrode_config(hardware_state_rx.borrow().hardware_connected, state.lead_mode); hardware_control_tx.try_send(StartStopSignal::StartSweep( state.lead_mode, electrode_config, state.sweep_points)).unwrap(); @@ -65,6 +77,10 @@ pub async fn app_control_loop(mut app_control_rx: mpsc::Receiver } } ControlCommand::Stop => { + if hardware_state_rx.borrow().hardware_connected == HardwareConnected::None { + info!("Cannot stop measurement: No hardware connected!"); + continue; + } info!("Stopping impedance hardware..."); if let Err(e) = hardware_control_tx.try_send(StartStopSignal::Stop) { info!("Failed to send stop command: {:?}", e); diff --git a/src/state.rs b/src/state.rs index e16305d..9703d4c 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,8 +1,7 @@ -use std::default; use std::sync::{Arc, Mutex}; +use crate::app::TabActive; use crate::icd::{BioImpedanceLeadMode, IcdDftNum, SweepPoints, ElectrodeConfiguration, ElectrodeOptionsWithMultiplexer}; - use crate::logging::LoggingStates; use crate::plot::{TimeSeriesPlot, BodePlot}; @@ -45,6 +44,7 @@ pub enum ControlCommand { ChangeDftNum(IcdDftNum), ChangeElectrodeSettings(ElectrodeSettings), ChangeSweepPoints(SweepPoints), + ChangeActiveTab(TabActive), Start(Mode), Stop, } @@ -107,6 +107,7 @@ pub struct AppState { pub dft_num: IcdDftNum, pub electrode_settings: ElectrodeSettings, pub sweep_points: SweepPoints, + pub tab_active: TabActive, pub logging: LoggingStates, } @@ -119,6 +120,7 @@ impl Default for AppState { dft_num: IcdDftNum::Num2048, electrode_settings: ElectrodeSettings::new(), sweep_points: SweepPoints::Eighteen, + tab_active: TabActive::Single, logging: LoggingStates::Idle, } } diff --git a/src/tcp.rs b/src/tcp.rs index 80a6ae0..c4248b8 100644 --- a/src/tcp.rs +++ b/src/tcp.rs @@ -2,9 +2,9 @@ use tokio::net::TcpListener; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter}; use tokio::sync::mpsc::Sender; -use crate::signals::StartStopSignal; +use crate::state::ControlCommand; -pub async fn tcp_command_server(tx: Sender) { +pub async fn tcp_command_server(app_control_tx: Sender) { let listener = TcpListener::bind("127.0.0.1:12345").await.unwrap(); println!("TCP command server listening on 12345"); @@ -12,12 +12,12 @@ pub async fn tcp_command_server(tx: Sender) { let (socket, addr) = listener.accept().await.unwrap(); println!("Client connected: {:?}", addr); - let tx = tx.clone(); + let tx = app_control_tx.clone(); tokio::spawn(async move { let (reader, writer) = socket.into_split(); - let mut reader = BufReader::new(reader); + let reader = BufReader::new(reader); let mut writer = BufWriter::new(writer); let mut lines = reader.lines(); @@ -46,11 +46,69 @@ pub async fn tcp_command_server(tx: Sender) { } } -fn parse_command(line: &str) -> Option { +fn parse_command(line: &str) -> Option { let parts: Vec<&str> = line.split_whitespace().collect(); match parts.as_slice() { - ["STOP"] => Some(StartStopSignal::Stop), + // ["SET_FREQ", freq_str] => { + // if let Ok(freq) = freq_str.parse::() { + // Some(ControlCommand::SetFrequency(freq)) + // } else { + // None + // } + // } + // ["SET_LEAD_MODE", mode_str] => { + // match mode_str.to_uppercase().as_str() { + // "LEAD_OFF" => Some(ControlCommand::ChangeLeadMode(crate::state::LeadMode::LeadOff)), + // "LEAD_I" => Some(ControlCommand::ChangeLeadMode(crate::state::LeadMode::LeadI)), + // "LEAD_II" => Some(ControlCommand::ChangeLeadMode(crate::state::LeadMode::LeadII)), + // "LEAD_III" => Some(ControlCommand::ChangeLeadMode(crate::state::LeadMode::LeadIII)), + // _ => None, + // } + // } + // ["SET_DFT_NUM", dft_num_str] => { + // if let Ok(dft_num) = dft_num_str.parse::() { + // Some(ControlCommand::ChangeDftNum(IcdDftNum(dft_num))) + // } else { + // None + // } + // } + // ["SET_SWEEP_POINTS", sweep_points_str] => { + // if let Ok(sweep_points) = sweep_points_str.parse::() { + // Some(ControlCommand::ChangeSweepPoints(SweepPoints(sweep_points))) + // } else { + // None + // } + // } + // ["SET_ELECTRODE_CONFIG", config_str] => { + // // Example: "SET_ELECTRODE_CONFIG 1,0,1,0,1,0,1,0" + // let config_parts: Vec<&str> = config_str.split(',').collect(); + // if config_parts.len() == 8 { + // let mut config = [false; 8]; + // for (i, part) in config_parts.iter().enumerate() { + // if let Ok(val) = part.parse::() { + // config[i] = val != 0; + // } else { + // return None; + // } + // } + // Some(ControlCommand::ChangeElectrodeSettings(crate::state::ElectrodeSettings(config))) + // } else { + // None + // } + // } + ["START", mode_str] => { + match mode_str.to_uppercase().as_str() { + "SINGLE" => { + Some(ControlCommand::Start(crate::state::Mode::Single)) + }, + "SWEEP" => { + Some(ControlCommand::Start(crate::state::Mode::Sweep)) + }, + _ => None, + } + } + ["STOP"] => Some(ControlCommand::Stop), _ => { eprintln!("Unknown command: {}", line);