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 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<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()));
@@ -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);
});
}
},
}

View File

@@ -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<ControlCommand>
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>
}
}
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;

View File

@@ -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<ElectrodeConfiguration>, IcdDftNum), // frequency in Hz, lead mode, electrode configuration, DFT number
StartSweep(BioImpedanceLeadMode, Option<ElectrodeConfiguration>, SweepPoints), // lead mode, electrode configuration, number of points per measurement
Stop,
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, Option<oneshot::Sender<()>>), // 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 {