mirror of
https://github.com/hubaldv/bioz-host-rs.git
synced 2026-07-23 16:47:43 +00:00
Route start/stop via middle layer.
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
91
src/app.rs
91
src/app.rs
@@ -19,7 +19,7 @@ use egui_plot::{Corner, GridInput, GridMark, Legend, Line, Plot, PlotPoint, Poin
|
|||||||
use egui_dock::{DockArea, DockState, Style};
|
use egui_dock::{DockArea, DockState, Style};
|
||||||
use egui_extras::{TableBuilder, Column};
|
use egui_extras::{TableBuilder, Column};
|
||||||
|
|
||||||
use crate::state::{AppState, ControlCommand, HardwareConnected, HardwareState, MeasurementDataState};
|
use crate::state::{AppState, ControlCommand, HardwareConnected, HardwareState, MeasurementDataState, Mode};
|
||||||
|
|
||||||
use crate::logging::LoggingStates;
|
use crate::logging::LoggingStates;
|
||||||
use crate::plot::{TimeSeriesPlot, BodePlot};
|
use crate::plot::{TimeSeriesPlot, BodePlot};
|
||||||
@@ -56,9 +56,7 @@ pub struct App {
|
|||||||
tree: DockState<String>,
|
tree: DockState<String>,
|
||||||
tab_viewer: TabViewer,
|
tab_viewer: TabViewer,
|
||||||
tab_active: TabActive,
|
tab_active: TabActive,
|
||||||
run_impedancemeter_tx: mpsc::Sender<StartStopSignal>,
|
|
||||||
log_tx: mpsc::Sender<LoggingSignal>,
|
log_tx: mpsc::Sender<LoggingSignal>,
|
||||||
pub on: Arc<Mutex<bool>>,
|
|
||||||
pub gui_logging_state: Arc<Mutex<LoggingStates>>,
|
pub gui_logging_state: Arc<Mutex<LoggingStates>>,
|
||||||
log_filename: String,
|
log_filename: String,
|
||||||
log_marker_modal: bool,
|
log_marker_modal: bool,
|
||||||
@@ -70,7 +68,6 @@ pub struct App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct TabViewer {
|
struct TabViewer {
|
||||||
on: Arc<Mutex<bool>>,
|
|
||||||
show_settings: bool,
|
show_settings: bool,
|
||||||
show_settings_toggle: Option<bool>,
|
show_settings_toggle: Option<bool>,
|
||||||
measurement_data: Arc<MeasurementDataState>,
|
measurement_data: Arc<MeasurementDataState>,
|
||||||
@@ -148,8 +145,8 @@ 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 Ok(on) = self.on.lock() {
|
if let running = self.hardware_state_rx.borrow().running {
|
||||||
ui.add_enabled_ui(!*on, |ui| {
|
ui.add_enabled_ui(!running, |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Lead Mode:");
|
ui.label("Lead Mode:");
|
||||||
|
|
||||||
@@ -180,7 +177,7 @@ impl TabViewer {
|
|||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
// Show lead configuration
|
// Show lead configuration
|
||||||
ui.label("Lead Configuration:");
|
ui.label("Lead Configuration:");
|
||||||
let hardware_connected = self.hardware_state_rx.borrow().connected;
|
let hardware_connected = self.hardware_state_rx.borrow().hardware_connected;
|
||||||
let lead_mode = self.app_state_rx.borrow().lead_mode;
|
let lead_mode = self.app_state_rx.borrow().lead_mode;
|
||||||
|
|
||||||
let mut settings = self.app_state_rx.borrow().electrode_settings;
|
let mut settings = self.app_state_rx.borrow().electrode_settings;
|
||||||
@@ -206,7 +203,7 @@ impl TabViewer {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
ui.add_enabled_ui(!*on, |ui| {
|
ui.add_enabled_ui(!running, |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Single Frequency:");
|
ui.label("Single Frequency:");
|
||||||
let mut freq = self.app_state_rx.borrow().single_frequency;
|
let mut freq = self.app_state_rx.borrow().single_frequency;
|
||||||
@@ -232,10 +229,10 @@ impl TabViewer {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
ui.add_enabled_ui(*on, |ui| {
|
ui.add_enabled_ui(running, |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Periods per DFT:");
|
ui.label("Periods per DFT:");
|
||||||
match (*on, self.hardware_state_rx.borrow().periods_per_dft) {
|
match (running, self.hardware_state_rx.borrow().periods_per_dft) {
|
||||||
(true, Some(periods)) => {
|
(true, Some(periods)) => {
|
||||||
ui.add(Label::new(format!("{:.2}", periods)));
|
ui.add(Label::new(format!("{:.2}", periods)));
|
||||||
},
|
},
|
||||||
@@ -306,8 +303,8 @@ 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 Ok(on) = self.on.lock() {
|
if let running = self.hardware_state_rx.borrow().running {
|
||||||
ui.add_enabled_ui(!*on, |ui| {
|
ui.add_enabled_ui(!running, |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Lead Mode:");
|
ui.label("Lead Mode:");
|
||||||
|
|
||||||
@@ -338,7 +335,7 @@ impl TabViewer {
|
|||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
// Show lead configuration
|
// Show lead configuration
|
||||||
ui.label("Lead Configuration:");
|
ui.label("Lead Configuration:");
|
||||||
let hardware_connected = self.hardware_state_rx.borrow().connected;
|
let hardware_connected = self.hardware_state_rx.borrow().hardware_connected;
|
||||||
let lead_mode = self.app_state_rx.borrow().lead_mode;
|
let lead_mode = self.app_state_rx.borrow().lead_mode;
|
||||||
|
|
||||||
let mut settings = self.app_state_rx.borrow().electrode_settings;
|
let mut settings = self.app_state_rx.borrow().electrode_settings;
|
||||||
@@ -363,7 +360,7 @@ impl TabViewer {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
ui.add_enabled_ui(!*on, |ui| {
|
ui.add_enabled_ui(!running, |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Measurement Points:");
|
ui.label("Measurement Points:");
|
||||||
let mut measurement_points = self.app_state_rx.borrow().sweep_points;
|
let mut measurement_points = self.app_state_rx.borrow().sweep_points;
|
||||||
@@ -381,7 +378,7 @@ impl TabViewer {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
ui.add_enabled_ui(*on, |ui| {
|
ui.add_enabled_ui(running, |ui| {
|
||||||
let (freq, periods_per_dft_vec) = self.hardware_state_rx.borrow().periods_per_dft_sweep.clone();
|
let (freq, periods_per_dft_vec) = self.hardware_state_rx.borrow().periods_per_dft_sweep.clone();
|
||||||
|
|
||||||
fn format_frequency(freq: u32) -> String {
|
fn format_frequency(freq: u32) -> String {
|
||||||
@@ -598,14 +595,12 @@ impl egui_dock::TabViewer for TabViewer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn new(run_impedancemeter_tx: mpsc::Sender<StartStopSignal>,
|
pub fn new(log_tx: mpsc::Sender<LoggingSignal>,
|
||||||
log_tx: mpsc::Sender<LoggingSignal>,
|
|
||||||
measurement_data: Arc<MeasurementDataState>,
|
measurement_data: Arc<MeasurementDataState>,
|
||||||
control_tx: mpsc::Sender<ControlCommand>,
|
control_tx: mpsc::Sender<ControlCommand>,
|
||||||
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 on = Arc::new(Mutex::new(true));
|
|
||||||
let tab_active = TabActive::Single;
|
let tab_active = TabActive::Single;
|
||||||
|
|
||||||
// Step 2: Now we can initialize tab_viewer
|
// Step 2: Now we can initialize tab_viewer
|
||||||
@@ -613,7 +608,6 @@ impl App {
|
|||||||
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 tab_viewer = TabViewer { measurement_data: measurement_data.clone(),
|
||||||
on: on.clone(),
|
|
||||||
show_settings: false,
|
show_settings: false,
|
||||||
show_settings_toggle: None,
|
show_settings_toggle: None,
|
||||||
control_tx: control_tx_clone,
|
control_tx: control_tx_clone,
|
||||||
@@ -626,9 +620,7 @@ impl 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,
|
||||||
run_impedancemeter_tx,
|
|
||||||
log_tx,
|
log_tx,
|
||||||
on,
|
|
||||||
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: format!("log_{}_single.csv", Local::now().format("%Y%m%d")),
|
||||||
log_marker_modal: false,
|
log_marker_modal: false,
|
||||||
@@ -657,31 +649,20 @@ impl App {
|
|||||||
// app.bode_plot.lock().unwrap().update_magnitudes(MeasurementPointSet::Eighteen, magnitudes);
|
// app.bode_plot.lock().unwrap().update_magnitudes(MeasurementPointSet::Eighteen, magnitudes);
|
||||||
// app.bode_plot.lock().unwrap().update_phases(MeasurementPointSet::Eighteen, phases);
|
// app.bode_plot.lock().unwrap().update_phases(MeasurementPointSet::Eighteen, phases);
|
||||||
|
|
||||||
app.update_start_stop();
|
app.toggle_start_stop();
|
||||||
app
|
app
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_start_stop(&self) {
|
pub fn toggle_start_stop(&self) {
|
||||||
match (self.tab_active, *self.on.lock().unwrap()) {
|
match (self.tab_active, self.hardware_state_rx.borrow().running) {
|
||||||
(TabActive::Single, true) => {
|
(TabActive::Single, false) => {
|
||||||
let lead_mode = self.app_state_rx.borrow().lead_mode;
|
self.control_tx.try_send(ControlCommand::Start(Mode::Single)).unwrap();
|
||||||
let electrode_config = self.app_state_rx.borrow().electrode_settings.to_electrode_config(self.hardware_state_rx.borrow().connected, lead_mode);
|
|
||||||
if let Err(e) = self.run_impedancemeter_tx.try_send(
|
|
||||||
StartStopSignal::StartSingle(self.app_state_rx.borrow().single_frequency, lead_mode, electrode_config, self.app_state_rx.borrow().dft_num)) {
|
|
||||||
error!("Failed to send start command: {:?}", e);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
(TabActive::Sweep, true) => {
|
(TabActive::Sweep, false) => {
|
||||||
let lead_mode = self.app_state_rx.borrow().lead_mode;
|
self.control_tx.try_send(ControlCommand::Start(Mode::Sweep)).unwrap();
|
||||||
let electrode_config = self.app_state_rx.borrow().electrode_settings.to_electrode_config(self.hardware_state_rx.borrow().connected, lead_mode);
|
|
||||||
if let Err(e) = self.run_impedancemeter_tx.try_send(StartStopSignal::StartSweep(lead_mode, electrode_config, self.app_state_rx.borrow().sweep_points)) {
|
|
||||||
error!("Failed to send start command: {:?}", e);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
(_, false) => {
|
(_, true) => {
|
||||||
if let Err(e) = self.run_impedancemeter_tx.try_send(StartStopSignal::Stop) {
|
self.control_tx.try_send(ControlCommand::Stop).unwrap();
|
||||||
error!("Failed to send stop command: {:?}", e);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
(_, _) => {}
|
(_, _) => {}
|
||||||
}
|
}
|
||||||
@@ -698,7 +679,7 @@ impl eframe::App for App {
|
|||||||
// Egui add a top bar
|
// Egui add a top bar
|
||||||
egui::TopBottomPanel::top("top_bar").show(ctx, |ui| {
|
egui::TopBottomPanel::top("top_bar").show(ctx, |ui| {
|
||||||
egui::MenuBar::new().ui(ui, |ui| {
|
egui::MenuBar::new().ui(ui, |ui| {
|
||||||
let is_connected = self.hardware_state_rx.borrow().connected != HardwareConnected::None;
|
let is_connected = self.hardware_state_rx.borrow().hardware_connected != HardwareConnected::None;
|
||||||
|
|
||||||
egui::widgets::global_theme_preference_switch(ui);
|
egui::widgets::global_theme_preference_switch(ui);
|
||||||
ui.separator();
|
ui.separator();
|
||||||
@@ -707,9 +688,9 @@ impl eframe::App for App {
|
|||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
let on = *self.on.lock().unwrap();
|
let running = self.hardware_state_rx.borrow().running;
|
||||||
|
|
||||||
let (color, text) = match on {
|
let (color, text) = match running {
|
||||||
true => (Color32::DARK_RED, "Stop"),
|
true => (Color32::DARK_RED, "Stop"),
|
||||||
false => (Color32::DARK_GREEN, "Start"),
|
false => (Color32::DARK_GREEN, "Start"),
|
||||||
};
|
};
|
||||||
@@ -724,8 +705,7 @@ impl eframe::App for App {
|
|||||||
.frame(true);
|
.frame(true);
|
||||||
|
|
||||||
if ui.add_enabled(is_connected, start_stop_button).clicked() {
|
if ui.add_enabled(is_connected, start_stop_button).clicked() {
|
||||||
*self.on.lock().unwrap() = !on;
|
self.toggle_start_stop();
|
||||||
self.update_start_stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
@@ -742,7 +722,7 @@ impl eframe::App for App {
|
|||||||
|
|
||||||
let mut logging_enabled = !matches!(gui_logging_state, LoggingStates::Idle);
|
let mut logging_enabled = !matches!(gui_logging_state, LoggingStates::Idle);
|
||||||
|
|
||||||
if ui.add_enabled(is_connected, toggle_start_stop(&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.clone()),
|
||||||
LoggingStates::Starting | LoggingStates::Logging => LoggingSignal::StopFileLogging,
|
LoggingStates::Starting | LoggingStates::Logging => LoggingSignal::StopFileLogging,
|
||||||
@@ -818,7 +798,7 @@ impl eframe::App for App {
|
|||||||
// Spacer to push the LED to the right
|
// Spacer to push the LED to the right
|
||||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||||
ui.scope(|ui| {
|
ui.scope(|ui| {
|
||||||
let (color, tooltip) = match self.hardware_state_rx.borrow().connected {
|
let (color, tooltip) = match self.hardware_state_rx.borrow().hardware_connected {
|
||||||
HardwareConnected::None => {
|
HardwareConnected::None => {
|
||||||
(Color32::DARK_RED, "Disconnected")
|
(Color32::DARK_RED, "Disconnected")
|
||||||
},
|
},
|
||||||
@@ -863,8 +843,7 @@ impl eframe::App for App {
|
|||||||
"Single" => {
|
"Single" => {
|
||||||
if self.tab_active != TabActive::Single {
|
if self.tab_active != TabActive::Single {
|
||||||
self.tab_active = TabActive::Single;
|
self.tab_active = TabActive::Single;
|
||||||
*self.on.lock().unwrap() = false;
|
self.control_tx.try_send(ControlCommand::Stop).unwrap();
|
||||||
self.update_start_stop();
|
|
||||||
if *self.gui_logging_state.lock().unwrap() == LoggingStates::Logging {
|
if *self.gui_logging_state.lock().unwrap() == LoggingStates::Logging {
|
||||||
self.log_tx.try_send(LoggingSignal::StopFileLogging).unwrap_or_else(|e| {
|
self.log_tx.try_send(LoggingSignal::StopFileLogging).unwrap_or_else(|e| {
|
||||||
error!("Failed to send logging logging signal: {:?}", e);
|
error!("Failed to send logging logging signal: {:?}", e);
|
||||||
@@ -877,8 +856,7 @@ impl eframe::App for App {
|
|||||||
"Sweep" => {
|
"Sweep" => {
|
||||||
if self.tab_active != TabActive::Sweep {
|
if self.tab_active != TabActive::Sweep {
|
||||||
self.tab_active = TabActive::Sweep;
|
self.tab_active = TabActive::Sweep;
|
||||||
*self.on.lock().unwrap() = false;
|
self.control_tx.try_send(ControlCommand::Stop).unwrap();
|
||||||
self.update_start_stop();
|
|
||||||
if *self.gui_logging_state.lock().unwrap() == LoggingStates::Logging {
|
if *self.gui_logging_state.lock().unwrap() == LoggingStates::Logging {
|
||||||
self.log_tx.try_send(LoggingSignal::StopFileLogging).unwrap_or_else(|e| {
|
self.log_tx.try_send(LoggingSignal::StopFileLogging).unwrap_or_else(|e| {
|
||||||
error!("Failed to send logging logging signal: {:?}", e);
|
error!("Failed to send logging logging signal: {:?}", e);
|
||||||
@@ -917,15 +895,12 @@ impl eframe::App for App {
|
|||||||
// Space to start/stop measurement
|
// Space to start/stop measurement
|
||||||
if ctx.input(|i| i.key_pressed(Key::Space))
|
if ctx.input(|i| i.key_pressed(Key::Space))
|
||||||
{
|
{
|
||||||
let value = *self.on.lock().unwrap();
|
self.toggle_start_stop();
|
||||||
*self.on.lock().unwrap() = !value;
|
|
||||||
self.update_start_stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send stop command when the window is closed
|
// Send stop command when the window is closed
|
||||||
if ctx.input(|i| i.viewport().close_requested()) {
|
if ctx.input(|i| i.viewport().close_requested()) {
|
||||||
*self.on.lock().unwrap() = false;
|
self.toggle_start_stop();
|
||||||
self.update_start_stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset view
|
// Reset view
|
||||||
@@ -1007,6 +982,6 @@ fn toggle_ui_start_stop(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
|
|||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle_start_stop(on: &mut bool) -> impl egui::Widget + '_ {
|
pub fn custom_toggle_widget(on: &mut bool) -> impl egui::Widget + '_ {
|
||||||
move |ui: &mut egui::Ui| toggle_ui_start_stop(ui, on)
|
move |ui: &mut egui::Ui| toggle_ui_start_stop(ui, on)
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@ use bioz_host_rs::signals::StartStopSignal;
|
|||||||
use bioz_host_rs::logging::log_data;
|
use bioz_host_rs::logging::log_data;
|
||||||
|
|
||||||
use bioz_host_rs::state::{AppState, ControlCommand, HardwareState, MeasurementDataState};
|
use bioz_host_rs::state::{AppState, ControlCommand, HardwareState, MeasurementDataState};
|
||||||
use bioz_host_rs::control::control_loop;
|
use bioz_host_rs::control::app_control_loop;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
@@ -28,21 +28,21 @@ async fn main() {
|
|||||||
// Enter the runtime so that `tokio::spawn` is available immediately.
|
// Enter the runtime so that `tokio::spawn` is available immediately.
|
||||||
// let _enter = rt.enter();
|
// let _enter = rt.enter();
|
||||||
|
|
||||||
// Init watch for hardware state
|
// Init watches for hardware state and app state
|
||||||
let (hardware_state_tx, hardware_state_rx) = watch::channel(HardwareState::default());
|
let (hardware_state_tx, hardware_state_rx) = watch::channel(HardwareState::default());
|
||||||
|
|
||||||
// Channel to communicate with the communication task.
|
|
||||||
let (run_impedancemeter_tx, run_impedancemeter_rx) = mpsc::channel::<StartStopSignal>(2);
|
|
||||||
let run_impedancemeter_tx_clone = run_impedancemeter_tx.clone();
|
|
||||||
|
|
||||||
// Control layer
|
|
||||||
let (control_tx, control_rx) = mpsc::channel::<ControlCommand>(32);
|
|
||||||
let (app_state_tx, app_state_rx) = watch::channel(AppState::default());
|
let (app_state_tx, app_state_rx) = watch::channel(AppState::default());
|
||||||
|
|
||||||
let control_tx_clone = control_tx.clone();
|
// Init control channels to update hardware and app
|
||||||
|
let (hardware_control_tx, hardware_control_rx) = mpsc::channel::<StartStopSignal>(2);
|
||||||
|
let (app_control_tx, app_control_rx) = mpsc::channel::<ControlCommand>(32);
|
||||||
|
|
||||||
|
// let run_impedancemeter_tx_clone = run_impedancemeter_tx.clone();
|
||||||
|
let hardware_state_rx_clone = hardware_state_rx.clone();
|
||||||
|
let hardware_control_tx_clone = hardware_control_tx.clone();
|
||||||
|
let app_control_tx_clone = app_control_tx.clone();
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
control_loop(control_rx, app_state_tx).await;
|
app_control_loop(app_control_rx, app_state_tx, hardware_state_rx, hardware_control_tx_clone).await;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Init measurement data state
|
// Init measurement data state
|
||||||
@@ -54,7 +54,8 @@ async fn main() {
|
|||||||
|
|
||||||
let measurement_data_clone = measurement_data.clone();
|
let measurement_data_clone = measurement_data.clone();
|
||||||
|
|
||||||
let app = App::new(run_impedancemeter_tx, log_tx, measurement_data_clone, control_tx_clone, app_state_rx, hardware_state_rx);
|
|
||||||
|
let app = App::new(log_tx, measurement_data_clone, app_control_tx_clone, app_state_rx, hardware_state_rx_clone);
|
||||||
|
|
||||||
let gui_logging_state_1 = app.gui_logging_state.clone();
|
let gui_logging_state_1 = app.gui_logging_state.clone();
|
||||||
let gui_logging_state_2 = app.gui_logging_state.clone();
|
let gui_logging_state_2 = app.gui_logging_state.clone();
|
||||||
@@ -63,18 +64,19 @@ 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 = run_impedancemeter_tx_clone.clone();
|
|
||||||
std::thread::spawn(move || {
|
// let tcp_run_impedancemeter_tx = hardware_control_tx.clone();
|
||||||
rt.block_on(bioz_host_rs::tcp::tcp_command_server(tcp_run_impedancemeter_tx));
|
// std::thread::spawn(move || {
|
||||||
});
|
// rt.block_on(bioz_host_rs::tcp::tcp_command_server(tcp_run_impedancemeter_tx));
|
||||||
|
// });
|
||||||
|
|
||||||
// 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");
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
rt.block_on(communicate_with_hardware(
|
rt.block_on(communicate_with_hardware(
|
||||||
run_impedancemeter_rx,
|
hardware_control_rx,
|
||||||
run_impedancemeter_tx_clone,
|
hardware_control_tx,
|
||||||
measurement_data,
|
measurement_data,
|
||||||
hardware_state_tx,
|
hardware_state_tx,
|
||||||
gui_logging_state_2,
|
gui_logging_state_2,
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ use tokio::select;
|
|||||||
use tokio::sync::{watch, mpsc::{Receiver, Sender}};
|
use tokio::sync::{watch, mpsc::{Receiver, Sender}};
|
||||||
|
|
||||||
use std::sync::atomic::{AtomicU32, Ordering};
|
use std::sync::atomic::{AtomicU32, Ordering};
|
||||||
use atomic_float::AtomicF32;
|
|
||||||
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
@@ -18,15 +17,14 @@ use crate::icd;
|
|||||||
use crate::client::WorkbookClient;
|
use crate::client::WorkbookClient;
|
||||||
|
|
||||||
use crate::logging::LoggingStates;
|
use crate::logging::LoggingStates;
|
||||||
use crate::plot::{TimeSeriesPlot, BodePlot};
|
|
||||||
|
|
||||||
use crate::signals::{LoggingSignal, StartStopSignal};
|
use crate::signals::{LoggingSignal, StartStopSignal};
|
||||||
|
|
||||||
use crate::state::HardwareConnected;
|
use crate::state::HardwareConnected;
|
||||||
|
|
||||||
pub async fn communicate_with_hardware(
|
pub async fn communicate_with_hardware(
|
||||||
mut run_impedancemeter_rx: Receiver<StartStopSignal>,
|
mut hardware_control_rx: Receiver<StartStopSignal>,
|
||||||
run_impedancemeter_tx: Sender<StartStopSignal>,
|
hardware_control_tx: Sender<StartStopSignal>,
|
||||||
measurement_data: Arc<MeasurementDataState>,
|
measurement_data: Arc<MeasurementDataState>,
|
||||||
hardware_state_tx: watch::Sender<HardwareState>,
|
hardware_state_tx: watch::Sender<HardwareState>,
|
||||||
gui_logging_state: Arc<Mutex<LoggingStates>>,
|
gui_logging_state: Arc<Mutex<LoggingStates>>,
|
||||||
@@ -57,16 +55,16 @@ pub async fn communicate_with_hardware(
|
|||||||
Ok(client) => {
|
Ok(client) => {
|
||||||
info!("Connected to hardware successfully.");
|
info!("Connected to hardware successfully.");
|
||||||
if let Some(mode) = settings.lock().unwrap().mode {
|
if let Some(mode) = settings.lock().unwrap().mode {
|
||||||
run_impedancemeter_tx.send(mode).await.unwrap();
|
hardware_control_tx.send(mode).await.unwrap();
|
||||||
}
|
}
|
||||||
match client.get_device_info().await.unwrap() {
|
match client.get_device_info().await.unwrap() {
|
||||||
MultiplexerCapability::Absent => {
|
MultiplexerCapability::Absent => {
|
||||||
hardware_state.connected = HardwareConnected::WithoutMultiplexer;
|
hardware_state.hardware_connected = HardwareConnected::WithoutMultiplexer;
|
||||||
hardware_state_tx.send(hardware_state.clone()).unwrap();
|
hardware_state_tx.send(hardware_state.clone()).unwrap();
|
||||||
info!("Connected device: Without Multiplexer");
|
info!("Connected device: Without Multiplexer");
|
||||||
},
|
},
|
||||||
MultiplexerCapability::Present => {
|
MultiplexerCapability::Present => {
|
||||||
hardware_state.connected = HardwareConnected::WithMultiplexer;
|
hardware_state.hardware_connected = HardwareConnected::WithMultiplexer;
|
||||||
hardware_state_tx.send(hardware_state.clone()).unwrap();
|
hardware_state_tx.send(hardware_state.clone()).unwrap();
|
||||||
info!("Connected device: With Multiplexer");
|
info!("Connected device: With Multiplexer");
|
||||||
},
|
},
|
||||||
@@ -189,13 +187,14 @@ pub async fn communicate_with_hardware(
|
|||||||
let log_tx_clone = log_tx.clone();
|
let log_tx_clone = log_tx.clone();
|
||||||
loop {
|
loop {
|
||||||
select! {
|
select! {
|
||||||
Some(frequency) = run_impedancemeter_rx.recv() => {
|
Some(frequency) = hardware_control_rx.recv() => {
|
||||||
match frequency {
|
match frequency {
|
||||||
StartStopSignal::StartSingle(freq, lead_mode, electrode_config, dft_num) => {
|
StartStopSignal::StartSingle(freq, lead_mode, electrode_config, dft_num) => {
|
||||||
match workbook_client.start_impedancemeter_single(freq, lead_mode, electrode_config, dft_num).await {
|
match workbook_client.start_impedancemeter_single(freq, lead_mode, electrode_config, dft_num).await {
|
||||||
Ok(Ok(periods)) => {
|
Ok(Ok(periods)) => {
|
||||||
info!("Impedance meter started at frequency: {} with periods per DFT: {}", freq, periods);
|
info!("Impedance meter started at frequency: {} with periods per DFT: {}", freq, periods);
|
||||||
settings.lock().unwrap().mode = Some(StartStopSignal::StartSingle(freq, lead_mode, electrode_config, dft_num));
|
settings.lock().unwrap().mode = Some(StartStopSignal::StartSingle(freq, lead_mode, electrode_config, dft_num));
|
||||||
|
hardware_state.running = true;
|
||||||
hardware_state.periods_per_dft = Some(periods);
|
hardware_state.periods_per_dft = Some(periods);
|
||||||
hardware_state_tx.send(hardware_state.clone()).unwrap();
|
hardware_state_tx.send(hardware_state.clone()).unwrap();
|
||||||
|
|
||||||
@@ -208,11 +207,13 @@ pub async fn communicate_with_hardware(
|
|||||||
},
|
},
|
||||||
Ok(Err(e)) => {
|
Ok(Err(e)) => {
|
||||||
error!("Failed to init on hardware: {:?}", e);
|
error!("Failed to init on hardware: {:?}", e);
|
||||||
|
hardware_state.running = false;
|
||||||
hardware_state.periods_per_dft = None;
|
hardware_state.periods_per_dft = None;
|
||||||
hardware_state_tx.send(hardware_state.clone()).unwrap();
|
hardware_state_tx.send(hardware_state.clone()).unwrap();
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Communication error when starting impedancemeter: {:?}", e);
|
error!("Communication error when starting impedancemeter: {:?}", e);
|
||||||
|
hardware_state.running = false;
|
||||||
hardware_state.periods_per_dft = None;
|
hardware_state.periods_per_dft = None;
|
||||||
hardware_state_tx.send(hardware_state.clone()).unwrap();
|
hardware_state_tx.send(hardware_state.clone()).unwrap();
|
||||||
}
|
}
|
||||||
@@ -221,8 +222,9 @@ pub async fn communicate_with_hardware(
|
|||||||
StartStopSignal::StartSweep(lead_mode, electrode_config, num_points) => {
|
StartStopSignal::StartSweep(lead_mode, electrode_config, num_points) => {
|
||||||
match workbook_client.start_impedancemeter_sweep(lead_mode, electrode_config, num_points).await {
|
match workbook_client.start_impedancemeter_sweep(lead_mode, electrode_config, num_points).await {
|
||||||
Ok(Ok(periods)) => {
|
Ok(Ok(periods)) => {
|
||||||
settings.lock().unwrap().mode = Some(StartStopSignal::StartSweep(lead_mode, electrode_config, num_points));
|
|
||||||
info!("Sweep Impedancemeter started.");
|
info!("Sweep Impedancemeter started.");
|
||||||
|
settings.lock().unwrap().mode = Some(StartStopSignal::StartSweep(lead_mode, electrode_config, num_points));
|
||||||
|
hardware_state.running = true;
|
||||||
match num_points {
|
match num_points {
|
||||||
SweepPoints::Eight => {
|
SweepPoints::Eight => {
|
||||||
hardware_state.periods_per_dft_sweep = (num_points.values().iter().copied().collect(), Some(periods.periods_per_dft_8.into_iter().collect()));
|
hardware_state.periods_per_dft_sweep = (num_points.values().iter().copied().collect(), Some(periods.periods_per_dft_8.into_iter().collect()));
|
||||||
@@ -245,11 +247,13 @@ pub async fn communicate_with_hardware(
|
|||||||
Ok(Err(e)) => {
|
Ok(Err(e)) => {
|
||||||
error!("Failed to sweep-init on hardware: {:?}", e);
|
error!("Failed to sweep-init on hardware: {:?}", e);
|
||||||
hardware_state.periods_per_dft_sweep = (num_points.values().iter().copied().collect(), None);
|
hardware_state.periods_per_dft_sweep = (num_points.values().iter().copied().collect(), None);
|
||||||
|
hardware_state.running = false;
|
||||||
hardware_state_tx.send(hardware_state.clone()).unwrap();
|
hardware_state_tx.send(hardware_state.clone()).unwrap();
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Communication error when starting impedancemeter: {:?}", e);
|
error!("Communication error when starting impedancemeter: {:?}", e);
|
||||||
hardware_state.periods_per_dft_sweep = (num_points.values().iter().copied().collect(), None);
|
hardware_state.periods_per_dft_sweep = (num_points.values().iter().copied().collect(), None);
|
||||||
|
hardware_state.running = false;
|
||||||
hardware_state_tx.send(hardware_state.clone()).unwrap();
|
hardware_state_tx.send(hardware_state.clone()).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,6 +263,7 @@ pub async fn communicate_with_hardware(
|
|||||||
error!("Failed to stop impedancemeter: {:?}", e);
|
error!("Failed to stop impedancemeter: {:?}", e);
|
||||||
} else {
|
} else {
|
||||||
settings.lock().unwrap().mode = Some(StartStopSignal::Stop);
|
settings.lock().unwrap().mode = Some(StartStopSignal::Stop);
|
||||||
|
hardware_state.running = false;
|
||||||
hardware_state.periods_per_dft = None;
|
hardware_state.periods_per_dft = None;
|
||||||
let (freq, _) = hardware_state.periods_per_dft_sweep.clone();
|
let (freq, _) = hardware_state.periods_per_dft_sweep.clone();
|
||||||
hardware_state.periods_per_dft_sweep = (freq, None);
|
hardware_state.periods_per_dft_sweep = (freq, None);
|
||||||
@@ -282,7 +287,7 @@ pub async fn communicate_with_hardware(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
info!("Communication with hardware ended.");
|
info!("Communication with hardware ended.");
|
||||||
hardware_state.connected = HardwareConnected::None;
|
hardware_state.hardware_connected = HardwareConnected::None;
|
||||||
hardware_state_tx.send(hardware_state).unwrap();
|
hardware_state_tx.send(hardware_state).unwrap();
|
||||||
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
use log::info;
|
use log::info;
|
||||||
use tokio::sync::{mpsc::Receiver, watch::Sender};
|
use tokio::sync::{mpsc, watch};
|
||||||
use crate::{state::{AppState, ControlCommand}};
|
use crate::{signals::StartStopSignal, state::{AppState, ControlCommand, HardwareConnected, HardwareState}};
|
||||||
|
|
||||||
pub async fn control_loop(mut rx: Receiver<ControlCommand>, tx: Sender<AppState>) {
|
use crate::state::Mode;
|
||||||
|
|
||||||
|
pub async fn app_control_loop(mut app_control_rx: mpsc::Receiver<ControlCommand>,
|
||||||
|
app_state_tx: watch::Sender<AppState>,
|
||||||
|
hardware_state_rx: watch::Receiver<HardwareState>,
|
||||||
|
hardware_control_tx: mpsc::Sender<StartStopSignal>) {
|
||||||
let mut state = AppState::default();
|
let mut state = AppState::default();
|
||||||
|
|
||||||
while let Some(cmd) = rx.recv().await {
|
while let Some(cmd) = app_control_rx.recv().await {
|
||||||
match cmd {
|
match cmd {
|
||||||
ControlCommand::SetFrequency(freq) => {
|
ControlCommand::SetFrequency(freq) => {
|
||||||
state.single_frequency = freq;
|
state.single_frequency = freq;
|
||||||
@@ -27,14 +32,46 @@ pub async fn control_loop(mut rx: Receiver<ControlCommand>, tx: Sender<AppState>
|
|||||||
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::Start => {
|
ControlCommand::Start(mode) => {
|
||||||
info!("Starting impedance hardware...");
|
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...");
|
||||||
|
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...");
|
||||||
|
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...");
|
||||||
|
hardware_control_tx.try_send(StartStopSignal::StartSweep(
|
||||||
|
state.lead_mode, None, state.sweep_points)).unwrap();
|
||||||
|
}
|
||||||
|
(Mode::Sweep, HardwareConnected::WithMultiplexer) => {
|
||||||
|
info!("Starting sweep measurement...");
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
(_, HardwareConnected::None) => {
|
||||||
|
info!("Cannot start measurement: No hardware connected!");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ControlCommand::Stop => {
|
ControlCommand::Stop => {
|
||||||
info!("Stopping impedance hardware...");
|
info!("Stopping impedance hardware...");
|
||||||
|
if let Err(e) = hardware_control_tx.try_send(StartStopSignal::Stop) {
|
||||||
|
info!("Failed to send stop command: {:?}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tx.send(state.clone()).unwrap();
|
app_state_tx.send(state.clone()).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
25
src/state.rs
25
src/state.rs
@@ -3,6 +3,7 @@ use std::sync::{Arc, Mutex};
|
|||||||
|
|
||||||
use crate::icd::{BioImpedanceLeadMode, IcdDftNum, SweepPoints, ElectrodeConfiguration, ElectrodeOptionsWithMultiplexer};
|
use crate::icd::{BioImpedanceLeadMode, IcdDftNum, SweepPoints, ElectrodeConfiguration, ElectrodeOptionsWithMultiplexer};
|
||||||
|
|
||||||
|
use crate::logging::LoggingStates;
|
||||||
use crate::plot::{TimeSeriesPlot, BodePlot};
|
use crate::plot::{TimeSeriesPlot, BodePlot};
|
||||||
|
|
||||||
use atomic_float::AtomicF32;
|
use atomic_float::AtomicF32;
|
||||||
@@ -44,7 +45,7 @@ pub enum ControlCommand {
|
|||||||
ChangeDftNum(IcdDftNum),
|
ChangeDftNum(IcdDftNum),
|
||||||
ChangeElectrodeSettings(ElectrodeSettings),
|
ChangeElectrodeSettings(ElectrodeSettings),
|
||||||
ChangeSweepPoints(SweepPoints),
|
ChangeSweepPoints(SweepPoints),
|
||||||
Start,
|
Start(Mode),
|
||||||
Stop,
|
Stop,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,24 +93,33 @@ impl ElectrodeSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum Mode {
|
||||||
|
Single,
|
||||||
|
Sweep,
|
||||||
|
}
|
||||||
// Application state that can be shared across the app and updated by the control loop
|
// Application state that can be shared across the app and updated by the control loop
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
|
pub mode: Mode,
|
||||||
pub single_frequency: u32,
|
pub single_frequency: u32,
|
||||||
pub lead_mode: BioImpedanceLeadMode,
|
pub lead_mode: BioImpedanceLeadMode,
|
||||||
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 logging: LoggingStates,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AppState {
|
impl Default for AppState {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
mode: Mode::Single,
|
||||||
single_frequency: 50000,
|
single_frequency: 50000,
|
||||||
lead_mode: BioImpedanceLeadMode::TwoLead,
|
lead_mode: BioImpedanceLeadMode::FourLead,
|
||||||
dft_num: IcdDftNum::Num2048,
|
dft_num: IcdDftNum::Num2048,
|
||||||
electrode_settings: ElectrodeSettings::new(),
|
electrode_settings: ElectrodeSettings::new(),
|
||||||
sweep_points: SweepPoints::Eighteen,
|
sweep_points: SweepPoints::Eighteen,
|
||||||
|
logging: LoggingStates::Idle,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,8 +127,8 @@ impl Default for AppState {
|
|||||||
// Hardware state that can be shared across the app and updated by the hardware loop
|
// Hardware state that can be shared across the app and updated by the hardware loop
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct HardwareState {
|
pub struct HardwareState {
|
||||||
pub connected: HardwareConnected,
|
pub hardware_connected: HardwareConnected,
|
||||||
// pub running: bool,
|
pub running: bool,
|
||||||
pub periods_per_dft: Option<f32>,
|
pub periods_per_dft: Option<f32>,
|
||||||
pub periods_per_dft_sweep: (Vec<u32>, Option<Vec<f32>>),
|
pub periods_per_dft_sweep: (Vec<u32>, Option<Vec<f32>>),
|
||||||
}
|
}
|
||||||
@@ -126,10 +136,11 @@ pub struct HardwareState {
|
|||||||
impl Default for HardwareState {
|
impl Default for HardwareState {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
connected: HardwareConnected::None,
|
hardware_connected: HardwareConnected::None,
|
||||||
// running: false,
|
running: false,
|
||||||
periods_per_dft: None,
|
periods_per_dft: None,
|
||||||
periods_per_dft_sweep: (SweepPoints::Eighteen.values().to_vec(), None),
|
periods_per_dft_sweep: (SweepPoints::Eighteen.values().to_vec(), None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user