mirror of
https://github.com/hubaldv/bioz-host-rs.git
synced 2026-07-24 00:57:44 +00:00
Tabcontrol via controller.rs.
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
535
src/app.rs
535
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 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_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 egui_extras::{TableBuilder, Column};
|
||||||
|
|
||||||
use crate::state::{AppState, ControlCommand, HardwareConnected, HardwareState, MeasurementDataState, Mode};
|
use crate::state::{AppState, ControlCommand, HardwareConnected, HardwareState, MeasurementDataState, Mode};
|
||||||
|
|
||||||
use crate::logging::LoggingStates;
|
use crate::logging::LoggingStates;
|
||||||
use crate::plot::{TimeSeriesPlot, BodePlot};
|
use crate::signals::LoggingSignal;
|
||||||
|
use crate::icd::{BioImpedanceLeadMode, IcdDftNum, SweepPoints, ElectrodeOptionsWithMultiplexer};
|
||||||
use crate::signals::{LoggingSignal, StartStopSignal};
|
|
||||||
|
|
||||||
use crate::icd::{BioImpedanceLeadMode, IcdDftNum, SweepPoints, ElectrodeConfiguration, ElectrodeOptionsWithMultiplexer};
|
|
||||||
|
|
||||||
const LEAD_MODES: [BioImpedanceLeadMode; 2] = [
|
const LEAD_MODES: [BioImpedanceLeadMode; 2] = [
|
||||||
BioImpedanceLeadMode::TwoLead,
|
BioImpedanceLeadMode::TwoLead,
|
||||||
@@ -46,7 +42,7 @@ const SWEEP_POINTS_VARIANTS: [SweepPoints; 2] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
#[derive(Clone, Copy,Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy,Debug, PartialEq, Eq)]
|
||||||
enum TabActive {
|
pub enum TabActive {
|
||||||
Single,
|
Single,
|
||||||
Sweep,
|
Sweep,
|
||||||
Shortcuts,
|
Shortcuts,
|
||||||
@@ -54,11 +50,11 @@ enum TabActive {
|
|||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
tree: DockState<String>,
|
tree: DockState<String>,
|
||||||
tab_viewer: TabViewer,
|
tab_viewer: CustomTabs,
|
||||||
tab_active: TabActive,
|
// tab_active: TabActive,
|
||||||
log_tx: mpsc::Sender<LoggingSignal>,
|
log_tx: mpsc::Sender<LoggingSignal>,
|
||||||
pub gui_logging_state: Arc<Mutex<LoggingStates>>,
|
pub gui_logging_state: Arc<Mutex<LoggingStates>>,
|
||||||
log_filename: String,
|
log_filename: Arc<Mutex<String>>,
|
||||||
log_marker_modal: bool,
|
log_marker_modal: bool,
|
||||||
log_marker: String,
|
log_marker: String,
|
||||||
measurement_data: Arc<MeasurementDataState>,
|
measurement_data: Arc<MeasurementDataState>,
|
||||||
@@ -67,9 +63,10 @@ pub struct App {
|
|||||||
hardware_state_rx: sync::watch::Receiver<HardwareState>
|
hardware_state_rx: sync::watch::Receiver<HardwareState>
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TabViewer {
|
struct CustomTabs {
|
||||||
show_settings: bool,
|
show_settings: bool,
|
||||||
show_settings_toggle: Option<bool>,
|
show_settings_toggle: Option<bool>,
|
||||||
|
log_filename: Arc<Mutex<String>>,
|
||||||
measurement_data: Arc<MeasurementDataState>,
|
measurement_data: Arc<MeasurementDataState>,
|
||||||
control_tx: mpsc::Sender<ControlCommand>,
|
control_tx: mpsc::Sender<ControlCommand>,
|
||||||
app_state_rx: sync::watch::Receiver<AppState>,
|
app_state_rx: sync::watch::Receiver<AppState>,
|
||||||
@@ -139,113 +136,112 @@ fn render_four_lead<T: ElectrodeOption>(
|
|||||||
changed
|
changed
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TabViewer {
|
impl CustomTabs {
|
||||||
fn single_tab(&mut self, ui: &mut egui::Ui) {
|
fn single_tab(&mut self, ui: &mut egui::Ui) {
|
||||||
egui::Frame::default().inner_margin(5).show(ui, |ui| {
|
egui::Frame::default().inner_margin(5).show(ui, |ui| {
|
||||||
let settings = CollapsingHeader::new("Settings")
|
let settings = CollapsingHeader::new("Settings")
|
||||||
.open(self.show_settings_toggle)
|
.open(self.show_settings_toggle)
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
if let running = self.hardware_state_rx.borrow().running {
|
let running = self.hardware_state_rx.borrow().running;
|
||||||
ui.add_enabled_ui(!running, |ui| {
|
ui.add_enabled_ui(!running, |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Lead Mode:");
|
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
|
// Map current lead mode to index
|
||||||
let mut index = LEAD_MODES
|
let mut index = LEAD_MODES
|
||||||
.iter()
|
.iter()
|
||||||
.position(|&m| m == lead_mode)
|
.position(|&m| m == lead_mode)
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
ComboBox::from_id_salt("LeadMode")
|
ComboBox::from_id_salt("LeadMode")
|
||||||
.width(60.0)
|
.width(60.0)
|
||||||
.show_index(ui, &mut index, LEAD_MODES.len(), |i| {
|
.show_index(ui, &mut index, LEAD_MODES.len(), |i| {
|
||||||
match LEAD_MODES[i] {
|
match LEAD_MODES[i] {
|
||||||
BioImpedanceLeadMode::TwoLead => "2-Lead",
|
BioImpedanceLeadMode::TwoLead => "2-Lead",
|
||||||
BioImpedanceLeadMode::FourLead => "4-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)"));
|
|
||||||
}
|
}
|
||||||
(HardwareConnected::WithoutMultiplexer, BioImpedanceLeadMode::FourLead) => {
|
.to_string()
|
||||||
ui.label(format!("Drive: I+ (CE0), I- (AIN1) | Sense: V+ (AIN2), V- (AIN3)"));
|
});
|
||||||
}
|
|
||||||
(HardwareConnected::WithMultiplexer, BioImpedanceLeadMode::TwoLead) => {
|
// Update lead mode if changed
|
||||||
if render_two_lead(ui, &mut settings.with_multiplexer_2_lead) {
|
if lead_mode != LEAD_MODES[index] {
|
||||||
self.control_tx.try_send(ControlCommand::ChangeElectrodeSettings(settings)).unwrap();
|
lead_mode = LEAD_MODES[index];
|
||||||
}
|
self.control_tx.try_send(ControlCommand::ChangeLeadMode(lead_mode)).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.horizontal(|ui| {
|
// Show lead configuration
|
||||||
ui.label("Single Frequency:");
|
ui.label("Lead Configuration:");
|
||||||
let mut freq = self.app_state_rx.borrow().single_frequency;
|
let hardware_connected = self.hardware_state_rx.borrow().hardware_connected;
|
||||||
let response = ui.add(DragValue::new(&mut freq).speed(0.1));
|
let lead_mode = self.app_state_rx.borrow().lead_mode;
|
||||||
if response.changed() {
|
|
||||||
self.control_tx.try_send(ControlCommand::SetFrequency(freq)).unwrap();
|
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");
|
(HardwareConnected::WithoutMultiplexer, BioImpedanceLeadMode::FourLead) => {
|
||||||
});
|
ui.label(format!("Drive: I+ (CE0), I- (AIN1) | Sense: V+ (AIN2), V- (AIN3)"));
|
||||||
ui.horizontal(|ui| {
|
}
|
||||||
ui.label("ADC samples per DFT:");
|
(HardwareConnected::WithMultiplexer, BioImpedanceLeadMode::TwoLead) => {
|
||||||
let mut dft_num = self.app_state_rx.borrow().dft_num;
|
if render_two_lead(ui, &mut settings.with_multiplexer_2_lead) {
|
||||||
let mut index = DFTNUM_VARIANTS.iter().position(|&x| x == dft_num).unwrap_or(0);
|
self.control_tx.try_send(ControlCommand::ChangeElectrodeSettings(settings)).unwrap();
|
||||||
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::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;
|
self.show_settings_toggle = None;
|
||||||
@@ -303,137 +299,136 @@ impl TabViewer {
|
|||||||
let settings = CollapsingHeader::new("Settings")
|
let settings = CollapsingHeader::new("Settings")
|
||||||
.open(self.show_settings_toggle)
|
.open(self.show_settings_toggle)
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
if let running = self.hardware_state_rx.borrow().running {
|
let running = self.hardware_state_rx.borrow().running;
|
||||||
ui.add_enabled_ui(!running, |ui| {
|
ui.add_enabled_ui(!running, |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Lead Mode:");
|
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
|
// Map current lead mode to index
|
||||||
let mut index = LEAD_MODES
|
let mut index = LEAD_MODES
|
||||||
.iter()
|
.iter()
|
||||||
.position(|&m| m == lead_mode)
|
.position(|&m| m == lead_mode)
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
ComboBox::from_id_salt("LeadMode")
|
ComboBox::from_id_salt("LeadMode")
|
||||||
.width(60.0)
|
.width(60.0)
|
||||||
.show_index(ui, &mut index, LEAD_MODES.len(), |i| {
|
.show_index(ui, &mut index, LEAD_MODES.len(), |i| {
|
||||||
match LEAD_MODES[i] {
|
match LEAD_MODES[i] {
|
||||||
BioImpedanceLeadMode::TwoLead => "2-Lead",
|
BioImpedanceLeadMode::TwoLead => "2-Lead",
|
||||||
BioImpedanceLeadMode::FourLead => "4-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)"));
|
|
||||||
}
|
}
|
||||||
(HardwareConnected::WithoutMultiplexer, BioImpedanceLeadMode::FourLead) => {
|
.to_string()
|
||||||
ui.label("Drive: I+ (CE0), I- (AIN1) | Sense: V+ (AIN2), V- (AIN3)");
|
});
|
||||||
}
|
|
||||||
(HardwareConnected::WithMultiplexer, BioImpedanceLeadMode::TwoLead) => {
|
// Update lead mode if changed
|
||||||
if render_two_lead(ui, &mut settings.with_multiplexer_2_lead) {
|
if lead_mode != LEAD_MODES[index] {
|
||||||
self.control_tx.try_send(ControlCommand::ChangeElectrodeSettings(settings)).unwrap();
|
lead_mode = LEAD_MODES[index];
|
||||||
}
|
self.control_tx.try_send(ControlCommand::ChangeLeadMode(lead_mode)).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.horizontal(|ui| {
|
// Show lead configuration
|
||||||
ui.label("Measurement Points:");
|
ui.label("Lead Configuration:");
|
||||||
let mut measurement_points = self.app_state_rx.borrow().sweep_points;
|
let hardware_connected = self.hardware_state_rx.borrow().hardware_connected;
|
||||||
let mut index = SWEEP_POINTS_VARIANTS.iter().position(|&x| x == measurement_points).unwrap_or(0);
|
let lead_mode = self.app_state_rx.borrow().lead_mode;
|
||||||
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 {
|
let mut settings = self.app_state_rx.borrow().electrode_settings;
|
||||||
if freq >= 1_000 {
|
match (hardware_connected, lead_mode) {
|
||||||
// kHz
|
(HardwareConnected::WithoutMultiplexer, BioImpedanceLeadMode::TwoLead) => {
|
||||||
if freq % 1_000 == 0 {
|
ui.label(format!("Drive/sense: Electrode+ (CE0), Electrode- (AIN1)"));
|
||||||
format!("{}k", (freq / 1_000) as u64)
|
}
|
||||||
} else {
|
(HardwareConnected::WithoutMultiplexer, BioImpedanceLeadMode::FourLead) => {
|
||||||
format!("{:.1}k", freq as f32 / 1_000.0)
|
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 {
|
} else {
|
||||||
// Hz
|
format!("{:.1}k", freq as f32 / 1_000.0)
|
||||||
if freq % 1 == 0 {
|
}
|
||||||
format!("{}", freq as u64)
|
} else {
|
||||||
} else {
|
// Hz
|
||||||
format!("{:.1}", freq)
|
if freq % 1 == 0 {
|
||||||
}
|
format!("{}", freq as u64)
|
||||||
|
} else {
|
||||||
|
format!("{:.1}", freq)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TableBuilder::new(ui)
|
TableBuilder::new(ui)
|
||||||
.column(Column::auto())
|
.column(Column::auto())
|
||||||
.columns(Column::remainder().at_most(30.0), freq.len())
|
.columns(Column::remainder().at_most(30.0), freq.len())
|
||||||
.header(10.0, |mut header| {
|
.header(10.0, |mut header| {
|
||||||
header.col(|ui| {
|
header.col(|ui| {
|
||||||
ui.label("Frequency [Hz]:");
|
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");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
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;
|
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;
|
type Tab = String;
|
||||||
|
|
||||||
fn title(&mut self, tab: &mut Self::Tab) -> eframe::egui::WidgetText {
|
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 {
|
impl App {
|
||||||
@@ -601,28 +622,34 @@ impl App {
|
|||||||
app_state_rx: watch::Receiver<AppState>,
|
app_state_rx: watch::Receiver<AppState>,
|
||||||
hardware_state_rx: watch::Receiver<HardwareState>) -> Self {
|
hardware_state_rx: watch::Receiver<HardwareState>) -> Self {
|
||||||
// Step 1: Initialize shared fields first
|
// 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
|
// Step 2: Now we can initialize tab_viewer
|
||||||
let control_tx_clone = control_tx.clone();
|
let control_tx_clone = control_tx.clone();
|
||||||
let app_state_clone = app_state_rx.clone();
|
let app_state_clone = app_state_rx.clone();
|
||||||
let hardware_state_clone = hardware_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: false,
|
||||||
show_settings_toggle: None,
|
show_settings_toggle: None,
|
||||||
|
log_filename: log_filename_clone,
|
||||||
control_tx: control_tx_clone,
|
control_tx: control_tx_clone,
|
||||||
app_state_rx: app_state_clone,
|
app_state_rx: app_state_clone,
|
||||||
hardware_state_rx: hardware_state_clone
|
hardware_state_rx: hardware_state_clone
|
||||||
};
|
};
|
||||||
|
// tab_viewer.on_tab_button(&mut "Single".to_string(), &egui::Response::clicked());
|
||||||
|
|
||||||
// Step 3: Construct App
|
// Step 3: Construct App
|
||||||
let app = App {
|
let app = App {
|
||||||
tree: DockState::new(vec!["Single".to_string(), "Sweep".to_string(), "Shortcuts".to_string()]),
|
tree: DockState::new(vec!["Single".to_string(), "Sweep".to_string(), "Shortcuts".to_string()]),
|
||||||
tab_viewer,
|
tab_viewer,
|
||||||
tab_active,
|
// tab_active,
|
||||||
log_tx,
|
log_tx,
|
||||||
gui_logging_state: Arc::new(Mutex::new(LoggingStates::Idle)),
|
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_modal: false,
|
||||||
log_marker: String::new(),
|
log_marker: String::new(),
|
||||||
measurement_data,
|
measurement_data,
|
||||||
@@ -654,7 +681,7 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle_start_stop(&self) {
|
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) => {
|
(TabActive::Single, false) => {
|
||||||
self.control_tx.try_send(ControlCommand::Start(Mode::Single)).unwrap();
|
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() {
|
if ui.add_enabled(is_connected, custom_toggle_widget(&mut logging_enabled)).changed() {
|
||||||
let signal = match gui_logging_state {
|
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,
|
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.add_enabled_ui(gui_logging_state == LoggingStates::Idle, |ui| {
|
||||||
ui.label("Log filename:");
|
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
|
// Spacer to push the LED to the right
|
||||||
@@ -837,48 +864,6 @@ impl eframe::App for App {
|
|||||||
.show_inside(ui, &mut self.tab_viewer);
|
.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
|
// CMD- or control-W to close window
|
||||||
if ctx.input(|i| i.modifiers.cmd_ctrl_matches(Modifiers::COMMAND))
|
if ctx.input(|i| i.modifiers.cmd_ctrl_matches(Modifiers::COMMAND))
|
||||||
&& ctx.input(|i| i.key_pressed(Key::W))
|
&& ctx.input(|i| i.key_pressed(Key::W))
|
||||||
@@ -927,7 +912,7 @@ impl eframe::App for App {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
LoggingStates::Idle => {
|
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);
|
error!("Failed to send logging signal: {:?}", e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -64,12 +64,12 @@ async fn main() {
|
|||||||
tokio::spawn(async move { log_data(log_rx, gui_logging_state_1).await });
|
tokio::spawn(async move { log_data(log_rx, gui_logging_state_1).await });
|
||||||
|
|
||||||
// TCP command server in separate thread
|
// 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();
|
let app_control_tx_clone = app_control_tx.clone();
|
||||||
// std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
// rt.block_on(bioz_host_rs::tcp::tcp_command_server(tcp_run_impedancemeter_tx));
|
rt.block_on(bioz_host_rs::tcp::tcp_command_server(app_control_tx_clone));
|
||||||
// });
|
});
|
||||||
|
|
||||||
// Execute the runtime in its own thread.
|
// Execute the runtime in its own thread.
|
||||||
let rt = Runtime::new().expect("Unable to create Runtime");
|
let rt = Runtime::new().expect("Unable to create Runtime");
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use log::info;
|
use log::info;
|
||||||
use tokio::sync::{mpsc, watch};
|
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;
|
use crate::state::Mode;
|
||||||
|
|
||||||
@@ -32,29 +32,41 @@ pub async fn app_control_loop(mut app_control_rx: mpsc::Receiver<ControlCommand>
|
|||||||
state.sweep_points = sweep_points;
|
state.sweep_points = sweep_points;
|
||||||
info!("Sweep points changed to {:?}!", 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) => {
|
ControlCommand::Start(mode) => {
|
||||||
|
if hardware_state_rx.borrow().hardware_connected == HardwareConnected::None {
|
||||||
|
info!("Cannot start measurement: No hardware connected!");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
state.mode = mode;
|
state.mode = mode;
|
||||||
info!("Starting impedance hardware with mode {:?}...", mode);
|
info!("Starting impedance hardware with mode {:?}...", mode);
|
||||||
|
|
||||||
match (mode, hardware_state_rx.borrow().hardware_connected) {
|
match (mode, hardware_state_rx.borrow().hardware_connected) {
|
||||||
(Mode::Single, HardwareConnected::WithoutMultiplexer) => {
|
(Mode::Single, HardwareConnected::WithoutMultiplexer) => {
|
||||||
info!("Starting single measurement...");
|
info!("Starting single measurement...");
|
||||||
|
state.tab_active = TabActive::Single;
|
||||||
hardware_control_tx.try_send(StartStopSignal::StartSingle(
|
hardware_control_tx.try_send(StartStopSignal::StartSingle(
|
||||||
state.single_frequency, state.lead_mode, None, state.dft_num)).unwrap();
|
state.single_frequency, state.lead_mode, None, state.dft_num)).unwrap();
|
||||||
}
|
}
|
||||||
(Mode::Single, HardwareConnected::WithMultiplexer) => {
|
(Mode::Single, HardwareConnected::WithMultiplexer) => {
|
||||||
info!("Starting single measurement...");
|
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);
|
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(
|
hardware_control_tx.try_send(StartStopSignal::StartSingle(
|
||||||
state.single_frequency, state.lead_mode, electrode_config, state.dft_num)).unwrap();
|
state.single_frequency, state.lead_mode, electrode_config, state.dft_num)).unwrap();
|
||||||
}
|
}
|
||||||
(Mode::Sweep, HardwareConnected::WithoutMultiplexer) => {
|
(Mode::Sweep, HardwareConnected::WithoutMultiplexer) => {
|
||||||
info!("Starting sweep measurement...");
|
info!("Starting sweep measurement...");
|
||||||
|
state.tab_active = TabActive::Sweep;
|
||||||
hardware_control_tx.try_send(StartStopSignal::StartSweep(
|
hardware_control_tx.try_send(StartStopSignal::StartSweep(
|
||||||
state.lead_mode, None, state.sweep_points)).unwrap();
|
state.lead_mode, None, state.sweep_points)).unwrap();
|
||||||
}
|
}
|
||||||
(Mode::Sweep, HardwareConnected::WithMultiplexer) => {
|
(Mode::Sweep, HardwareConnected::WithMultiplexer) => {
|
||||||
info!("Starting sweep measurement...");
|
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);
|
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(
|
hardware_control_tx.try_send(StartStopSignal::StartSweep(
|
||||||
state.lead_mode, electrode_config, state.sweep_points)).unwrap();
|
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>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ControlCommand::Stop => {
|
ControlCommand::Stop => {
|
||||||
|
if hardware_state_rx.borrow().hardware_connected == HardwareConnected::None {
|
||||||
|
info!("Cannot stop measurement: No hardware connected!");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
info!("Stopping impedance hardware...");
|
info!("Stopping impedance hardware...");
|
||||||
if let Err(e) = hardware_control_tx.try_send(StartStopSignal::Stop) {
|
if let Err(e) = hardware_control_tx.try_send(StartStopSignal::Stop) {
|
||||||
info!("Failed to send stop command: {:?}", e);
|
info!("Failed to send stop command: {:?}", e);
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
use std::default;
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use crate::app::TabActive;
|
||||||
use crate::icd::{BioImpedanceLeadMode, IcdDftNum, SweepPoints, ElectrodeConfiguration, ElectrodeOptionsWithMultiplexer};
|
use crate::icd::{BioImpedanceLeadMode, IcdDftNum, SweepPoints, ElectrodeConfiguration, ElectrodeOptionsWithMultiplexer};
|
||||||
|
|
||||||
use crate::logging::LoggingStates;
|
use crate::logging::LoggingStates;
|
||||||
use crate::plot::{TimeSeriesPlot, BodePlot};
|
use crate::plot::{TimeSeriesPlot, BodePlot};
|
||||||
|
|
||||||
@@ -45,6 +44,7 @@ pub enum ControlCommand {
|
|||||||
ChangeDftNum(IcdDftNum),
|
ChangeDftNum(IcdDftNum),
|
||||||
ChangeElectrodeSettings(ElectrodeSettings),
|
ChangeElectrodeSettings(ElectrodeSettings),
|
||||||
ChangeSweepPoints(SweepPoints),
|
ChangeSweepPoints(SweepPoints),
|
||||||
|
ChangeActiveTab(TabActive),
|
||||||
Start(Mode),
|
Start(Mode),
|
||||||
Stop,
|
Stop,
|
||||||
}
|
}
|
||||||
@@ -107,6 +107,7 @@ pub struct AppState {
|
|||||||
pub dft_num: IcdDftNum,
|
pub dft_num: IcdDftNum,
|
||||||
pub electrode_settings: ElectrodeSettings,
|
pub electrode_settings: ElectrodeSettings,
|
||||||
pub sweep_points: SweepPoints,
|
pub sweep_points: SweepPoints,
|
||||||
|
pub tab_active: TabActive,
|
||||||
pub logging: LoggingStates,
|
pub logging: LoggingStates,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,6 +120,7 @@ impl Default for AppState {
|
|||||||
dft_num: IcdDftNum::Num2048,
|
dft_num: IcdDftNum::Num2048,
|
||||||
electrode_settings: ElectrodeSettings::new(),
|
electrode_settings: ElectrodeSettings::new(),
|
||||||
sweep_points: SweepPoints::Eighteen,
|
sweep_points: SweepPoints::Eighteen,
|
||||||
|
tab_active: TabActive::Single,
|
||||||
logging: LoggingStates::Idle,
|
logging: LoggingStates::Idle,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
70
src/tcp.rs
70
src/tcp.rs
@@ -2,9 +2,9 @@ use tokio::net::TcpListener;
|
|||||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter};
|
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter};
|
||||||
use tokio::sync::mpsc::Sender;
|
use tokio::sync::mpsc::Sender;
|
||||||
|
|
||||||
use crate::signals::StartStopSignal;
|
use crate::state::ControlCommand;
|
||||||
|
|
||||||
pub async fn tcp_command_server(tx: Sender<StartStopSignal>) {
|
pub async fn tcp_command_server(app_control_tx: Sender<ControlCommand>) {
|
||||||
let listener = TcpListener::bind("127.0.0.1:12345").await.unwrap();
|
let listener = TcpListener::bind("127.0.0.1:12345").await.unwrap();
|
||||||
println!("TCP command server listening on 12345");
|
println!("TCP command server listening on 12345");
|
||||||
|
|
||||||
@@ -12,12 +12,12 @@ pub async fn tcp_command_server(tx: Sender<StartStopSignal>) {
|
|||||||
let (socket, addr) = listener.accept().await.unwrap();
|
let (socket, addr) = listener.accept().await.unwrap();
|
||||||
println!("Client connected: {:?}", addr);
|
println!("Client connected: {:?}", addr);
|
||||||
|
|
||||||
let tx = tx.clone();
|
let tx = app_control_tx.clone();
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let (reader, writer) = socket.into_split();
|
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 writer = BufWriter::new(writer);
|
||||||
let mut lines = reader.lines();
|
let mut lines = reader.lines();
|
||||||
|
|
||||||
@@ -46,11 +46,69 @@ pub async fn tcp_command_server(tx: Sender<StartStopSignal>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_command(line: &str) -> Option<StartStopSignal> {
|
fn parse_command(line: &str) -> Option<ControlCommand> {
|
||||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||||
|
|
||||||
match parts.as_slice() {
|
match parts.as_slice() {
|
||||||
["STOP"] => Some(StartStopSignal::Stop),
|
// ["SET_FREQ", freq_str] => {
|
||||||
|
// if let Ok(freq) = freq_str.parse::<f32>() {
|
||||||
|
// 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::<u32>() {
|
||||||
|
// Some(ControlCommand::ChangeDftNum(IcdDftNum(dft_num)))
|
||||||
|
// } else {
|
||||||
|
// None
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ["SET_SWEEP_POINTS", sweep_points_str] => {
|
||||||
|
// if let Ok(sweep_points) = sweep_points_str.parse::<u32>() {
|
||||||
|
// 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::<u8>() {
|
||||||
|
// 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);
|
eprintln!("Unknown command: {}", line);
|
||||||
|
|||||||
Reference in New Issue
Block a user