From 0b07b527642d5350c7ebaec3a9bd0cb72d55e4cc Mon Sep 17 00:00:00 2001 From: Hubald Verzijl Date: Thu, 6 Aug 2026 17:59:39 +0200 Subject: [PATCH] Add feedback to controller after hardware commands. --- src/communication.rs | 108 ++++++++++++++++++++++++++++++++++++------- src/control.rs | 80 +++++++++++++++++++++++++------- src/signals.rs | 10 ++-- 3 files changed, 160 insertions(+), 38 deletions(-) diff --git a/src/communication.rs b/src/communication.rs index bda7948..32e958d 100644 --- a/src/communication.rs +++ b/src/communication.rs @@ -9,7 +9,7 @@ use std::sync::atomic::{AtomicU32, Ordering}; 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}; @@ -42,9 +42,23 @@ pub async fn communicate_with_hardware( } }); - #[derive(Default, Clone, Copy)] + #[derive(Clone, Debug, Default)] struct Settings { - mode: Option, + mode: Option, + } + #[derive(Clone, Debug)] + enum MeasurementMode { + Single { + freq: u32, + lead_mode: BioImpedanceLeadMode, + electrode_config: Option, + dft_num: IcdDftNum, + }, + Sweep { + lead_mode: BioImpedanceLeadMode, + electrode_config: Option, + num_points: SweepPoints, + }, } let settings = Arc::new(Mutex::new(Settings::default())); @@ -54,9 +68,45 @@ pub async fn communicate_with_hardware( let workbook_client = match WorkbookClient::new() { Ok(client) => { info!("Connected to hardware successfully."); - if let Some(mode) = settings.lock().unwrap().mode { - hardware_control_tx.send(mode).await.unwrap(); + let mode = { + 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() { MultiplexerCapability::Absent => { hardware_state.hardware_connected = HardwareConnected::WithoutMultiplexer; @@ -112,9 +162,9 @@ pub async fn communicate_with_hardware( } // Send logging signal if *logging_state_rx_clone.borrow() == LoggingState::Logging { - let settings = *settings_clone.lock().unwrap(); + let settings = settings_clone.lock().unwrap(); 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)) { 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(); loop { select! { - Some(frequency) = hardware_control_rx.recv() => { - match frequency { - StartStopSignal::StartSingle(freq, lead_mode, electrode_config, dft_num) => { + Some(start_stop_signal) = hardware_control_rx.recv() => { + match start_stop_signal { + 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 { Ok(Ok(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.periods_per_dft = Some(periods); 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 if *logging_state_rx.borrow() == LoggingState::Logging { 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 { Ok(Ok(periods)) => { 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; match num_points { SweepPoints::Partial => { @@ -250,7 +318,10 @@ pub async fn communicate_with_hardware( 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 if *logging_state_rx.borrow() == LoggingState::Logging { 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 { error!("Failed to stop impedancemeter: {:?}", e); } else { - settings.lock().unwrap().mode = Some(StartStopSignal::Stop); + settings.lock().unwrap().mode = None; hardware_state.running = false; hardware_state.periods_per_dft = None; let (freq, _) = hardware_state.periods_per_dft_sweep.clone(); hardware_state.periods_per_dft_sweep = (freq, None); hardware_state_tx.send(hardware_state.clone()).unwrap(); info!("Impedancemeter stopped."); + stop_tx.send(()).unwrap_or_else(|e| { + error!("Failed to send stop confirmation: {:?}", e); + }); } }, } diff --git a/src/control.rs b/src/control.rs index 2caac43..82dc357 100644 --- a/src/control.rs +++ b/src/control.rs @@ -1,5 +1,5 @@ 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::state::Mode; @@ -37,43 +37,82 @@ pub async fn app_control_loop(mut app_control_rx: mpsc::Receiver info!("Active tab changed to {:?}!", tab_active); } 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!"); continue; } - if hardware_state_rx.borrow().running { + if hw.running { 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); state.mode = mode; - match (mode, hardware_state_rx.borrow().hardware_connected) { + match (mode, hw.hardware_connected) { (Mode::Single, HardwareConnected::WithoutMultiplexer) => { info!("Starting single measurement..."); state.tab_active = TabActive::Single; - hardware_control_tx.try_send(StartStopSignal::StartSingle( - state.single_frequency, state.lead_mode, None, state.dft_num)).unwrap(); + let (start_tx, start_rx) = oneshot::channel(); + 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) => { info!("Starting single measurement..."); state.tab_active = TabActive::Single; - let electrode_config = state.electrode_settings.to_electrode_config(hardware_state_rx.borrow().hardware_connected, state.lead_mode); - hardware_control_tx.try_send(StartStopSignal::StartSingle( - state.single_frequency, state.lead_mode, electrode_config, state.dft_num)).unwrap(); + let electrode_config = state.electrode_settings.to_electrode_config(hw.hardware_connected, state.lead_mode); + let (start_tx, start_rx) = oneshot::channel(); + 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) => { info!("Starting sweep measurement..."); state.tab_active = TabActive::Sweep; - hardware_control_tx.try_send(StartStopSignal::StartSweep( - state.lead_mode, None, state.sweep_points)).unwrap(); + let (start_tx, start_rx) = oneshot::channel(); + 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) => { info!("Starting sweep measurement..."); state.tab_active = TabActive::Sweep; - let electrode_config = state.electrode_settings.to_electrode_config(hardware_state_rx.borrow().hardware_connected, state.lead_mode); - hardware_control_tx.try_send(StartStopSignal::StartSweep( - state.lead_mode, electrode_config, state.sweep_points)).unwrap(); + let electrode_config = state.electrode_settings.to_electrode_config(hw.hardware_connected, state.lead_mode); + let (start_tx, start_rx) = oneshot::channel(); + 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) => { info!("Cannot start measurement: No hardware connected!"); @@ -81,14 +120,21 @@ pub async fn app_control_loop(mut app_control_rx: mpsc::Receiver } } 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!"); continue; } 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); } + // Wait for the stop confirmation + if let Err(e) = stop_rx.await { + info!("Failed to receive stop confirmation: {:?}", e); + } } ControlCommand::TcpConnected(connected) => { state.tcp_connected = connected; diff --git a/src/signals.rs b/src/signals.rs index fcf73b0..3ff7d85 100644 --- a/src/signals.rs +++ b/src/signals.rs @@ -1,12 +1,14 @@ use std::time::SystemTime; +use tokio::sync::{oneshot}; + use crate::icd::{BioImpedanceLeadMode, IcdDftNum, ElectrodeConfiguration, SweepPoints}; -#[derive(Copy, Clone, Debug)] +#[derive(Debug)] pub enum StartStopSignal { - StartSingle(u32, BioImpedanceLeadMode, Option, IcdDftNum), // frequency in Hz, lead mode, electrode configuration, DFT number - StartSweep(BioImpedanceLeadMode, Option, SweepPoints), // lead mode, electrode configuration, number of points per measurement - Stop, + StartSingle(u32, BioImpedanceLeadMode, Option, IcdDftNum, Option>), // frequency in Hz, lead mode, electrode configuration, DFT number, signal channel to confirm start + StartSweep(BioImpedanceLeadMode, Option, SweepPoints, Option>), // lead mode, electrode configuration, number of points per measurement, signal channel to confirm start + Stop(oneshot::Sender<()>), // signal channel to confirm stop } pub enum LoggingSignal {