Updated logger as separate module.

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-04-30 10:15:36 +02:00
parent c35287c02f
commit cd80468f54
6 changed files with 98 additions and 97 deletions

View File

@@ -20,7 +20,7 @@ use egui_dock::{DockArea, DockState, Style, TabViewer};
use egui_extras::{TableBuilder, Column};
use crate::state::{AppState, ControlCommand, HardwareConnected, HardwareState, MeasurementDataState, Mode};
use crate::logging::LoggingStates;
use crate::logging::LoggingState;
use crate::signals::LoggingSignal;
use crate::icd::{BioImpedanceLeadMode, IcdDftNum, SweepPoints, ElectrodeOptionsWithMultiplexer};
@@ -52,15 +52,15 @@ pub struct App {
tree: DockState<String>,
tab_viewer: CustomTabs,
// tab_active: TabActive,
log_tx: mpsc::Sender<LoggingSignal>,
pub gui_logging_state: Arc<Mutex<LoggingStates>>,
log_filename: Arc<Mutex<String>>,
log_marker_modal: bool,
log_marker: String,
measurement_data: Arc<MeasurementDataState>,
control_tx: mpsc::Sender<ControlCommand>,
logging_control_tx: mpsc::Sender<LoggingSignal>,
app_state_rx: sync::watch::Receiver<AppState>,
hardware_state_rx: sync::watch::Receiver<HardwareState>
hardware_state_rx: sync::watch::Receiver<HardwareState>,
logging_state_rx: sync::watch::Receiver<LoggingState>,
}
struct CustomTabs {
@@ -616,11 +616,13 @@ impl TabViewer for CustomTabs {
}
impl App {
pub fn new(log_tx: mpsc::Sender<LoggingSignal>,
measurement_data: Arc<MeasurementDataState>,
pub fn new(measurement_data: Arc<MeasurementDataState>,
control_tx: mpsc::Sender<ControlCommand>,
logging_control_tx: mpsc::Sender<LoggingSignal>,
app_state_rx: watch::Receiver<AppState>,
hardware_state_rx: watch::Receiver<HardwareState>) -> Self {
hardware_state_rx: watch::Receiver<HardwareState>,
logging_state_rx: watch::Receiver<LoggingState>
) -> Self {
// Step 1: Initialize shared fields first
// let tab_active = app_state_rx.borrow().tab_active;
@@ -647,15 +649,15 @@ impl App {
tree: DockState::new(vec!["Single".to_string(), "Sweep".to_string(), "Shortcuts".to_string()]),
tab_viewer,
// tab_active,
log_tx,
gui_logging_state: Arc::new(Mutex::new(LoggingStates::Idle)),
log_filename,
log_marker_modal: false,
log_marker: String::new(),
measurement_data,
control_tx,
logging_control_tx,
app_state_rx,
hardware_state_rx,
logging_state_rx,
};
// For testing purposes, populate the Bode plot with a sample low-pass filter response
@@ -745,17 +747,17 @@ impl eframe::App for App {
ui.add_enabled(is_connected, Label::new("Logging:"));
let gui_logging_state = *self.gui_logging_state.lock().unwrap();
let gui_logging_state = *self.logging_state_rx.borrow();
let mut logging_enabled = !matches!(gui_logging_state, LoggingStates::Idle);
let mut logging_enabled = !matches!(gui_logging_state, LoggingState::Idle);
if ui.add_enabled(is_connected, custom_toggle_widget(&mut logging_enabled)).changed() {
let signal = match gui_logging_state {
LoggingStates::Idle => LoggingSignal::StartFileLogging(self.log_filename.lock().unwrap().clone()),
LoggingStates::Starting | LoggingStates::Logging => LoggingSignal::StopFileLogging,
LoggingState::Idle => LoggingSignal::StartFileLogging(self.log_filename.lock().unwrap().clone()),
LoggingState::Starting | LoggingState::Logging => LoggingSignal::StopFileLogging,
};
self.log_tx.try_send(signal).unwrap_or_else(|e| {
self.logging_control_tx.try_send(signal).unwrap_or_else(|e| {
error!("Failed to send logging toggle signal: {:?}", e);
});
};
@@ -796,8 +798,7 @@ impl eframe::App for App {
let enter_pressed = text_edit_response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter));
if add_clicked || enter_pressed {
info!("Adding marker: {}", self.log_marker);
self.log_tx.try_send(LoggingSignal::AddMarker(self.log_marker.clone())).unwrap_or_else(|e| {
self.logging_control_tx.try_send(LoggingSignal::AddMarker(self.log_marker.clone())).unwrap_or_else(|e| {
error!("Failed to send logging marker signal: {:?}", e);
});
self.log_marker = String::new();
@@ -817,7 +818,7 @@ impl eframe::App for App {
ui.separator();
ui.add_enabled_ui(gui_logging_state == LoggingStates::Idle, |ui| {
ui.add_enabled_ui(gui_logging_state == LoggingState::Idle, |ui| {
ui.label("Log filename:");
TextEdit::singleline(&mut *self.log_filename.lock().unwrap()).desired_width(150.0).id(Id::new("file_name_field")).ui(ui);
});
@@ -894,25 +895,25 @@ impl eframe::App for App {
}
// Toggle marker modal
if ctx.input(|i| i.key_pressed(egui::Key::A)) && *self.gui_logging_state.lock().unwrap() == LoggingStates::Logging {
if ctx.input(|i| i.key_pressed(egui::Key::A)) && *self.logging_state_rx.borrow() == LoggingState::Logging {
self.log_marker_modal = !self.log_marker_modal;
}
// Enable/disable GUI logging
if ctx.input(|i| i.key_pressed(egui::Key::L)) {
let gui_logging_enabled = *self.gui_logging_state.lock().unwrap();
let gui_logging_enabled = *self.logging_state_rx.borrow();
match gui_logging_enabled {
LoggingStates::Starting => {
LoggingState::Starting => {
// If currently starting, do nothing
return;
},
LoggingStates::Logging => {
if let Err(e) = self.log_tx.try_send(LoggingSignal::StopFileLogging) {
LoggingState::Logging => {
if let Err(e) = self.logging_control_tx.try_send(LoggingSignal::StopFileLogging) {
error!("Failed to send logging signal: {:?}", e);
}
},
LoggingStates::Idle => {
if let Err(e) = self.log_tx.try_send(LoggingSignal::StartFileLogging(self.log_filename.lock().unwrap().clone())) {
LoggingState::Idle => {
if let Err(e) = self.logging_control_tx.try_send(LoggingSignal::StartFileLogging(self.log_filename.lock().unwrap().clone())) {
error!("Failed to send logging signal: {:?}", e);
}
},

View File

@@ -14,7 +14,7 @@ use bioz_host_rs::communication::communicate_with_hardware;
use tokio::sync::{watch, mpsc};
use bioz_host_rs::signals::StartStopSignal;
use bioz_host_rs::logging::log_data;
use bioz_host_rs::logging::{LoggingState, log_data};
use bioz_host_rs::state::{AppState, ControlCommand, HardwareState, MeasurementDataState};
use bioz_host_rs::control::app_control_loop;
@@ -25,65 +25,62 @@ async fn main() {
log::set_max_level(log::LevelFilter::Info);
info!("Starting Bioz Impedance Visualizer...");
// Enter the runtime so that `tokio::spawn` is available immediately.
// let _enter = rt.enter();
// Init watches for hardware state and app state
let (hardware_state_tx, hardware_state_rx) = watch::channel(HardwareState::default());
// Init watches for hardware state, app state and logging state
let (app_state_tx, app_state_rx) = watch::channel(AppState::default());
let (hardware_state_tx, hardware_state_rx) = watch::channel(HardwareState::default());
let (logging_state_tx, logging_state_rx) = watch::channel(LoggingState::default());
// Init control channels to update hardware and app
let (hardware_control_tx, hardware_control_rx) = mpsc::channel::<StartStopSignal>(2);
// Init control channels to update hardware, app and logging
let (app_control_tx, app_control_rx) = mpsc::channel::<ControlCommand>(32);
// let run_impedancemeter_tx_clone = run_impedancemeter_tx.clone();
let hardware_state_rx_clone = hardware_state_rx.clone();
let hardware_control_tx_clone = hardware_control_tx.clone();
let app_control_tx_clone = app_control_tx.clone();
tokio::spawn(async move {
app_control_loop(app_control_rx, app_state_tx, hardware_state_rx, hardware_control_tx_clone).await;
});
let (hardware_control_tx, hardware_control_rx) = mpsc::channel::<StartStopSignal>(2);
let (logging_control_tx, logging_control_rx) = mpsc::channel::<LoggingSignal>(10);
// Init measurement data state
let measurement_data = Arc::new(MeasurementDataState::default());
// Logging
let (log_tx, log_rx) = mpsc::channel::<LoggingSignal>(10);
let log_tx_clone = log_tx.clone();
let measurement_data_clone = measurement_data.clone();
let app = App::new(log_tx, measurement_data_clone, app_control_tx_clone, app_state_rx, hardware_state_rx_clone);
let gui_logging_state_1 = app.gui_logging_state.clone();
let gui_logging_state_2 = app.gui_logging_state.clone();
// Log thread
tokio::spawn(async move { log_data(log_rx, gui_logging_state_1).await });
// TCP command server in separate thread
let rt = Runtime::new().expect("Unable to create Runtime");
let app_control_tx_clone = app_control_tx.clone();
std::thread::spawn(move || {
rt.block_on(bioz_host_rs::tcp::tcp_command_server(app_control_tx_clone));
// Start control loop
let hardware_control_tx_clone = hardware_control_tx.clone();
let hardware_state_rx_clone = hardware_state_rx.clone();
tokio::spawn(async move {
app_control_loop(app_control_rx, app_state_tx, hardware_control_tx_clone, hardware_state_rx_clone).await;
});
// Execute the runtime in its own thread.
// Start hardware communication loop in separate thread
let rt = Runtime::new().expect("Unable to create Runtime");
let logging_control_tx_clone = logging_control_tx.clone();
let logging_state_rx_clone = logging_state_rx.clone();
std::thread::spawn(move || {
rt.block_on(communicate_with_hardware(
hardware_control_rx,
hardware_control_tx,
measurement_data,
hardware_state_tx,
gui_logging_state_2,
log_tx_clone,
logging_control_tx_clone,
logging_state_rx_clone,
));
});
// Start logging loop
tokio::spawn(async move { log_data(logging_control_rx, logging_state_tx).await });
// Start TCP command server in separate thread
let rt = Runtime::new().expect("Unable to create Runtime");
let app_control_tx_clone = app_control_tx.clone();
std::thread::spawn(move || {
rt.block_on(bioz_host_rs::tcp::tcp_command_server(app_control_tx_clone));
});
// Create the app and run it in the main thread
let app = App::new(
measurement_data_clone,
app_control_tx,
logging_control_tx,
app_state_rx,
hardware_state_rx,
logging_state_rx);
// Run the GUI in the main thread.
let mut native_options = NativeOptions::default();
native_options.viewport.inner_size = Some(Vec2::new(850.0, 600.0));

View File

@@ -16,7 +16,7 @@ use crate::state::{HardwareState, MeasurementDataState};
use crate::icd;
use crate::client::WorkbookClient;
use crate::logging::LoggingStates;
use crate::logging::LoggingState;
use crate::signals::{LoggingSignal, StartStopSignal};
@@ -27,8 +27,8 @@ pub async fn communicate_with_hardware(
hardware_control_tx: Sender<StartStopSignal>,
measurement_data: Arc<MeasurementDataState>,
hardware_state_tx: watch::Sender<HardwareState>,
gui_logging_state: Arc<Mutex<LoggingStates>>,
log_tx: Sender<LoggingSignal>,
logging_control_tx: Sender<LoggingSignal>,
logging_state_rx: watch::Receiver<LoggingState>,
) {
let data_counter = Arc::new(AtomicU32::new(0));
let data_counter_clone = data_counter.clone();
@@ -89,8 +89,8 @@ pub async fn communicate_with_hardware(
let data_counter_clone_single = data_counter_clone.clone();
// Clone log_tx for the task
let gui_logging_state_clone = gui_logging_state.clone();
let log_tx_clone = log_tx.clone();
let logging_control_tx_clone = logging_control_tx.clone();
let logging_state_rx_clone = logging_state_rx.clone();
let settings_clone = settings.clone();
@@ -111,11 +111,11 @@ pub async fn communicate_with_hardware(
data_counter_clone_single.fetch_add(1, Ordering::Relaxed);
}
// Send logging signal
if *gui_logging_state_clone.lock().unwrap() == LoggingStates::Logging {
if *logging_state_rx_clone.borrow() == LoggingState::Logging {
let settings = *settings_clone.lock().unwrap();
match settings.mode {
Some(StartStopSignal::StartSingle(freq, _, _, _)) => {
if let Err(e) = log_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);
}
},
@@ -142,8 +142,8 @@ pub async fn communicate_with_hardware(
let data_counter_clone_sweep = data_counter_clone.clone();
// Clone log_tx for the task
let gui_logging_state_clone = gui_logging_state.clone();
let log_tx_clone = log_tx.clone();
let logging_control_tx_clone = logging_control_tx.clone();
let logging_state_rx_clone = logging_state_rx.clone();
tokio::spawn(async move {
while let Ok(val) = sweep_impedance_sub.recv().await {
@@ -158,8 +158,8 @@ pub async fn communicate_with_hardware(
bode_plot.update_magnitudes(SweepPoints::Eight, magnitudes.clone());
bode_plot.update_phases(SweepPoints::Eight, phases.clone());
}
if *gui_logging_state_clone.lock().unwrap() == LoggingStates::Logging {
if let Err(e) = log_tx_clone.send(LoggingSignal::SweepImpedance(SystemTime::now(), SweepPoints::Eight.values().to_vec(), magnitudes.clone(), phases.clone())).await {
if *logging_state_rx_clone.borrow() == LoggingState::Logging {
if let Err(e) = logging_control_tx_clone.try_send(LoggingSignal::SweepImpedance(SystemTime::now(), SweepPoints::Eight.values().to_vec(), magnitudes.clone(), phases.clone())) {
error!("Failed to send logging signal: {:?}", e);
}
}
@@ -172,8 +172,8 @@ pub async fn communicate_with_hardware(
bode_plot.update_magnitudes(SweepPoints::Eighteen, magnitudes.clone());
bode_plot.update_phases(SweepPoints::Eighteen, phases.clone());
}
if *gui_logging_state_clone.lock().unwrap() == LoggingStates::Logging {
if let Err(e) = log_tx_clone.send(LoggingSignal::SweepImpedance(SystemTime::now(), SweepPoints::Eighteen.values().to_vec(), magnitudes.clone(), phases.clone())).await {
if *logging_state_rx_clone.borrow() == LoggingState::Logging {
if let Err(e) = logging_control_tx_clone.try_send(LoggingSignal::SweepImpedance(SystemTime::now(), SweepPoints::Eighteen.values().to_vec(), magnitudes.clone(), phases.clone())) {
error!("Failed to send logging signal: {:?}", e);
}
}
@@ -184,7 +184,7 @@ pub async fn communicate_with_hardware(
}
});
let log_tx_clone = log_tx.clone();
let logging_control_tx_clone = logging_control_tx.clone();
loop {
select! {
Some(frequency) = hardware_control_rx.recv() => {
@@ -199,8 +199,8 @@ pub async fn communicate_with_hardware(
hardware_state_tx.send(hardware_state.clone()).unwrap();
// When logging add electrode configuration to logging file
if *gui_logging_state.lock().unwrap() == LoggingStates::Logging {
if let Err(e) = log_tx_clone.send(LoggingSignal::ElectrodeCongiguration(electrode_config)).await {
if *logging_state_rx.borrow() == LoggingState::Logging {
if let Err(e) = logging_control_tx_clone.try_send(LoggingSignal::ElectrodeCongiguration(electrode_config)) {
error!("Failed to send logging signal: {:?}", e);
}
}
@@ -238,8 +238,8 @@ pub async fn communicate_with_hardware(
}
// When logging add electrode configuration to logging file
if *gui_logging_state.lock().unwrap() == LoggingStates::Logging {
if let Err(e) = log_tx_clone.send(LoggingSignal::ElectrodeCongiguration(electrode_config)).await {
if *logging_state_rx.borrow() == LoggingState::Logging {
if let Err(e) = logging_control_tx_clone.try_send(LoggingSignal::ElectrodeCongiguration(electrode_config)) {
error!("Failed to send logging signal: {:?}", e);
}
}

View File

@@ -6,8 +6,8 @@ use crate::state::Mode;
pub async fn app_control_loop(mut app_control_rx: mpsc::Receiver<ControlCommand>,
app_state_tx: watch::Sender<AppState>,
hardware_state_rx: watch::Receiver<HardwareState>,
hardware_control_tx: mpsc::Sender<StartStopSignal>) {
hardware_control_tx: mpsc::Sender<StartStopSignal>,
hardware_state_rx: watch::Receiver<HardwareState>) {
let mut state = AppState::default();
while let Some(cmd) = app_control_rx.recv().await {

View File

@@ -1,10 +1,9 @@
use bioz_icd_rs::ElectrodeConfiguration;
use log::info;
use std::sync::{Arc, Mutex};
use std::time::UNIX_EPOCH;
use tokio::sync::mpsc::Receiver;
use tokio::sync::{mpsc::Receiver, watch::Sender};
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
@@ -13,22 +12,28 @@ use rfd::AsyncFileDialog;
use crate::signals::LoggingSignal;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LoggingStates {
pub enum LoggingState {
Idle,
Starting,
Logging,
}
impl Default for LoggingState {
fn default() -> Self {
LoggingState::Idle
}
}
pub async fn log_data(
mut data: Receiver<LoggingSignal>,
gui_logging_state: Arc<Mutex<LoggingStates>>,
mut logging_control_rx: Receiver<LoggingSignal>,
logging_state_tx: Sender<LoggingState>,
) {
let mut file: Option<File> = None;
let mut logging_marker = String::new();
let mut electrode_configuration = String::new();
loop {
match data.recv().await {
match logging_control_rx.recv().await {
Some(signal) => {
match signal {
LoggingSignal::SingleImpedance(timestamp, frequency, magnitude, phase) => {
@@ -75,7 +80,7 @@ pub async fn log_data(
}
LoggingSignal::StartFileLogging(filename) => {
// Update global logging state
*gui_logging_state.lock().unwrap() = LoggingStates::Starting;
logging_state_tx.send(LoggingState::Starting).unwrap();
// Open a folder picker for the user
let folder = match AsyncFileDialog::new()
@@ -85,7 +90,7 @@ pub async fn log_data(
Some(f) => f,
None => {
info!("File logging cancelled by user");
*gui_logging_state.lock().unwrap() = LoggingStates::Idle;
logging_state_tx.send(LoggingState::Idle).unwrap();
continue;
}
};
@@ -123,7 +128,7 @@ pub async fn log_data(
}
// Update global logging state
*gui_logging_state.lock().unwrap() = LoggingStates::Logging;
logging_state_tx.send(LoggingState::Logging).unwrap();
}
LoggingSignal::StopFileLogging => {
if file.is_some() {
@@ -131,9 +136,10 @@ pub async fn log_data(
file = None;
}
// Update global logging state
*gui_logging_state.lock().unwrap() = LoggingStates::Idle;
logging_state_tx.send(LoggingState::Idle).unwrap();
}
LoggingSignal::AddMarker(marker) => {
info!("Adding marker: {}", marker);
logging_marker = marker;
}
LoggingSignal::ElectrodeCongiguration(config) => {

View File

@@ -2,7 +2,6 @@ use std::sync::{Arc, Mutex};
use crate::app::TabActive;
use crate::icd::{BioImpedanceLeadMode, IcdDftNum, SweepPoints, ElectrodeConfiguration, ElectrodeOptionsWithMultiplexer};
use crate::logging::LoggingStates;
use crate::plot::{TimeSeriesPlot, BodePlot};
use atomic_float::AtomicF32;
@@ -108,7 +107,6 @@ pub struct AppState {
pub electrode_settings: ElectrodeSettings,
pub sweep_points: SweepPoints,
pub tab_active: TabActive,
pub logging: LoggingStates,
}
impl Default for AppState {
@@ -121,7 +119,6 @@ impl Default for AppState {
electrode_settings: ElectrodeSettings::new(),
sweep_points: SweepPoints::Eighteen,
tab_active: TabActive::Single,
logging: LoggingStates::Idle,
}
}
}