From 7229d4cd337743433a28e97e1cf40131e994f2ef Mon Sep 17 00:00:00 2001 From: Hubald Verzijl Date: Wed, 15 Oct 2025 18:11:10 +0200 Subject: [PATCH] Added logger to GUI. --- .gitignore | 3 +- Cargo.lock | 69 ++++++++++++++++++++--- Cargo.toml | 2 + src/app.rs | 127 ++++++++++++++++++++++++++++--------------- src/bin/main_gui.rs | 31 +++-------- src/communication.rs | 39 +++++++++---- src/lib.rs | 1 + src/logging.rs | 77 ++++++++++++++++++++++++++ src/plot.rs | 4 +- src/signals.rs | 10 +++- 10 files changed, 269 insertions(+), 94 deletions(-) create mode 100644 src/logging.rs diff --git a/.gitignore b/.gitignore index 212de44..1709e7d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target -.DS_Store \ No newline at end of file +.DS_Store +log_* \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 3d6503b..86f5286 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -172,6 +172,15 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "arboard" version = "3.6.0" @@ -439,6 +448,7 @@ version = "0.1.0" dependencies = [ "atomic_float", "bioz-icd-rs", + "chrono", "defmt", "eframe", "egui_dock", @@ -622,6 +632,19 @@ dependencies = [ "libc", ] +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link 0.2.1", +] + [[package]] name = "clipboard-win" version = "5.4.1" @@ -1587,6 +1610,30 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "2.0.0" @@ -4035,7 +4082,7 @@ dependencies = [ "windows-collections", "windows-core", "windows-future", - "windows-link", + "windows-link 0.1.3", "windows-numerics", ] @@ -4056,7 +4103,7 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", - "windows-link", + "windows-link 0.1.3", "windows-result", "windows-strings", ] @@ -4068,7 +4115,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ "windows-core", - "windows-link", + "windows-link 0.1.3", "windows-threading", ] @@ -4100,6 +4147,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-numerics" version = "0.2.0" @@ -4107,7 +4160,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ "windows-core", - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -4116,7 +4169,7 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -4125,7 +4178,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -4225,7 +4278,7 @@ version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ - "windows-link", + "windows-link 0.1.3", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -4242,7 +4295,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d619f75..7290f0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ egui_extras = "0.32.3" log = "0.4.27" simple_logger = "5.0.0" atomic_float = "1.1.0" +chrono = { version = "0.4.42" } [dependencies.bioz-icd-rs] path = "../bioz-icd-rs" @@ -35,6 +36,7 @@ features = [ "rt-multi-thread", "macros", "time", + "fs", ] [dependencies.tokio-serial] diff --git a/src/app.rs b/src/app.rs index 3b5557d..45a7ba0 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,21 +1,25 @@ -use log::info; +use log::{info, error}; + +use core::f32; use std::f32::consts::PI; use std::ops::RangeInclusive; use std::sync::{Arc, Mutex}; use std::sync::atomic::{AtomicBool, Ordering}; +use chrono::Local; + use atomic_float::AtomicF32; use tokio::{sync::mpsc::{Sender}}; -use eframe::egui::{self, Button, CollapsingHeader, Color32, ComboBox, DragValue, Key, Label, Layout, Modifiers}; +use eframe::egui::{self, Button, CollapsingHeader, Color32, ComboBox, DragValue, Id, Key, Label, Layout, Modifiers, TextEdit, Widget}; use egui_plot::{Corner, GridInput, GridMark, Legend, Line, Plot, PlotPoint, Points}; use egui_dock::{DockArea, DockState, Style}; use egui_extras::{TableBuilder, Column}; use crate::plot::{TimeSeriesPlot, BodePlot}; -use crate::signals::StartStopSignal; +use crate::signals::{LoggingSignal, StartStopSignal}; use crate::icd::{IcdDftNum, MeasurementPointSet}; @@ -42,6 +46,7 @@ pub struct App { tree: DockState, tab_viewer: TabViewer, run_impedancemeter_tx: Sender, + log_tx: Sender, pub magnitude: Arc>, pub phase: Arc>, pub magnitude_series: Arc>, @@ -55,8 +60,9 @@ pub struct App { pub dft_num: Arc>, pub measurement_points: Arc>, pub periods_per_dft: Arc>>, - pub periods_per_dft_multi: Arc, Option>)>>, + pub periods_per_dft_multi: Arc, Option>)>>, pub gui_logging_enabled: Arc, + log_filename: String, } struct TabViewer { @@ -70,7 +76,7 @@ struct TabViewer { dft_num: Arc>, measurement_points: Arc>, periods_per_dft: Arc>>, - periods_per_dft_multi: Arc, Option>)>>, + periods_per_dft_multi: Arc, Option>)>>, show_settings: bool, show_settings_toggle: Option, } @@ -200,17 +206,17 @@ impl TabViewer { ui.add_enabled_ui(*on, |ui| { let (freq, periods_per_dft_vec) = self.periods_per_dft_multi.lock().unwrap().clone(); - fn format_frequency(freq: f32) -> String { - if freq >= 1_000.0 { + fn format_frequency(freq: u32) -> String { + if freq >= 1_000 { // kHz - if freq % 1_000.0 == 0.0 { - format!("{}k", (freq / 1_000.0) as u64) + if freq % 1_000 == 0 { + format!("{}k", (freq / 1_000) as u64) } else { - format!("{:.1}k", freq / 1_000.0) + format!("{:.1}k", freq / 1_000) } } else { // Hz - if freq % 1.0 == 0.0 { + if freq % 1 == 0 { format!("{}", freq as u64) } else { format!("{:.1}", freq) @@ -413,7 +419,7 @@ impl egui_dock::TabViewer for TabViewer { } impl App { - pub fn new(run_impedancemeter_tx: Sender) -> Self { + pub fn new(run_impedancemeter_tx: Sender, log_tx: Sender) -> Self { // Step 1: Initialize shared fields first let magnitude = Arc::new(Mutex::new(0.0)); let phase = Arc::new(Mutex::new(0.0)); @@ -450,6 +456,7 @@ impl App { tree: DockState::new(vec!["Single".to_string(), "Multi".to_string(), "Shortcuts".to_string()]), tab_viewer, run_impedancemeter_tx, + log_tx, magnitude, phase, magnitude_series, @@ -465,6 +472,7 @@ impl App { periods_per_dft, periods_per_dft_multi, gui_logging_enabled: Arc::new(AtomicBool::new(false)), + log_filename: format!("log_{}.csv", Local::now().format("%Y%m%d")) }; // For testing purposes, populate the Bode plot with a sample low-pass filter response @@ -493,17 +501,17 @@ impl App { match (self.tab_active, *self.on.lock().unwrap()) { (TabActive::Single, true) => { if let Err(e) = self.run_impedancemeter_tx.try_send(StartStopSignal::StartSingle(*self.single_frequency.lock().unwrap(), *self.dft_num.lock().unwrap())) { - eprintln!("Failed to send start command: {:?}", e); + error!("Failed to send start command: {:?}", e); } }, (TabActive::Multi, true) => { if let Err(e) = self.run_impedancemeter_tx.try_send(StartStopSignal::StartMulti(*self.measurement_points.lock().unwrap())) { - eprintln!("Failed to send start command: {:?}", e); + error!("Failed to send start command: {:?}", e); } }, (_, false) => { if let Err(e) = self.run_impedancemeter_tx.try_send(StartStopSignal::Stop) { - eprintln!("Failed to send stop command: {:?}", e); + error!("Failed to send stop command: {:?}", e); } }, (_, _) => {} @@ -545,8 +553,24 @@ impl eframe::App for App { let mut gui_logging_enabled = self.gui_logging_enabled.load(Ordering::Relaxed); if ui.add(egui::Checkbox::new(&mut gui_logging_enabled, "Logging")).changed() { self.gui_logging_enabled.store(gui_logging_enabled, Ordering::Relaxed); + if gui_logging_enabled { + if let Err(e) = self.log_tx.try_send(LoggingSignal::StartFileLogging(self.log_filename.clone())) { + error!("Failed to send logging signal: {:?}", e); + } + } else { + if let Err(e) = self.log_tx.try_send(LoggingSignal::StopFileLogging) { + error!("Failed to send logging signal: {:?}", e); + } + } } + ui.separator(); + + ui.add_enabled_ui(!gui_logging_enabled, |ui| { + ui.label("Log filename:"); + TextEdit::singleline(&mut self.log_filename).desired_width(125.0).id(Id::new("file_name_field")).ui(ui); + }); + // Spacer to push the LED to the right ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { ui.scope(|ui| { @@ -619,40 +643,55 @@ impl eframe::App for App { { ctx.send_viewport_cmd(egui::ViewportCommand::Close); } + + // Check if the file name field is focused + // If it is, we don't want to trigger shortcuts + let file_name_field_is_focused = ctx.memory(|memory| memory.has_focus(Id::new("file_name_field"))); + if !file_name_field_is_focused { - // Space to start/stop measurement - if ctx.input(|i| i.key_pressed(Key::Space)) - { - let value = *self.on.lock().unwrap(); - *self.on.lock().unwrap() = !value; - self.update_start_stop(); - } + // Space to start/stop measurement + if ctx.input(|i| i.key_pressed(Key::Space)) + { + let value = *self.on.lock().unwrap(); + *self.on.lock().unwrap() = !value; + self.update_start_stop(); + } - // Send stop command when the window is closed - if ctx.input(|i| i.viewport().close_requested()) { - *self.on.lock().unwrap() = false; - self.update_start_stop(); - } + // Send stop command when the window is closed + if ctx.input(|i| i.viewport().close_requested()) { + *self.on.lock().unwrap() = false; + self.update_start_stop(); + } - // Reset view - if ctx.input(|i| i.key_pressed(Key::R)) { - self.reset_view(); - } + // Reset view + if ctx.input(|i| i.key_pressed(Key::R)) { + self.reset_view(); + } - // Enable/disable GUI logging - if ctx.input(|i| i.key_pressed(egui::Key::L)) { - let mut gui_logging_enabled = self.gui_logging_enabled.load(Ordering::Relaxed); - gui_logging_enabled = !gui_logging_enabled; - self.gui_logging_enabled.store(gui_logging_enabled, Ordering::Relaxed); - } + // Enable/disable GUI logging + if ctx.input(|i| i.key_pressed(egui::Key::L)) { + let mut gui_logging_enabled = self.gui_logging_enabled.load(Ordering::Relaxed); + gui_logging_enabled = !gui_logging_enabled; + self.gui_logging_enabled.store(gui_logging_enabled, Ordering::Relaxed); + if gui_logging_enabled { + if let Err(e) = self.log_tx.try_send(LoggingSignal::StartFileLogging(self.log_filename.clone())) { + error!("Failed to send logging signal: {:?}", e); + } + } else { + if let Err(e) = self.log_tx.try_send(LoggingSignal::StopFileLogging) { + error!("Failed to send logging signal: {:?}", e); + } + } + } - // Toggle setttings view - if ctx.input(|i| i.key_pressed(egui::Key::S)) { - self.tab_viewer.show_settings = !self.tab_viewer.show_settings; - if self.tab_viewer.show_settings { - self.tab_viewer.show_settings_toggle = Some(true); - } else { - self.tab_viewer.show_settings_toggle = Some(false); + // Toggle setttings view + if ctx.input(|i| i.key_pressed(egui::Key::S)) { + self.tab_viewer.show_settings = !self.tab_viewer.show_settings; + if self.tab_viewer.show_settings { + self.tab_viewer.show_settings_toggle = Some(true); + } else { + self.tab_viewer.show_settings_toggle = Some(false); + } } } diff --git a/src/bin/main_gui.rs b/src/bin/main_gui.rs index 0ca1e18..6c26cce 100644 --- a/src/bin/main_gui.rs +++ b/src/bin/main_gui.rs @@ -9,6 +9,7 @@ use bioz_host_rs::communication::communicate_with_hardware; use tokio::sync::mpsc::{self}; use bioz_host_rs::signals::StartStopSignal; +use bioz_host_rs::logging::log_data; #[tokio::main] async fn main() { @@ -26,9 +27,10 @@ async fn main() { let run_impedancemeter_tx_clone = run_impedancemeter_tx.clone(); // Logging - let (log_tx, mut log_rx) = mpsc::channel::(10); + let (log_tx, log_rx) = mpsc::channel::(10); + let log_tx_clone = log_tx.clone(); - let app = App::new(run_impedancemeter_tx); + let app = App::new(run_impedancemeter_tx, log_tx); let magnitude_clone = app.magnitude.clone(); let phase_clone = app.phase.clone(); let magnitude_series_clone = app.magnitude_series.clone(); @@ -43,27 +45,8 @@ async fn main() { let gui_logging_enabled = app.gui_logging_enabled.clone(); - // Log thread - tokio::spawn(async move { - loop { - match log_rx.recv().await { - Some(signal) => { - match signal { - LoggingSignal::SingleImpedance(magnitude, phase) => { - info!("Single Impedance - Magnitude: {:.3}, Phase: {:.3}", magnitude, phase); - } - LoggingSignal::MultiImpedance(magnitudes, phases) => { - info!("Multi Impedance - Magnitudes: {:?}, Phases: {:?}", magnitudes, phases); - } - } - } - None => { - // Channel closed - break; - } - } - } - }); + // Log thread + tokio::spawn(async move { log_data(log_rx).await }); // Execute the runtime in its own thread. std::thread::spawn(move || { @@ -80,7 +63,7 @@ async fn main() { periods_per_dft, periods_per_dft_multi, gui_logging_enabled, - log_tx, + log_tx_clone, )); }); diff --git a/src/communication.rs b/src/communication.rs index 5c4bad5..fc66510 100644 --- a/src/communication.rs +++ b/src/communication.rs @@ -1,7 +1,9 @@ +use std::time::SystemTime; + use log::{error, info}; use tokio::select; -use tokio::sync::mpsc::{Sender, Receiver}; +use tokio::sync::mpsc::{Receiver, Sender}; use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use atomic_float::AtomicF32; @@ -28,7 +30,7 @@ pub async fn communicate_with_hardware( connected: Arc, data_frequency: Arc, periods_per_dft: Arc>>, - periods_per_dft_multi: Arc, Option>)>>, + periods_per_dft_multi: Arc, Option>)>>, gui_logging_enabled: Arc, log_tx: Sender, ) { @@ -42,17 +44,17 @@ pub async fn communicate_with_hardware( } }); - #[derive(Default)] + #[derive(Default, Clone, Copy)] struct Settings { frequency: Option, } - let mut settings = Settings::default(); + let settings = Arc::new(Mutex::new(Settings::default())); loop { let workbook_client = match WorkbookClient::new() { Ok(client) => { info!("Connected to hardware successfully."); - if let Some(frequency) = settings.frequency { + if let Some(frequency) = settings.lock().unwrap().frequency { run_impedancemeter_tx.send(frequency).await.unwrap(); } connected.store(true, Ordering::Relaxed); @@ -79,6 +81,8 @@ pub async fn communicate_with_hardware( let gui_logging_enabled_clone = gui_logging_enabled.clone(); let log_tx_clone = log_tx.clone(); + let settings_clone = settings.clone(); + tokio::spawn(async move { while let Ok(val) = single_impedance_sub.recv().await { { @@ -97,9 +101,20 @@ pub async fn communicate_with_hardware( } // Send logging signal if gui_logging_enabled_clone.load(Ordering::Relaxed) { - if let Err(e) = log_tx_clone.try_send(LoggingSignal::SingleImpedance(val.magnitude, val.phase)) { - error!("Failed to send logging signal: {:?}", e); + let settings = *settings_clone.lock().unwrap(); + match settings.frequency { + Some(StartStopSignal::StartSingle(freq, _)) => { + if let Err(e) = log_tx_clone.send(LoggingSignal::SingleImpedance(SystemTime::now(), freq, val.magnitude, val.phase)).await { + error!("Failed to send logging signal: {:?}", e); + } + }, + _ => { + error!("Frequency not set for single impedance logging."); + }, } + // if let Err(e) = log_tx_clone.send(LoggingSignal::SingleImpedance(SystemTime::now(), settings.frequency, val.magnitude, val.phase)).await { + // error!("Failed to send logging signal: {:?}", e); + // } } } info!("SingleImpedanceOutputTopic subscription ended."); @@ -133,7 +148,7 @@ pub async fn communicate_with_hardware( bode_plot.update_phases(MeasurementPointSet::Eight, phases.clone()); } if gui_logging_enabled_clone.load(Ordering::Relaxed) { - if let Err(e) = log_tx_clone.try_send(LoggingSignal::MultiImpedance(magnitudes.clone(), phases.clone())) { + if let Err(e) = log_tx_clone.send(LoggingSignal::MultiImpedance(SystemTime::now(), MeasurementPointSet::Eight.values().to_vec(), magnitudes.clone(), phases.clone())).await { error!("Failed to send logging signal: {:?}", e); } } @@ -147,7 +162,7 @@ pub async fn communicate_with_hardware( bode_plot.update_phases(MeasurementPointSet::Eighteen, phases.clone()); } if gui_logging_enabled_clone.load(Ordering::Relaxed) { - if let Err(e) = log_tx_clone.try_send(LoggingSignal::MultiImpedance(magnitudes.clone(), phases.clone())) { + if let Err(e) = log_tx_clone.send(LoggingSignal::MultiImpedance(SystemTime::now(), MeasurementPointSet::Eighteen.values().to_vec(), magnitudes.clone(), phases.clone())).await { error!("Failed to send logging signal: {:?}", e); } } @@ -166,7 +181,7 @@ pub async fn communicate_with_hardware( match workbook_client.start_impedancemeter_single(freq, dft_num).await { Ok(Ok(periods)) => { info!("Impedance meter started at frequency: {} with periods per DFT: {}", freq, periods); - settings.frequency = Some(StartStopSignal::StartSingle(freq, dft_num)); + settings.lock().unwrap().frequency = Some(StartStopSignal::StartSingle(freq, dft_num)); *periods_per_dft.lock().unwrap() = Some(periods); }, Ok(Err(e)) => { @@ -182,7 +197,7 @@ pub async fn communicate_with_hardware( StartStopSignal::StartMulti(num_points) => { match workbook_client.start_impedancemeter_multi(num_points).await { Ok(Ok(periods)) => { - settings.frequency = Some(StartStopSignal::StartMulti(num_points)); + settings.lock().unwrap().frequency = Some(StartStopSignal::StartMulti(num_points)); info!("Multi-point Impedancemeter started."); match num_points { MeasurementPointSet::Eight => { @@ -207,7 +222,7 @@ pub async fn communicate_with_hardware( if let Err(e) = workbook_client.stop_impedancemeter().await { error!("Failed to stop impedancemeter: {:?}", e); } else { - settings.frequency = Some(StartStopSignal::Stop); + settings.lock().unwrap().frequency = Some(StartStopSignal::Stop); *periods_per_dft.lock().unwrap() = None; let (freq, _) = periods_per_dft_multi.lock().unwrap().clone(); *periods_per_dft_multi.lock().unwrap() = (freq, None); diff --git a/src/lib.rs b/src/lib.rs index efd5ce1..75b8bd4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ pub mod app; pub mod communication; pub mod plot; pub mod signals; +pub mod logging; pub use bioz_icd_rs as icd; pub async fn read_line() -> String { diff --git a/src/logging.rs b/src/logging.rs new file mode 100644 index 0000000..56bb934 --- /dev/null +++ b/src/logging.rs @@ -0,0 +1,77 @@ +use log::info; + +use tokio::sync::mpsc::Receiver; +use tokio::fs::File; +use tokio::io::AsyncWriteExt; +use std::path::Path; +use std::time::UNIX_EPOCH; + +use crate::signals::LoggingSignal; + +pub async fn log_data(mut data: Receiver) { + let mut file: Option = None; + + loop { + match data.recv().await { + Some(signal) => { + match signal { + LoggingSignal::SingleImpedance(timestamp, frequency, magnitude, phase) => { + if let Some(f) = file.as_mut() { + let _ = f + .write_all(format!("{},{},{:.3},{:.3}\n", timestamp.duration_since(UNIX_EPOCH).unwrap().as_millis(), frequency, magnitude, phase).as_bytes()) + .await; + } + } + LoggingSignal::MultiImpedance(timestamp, frequencies, magnitudes, phases) => { + if let Some(f) = file.as_mut() { + for i in 0..frequencies.len() { + let _ = f.write_all( + format!("{},{},{},{},{}\n", + timestamp.duration_since(UNIX_EPOCH).unwrap().as_millis(), + frequencies[i], + magnitudes[i], + phases[i], + i // optional index + ).as_bytes() + ).await; + } + } + } + LoggingSignal::StartFileLogging(filename) => { + let path = Path::new(&filename); + let base = path.file_stem().and_then(|s| s.to_str()).unwrap_or("log"); + let ext = path.extension().and_then(|s| s.to_str()).unwrap_or(""); + + let mut counter = 0; + let mut new_filename = filename.clone(); + while Path::new(&new_filename).exists() { + counter += 1; + new_filename = if ext.is_empty() { + format!("{}_{}", base, counter) + } else { + format!("{}_{}.{}", base, counter, ext) + }; + } + + match File::create(&new_filename).await { + Ok(f) => { + info!("Started file logging to: {}", new_filename); + file = Some(f); + } + Err(e) => { + info!("Failed to create log file '{}': {}", new_filename, e); + } + } + } + LoggingSignal::StopFileLogging => { + if file.is_some() { + info!("Stopped file logging"); + file = None; + } + } + } + } + None => break, // Channel closed + } + } +} diff --git a/src/plot.rs b/src/plot.rs index b7a9232..9828fca 100644 --- a/src/plot.rs +++ b/src/plot.rs @@ -66,12 +66,12 @@ impl BodePlot { pub fn update_magnitudes(&mut self, points: MeasurementPointSet, magnitudes: Vec) { let freqs = points.values().to_vec(); // self.magnitudes = freqs.into_iter().zip(magnitudes.into_iter()).map(|(f, m)| PlotPoint::new(f.log10(), 20.0 * m.log10() as f32)).collect(); - self.magnitudes = freqs.into_iter().zip(magnitudes.into_iter()).map(|(f, m)| PlotPoint::new(f.log10(), m as f32)).collect(); + self.magnitudes = freqs.into_iter().zip(magnitudes.into_iter()).map(|(f, m)| PlotPoint::new((f as f32).log10(), m)).collect(); // Convert to f32 first due to rouding errors } pub fn update_phases(&mut self, points: MeasurementPointSet, phases: Vec) { let freqs = points.values().to_vec(); - self.phases = freqs.into_iter().zip(phases.into_iter()).map(|(f, p)| PlotPoint::new(f.log10(), p as f64)).collect(); + self.phases = freqs.into_iter().zip(phases.into_iter()).map(|(f, p)| PlotPoint::new((f as f32).log10(), p)).collect(); // Convert to f32 first due to rouding errors } pub fn plot_magnitudes(&self) -> PlotPoints { diff --git a/src/signals.rs b/src/signals.rs index e5af6f6..9303818 100644 --- a/src/signals.rs +++ b/src/signals.rs @@ -1,6 +1,8 @@ +use std::time::SystemTime; + use crate::icd::{IcdDftNum, MeasurementPointSet}; -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub enum StartStopSignal { StartSingle(u32, IcdDftNum), // frequency in Hz, DFT number StartMulti(MeasurementPointSet), // DFT number, number of points per measurement @@ -8,6 +10,8 @@ pub enum StartStopSignal { } pub enum LoggingSignal { - SingleImpedance(f32, f32), // magnitude, phase - MultiImpedance(Vec, Vec), // magnitude, phase + SingleImpedance(SystemTime, u32, f32, f32), // frequency, magnitude, phase + MultiImpedance(SystemTime, Vec, Vec, Vec), // frequency, magnitude, phase + StartFileLogging(String), // e.g. filename + StopFileLogging, } \ No newline at end of file