mirror of
https://github.com/hubaldv/bioz-host-rs.git
synced 2026-07-23 16:47:43 +00:00
Major commit adding a control layer in between for later TCP use.
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
321
src/app.rs
321
src/app.rs
@@ -1,6 +1,7 @@
|
|||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use log::{info, error};
|
use log::{info, error};
|
||||||
|
use tokio::sync;
|
||||||
|
|
||||||
use core::f32;
|
use core::f32;
|
||||||
|
|
||||||
@@ -11,69 +12,27 @@ use std::sync::atomic::Ordering;
|
|||||||
|
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
|
|
||||||
use atomic_float::AtomicF32;
|
use tokio::sync::{mpsc, watch};
|
||||||
use tokio::{sync::mpsc::{Sender}};
|
|
||||||
|
|
||||||
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};
|
||||||
use egui_extras::{TableBuilder, Column};
|
use egui_extras::{TableBuilder, Column};
|
||||||
|
|
||||||
|
use crate::state::{AppState, ControlCommand, HardwareConnected, HardwareState, MeasurementDataState};
|
||||||
|
|
||||||
use crate::logging::LoggingStates;
|
use crate::logging::LoggingStates;
|
||||||
use crate::plot::{TimeSeriesPlot, BodePlot};
|
use crate::plot::{TimeSeriesPlot, BodePlot};
|
||||||
|
|
||||||
use crate::signals::{LoggingSignal, StartStopSignal};
|
use crate::signals::{LoggingSignal, StartStopSignal};
|
||||||
|
|
||||||
use crate::icd::{BioImpedanceLeadMode, IcdDftNum, MeasurementPointSet, ElectrodeConfiguration, ElectrodeOptionsWithMultiplexer};
|
use crate::icd::{BioImpedanceLeadMode, IcdDftNum, SweepPoints, ElectrodeConfiguration, ElectrodeOptionsWithMultiplexer};
|
||||||
|
|
||||||
const LEAD_MODES: [BioImpedanceLeadMode; 2] = [
|
const LEAD_MODES: [BioImpedanceLeadMode; 2] = [
|
||||||
BioImpedanceLeadMode::TwoLead,
|
BioImpedanceLeadMode::TwoLead,
|
||||||
BioImpedanceLeadMode::FourLead,
|
BioImpedanceLeadMode::FourLead,
|
||||||
];
|
];
|
||||||
|
|
||||||
struct ElectrodeSettings {
|
|
||||||
with_multiplexer_2_lead: [ElectrodeOptionsWithMultiplexer; 2],
|
|
||||||
with_multiplexer_4_lead: [ElectrodeOptionsWithMultiplexer; 4],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ElectrodeSettings {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
with_multiplexer_2_lead: [
|
|
||||||
ElectrodeOptionsWithMultiplexer::E1, // Electrode+ default
|
|
||||||
ElectrodeOptionsWithMultiplexer::E23, // Electrode- default
|
|
||||||
],
|
|
||||||
with_multiplexer_4_lead: [
|
|
||||||
ElectrodeOptionsWithMultiplexer::E1, // I+ default
|
|
||||||
ElectrodeOptionsWithMultiplexer::E23, // I- default
|
|
||||||
ElectrodeOptionsWithMultiplexer::E10, // V+ default
|
|
||||||
ElectrodeOptionsWithMultiplexer::E12, // V- default
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_electrode_config(
|
|
||||||
&self,
|
|
||||||
hardware_connected: HardwareConnected,
|
|
||||||
lead_mode: BioImpedanceLeadMode,
|
|
||||||
) -> Option<ElectrodeConfiguration> {
|
|
||||||
if hardware_connected != HardwareConnected::WithMultiplexer {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
match lead_mode {
|
|
||||||
BioImpedanceLeadMode::TwoLead => {
|
|
||||||
let [a, b] = self.with_multiplexer_2_lead;
|
|
||||||
Some(ElectrodeConfiguration::WithMultiplexer2Lead(a, b))
|
|
||||||
}
|
|
||||||
BioImpedanceLeadMode::FourLead => {
|
|
||||||
let [a, b, c, d] = self.with_multiplexer_4_lead;
|
|
||||||
Some(ElectrodeConfiguration::WithMultiplexer4Lead(a, b, c, d))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const DFTNUM_VARIANTS: [IcdDftNum; 13] = [
|
const DFTNUM_VARIANTS: [IcdDftNum; 13] = [
|
||||||
IcdDftNum::Num4, IcdDftNum::Num8, IcdDftNum::Num16, IcdDftNum::Num32,
|
IcdDftNum::Num4, IcdDftNum::Num8, IcdDftNum::Num16, IcdDftNum::Num32,
|
||||||
IcdDftNum::Num64, IcdDftNum::Num128, IcdDftNum::Num256, IcdDftNum::Num512,
|
IcdDftNum::Num64, IcdDftNum::Num128, IcdDftNum::Num256, IcdDftNum::Num512,
|
||||||
@@ -81,9 +40,9 @@ const DFTNUM_VARIANTS: [IcdDftNum; 13] = [
|
|||||||
IcdDftNum::Num8192, IcdDftNum::Num16384,
|
IcdDftNum::Num8192, IcdDftNum::Num16384,
|
||||||
];
|
];
|
||||||
|
|
||||||
const MEASUREMENT_POINTS_VARIANTS: [MeasurementPointSet; 2] = [
|
const SWEEP_POINTS_VARIANTS: [SweepPoints; 2] = [
|
||||||
MeasurementPointSet::Eight,
|
SweepPoints::Eight,
|
||||||
MeasurementPointSet::Eighteen,
|
SweepPoints::Eighteen,
|
||||||
];
|
];
|
||||||
|
|
||||||
#[derive(Clone, Copy,Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy,Debug, PartialEq, Eq)]
|
||||||
@@ -93,57 +52,31 @@ enum TabActive {
|
|||||||
Shortcuts,
|
Shortcuts,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub enum HardwareConnected {
|
|
||||||
None,
|
|
||||||
WithoutMultiplexer,
|
|
||||||
WithMultiplexer,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
tree: DockState<String>,
|
tree: DockState<String>,
|
||||||
tab_viewer: TabViewer,
|
tab_viewer: TabViewer,
|
||||||
run_impedancemeter_tx: Sender<StartStopSignal>,
|
|
||||||
log_tx: Sender<LoggingSignal>,
|
|
||||||
pub magnitude: Arc<Mutex<f32>>,
|
|
||||||
pub phase: Arc<Mutex<f32>>,
|
|
||||||
pub magnitude_series: Arc<Mutex<TimeSeriesPlot>>,
|
|
||||||
pub phase_series: Arc<Mutex<TimeSeriesPlot>>,
|
|
||||||
pub bode_plot: Arc<Mutex<BodePlot>>,
|
|
||||||
pub hardware_connected: Arc<Mutex<HardwareConnected>>,
|
|
||||||
pub on: Arc<Mutex<bool>>,
|
|
||||||
tab_active: TabActive,
|
tab_active: TabActive,
|
||||||
pub data_frequency: Arc<AtomicF32>,
|
run_impedancemeter_tx: mpsc::Sender<StartStopSignal>,
|
||||||
pub single_frequency: Arc<Mutex<u32>>,
|
log_tx: mpsc::Sender<LoggingSignal>,
|
||||||
pub lead_mode: Arc<Mutex<BioImpedanceLeadMode>>,
|
pub on: Arc<Mutex<bool>>,
|
||||||
electrode_settings: Arc<Mutex<ElectrodeSettings>>,
|
|
||||||
pub dft_num: Arc<Mutex<IcdDftNum>>,
|
|
||||||
pub measurement_points: Arc<Mutex<MeasurementPointSet>>,
|
|
||||||
pub periods_per_dft: Arc<Mutex<Option<f32>>>,
|
|
||||||
pub periods_per_dft_sweep: Arc<Mutex<(Vec<u32>, Option<Vec<f32>>)>>,
|
|
||||||
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,
|
||||||
log_marker: String,
|
log_marker: String,
|
||||||
|
measurement_data: Arc<MeasurementDataState>,
|
||||||
|
control_tx: mpsc::Sender<ControlCommand>,
|
||||||
|
app_state_rx: sync::watch::Receiver<AppState>,
|
||||||
|
hardware_state_rx: sync::watch::Receiver<HardwareState>
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TabViewer {
|
struct TabViewer {
|
||||||
magnitude: Arc<Mutex<f32>>,
|
|
||||||
phase: Arc<Mutex<f32>>,
|
|
||||||
magnitude_series: Arc<Mutex<TimeSeriesPlot>>,
|
|
||||||
phase_series: Arc<Mutex<TimeSeriesPlot>>,
|
|
||||||
bode_plot: Arc<Mutex<BodePlot>>,
|
|
||||||
on: Arc<Mutex<bool>>,
|
on: Arc<Mutex<bool>>,
|
||||||
single_frequency: Arc<Mutex<u32>>,
|
|
||||||
hardware_connected: Arc<Mutex<HardwareConnected>>,
|
|
||||||
lead_mode: Arc<Mutex<BioImpedanceLeadMode>>,
|
|
||||||
electrode_settings: Arc<Mutex<ElectrodeSettings>>,
|
|
||||||
dft_num: Arc<Mutex<IcdDftNum>>,
|
|
||||||
measurement_points: Arc<Mutex<MeasurementPointSet>>,
|
|
||||||
periods_per_dft: Arc<Mutex<Option<f32>>>,
|
|
||||||
periods_per_dft_sweep: Arc<Mutex<(Vec<u32>, Option<Vec<f32>>)>>,
|
|
||||||
show_settings: bool,
|
show_settings: bool,
|
||||||
show_settings_toggle: Option<bool>,
|
show_settings_toggle: Option<bool>,
|
||||||
|
measurement_data: Arc<MeasurementDataState>,
|
||||||
|
control_tx: mpsc::Sender<ControlCommand>,
|
||||||
|
app_state_rx: sync::watch::Receiver<AppState>,
|
||||||
|
hardware_state_rx: sync::watch::Receiver<HardwareState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
trait ElectrodeOption: Copy + Debug + PartialEq + 'static {
|
trait ElectrodeOption: Copy + Debug + PartialEq + 'static {
|
||||||
@@ -158,46 +91,55 @@ fn electrode_combo<T: ElectrodeOption>(
|
|||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
id: &str,
|
id: &str,
|
||||||
value: &mut T,
|
value: &mut T,
|
||||||
) {
|
) -> bool {
|
||||||
|
let mut changed = false;
|
||||||
egui::ComboBox::from_id_salt(id)
|
egui::ComboBox::from_id_salt(id)
|
||||||
.selected_text(format!("{:?}", value))
|
.selected_text(format!("{:?}", value))
|
||||||
.width(60.0)
|
.width(60.0)
|
||||||
.show_ui(ui, |ui| {
|
.show_ui(ui, |ui| {
|
||||||
for &option in T::ALL {
|
for &option in T::ALL {
|
||||||
ui.selectable_value(value, option, format!("{:?}", option));
|
let response = ui.selectable_value(value, option, format!("{:?}", option));
|
||||||
|
if response.changed() {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
changed
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_two_lead<T: ElectrodeOption>(
|
fn render_two_lead<T: ElectrodeOption>(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
values: &mut [T; 2],
|
values: &mut [T; 2],
|
||||||
) {
|
) -> bool {
|
||||||
|
let mut changed = false;
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Drive/sense: Electrode+ (");
|
ui.label("Drive/sense: Electrode+ (");
|
||||||
electrode_combo(ui, "e1_select", &mut values[0]);
|
changed |= electrode_combo(ui, "e1_select", &mut values[0]);
|
||||||
ui.label("), Electrode- (");
|
ui.label("), Electrode- (");
|
||||||
electrode_combo(ui, "e2_select", &mut values[1]);
|
changed |= electrode_combo(ui, "e2_select", &mut values[1]);
|
||||||
ui.label(")");
|
ui.label(")");
|
||||||
});
|
});
|
||||||
|
changed
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_four_lead<T: ElectrodeOption>(
|
fn render_four_lead<T: ElectrodeOption>(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
values: &mut [T; 4],
|
values: &mut [T; 4],
|
||||||
) {
|
) -> bool {
|
||||||
|
let mut changed = false;
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Drive: I+ (");
|
ui.label("Drive: I+ (");
|
||||||
electrode_combo(ui, "i1_select", &mut values[0]);
|
changed |= electrode_combo(ui, "i1_select", &mut values[0]);
|
||||||
ui.label("), I- (");
|
ui.label("), I- (");
|
||||||
electrode_combo(ui, "i2_select", &mut values[1]);
|
changed |= electrode_combo(ui, "i2_select", &mut values[1]);
|
||||||
|
|
||||||
ui.label(") | Sense: V+ (");
|
ui.label(") | Sense: V+ (");
|
||||||
electrode_combo(ui, "v1_select", &mut values[2]);
|
changed |= electrode_combo(ui, "v1_select", &mut values[2]);
|
||||||
ui.label("), V- (");
|
ui.label("), V- (");
|
||||||
electrode_combo(ui, "v2_select", &mut values[3]);
|
changed |= electrode_combo(ui, "v2_select", &mut values[3]);
|
||||||
ui.label(")");
|
ui.label(")");
|
||||||
});
|
});
|
||||||
|
changed
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TabViewer {
|
impl TabViewer {
|
||||||
@@ -211,12 +153,12 @@ impl TabViewer {
|
|||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Lead Mode:");
|
ui.label("Lead Mode:");
|
||||||
|
|
||||||
let mut lead_mode = self.lead_mode.lock().unwrap();
|
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")
|
||||||
@@ -230,19 +172,20 @@ impl TabViewer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Update lead mode if changed
|
// Update lead mode if changed
|
||||||
if *lead_mode != LEAD_MODES[index] {
|
if lead_mode != LEAD_MODES[index] {
|
||||||
*lead_mode = LEAD_MODES[index];
|
lead_mode = LEAD_MODES[index];
|
||||||
info!("Lead Mode setting changed!");
|
self.control_tx.try_send(ControlCommand::ChangeLeadMode(lead_mode)).unwrap();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
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_connected.lock().unwrap();
|
let hardware_connected = self.hardware_state_rx.borrow().connected;
|
||||||
let lead_mode = self.lead_mode.lock().unwrap();
|
let lead_mode = self.app_state_rx.borrow().lead_mode;
|
||||||
|
|
||||||
let mut settings = self.electrode_settings.lock().unwrap();
|
let mut settings = self.app_state_rx.borrow().electrode_settings;
|
||||||
match (*hardware_connected, *lead_mode) {
|
|
||||||
|
match (hardware_connected, lead_mode) {
|
||||||
(HardwareConnected::WithoutMultiplexer, BioImpedanceLeadMode::TwoLead) => {
|
(HardwareConnected::WithoutMultiplexer, BioImpedanceLeadMode::TwoLead) => {
|
||||||
ui.label(format!("Drive/sense: Electrode+ (CE0), Electrode- (AIN1)"));
|
ui.label(format!("Drive/sense: Electrode+ (CE0), Electrode- (AIN1)"));
|
||||||
}
|
}
|
||||||
@@ -250,10 +193,14 @@ impl TabViewer {
|
|||||||
ui.label(format!("Drive: I+ (CE0), I- (AIN1) | Sense: V+ (AIN2), V- (AIN3)"));
|
ui.label(format!("Drive: I+ (CE0), I- (AIN1) | Sense: V+ (AIN2), V- (AIN3)"));
|
||||||
}
|
}
|
||||||
(HardwareConnected::WithMultiplexer, BioImpedanceLeadMode::TwoLead) => {
|
(HardwareConnected::WithMultiplexer, BioImpedanceLeadMode::TwoLead) => {
|
||||||
render_two_lead(ui, &mut settings.with_multiplexer_2_lead);
|
if render_two_lead(ui, &mut settings.with_multiplexer_2_lead) {
|
||||||
|
self.control_tx.try_send(ControlCommand::ChangeElectrodeSettings(settings)).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
(HardwareConnected::WithMultiplexer, BioImpedanceLeadMode::FourLead) => {
|
(HardwareConnected::WithMultiplexer, BioImpedanceLeadMode::FourLead) => {
|
||||||
render_four_lead(ui, &mut settings.with_multiplexer_4_lead);
|
if render_four_lead(ui, &mut settings.with_multiplexer_4_lead) {
|
||||||
|
self.control_tx.try_send(ControlCommand::ChangeElectrodeSettings(settings)).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
(HardwareConnected::None, _) => {}
|
(HardwareConnected::None, _) => {}
|
||||||
}
|
}
|
||||||
@@ -262,31 +209,33 @@ impl TabViewer {
|
|||||||
ui.add_enabled_ui(!*on, |ui| {
|
ui.add_enabled_ui(!*on, |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Single Frequency:");
|
ui.label("Single Frequency:");
|
||||||
if let Ok(mut freq) = self.single_frequency.lock() {
|
let mut freq = self.app_state_rx.borrow().single_frequency;
|
||||||
ui.add(DragValue::new(&mut *freq).speed(0.1));
|
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.label("Hz");
|
||||||
});
|
});
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("ADC samples per DFT:");
|
ui.label("ADC samples per DFT:");
|
||||||
let mut dft_num = self.dft_num.lock().unwrap();
|
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);
|
let mut index = DFTNUM_VARIANTS.iter().position(|&x| x == dft_num).unwrap_or(0);
|
||||||
ComboBox::from_id_salt("Dftnum")
|
ComboBox::from_id_salt("Dftnum")
|
||||||
.width(75.0)
|
.width(75.0)
|
||||||
.show_index(ui, &mut index, DFTNUM_VARIANTS.len(), |i| {
|
.show_index(ui, &mut index, DFTNUM_VARIANTS.len(), |i| {
|
||||||
format!("{}", 1 << (2 + i)) // 2^2 = 4, 2^3 = 8, ..., 2^14 = 16384
|
format!("{}", 1 << (2 + i)) // 2^2 = 4, 2^3 = 8, ..., 2^14 = 16384
|
||||||
});
|
});
|
||||||
let new_value = DFTNUM_VARIANTS[index];
|
let new_value = DFTNUM_VARIANTS[index];
|
||||||
if *dft_num != new_value {
|
if dft_num != new_value {
|
||||||
*dft_num = new_value;
|
dft_num = new_value;
|
||||||
info!("DFTNUM setting changed!");
|
self.control_tx.try_send(ControlCommand::ChangeDftNum(dft_num)).unwrap();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
ui.add_enabled_ui(*on, |ui| {
|
ui.add_enabled_ui(*on, |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Periods per DFT:");
|
ui.label("Periods per DFT:");
|
||||||
match (*on, *self.periods_per_dft.lock().unwrap()) {
|
match (*on, 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)));
|
||||||
},
|
},
|
||||||
@@ -316,14 +265,14 @@ impl TabViewer {
|
|||||||
Layout::top_down(egui::Align::Min),
|
Layout::top_down(egui::Align::Min),
|
||||||
|ui| {
|
|ui| {
|
||||||
// Magnitude
|
// Magnitude
|
||||||
let magnitude = self.magnitude_series.lock().unwrap();
|
let magnitude = self.measurement_data.magnitude_series.lock().unwrap();
|
||||||
Plot::new("magnitude")
|
Plot::new("magnitude")
|
||||||
.legend(Legend::default().position(Corner::LeftTop))
|
.legend(Legend::default().position(Corner::LeftTop))
|
||||||
.y_axis_label("Magnitude [Ω]")
|
.y_axis_label("Magnitude [Ω]")
|
||||||
.y_axis_min_width(80.0)
|
.y_axis_min_width(80.0)
|
||||||
.show(ui, |plot_ui| {
|
.show(ui, |plot_ui| {
|
||||||
plot_ui.line(
|
plot_ui.line(
|
||||||
Line::new(format!("Magnitude at {} Hz", self.single_frequency.lock().unwrap()), magnitude.plot_values())
|
Line::new(format!("Magnitude at {} Hz", self.app_state_rx.borrow().single_frequency), magnitude.plot_values())
|
||||||
.color(Color32::BLUE)
|
.color(Color32::BLUE)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -335,14 +284,14 @@ impl TabViewer {
|
|||||||
Layout::top_down(egui::Align::Min),
|
Layout::top_down(egui::Align::Min),
|
||||||
|ui| {
|
|ui| {
|
||||||
// Phase
|
// Phase
|
||||||
let phase = self.phase_series.lock().unwrap();
|
let phase = self.measurement_data.phase_series.lock().unwrap();
|
||||||
Plot::new("phase")
|
Plot::new("phase")
|
||||||
.legend(Legend::default().position(Corner::LeftTop))
|
.legend(Legend::default().position(Corner::LeftTop))
|
||||||
.y_axis_label("Phase [rad]")
|
.y_axis_label("Phase [rad]")
|
||||||
.y_axis_min_width(80.0)
|
.y_axis_min_width(80.0)
|
||||||
.show(ui, |plot_ui| {
|
.show(ui, |plot_ui| {
|
||||||
plot_ui.line(
|
plot_ui.line(
|
||||||
Line::new(format!("Phase at {} Hz", self.single_frequency.lock().unwrap()), phase.plot_values())
|
Line::new(format!("Phase at {} Hz", self.app_state_rx.borrow().single_frequency), phase.plot_values())
|
||||||
.color(Color32::RED)
|
.color(Color32::RED)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -362,12 +311,12 @@ impl TabViewer {
|
|||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Lead Mode:");
|
ui.label("Lead Mode:");
|
||||||
|
|
||||||
let mut lead_mode = self.lead_mode.lock().unwrap();
|
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")
|
||||||
@@ -381,19 +330,19 @@ impl TabViewer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Update lead mode if changed
|
// Update lead mode if changed
|
||||||
if *lead_mode != LEAD_MODES[index] {
|
if lead_mode != LEAD_MODES[index] {
|
||||||
*lead_mode = LEAD_MODES[index];
|
lead_mode = LEAD_MODES[index];
|
||||||
info!("Lead Mode setting changed!");
|
self.control_tx.try_send(ControlCommand::ChangeLeadMode(lead_mode)).unwrap();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
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_connected.lock().unwrap();
|
let hardware_connected = self.hardware_state_rx.borrow().connected;
|
||||||
let lead_mode = self.lead_mode.lock().unwrap();
|
let lead_mode = self.app_state_rx.borrow().lead_mode;
|
||||||
|
|
||||||
let mut settings = self.electrode_settings.lock().unwrap();
|
let mut settings = self.app_state_rx.borrow().electrode_settings;
|
||||||
match (*hardware_connected, *lead_mode) {
|
match (hardware_connected, lead_mode) {
|
||||||
(HardwareConnected::WithoutMultiplexer, BioImpedanceLeadMode::TwoLead) => {
|
(HardwareConnected::WithoutMultiplexer, BioImpedanceLeadMode::TwoLead) => {
|
||||||
ui.label(format!("Drive/sense: Electrode+ (CE0), Electrode- (AIN1)"));
|
ui.label(format!("Drive/sense: Electrode+ (CE0), Electrode- (AIN1)"));
|
||||||
}
|
}
|
||||||
@@ -401,10 +350,14 @@ impl TabViewer {
|
|||||||
ui.label("Drive: I+ (CE0), I- (AIN1) | Sense: V+ (AIN2), V- (AIN3)");
|
ui.label("Drive: I+ (CE0), I- (AIN1) | Sense: V+ (AIN2), V- (AIN3)");
|
||||||
}
|
}
|
||||||
(HardwareConnected::WithMultiplexer, BioImpedanceLeadMode::TwoLead) => {
|
(HardwareConnected::WithMultiplexer, BioImpedanceLeadMode::TwoLead) => {
|
||||||
render_two_lead(ui, &mut settings.with_multiplexer_2_lead);
|
if render_two_lead(ui, &mut settings.with_multiplexer_2_lead) {
|
||||||
|
self.control_tx.try_send(ControlCommand::ChangeElectrodeSettings(settings)).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
(HardwareConnected::WithMultiplexer, BioImpedanceLeadMode::FourLead) => {
|
(HardwareConnected::WithMultiplexer, BioImpedanceLeadMode::FourLead) => {
|
||||||
render_four_lead(ui, &mut settings.with_multiplexer_4_lead);
|
if render_four_lead(ui, &mut settings.with_multiplexer_4_lead) {
|
||||||
|
self.control_tx.try_send(ControlCommand::ChangeElectrodeSettings(settings)).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
(HardwareConnected::None, _) => {}
|
(HardwareConnected::None, _) => {}
|
||||||
}
|
}
|
||||||
@@ -413,22 +366,23 @@ impl TabViewer {
|
|||||||
ui.add_enabled_ui(!*on, |ui| {
|
ui.add_enabled_ui(!*on, |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Measurement Points:");
|
ui.label("Measurement Points:");
|
||||||
let mut measurement_points = self.measurement_points.lock().unwrap();
|
let mut measurement_points = self.app_state_rx.borrow().sweep_points;
|
||||||
let mut index = MEASUREMENT_POINTS_VARIANTS.iter().position(|&x| x == *measurement_points).unwrap_or(0);
|
let mut index = SWEEP_POINTS_VARIANTS.iter().position(|&x| x == measurement_points).unwrap_or(0);
|
||||||
ComboBox::from_id_salt("MeasurementPoints")
|
ComboBox::from_id_salt("MeasurementPoints")
|
||||||
.width(75.0)
|
.width(75.0)
|
||||||
.show_index(ui, &mut index, MEASUREMENT_POINTS_VARIANTS.len(), |i| {
|
.show_index(ui, &mut index, SWEEP_POINTS_VARIANTS.len(), |i| {
|
||||||
format!("{:?}", MEASUREMENT_POINTS_VARIANTS[i].len())
|
format!("{:?}", SWEEP_POINTS_VARIANTS[i].len())
|
||||||
});
|
});
|
||||||
let new_value = MEASUREMENT_POINTS_VARIANTS[index];
|
let new_value = SWEEP_POINTS_VARIANTS[index];
|
||||||
if *measurement_points != new_value {
|
if measurement_points != new_value {
|
||||||
*measurement_points = new_value;
|
measurement_points = new_value;
|
||||||
info!("Measurement Points setting changed!");
|
self.control_tx.try_send(ControlCommand::ChangeSweepPoints(measurement_points)).unwrap();
|
||||||
|
info!("Sweep Points setting changed!");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
ui.add_enabled_ui(*on, |ui| {
|
ui.add_enabled_ui(*on, |ui| {
|
||||||
let (freq, periods_per_dft_vec) = self.periods_per_dft_sweep.lock().unwrap().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 {
|
||||||
if freq >= 1_000 {
|
if freq >= 1_000 {
|
||||||
@@ -499,7 +453,7 @@ impl TabViewer {
|
|||||||
Layout::top_down(egui::Align::Min),
|
Layout::top_down(egui::Align::Min),
|
||||||
|ui| {
|
|ui| {
|
||||||
// Magnitude
|
// Magnitude
|
||||||
let bode_plot = self.bode_plot.lock().unwrap();
|
let bode_plot = self.measurement_data.bode_plot.lock().unwrap();
|
||||||
Plot::new("bode_mag")
|
Plot::new("bode_mag")
|
||||||
.legend(Legend::default().position(Corner::LeftTop))
|
.legend(Legend::default().position(Corner::LeftTop))
|
||||||
.y_axis_label("Magnitude [Ω]")
|
.y_axis_label("Magnitude [Ω]")
|
||||||
@@ -529,7 +483,7 @@ impl TabViewer {
|
|||||||
Layout::top_down(egui::Align::Min),
|
Layout::top_down(egui::Align::Min),
|
||||||
|ui| {
|
|ui| {
|
||||||
// Phase
|
// Phase
|
||||||
let bode_plot = self.bode_plot.lock().unwrap();
|
let bode_plot = self.measurement_data.bode_plot.lock().unwrap();
|
||||||
Plot::new("bode_phase")
|
Plot::new("bode_phase")
|
||||||
.legend(Legend::default().position(Corner::LeftTop))
|
.legend(Legend::default().position(Corner::LeftTop))
|
||||||
.y_axis_label("Phase [rad]")
|
.y_axis_label("Phase [rad]")
|
||||||
@@ -644,70 +598,45 @@ impl egui_dock::TabViewer for TabViewer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn new(run_impedancemeter_tx: Sender<StartStopSignal>, log_tx: Sender<LoggingSignal>) -> Self {
|
pub fn new(run_impedancemeter_tx: mpsc::Sender<StartStopSignal>,
|
||||||
// Step 1: Initialize shared fields first
|
log_tx: mpsc::Sender<LoggingSignal>,
|
||||||
let magnitude = Arc::new(Mutex::new(0.0));
|
measurement_data: Arc<MeasurementDataState>,
|
||||||
let phase = Arc::new(Mutex::new(0.0));
|
control_tx: mpsc::Sender<ControlCommand>,
|
||||||
let magnitude_series = Arc::new(Mutex::new(TimeSeriesPlot::new()));
|
app_state_rx: watch::Receiver<AppState>,
|
||||||
let phase_series = Arc::new(Mutex::new(TimeSeriesPlot::new()));
|
hardware_state_rx: watch::Receiver<HardwareState>) -> Self {
|
||||||
let bode_plot = Arc::new(Mutex::new(BodePlot::new()));
|
// Step 1: Initialize shared fields first
|
||||||
let single_frequency = Arc::new(Mutex::new(50000));
|
|
||||||
let hardware_connected = Arc::new(Mutex::new(HardwareConnected::None));
|
|
||||||
let lead_mode = Arc::new(Mutex::new(BioImpedanceLeadMode::FourLead));
|
|
||||||
let electrode_settings = Arc::new(Mutex::new(ElectrodeSettings::new()));
|
|
||||||
let dft_num = Arc::new(Mutex::new(IcdDftNum::Num2048));
|
|
||||||
let measurement_points = Arc::new(Mutex::new(MeasurementPointSet::Eighteen));
|
|
||||||
let periods_per_dft = Arc::new(Mutex::new(None));
|
|
||||||
let periods_per_dft_sweep = Arc::new(Mutex::new((MeasurementPointSet::Eighteen.values().to_vec(), None)));
|
|
||||||
let on = Arc::new(Mutex::new(true));
|
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
|
||||||
let tab_viewer = TabViewer {
|
let control_tx_clone = control_tx.clone();
|
||||||
magnitude: magnitude.clone(),
|
let app_state_clone = app_state_rx.clone();
|
||||||
phase: phase.clone(),
|
let hardware_state_clone = hardware_state_rx.clone();
|
||||||
magnitude_series: magnitude_series.clone(),
|
let tab_viewer = TabViewer { measurement_data: measurement_data.clone(),
|
||||||
phase_series: phase_series.clone(),
|
|
||||||
bode_plot: bode_plot.clone(),
|
|
||||||
single_frequency: single_frequency.clone(),
|
|
||||||
hardware_connected: hardware_connected.clone(),
|
|
||||||
lead_mode: lead_mode.clone(),
|
|
||||||
electrode_settings: electrode_settings.clone(),
|
|
||||||
dft_num: dft_num.clone(),
|
|
||||||
measurement_points: measurement_points.clone(),
|
|
||||||
periods_per_dft: periods_per_dft.clone(),
|
|
||||||
periods_per_dft_sweep: periods_per_dft_sweep.clone(),
|
|
||||||
on: on.clone(),
|
on: on.clone(),
|
||||||
show_settings: false,
|
show_settings: false,
|
||||||
show_settings_toggle: None,
|
show_settings_toggle: None,
|
||||||
|
control_tx: control_tx_clone,
|
||||||
|
app_state_rx: app_state_clone,
|
||||||
|
hardware_state_rx: hardware_state_clone
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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,
|
||||||
run_impedancemeter_tx,
|
run_impedancemeter_tx,
|
||||||
log_tx,
|
log_tx,
|
||||||
magnitude,
|
|
||||||
phase,
|
|
||||||
magnitude_series,
|
|
||||||
phase_series,
|
|
||||||
bode_plot,
|
|
||||||
hardware_connected,
|
|
||||||
on,
|
on,
|
||||||
tab_active,
|
|
||||||
data_frequency: Arc::new(AtomicF32::new(0.0)),
|
|
||||||
single_frequency,
|
|
||||||
lead_mode,
|
|
||||||
electrode_settings,
|
|
||||||
dft_num,
|
|
||||||
measurement_points,
|
|
||||||
periods_per_dft,
|
|
||||||
periods_per_dft_sweep,
|
|
||||||
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,
|
||||||
log_marker: String::new(),
|
log_marker: String::new(),
|
||||||
|
measurement_data,
|
||||||
|
control_tx,
|
||||||
|
app_state_rx,
|
||||||
|
hardware_state_rx,
|
||||||
};
|
};
|
||||||
|
|
||||||
// For testing purposes, populate the Bode plot with a sample low-pass filter response
|
// For testing purposes, populate the Bode plot with a sample low-pass filter response
|
||||||
@@ -735,17 +664,17 @@ impl App {
|
|||||||
pub fn update_start_stop(&self) {
|
pub fn update_start_stop(&self) {
|
||||||
match (self.tab_active, *self.on.lock().unwrap()) {
|
match (self.tab_active, *self.on.lock().unwrap()) {
|
||||||
(TabActive::Single, true) => {
|
(TabActive::Single, true) => {
|
||||||
let lead_mode = *self.lead_mode.lock().unwrap();
|
let lead_mode = self.app_state_rx.borrow().lead_mode;
|
||||||
let electrode_config = self.electrode_settings.lock().unwrap().to_electrode_config(*self.hardware_connected.lock().unwrap(), lead_mode);
|
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(
|
if let Err(e) = self.run_impedancemeter_tx.try_send(
|
||||||
StartStopSignal::StartSingle(*self.single_frequency.lock().unwrap(), lead_mode, electrode_config, *self.dft_num.lock().unwrap())) {
|
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);
|
error!("Failed to send start command: {:?}", e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(TabActive::Sweep, true) => {
|
(TabActive::Sweep, true) => {
|
||||||
let lead_mode = *self.lead_mode.lock().unwrap();
|
let lead_mode = self.app_state_rx.borrow().lead_mode;
|
||||||
let electrode_config = self.electrode_settings.lock().unwrap().to_electrode_config(*self.hardware_connected.lock().unwrap(), lead_mode);
|
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.measurement_points.lock().unwrap())) {
|
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);
|
error!("Failed to send start command: {:?}", e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -759,8 +688,8 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset_view(&self) {
|
pub fn reset_view(&self) {
|
||||||
self.magnitude_series.lock().unwrap().clear();
|
self.measurement_data.magnitude_series.lock().unwrap().clear();
|
||||||
self.phase_series.lock().unwrap().clear();
|
self.measurement_data.phase_series.lock().unwrap().clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -769,12 +698,12 @@ 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_connected.lock().unwrap() != HardwareConnected::None;
|
let is_connected = self.hardware_state_rx.borrow().connected != HardwareConnected::None;
|
||||||
|
|
||||||
egui::widgets::global_theme_preference_switch(ui);
|
egui::widgets::global_theme_preference_switch(ui);
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
ui.label(format!("Data rate: {} Hz", self.data_frequency.load(Ordering::Relaxed)));
|
ui.label(format!("Data rate: {} Hz", self.measurement_data.sampling_rate.load(Ordering::Relaxed)));
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
@@ -889,7 +818,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_connected.lock().unwrap() {
|
let (color, tooltip) = match self.hardware_state_rx.borrow().connected {
|
||||||
HardwareConnected::None => {
|
HardwareConnected::None => {
|
||||||
(Color32::DARK_RED, "Disconnected")
|
(Color32::DARK_RED, "Disconnected")
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use eframe::NativeOptions;
|
use eframe::NativeOptions;
|
||||||
use eframe::egui::Vec2;
|
use eframe::egui::Vec2;
|
||||||
|
|
||||||
@@ -9,11 +11,14 @@ use bioz_host_rs::{app::App, signals::LoggingSignal};
|
|||||||
|
|
||||||
use bioz_host_rs::communication::communicate_with_hardware;
|
use bioz_host_rs::communication::communicate_with_hardware;
|
||||||
|
|
||||||
use tokio::sync::mpsc::{self};
|
use tokio::sync::{watch, mpsc};
|
||||||
|
|
||||||
use bioz_host_rs::signals::StartStopSignal;
|
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::control::control_loop;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
SimpleLogger::new().init().expect("Failed to initialize logger");
|
SimpleLogger::new().init().expect("Failed to initialize logger");
|
||||||
@@ -23,26 +28,33 @@ 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
|
||||||
|
let (hardware_state_tx, hardware_state_rx) = watch::channel(HardwareState::default());
|
||||||
|
|
||||||
// Channel to communicate with the communication task.
|
// Channel to communicate with the communication task.
|
||||||
let (run_impedancemeter_tx, run_impedancemeter_rx) = mpsc::channel::<StartStopSignal>(2);
|
let (run_impedancemeter_tx, run_impedancemeter_rx) = mpsc::channel::<StartStopSignal>(2);
|
||||||
let run_impedancemeter_tx_clone = run_impedancemeter_tx.clone();
|
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 control_tx_clone = control_tx.clone();
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
control_loop(control_rx, app_state_tx).await;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Init measurement data state
|
||||||
|
let measurement_data = Arc::new(MeasurementDataState::default());
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
let (log_tx, log_rx) = mpsc::channel::<LoggingSignal>(10);
|
let (log_tx, log_rx) = mpsc::channel::<LoggingSignal>(10);
|
||||||
let log_tx_clone = log_tx.clone();
|
let log_tx_clone = log_tx.clone();
|
||||||
|
|
||||||
let app = App::new(run_impedancemeter_tx, log_tx);
|
let measurement_data_clone = measurement_data.clone();
|
||||||
let magnitude_clone = app.magnitude.clone();
|
|
||||||
let phase_clone = app.phase.clone();
|
|
||||||
let magnitude_series_clone = app.magnitude_series.clone();
|
|
||||||
let phase_series_clone = app.phase_series.clone();
|
|
||||||
let bode_clone = app.bode_plot.clone();
|
|
||||||
let hardware_connected_clone = app.hardware_connected.clone();
|
|
||||||
|
|
||||||
let data_frequency_clone = app.data_frequency.clone();
|
let app = App::new(run_impedancemeter_tx, log_tx, measurement_data_clone, control_tx_clone, app_state_rx, hardware_state_rx);
|
||||||
|
|
||||||
let periods_per_dft = app.periods_per_dft.clone();
|
|
||||||
let periods_per_dft_sweep = app.periods_per_dft_sweep.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,15 +75,8 @@ async fn main() {
|
|||||||
rt.block_on(communicate_with_hardware(
|
rt.block_on(communicate_with_hardware(
|
||||||
run_impedancemeter_rx,
|
run_impedancemeter_rx,
|
||||||
run_impedancemeter_tx_clone,
|
run_impedancemeter_tx_clone,
|
||||||
magnitude_clone,
|
measurement_data,
|
||||||
phase_clone,
|
hardware_state_tx,
|
||||||
magnitude_series_clone,
|
|
||||||
phase_series_clone,
|
|
||||||
bode_clone,
|
|
||||||
hardware_connected_clone,
|
|
||||||
data_frequency_clone,
|
|
||||||
periods_per_dft,
|
|
||||||
periods_per_dft_sweep,
|
|
||||||
gui_logging_state_2,
|
gui_logging_state_2,
|
||||||
log_tx_clone,
|
log_tx_clone,
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use bioz_icd_rs::{
|
|||||||
BioImpedanceLeadMode, ElectrodeConfiguration, GetMultiplexerCapabilityEndpoint, GetUniqueIdEndpoint, ImpedanceInitResult, PingEndpoint, SetGreenLedEndpoint, SingleImpedanceStartRequest, StartSingleImpedanceEndpoint, StartSweepImpedanceEndpoint, StopImpedanceEndpoint, SweepImpedanceInitResult, SweepImpedanceStartRequest
|
BioImpedanceLeadMode, ElectrodeConfiguration, GetMultiplexerCapabilityEndpoint, GetUniqueIdEndpoint, ImpedanceInitResult, PingEndpoint, SetGreenLedEndpoint, SingleImpedanceStartRequest, StartSingleImpedanceEndpoint, StartSweepImpedanceEndpoint, StopImpedanceEndpoint, SweepImpedanceInitResult, SweepImpedanceStartRequest
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::icd::{IcdDftNum, MeasurementPointSet, MultiplexerCapability};
|
use crate::icd::{IcdDftNum, SweepPoints, MultiplexerCapability};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct WorkbookClient {
|
pub struct WorkbookClient {
|
||||||
@@ -84,7 +84,7 @@ impl WorkbookClient {
|
|||||||
&self,
|
&self,
|
||||||
lead_mode: BioImpedanceLeadMode,
|
lead_mode: BioImpedanceLeadMode,
|
||||||
electrode_config: Option<ElectrodeConfiguration>,
|
electrode_config: Option<ElectrodeConfiguration>,
|
||||||
points: MeasurementPointSet,
|
points: SweepPoints,
|
||||||
) -> Result<SweepImpedanceInitResult, WorkbookError<Infallible>> {
|
) -> Result<SweepImpedanceInitResult, WorkbookError<Infallible>> {
|
||||||
let response = self.client
|
let response = self.client
|
||||||
.send_resp::<StartSweepImpedanceEndpoint>(&SweepImpedanceStartRequest { lead_mode, electrode_config, points })
|
.send_resp::<StartSweepImpedanceEndpoint>(&SweepImpedanceStartRequest { lead_mode, electrode_config, points })
|
||||||
|
|||||||
@@ -3,14 +3,16 @@ use std::time::SystemTime;
|
|||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
|
|
||||||
use tokio::select;
|
use tokio::select;
|
||||||
use tokio::sync::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 atomic_float::AtomicF32;
|
||||||
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use bioz_icd_rs::{MeasurementPointSet, MultiplexerCapability};
|
use bioz_icd_rs::{SweepPoints, MultiplexerCapability};
|
||||||
|
|
||||||
|
use crate::state::{HardwareState, MeasurementDataState};
|
||||||
|
|
||||||
use crate::icd;
|
use crate::icd;
|
||||||
use crate::client::WorkbookClient;
|
use crate::client::WorkbookClient;
|
||||||
@@ -20,29 +22,24 @@ use crate::plot::{TimeSeriesPlot, BodePlot};
|
|||||||
|
|
||||||
use crate::signals::{LoggingSignal, StartStopSignal};
|
use crate::signals::{LoggingSignal, StartStopSignal};
|
||||||
|
|
||||||
use crate::app::HardwareConnected;
|
use crate::state::HardwareConnected;
|
||||||
|
|
||||||
pub async fn communicate_with_hardware(
|
pub async fn communicate_with_hardware(
|
||||||
mut run_impedancemeter_rx: Receiver<StartStopSignal>,
|
mut run_impedancemeter_rx: Receiver<StartStopSignal>,
|
||||||
run_impedancemeter_tx: Sender<StartStopSignal>,
|
run_impedancemeter_tx: Sender<StartStopSignal>,
|
||||||
magnitude: Arc<Mutex<f32>>,
|
measurement_data: Arc<MeasurementDataState>,
|
||||||
phase: Arc<Mutex<f32>>,
|
hardware_state_tx: watch::Sender<HardwareState>,
|
||||||
magnitude_series: Arc<Mutex<TimeSeriesPlot>>,
|
|
||||||
phase_series: Arc<Mutex<TimeSeriesPlot>>,
|
|
||||||
bode_series: Arc<Mutex<BodePlot>>,
|
|
||||||
connected: Arc<Mutex<HardwareConnected>>,
|
|
||||||
data_frequency: Arc<AtomicF32>,
|
|
||||||
periods_per_dft: Arc<Mutex<Option<f32>>>,
|
|
||||||
periods_per_dft_sweep: Arc<Mutex<(Vec<u32>, Option<Vec<f32>>)>>,
|
|
||||||
gui_logging_state: Arc<Mutex<LoggingStates>>,
|
gui_logging_state: Arc<Mutex<LoggingStates>>,
|
||||||
log_tx: Sender<LoggingSignal>,
|
log_tx: Sender<LoggingSignal>,
|
||||||
) {
|
) {
|
||||||
let data_counter = Arc::new(AtomicU32::new(0));
|
let data_counter = Arc::new(AtomicU32::new(0));
|
||||||
let data_counter_clone = data_counter.clone();
|
let data_counter_clone = data_counter.clone();
|
||||||
|
let sampling_rate_clone = measurement_data.sampling_rate.clone();
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
||||||
data_frequency.store(data_counter.load(Ordering::Relaxed) as f32, Ordering::Relaxed);
|
sampling_rate_clone.store(data_counter.load(Ordering::Relaxed) as f32, Ordering::Relaxed);
|
||||||
data_counter.store(0, Ordering::Relaxed);
|
data_counter.store(0, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -54,6 +51,8 @@ pub async fn communicate_with_hardware(
|
|||||||
let settings = Arc::new(Mutex::new(Settings::default()));
|
let settings = Arc::new(Mutex::new(Settings::default()));
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
let mut hardware_state = HardwareState::default();
|
||||||
|
|
||||||
let workbook_client = match WorkbookClient::new() {
|
let workbook_client = match WorkbookClient::new() {
|
||||||
Ok(client) => {
|
Ok(client) => {
|
||||||
info!("Connected to hardware successfully.");
|
info!("Connected to hardware successfully.");
|
||||||
@@ -62,11 +61,13 @@ pub async fn communicate_with_hardware(
|
|||||||
}
|
}
|
||||||
match client.get_device_info().await.unwrap() {
|
match client.get_device_info().await.unwrap() {
|
||||||
MultiplexerCapability::Absent => {
|
MultiplexerCapability::Absent => {
|
||||||
*connected.lock().unwrap() = HardwareConnected::WithoutMultiplexer;
|
hardware_state.connected = HardwareConnected::WithoutMultiplexer;
|
||||||
|
hardware_state_tx.send(hardware_state.clone()).unwrap();
|
||||||
info!("Connected device: Without Multiplexer");
|
info!("Connected device: Without Multiplexer");
|
||||||
},
|
},
|
||||||
MultiplexerCapability::Present => {
|
MultiplexerCapability::Present => {
|
||||||
*connected.lock().unwrap() = HardwareConnected::WithMultiplexer;
|
hardware_state.connected = HardwareConnected::WithMultiplexer;
|
||||||
|
hardware_state_tx.send(hardware_state.clone()).unwrap();
|
||||||
info!("Connected device: With Multiplexer");
|
info!("Connected device: With Multiplexer");
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -86,7 +87,7 @@ pub async fn communicate_with_hardware(
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let data = (magnitude_series.clone(), phase_series.clone(), magnitude.clone(), phase.clone());
|
let data = (measurement_data.magnitude_series.clone(), measurement_data.phase_series.clone(), measurement_data.magnitude.clone(), measurement_data.phase.clone());
|
||||||
let data_counter_clone_single = data_counter_clone.clone();
|
let data_counter_clone_single = data_counter_clone.clone();
|
||||||
|
|
||||||
// Clone log_tx for the task
|
// Clone log_tx for the task
|
||||||
@@ -139,7 +140,7 @@ pub async fn communicate_with_hardware(
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let data = bode_series.clone();
|
let data = measurement_data.bode_plot.clone();
|
||||||
let data_counter_clone_sweep = data_counter_clone.clone();
|
let data_counter_clone_sweep = data_counter_clone.clone();
|
||||||
|
|
||||||
// Clone log_tx for the task
|
// Clone log_tx for the task
|
||||||
@@ -151,30 +152,30 @@ pub async fn communicate_with_hardware(
|
|||||||
|
|
||||||
match val.points {
|
match val.points {
|
||||||
|
|
||||||
MeasurementPointSet::Eight => {
|
SweepPoints::Eight => {
|
||||||
let magnitudes: Vec<f32> = val.magnitudes_8.into_iter().collect();
|
let magnitudes: Vec<f32> = val.magnitudes_8.into_iter().collect();
|
||||||
let phases: Vec<f32> = val.phases_8.into_iter().collect();
|
let phases: Vec<f32> = val.phases_8.into_iter().collect();
|
||||||
{
|
{
|
||||||
let mut bode_plot = data.lock().unwrap();
|
let mut bode_plot = data.lock().unwrap();
|
||||||
bode_plot.update_magnitudes(MeasurementPointSet::Eight, magnitudes.clone());
|
bode_plot.update_magnitudes(SweepPoints::Eight, magnitudes.clone());
|
||||||
bode_plot.update_phases(MeasurementPointSet::Eight, phases.clone());
|
bode_plot.update_phases(SweepPoints::Eight, phases.clone());
|
||||||
}
|
}
|
||||||
if *gui_logging_state_clone.lock().unwrap() == LoggingStates::Logging {
|
if *gui_logging_state_clone.lock().unwrap() == LoggingStates::Logging {
|
||||||
if let Err(e) = log_tx_clone.send(LoggingSignal::SweepImpedance(SystemTime::now(), MeasurementPointSet::Eight.values().to_vec(), magnitudes.clone(), phases.clone())).await {
|
if let Err(e) = log_tx_clone.send(LoggingSignal::SweepImpedance(SystemTime::now(), SweepPoints::Eight.values().to_vec(), magnitudes.clone(), phases.clone())).await {
|
||||||
error!("Failed to send logging signal: {:?}", e);
|
error!("Failed to send logging signal: {:?}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
MeasurementPointSet::Eighteen => {
|
SweepPoints::Eighteen => {
|
||||||
let magnitudes: Vec<f32> = val.magnitudes_18.into_iter().collect();
|
let magnitudes: Vec<f32> = val.magnitudes_18.into_iter().collect();
|
||||||
let phases: Vec<f32> = val.phases_18.into_iter().collect();
|
let phases: Vec<f32> = val.phases_18.into_iter().collect();
|
||||||
{
|
{
|
||||||
let mut bode_plot = data.lock().unwrap();
|
let mut bode_plot = data.lock().unwrap();
|
||||||
bode_plot.update_magnitudes(MeasurementPointSet::Eighteen, magnitudes.clone());
|
bode_plot.update_magnitudes(SweepPoints::Eighteen, magnitudes.clone());
|
||||||
bode_plot.update_phases(MeasurementPointSet::Eighteen, phases.clone());
|
bode_plot.update_phases(SweepPoints::Eighteen, phases.clone());
|
||||||
}
|
}
|
||||||
if *gui_logging_state_clone.lock().unwrap() == LoggingStates::Logging {
|
if *gui_logging_state_clone.lock().unwrap() == LoggingStates::Logging {
|
||||||
if let Err(e) = log_tx_clone.send(LoggingSignal::SweepImpedance(SystemTime::now(), MeasurementPointSet::Eighteen.values().to_vec(), magnitudes.clone(), phases.clone())).await {
|
if let Err(e) = log_tx_clone.send(LoggingSignal::SweepImpedance(SystemTime::now(), SweepPoints::Eighteen.values().to_vec(), magnitudes.clone(), phases.clone())).await {
|
||||||
error!("Failed to send logging signal: {:?}", e);
|
error!("Failed to send logging signal: {:?}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,7 +196,8 @@ pub async fn communicate_with_hardware(
|
|||||||
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));
|
||||||
*periods_per_dft.lock().unwrap() = Some(periods);
|
hardware_state.periods_per_dft = Some(periods);
|
||||||
|
hardware_state_tx.send(hardware_state.clone()).unwrap();
|
||||||
|
|
||||||
// When logging add electrode configuration to logging file
|
// When logging add electrode configuration to logging file
|
||||||
if *gui_logging_state.lock().unwrap() == LoggingStates::Logging {
|
if *gui_logging_state.lock().unwrap() == LoggingStates::Logging {
|
||||||
@@ -206,11 +208,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);
|
||||||
*periods_per_dft.lock().unwrap() = None;
|
hardware_state.periods_per_dft = None;
|
||||||
|
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);
|
||||||
*periods_per_dft.lock().unwrap() = None;
|
hardware_state.periods_per_dft = None;
|
||||||
|
hardware_state_tx.send(hardware_state.clone()).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -220,11 +224,14 @@ pub async fn communicate_with_hardware(
|
|||||||
settings.lock().unwrap().mode = Some(StartStopSignal::StartSweep(lead_mode, electrode_config, num_points));
|
settings.lock().unwrap().mode = Some(StartStopSignal::StartSweep(lead_mode, electrode_config, num_points));
|
||||||
info!("Sweep Impedancemeter started.");
|
info!("Sweep Impedancemeter started.");
|
||||||
match num_points {
|
match num_points {
|
||||||
MeasurementPointSet::Eight => {
|
SweepPoints::Eight => {
|
||||||
*periods_per_dft_sweep.lock().unwrap() = (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()));
|
||||||
|
hardware_state_tx.send(hardware_state.clone()).unwrap();
|
||||||
|
|
||||||
},
|
},
|
||||||
MeasurementPointSet::Eighteen => {
|
SweepPoints::Eighteen => {
|
||||||
*periods_per_dft_sweep.lock().unwrap() = (num_points.values().iter().copied().collect(), Some(periods.periods_per_dft_18.into_iter().collect()));
|
hardware_state.periods_per_dft_sweep = (num_points.values().iter().copied().collect(), Some(periods.periods_per_dft_18.into_iter().collect()));
|
||||||
|
hardware_state_tx.send(hardware_state.clone()).unwrap();
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,11 +244,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);
|
||||||
*periods_per_dft_sweep.lock().unwrap() = (num_points.values().iter().copied().collect(), None);
|
hardware_state.periods_per_dft_sweep = (num_points.values().iter().copied().collect(), None);
|
||||||
|
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);
|
||||||
*periods_per_dft_sweep.lock().unwrap() = (num_points.values().iter().copied().collect(), None);
|
hardware_state.periods_per_dft_sweep = (num_points.values().iter().copied().collect(), None);
|
||||||
|
hardware_state_tx.send(hardware_state.clone()).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -250,9 +259,10 @@ 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);
|
||||||
*periods_per_dft.lock().unwrap() = None;
|
hardware_state.periods_per_dft = None;
|
||||||
let (freq, _) = periods_per_dft_sweep.lock().unwrap().clone();
|
let (freq, _) = hardware_state.periods_per_dft_sweep.clone();
|
||||||
*periods_per_dft_sweep.lock().unwrap() = (freq, None);
|
hardware_state.periods_per_dft_sweep = (freq, None);
|
||||||
|
hardware_state_tx.send(hardware_state.clone()).unwrap();
|
||||||
info!("Impedancemeter stopped.");
|
info!("Impedancemeter stopped.");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -272,7 +282,8 @@ pub async fn communicate_with_hardware(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
info!("Communication with hardware ended.");
|
info!("Communication with hardware ended.");
|
||||||
*connected.lock().unwrap() = HardwareConnected::None;
|
hardware_state.connected = HardwareConnected::None;
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
40
src/control.rs
Normal file
40
src/control.rs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
use log::info;
|
||||||
|
use tokio::sync::{mpsc::Receiver, watch::Sender};
|
||||||
|
use crate::{state::{AppState, ControlCommand}};
|
||||||
|
|
||||||
|
pub async fn control_loop(mut rx: Receiver<ControlCommand>, tx: Sender<AppState>) {
|
||||||
|
let mut state = AppState::default();
|
||||||
|
|
||||||
|
while let Some(cmd) = rx.recv().await {
|
||||||
|
match cmd {
|
||||||
|
ControlCommand::SetFrequency(freq) => {
|
||||||
|
state.single_frequency = freq;
|
||||||
|
info!("Frequency setting changed to {}!", freq);
|
||||||
|
}
|
||||||
|
ControlCommand::ChangeLeadMode(lead_mode) => {
|
||||||
|
state.lead_mode = lead_mode;
|
||||||
|
info!("Lead mode changed to {:?}!", lead_mode);
|
||||||
|
}
|
||||||
|
ControlCommand::ChangeDftNum(dft_num) => {
|
||||||
|
state.dft_num = dft_num;
|
||||||
|
info!("DFT number changed to {:?}!", dft_num);
|
||||||
|
}
|
||||||
|
ControlCommand::ChangeElectrodeSettings(electrode_settings) => {
|
||||||
|
state.electrode_settings = electrode_settings;
|
||||||
|
info!("Electrode settings changed to {:?}!", electrode_settings);
|
||||||
|
}
|
||||||
|
ControlCommand::ChangeSweepPoints(sweep_points) => {
|
||||||
|
state.sweep_points = sweep_points;
|
||||||
|
info!("Sweep points changed to {:?}!", sweep_points);
|
||||||
|
}
|
||||||
|
ControlCommand::Start => {
|
||||||
|
info!("Starting impedance hardware...");
|
||||||
|
}
|
||||||
|
ControlCommand::Stop => {
|
||||||
|
info!("Stopping impedance hardware...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.send(state.clone()).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,9 @@ pub mod app;
|
|||||||
pub mod communication;
|
pub mod communication;
|
||||||
pub mod tcp;
|
pub mod tcp;
|
||||||
pub mod plot;
|
pub mod plot;
|
||||||
|
pub mod control;
|
||||||
pub mod signals;
|
pub mod signals;
|
||||||
|
pub mod state;
|
||||||
pub mod logging;
|
pub mod logging;
|
||||||
pub use bioz_icd_rs as icd;
|
pub use bioz_icd_rs as icd;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::collections::VecDeque;
|
|||||||
|
|
||||||
use egui_plot::{PlotPoint, PlotPoints};
|
use egui_plot::{PlotPoint, PlotPoints};
|
||||||
|
|
||||||
use bioz_icd_rs::MeasurementPointSet;
|
use bioz_icd_rs::SweepPoints;
|
||||||
|
|
||||||
pub struct TimeSeriesPlot {
|
pub struct TimeSeriesPlot {
|
||||||
pub values: VecDeque<PlotPoint>,
|
pub values: VecDeque<PlotPoint>,
|
||||||
@@ -63,13 +63,13 @@ impl BodePlot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_magnitudes(&mut self, points: MeasurementPointSet, magnitudes: Vec<f32>) {
|
pub fn update_magnitudes(&mut self, points: SweepPoints, magnitudes: Vec<f32>) {
|
||||||
let freqs = points.values().to_vec();
|
let freqs = points.values().to_vec();
|
||||||
// self.magnitudes = freqs.into_iter().zip(magnitudes.into_iter()).map(|(f, m)| PlotPoint::new(f.log10(), 20.0 * m.log10() as f32)).collect();
|
// self.magnitudes = freqs.into_iter().zip(magnitudes.into_iter()).map(|(f, m)| PlotPoint::new(f.log10(), 20.0 * m.log10() as f32)).collect();
|
||||||
self.magnitudes = freqs.into_iter().zip(magnitudes.into_iter()).map(|(f, m)| PlotPoint::new((f as f32).log10(), m)).collect(); // Convert to f32 first due to rouding errors
|
self.magnitudes = freqs.into_iter().zip(magnitudes.into_iter()).map(|(f, m)| PlotPoint::new((f as f32).log10(), m)).collect(); // Convert to f32 first due to rouding errors
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_phases(&mut self, points: MeasurementPointSet, phases: Vec<f32>) {
|
pub fn update_phases(&mut self, points: SweepPoints, phases: Vec<f32>) {
|
||||||
let freqs = points.values().to_vec();
|
let freqs = points.values().to_vec();
|
||||||
self.phases = freqs.into_iter().zip(phases.into_iter()).map(|(f, p)| PlotPoint::new((f as f32).log10(), p)).collect(); // Convert to f32 first due to rouding errors
|
self.phases = freqs.into_iter().zip(phases.into_iter()).map(|(f, p)| PlotPoint::new((f as f32).log10(), p)).collect(); // Convert to f32 first due to rouding errors
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use crate::icd::{BioImpedanceLeadMode, IcdDftNum, ElectrodeConfiguration, MeasurementPointSet};
|
use crate::icd::{BioImpedanceLeadMode, IcdDftNum, ElectrodeConfiguration, SweepPoints};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum StartStopSignal {
|
pub enum StartStopSignal {
|
||||||
StartSingle(u32, BioImpedanceLeadMode, Option<ElectrodeConfiguration>, IcdDftNum), // frequency in Hz, lead mode, electrode configuration, DFT number
|
StartSingle(u32, BioImpedanceLeadMode, Option<ElectrodeConfiguration>, IcdDftNum), // frequency in Hz, lead mode, electrode configuration, DFT number
|
||||||
StartSweep(BioImpedanceLeadMode, Option<ElectrodeConfiguration>, MeasurementPointSet), // lead mode, electrode configuration, number of points per measurement
|
StartSweep(BioImpedanceLeadMode, Option<ElectrodeConfiguration>, SweepPoints), // lead mode, electrode configuration, number of points per measurement
|
||||||
Stop,
|
Stop,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
135
src/state.rs
Normal file
135
src/state.rs
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
use std::default;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use crate::icd::{BioImpedanceLeadMode, IcdDftNum, SweepPoints, ElectrodeConfiguration, ElectrodeOptionsWithMultiplexer};
|
||||||
|
|
||||||
|
use crate::plot::{TimeSeriesPlot, BodePlot};
|
||||||
|
|
||||||
|
use atomic_float::AtomicF32;
|
||||||
|
|
||||||
|
// Measurement data state shared across the app
|
||||||
|
pub struct MeasurementDataState {
|
||||||
|
pub magnitude: Arc<Mutex<f32>>,
|
||||||
|
pub phase: Arc<Mutex<f32>>,
|
||||||
|
pub magnitude_series: Arc<Mutex<TimeSeriesPlot>>,
|
||||||
|
pub phase_series: Arc<Mutex<TimeSeriesPlot>>,
|
||||||
|
pub bode_plot: Arc<Mutex<BodePlot>>,
|
||||||
|
pub sampling_rate: Arc<AtomicF32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MeasurementDataState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
magnitude: Arc::new(Mutex::new(0.0)),
|
||||||
|
phase: Arc::new(Mutex::new(0.0)),
|
||||||
|
magnitude_series: Arc::new(Mutex::new(TimeSeriesPlot::new())),
|
||||||
|
phase_series: Arc::new(Mutex::new(TimeSeriesPlot::new())),
|
||||||
|
bode_plot: Arc::new(Mutex::new(BodePlot::new())),
|
||||||
|
sampling_rate: Arc::new(AtomicF32::new(0.0)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum HardwareConnected {
|
||||||
|
None,
|
||||||
|
WithoutMultiplexer,
|
||||||
|
WithMultiplexer,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ControlCommand {
|
||||||
|
SetFrequency(u32),
|
||||||
|
ChangeLeadMode(BioImpedanceLeadMode),
|
||||||
|
ChangeDftNum(IcdDftNum),
|
||||||
|
ChangeElectrodeSettings(ElectrodeSettings),
|
||||||
|
ChangeSweepPoints(SweepPoints),
|
||||||
|
Start,
|
||||||
|
Stop,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct ElectrodeSettings {
|
||||||
|
pub with_multiplexer_2_lead: [ElectrodeOptionsWithMultiplexer; 2],
|
||||||
|
pub with_multiplexer_4_lead: [ElectrodeOptionsWithMultiplexer; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ElectrodeSettings {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
with_multiplexer_2_lead: [
|
||||||
|
ElectrodeOptionsWithMultiplexer::E1, // Electrode+ default
|
||||||
|
ElectrodeOptionsWithMultiplexer::E23, // Electrode- default
|
||||||
|
],
|
||||||
|
with_multiplexer_4_lead: [
|
||||||
|
ElectrodeOptionsWithMultiplexer::E1, // I+ default
|
||||||
|
ElectrodeOptionsWithMultiplexer::E23, // I- default
|
||||||
|
ElectrodeOptionsWithMultiplexer::E10, // V+ default
|
||||||
|
ElectrodeOptionsWithMultiplexer::E12, // V- default
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_electrode_config(
|
||||||
|
&self,
|
||||||
|
hardware_connected: HardwareConnected,
|
||||||
|
lead_mode: BioImpedanceLeadMode,
|
||||||
|
) -> Option<ElectrodeConfiguration> {
|
||||||
|
if hardware_connected != HardwareConnected::WithMultiplexer {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
match lead_mode {
|
||||||
|
BioImpedanceLeadMode::TwoLead => {
|
||||||
|
let [a, b] = self.with_multiplexer_2_lead;
|
||||||
|
Some(ElectrodeConfiguration::WithMultiplexer2Lead(a, b))
|
||||||
|
}
|
||||||
|
BioImpedanceLeadMode::FourLead => {
|
||||||
|
let [a, b, c, d] = self.with_multiplexer_4_lead;
|
||||||
|
Some(ElectrodeConfiguration::WithMultiplexer4Lead(a, b, c, d))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Application state that can be shared across the app and updated by the control loop
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct AppState {
|
||||||
|
pub single_frequency: u32,
|
||||||
|
pub lead_mode: BioImpedanceLeadMode,
|
||||||
|
pub dft_num: IcdDftNum,
|
||||||
|
pub electrode_settings: ElectrodeSettings,
|
||||||
|
pub sweep_points: SweepPoints,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AppState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
single_frequency: 50000,
|
||||||
|
lead_mode: BioImpedanceLeadMode::TwoLead,
|
||||||
|
dft_num: IcdDftNum::Num2048,
|
||||||
|
electrode_settings: ElectrodeSettings::new(),
|
||||||
|
sweep_points: SweepPoints::Eighteen,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hardware state that can be shared across the app and updated by the hardware loop
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct HardwareState {
|
||||||
|
pub connected: HardwareConnected,
|
||||||
|
// pub running: bool,
|
||||||
|
pub periods_per_dft: Option<f32>,
|
||||||
|
pub periods_per_dft_sweep: (Vec<u32>, Option<Vec<f32>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for HardwareState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
connected: HardwareConnected::None,
|
||||||
|
// running: false,
|
||||||
|
periods_per_dft: None,
|
||||||
|
periods_per_dft_sweep: (SweepPoints::Eighteen.values().to_vec(), None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user