mirror of
https://github.com/hubaldv/bioz-host-rs.git
synced 2026-09-07 15:17:21 +00:00
Add feedback to controller after hardware commands.
This commit is contained in:
@@ -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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user