Add feedback to controller after hardware commands.

This commit is contained in:
2026-08-06 17:59:39 +02:00
parent b875e988a4
commit 0b07b52764
3 changed files with 160 additions and 38 deletions

View File

@@ -9,7 +9,7 @@ use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use bioz_icd_rs::{SweepPoints, MultiplexerCapability}; use bioz_icd_rs::{BioImpedanceLeadMode, ElectrodeConfiguration, IcdDftNum, MultiplexerCapability, SweepPoints};
use crate::state::{HardwareState, MeasurementDataState}; use crate::state::{HardwareState, MeasurementDataState};
@@ -42,9 +42,23 @@ pub async fn communicate_with_hardware(
} }
}); });
#[derive(Default, Clone, Copy)] #[derive(Clone, Debug, Default)]
struct Settings { struct Settings {
mode: Option<StartStopSignal>, mode: Option<MeasurementMode>,
}
#[derive(Clone, Debug)]
enum MeasurementMode {
Single {
freq: u32,
lead_mode: BioImpedanceLeadMode,
electrode_config: Option<ElectrodeConfiguration>,
dft_num: IcdDftNum,
},
Sweep {
lead_mode: BioImpedanceLeadMode,
electrode_config: Option<ElectrodeConfiguration>,
num_points: SweepPoints,
},
} }
let settings = Arc::new(Mutex::new(Settings::default())); let settings = Arc::new(Mutex::new(Settings::default()));
@@ -54,9 +68,45 @@ pub async fn communicate_with_hardware(
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.");
if let Some(mode) = settings.lock().unwrap().mode { let mode = {
hardware_control_tx.send(mode).await.unwrap(); let settings = settings.lock().unwrap();
settings.mode.clone()
};
// If there was a previous mode, attempt to reconnect with the same settings
if let Some(mode) = mode {
let command = match mode {
MeasurementMode::Single {
freq,
lead_mode,
electrode_config,
dft_num,
} => StartStopSignal::StartSingle(
freq,
lead_mode,
electrode_config,
dft_num,
None,
),
MeasurementMode::Sweep {
lead_mode,
electrode_config,
num_points,
} => StartStopSignal::StartSweep(
lead_mode,
electrode_config,
num_points,
None,
),
};
hardware_control_tx.send(command).await.unwrap();
// Do not wait for the start confirmation, as the select! loop is not running yet (see below)
} }
// Check the multiplexer capability of the connected device
match client.get_device_info().await.unwrap() { match client.get_device_info().await.unwrap() {
MultiplexerCapability::Absent => { MultiplexerCapability::Absent => {
hardware_state.hardware_connected = HardwareConnected::WithoutMultiplexer; hardware_state.hardware_connected = HardwareConnected::WithoutMultiplexer;
@@ -112,9 +162,9 @@ pub async fn communicate_with_hardware(
} }
// Send logging signal // Send logging signal
if *logging_state_rx_clone.borrow() == LoggingState::Logging { if *logging_state_rx_clone.borrow() == LoggingState::Logging {
let settings = *settings_clone.lock().unwrap(); let settings = settings_clone.lock().unwrap();
match settings.mode { match settings.mode {
Some(StartStopSignal::StartSingle(freq, _, _, _)) => { Some(MeasurementMode::Single { freq, .. }) => {
if let Err(e) = logging_control_tx_clone.try_send(LoggingSignal::SingleImpedance(SystemTime::now(), freq, val.magnitude, val.phase)) { if let Err(e) = logging_control_tx_clone.try_send(LoggingSignal::SingleImpedance(SystemTime::now(), freq, val.magnitude, val.phase)) {
error!("Failed to send logging signal: {:?}", e); error!("Failed to send logging signal: {:?}", e);
} }
@@ -201,17 +251,28 @@ pub async fn communicate_with_hardware(
let logging_control_tx_clone = logging_control_tx.clone(); let logging_control_tx_clone = logging_control_tx.clone();
loop { loop {
select! { select! {
Some(frequency) = hardware_control_rx.recv() => { Some(start_stop_signal) = hardware_control_rx.recv() => {
match frequency { match start_stop_signal {
StartStopSignal::StartSingle(freq, lead_mode, electrode_config, dft_num) => { StartStopSignal::StartSingle(freq, lead_mode, electrode_config, dft_num, start_tx) => {
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));
// Store reconnect information only
settings.lock().unwrap().mode = Some(MeasurementMode::Single {
freq,
lead_mode,
electrode_config,
dft_num,
});
hardware_state.running = true; 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();
// Send confirmation back to the caller
if let Some(start_tx) = start_tx {
let _ = start_tx.send(());
}
// When logging add electrode configuration to logging file // When logging add electrode configuration to logging file
if *logging_state_rx.borrow() == LoggingState::Logging { if *logging_state_rx.borrow() == LoggingState::Logging {
if let Err(e) = logging_control_tx_clone.try_send(LoggingSignal::ElectrodeCongiguration(electrode_config)) { if let Err(e) = logging_control_tx_clone.try_send(LoggingSignal::ElectrodeCongiguration(electrode_config)) {
@@ -233,11 +294,18 @@ pub async fn communicate_with_hardware(
} }
} }
}, },
StartStopSignal::StartSweep(lead_mode, electrode_config, num_points) => { StartStopSignal::StartSweep(lead_mode, electrode_config, num_points, start_tx) => {
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)) => {
info!("Sweep Impedancemeter started."); info!("Sweep Impedancemeter started.");
settings.lock().unwrap().mode = Some(StartStopSignal::StartSweep(lead_mode, electrode_config, num_points));
// Store reconnect information only
settings.lock().unwrap().mode = Some(MeasurementMode::Sweep {
lead_mode,
electrode_config,
num_points,
});
hardware_state.running = true; hardware_state.running = true;
match num_points { match num_points {
SweepPoints::Partial => { SweepPoints::Partial => {
@@ -250,7 +318,10 @@ pub async fn communicate_with_hardware(
hardware_state_tx.send(hardware_state.clone()).unwrap(); hardware_state_tx.send(hardware_state.clone()).unwrap();
}, },
} }
// Send confirmation back to the caller
if let Some(start_tx) = start_tx {
let _ = start_tx.send(());
}
// When logging add electrode configuration to logging file // When logging add electrode configuration to logging file
if *logging_state_rx.borrow() == LoggingState::Logging { if *logging_state_rx.borrow() == LoggingState::Logging {
if let Err(e) = logging_control_tx_clone.try_send(LoggingSignal::ElectrodeCongiguration(electrode_config)) { if let Err(e) = logging_control_tx_clone.try_send(LoggingSignal::ElectrodeCongiguration(electrode_config)) {
@@ -272,17 +343,20 @@ pub async fn communicate_with_hardware(
} }
} }
}, },
StartStopSignal::Stop => { StartStopSignal::Stop(stop_tx) => {
if let Err(e) = workbook_client.stop_impedancemeter().await { if let Err(e) = workbook_client.stop_impedancemeter().await {
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 = None;
hardware_state.running = false; 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);
hardware_state_tx.send(hardware_state.clone()).unwrap(); hardware_state_tx.send(hardware_state.clone()).unwrap();
info!("Impedancemeter stopped."); info!("Impedancemeter stopped.");
stop_tx.send(()).unwrap_or_else(|e| {
error!("Failed to send stop confirmation: {:?}", e);
});
} }
}, },
} }

View File

@@ -1,5 +1,5 @@
use log::info; use log::info;
use tokio::sync::{mpsc, watch}; use tokio::sync::{mpsc, watch, oneshot};
use crate::{app::TabActive, signals::StartStopSignal, state::{AppState, ControlCommand, HardwareConnected, HardwareState}}; use crate::{app::TabActive, signals::StartStopSignal, state::{AppState, ControlCommand, HardwareConnected, HardwareState}};
use crate::state::Mode; use crate::state::Mode;
@@ -37,43 +37,82 @@ pub async fn app_control_loop(mut app_control_rx: mpsc::Receiver<ControlCommand>
info!("Active tab changed to {:?}!", tab_active); info!("Active tab changed to {:?}!", tab_active);
} }
ControlCommand::Start(mode) => { ControlCommand::Start(mode) => {
if hardware_state_rx.borrow().hardware_connected == HardwareConnected::None { // Snapshot the current hardware state to avoid holding a `watch::Ref`
// (which is !Send) across `.await` points.
let hw = hardware_state_rx.borrow().clone();
if hw.hardware_connected == HardwareConnected::None {
info!("Cannot start measurement: No hardware connected!"); info!("Cannot start measurement: No hardware connected!");
continue; continue;
} }
if hardware_state_rx.borrow().running { if hw.running {
info!("Measurement already running, stopping first..."); info!("Measurement already running, stopping first...");
hardware_control_tx.try_send(StartStopSignal::Stop).unwrap();
let (stop_tx, stop_rx) = oneshot::channel();
if let Err(e) = hardware_control_tx.send(StartStopSignal::Stop(stop_tx)).await {
info!("Failed to send stop command: {:?}", e);
}
// Wait for the stop confirmation
if let Err(e) = stop_rx.await {
info!("Failed to receive stop confirmation: {:?}", e);
}
} }
info!("Starting impedance hardware with mode {:?}...", mode); info!("Starting impedance hardware with mode {:?}...", mode);
state.mode = mode; state.mode = mode;
match (mode, hardware_state_rx.borrow().hardware_connected) { match (mode, hw.hardware_connected) {
(Mode::Single, HardwareConnected::WithoutMultiplexer) => { (Mode::Single, HardwareConnected::WithoutMultiplexer) => {
info!("Starting single measurement..."); info!("Starting single measurement...");
state.tab_active = TabActive::Single; state.tab_active = TabActive::Single;
hardware_control_tx.try_send(StartStopSignal::StartSingle( let (start_tx, start_rx) = oneshot::channel();
state.single_frequency, state.lead_mode, None, state.dft_num)).unwrap(); if let Err(e) = hardware_control_tx.send(StartStopSignal::StartSingle(
state.single_frequency, state.lead_mode, None, state.dft_num, Some(start_tx))).await {
info!("Failed to send start command: {:?}", e);
}
// Wait for the start confirmation
if let Err(e) = start_rx.await {
info!("Failed to receive start confirmation: {:?}", e);
}
} }
(Mode::Single, HardwareConnected::WithMultiplexer) => { (Mode::Single, HardwareConnected::WithMultiplexer) => {
info!("Starting single measurement..."); info!("Starting single measurement...");
state.tab_active = TabActive::Single; state.tab_active = TabActive::Single;
let electrode_config = state.electrode_settings.to_electrode_config(hardware_state_rx.borrow().hardware_connected, state.lead_mode); let electrode_config = state.electrode_settings.to_electrode_config(hw.hardware_connected, state.lead_mode);
hardware_control_tx.try_send(StartStopSignal::StartSingle( let (start_tx, start_rx) = oneshot::channel();
state.single_frequency, state.lead_mode, electrode_config, state.dft_num)).unwrap(); if let Err(e) = hardware_control_tx.send(StartStopSignal::StartSingle(
state.single_frequency, state.lead_mode, electrode_config, state.dft_num, Some(start_tx))).await {
info!("Failed to send start command: {:?}", e);
}
// Wait for the start confirmation
if let Err(e) = start_rx.await {
info!("Failed to receive start confirmation: {:?}", e);
}
} }
(Mode::Sweep, HardwareConnected::WithoutMultiplexer) => { (Mode::Sweep, HardwareConnected::WithoutMultiplexer) => {
info!("Starting sweep measurement..."); info!("Starting sweep measurement...");
state.tab_active = TabActive::Sweep; state.tab_active = TabActive::Sweep;
hardware_control_tx.try_send(StartStopSignal::StartSweep( let (start_tx, start_rx) = oneshot::channel();
state.lead_mode, None, state.sweep_points)).unwrap(); if let Err(e) = hardware_control_tx.send(StartStopSignal::StartSweep(
state.lead_mode, None, state.sweep_points, Some(start_tx))).await {
info!("Failed to send start command: {:?}", e);
}
// Wait for the start confirmation
if let Err(e) = start_rx.await {
info!("Failed to receive start confirmation: {:?}", e);
}
} }
(Mode::Sweep, HardwareConnected::WithMultiplexer) => { (Mode::Sweep, HardwareConnected::WithMultiplexer) => {
info!("Starting sweep measurement..."); info!("Starting sweep measurement...");
state.tab_active = TabActive::Sweep; state.tab_active = TabActive::Sweep;
let electrode_config = state.electrode_settings.to_electrode_config(hardware_state_rx.borrow().hardware_connected, state.lead_mode); let electrode_config = state.electrode_settings.to_electrode_config(hw.hardware_connected, state.lead_mode);
hardware_control_tx.try_send(StartStopSignal::StartSweep( let (start_tx, start_rx) = oneshot::channel();
state.lead_mode, electrode_config, state.sweep_points)).unwrap(); if let Err(e) = hardware_control_tx.send(StartStopSignal::StartSweep(
state.lead_mode, electrode_config, state.sweep_points, Some(start_tx))).await {
info!("Failed to send start command: {:?}", e);
}
// Wait for the start confirmation
if let Err(e) = start_rx.await {
info!("Failed to receive start confirmation: {:?}", e);
}
} }
(_, HardwareConnected::None) => { (_, HardwareConnected::None) => {
info!("Cannot start measurement: No hardware connected!"); info!("Cannot start measurement: No hardware connected!");
@@ -81,14 +120,21 @@ pub async fn app_control_loop(mut app_control_rx: mpsc::Receiver<ControlCommand>
} }
} }
ControlCommand::Stop => { ControlCommand::Stop => {
if hardware_state_rx.borrow().hardware_connected == HardwareConnected::None { let hw = hardware_state_rx.borrow().clone();
if hw.hardware_connected == HardwareConnected::None {
info!("Cannot stop measurement: No hardware connected!"); info!("Cannot stop measurement: No hardware connected!");
continue; continue;
} }
info!("Stopping impedance hardware..."); info!("Stopping impedance hardware...");
if let Err(e) = hardware_control_tx.try_send(StartStopSignal::Stop) {
let (stop_tx, stop_rx) = oneshot::channel();
if let Err(e) = hardware_control_tx.send(StartStopSignal::Stop(stop_tx)).await {
info!("Failed to send stop command: {:?}", e); info!("Failed to send stop command: {:?}", e);
} }
// Wait for the stop confirmation
if let Err(e) = stop_rx.await {
info!("Failed to receive stop confirmation: {:?}", e);
}
} }
ControlCommand::TcpConnected(connected) => { ControlCommand::TcpConnected(connected) => {
state.tcp_connected = connected; state.tcp_connected = connected;

View File

@@ -1,12 +1,14 @@
use std::time::SystemTime; use std::time::SystemTime;
use tokio::sync::{oneshot};
use crate::icd::{BioImpedanceLeadMode, IcdDftNum, ElectrodeConfiguration, SweepPoints}; use crate::icd::{BioImpedanceLeadMode, IcdDftNum, ElectrodeConfiguration, SweepPoints};
#[derive(Copy, Clone, Debug)] #[derive(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, Option<oneshot::Sender<()>>), // frequency in Hz, lead mode, electrode configuration, DFT number, signal channel to confirm start
StartSweep(BioImpedanceLeadMode, Option<ElectrodeConfiguration>, SweepPoints), // lead mode, electrode configuration, number of points per measurement StartSweep(BioImpedanceLeadMode, Option<ElectrodeConfiguration>, SweepPoints, Option<oneshot::Sender<()>>), // lead mode, electrode configuration, number of points per measurement, signal channel to confirm start
Stop, Stop(oneshot::Sender<()>), // signal channel to confirm stop
} }
pub enum LoggingSignal { pub enum LoggingSignal {