From beefe733c058adf197831f6eeba3afac3ea53d9a Mon Sep 17 00:00:00 2001 From: Hubald Verzijl Date: Fri, 12 Sep 2025 22:40:55 +0200 Subject: [PATCH 1/4] Included setting dft number from gui. --- src/app.rs | 35 ++++++++++++++++++++++++++++++++--- src/bin/main_cli.rs | 8 ++++---- src/client.rs | 5 ++++- src/communication.rs | 6 +++--- src/signals.rs | 4 +++- 5 files changed, 46 insertions(+), 12 deletions(-) diff --git a/src/app.rs b/src/app.rs index 154269b..39cc604 100644 --- a/src/app.rs +++ b/src/app.rs @@ -6,7 +6,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use atomic_float::AtomicF32; use tokio::{sync::mpsc::{Sender}}; -use eframe::egui::{self, Button, CollapsingHeader, Color32, DragValue, Key, Label, Layout, Modifiers}; +use eframe::egui::{self, Button, CollapsingHeader, Color32, ComboBox, DragValue, Key, Label, Layout, Modifiers}; use egui_plot::{Corner, Legend, Line, Plot, PlotPoints, Points, PlotBounds}; use egui_dock::{DockArea, DockState, Style}; @@ -14,6 +14,15 @@ use crate::plot::TimeSeriesPlot; use crate::signals::SingleFrequencySignal; +use crate::icd::IcdDftNum; + +const DFTNUM_VARIANTS: [IcdDftNum; 13] = [ + IcdDftNum::Num4, IcdDftNum::Num8, IcdDftNum::Num16, IcdDftNum::Num32, + IcdDftNum::Num64, IcdDftNum::Num128, IcdDftNum::Num256, IcdDftNum::Num512, + IcdDftNum::Num1024, IcdDftNum::Num2048, IcdDftNum::Num4096, + IcdDftNum::Num8192, IcdDftNum::Num16384, +]; + #[derive(Clone, Copy,Debug, PartialEq, Eq)] enum TabActive { Single, @@ -33,7 +42,8 @@ pub struct App { pub on: Arc>, tab_active: TabActive, pub data_frequency: Arc, - pub single_frequency: Arc> + pub single_frequency: Arc>, + pub dft_num: Arc>, } struct TabViewer { @@ -43,6 +53,7 @@ struct TabViewer { phase_series: Arc>, on: Arc>, single_frequency: Arc>, + dft_num: Arc>, show_settings: bool, show_settings_toggle: Option, } @@ -62,6 +73,21 @@ impl TabViewer { } ui.label("Hz"); }); + ui.horizontal(|ui| { + ui.label("ADC samples per DFT:"); + let mut dft_num = self.dft_num.lock().unwrap(); + let mut index = DFTNUM_VARIANTS.iter().position(|&x| x == *dft_num).unwrap_or(0); + ComboBox::from_id_salt("Dftnum") + .width(75.0) + .show_index(ui, &mut index, DFTNUM_VARIANTS.len(), |i| { + format!("{}", 1 << (2 + i)) // 2^2 = 4, 2^3 = 8, ..., 2^14 = 16384 + }); + let new_value = DFTNUM_VARIANTS[index]; + if *dft_num != new_value { + *dft_num = new_value; + info!("DFTNUM setting changed!"); + }; + }); }); } }); @@ -155,6 +181,7 @@ impl App { let magnitude_series = Arc::new(Mutex::new(TimeSeriesPlot::new())); let phase_series = Arc::new(Mutex::new(TimeSeriesPlot::new())); let single_frequency = Arc::new(Mutex::new(50000)); + let dft_num = Arc::new(Mutex::new(IcdDftNum::Num2048)); let on = Arc::new(Mutex::new(true)); let tab_active = TabActive::Single; @@ -165,6 +192,7 @@ impl App { magnitude_series: magnitude_series.clone(), phase_series: phase_series.clone(), single_frequency: single_frequency.clone(), + dft_num: dft_num.clone(), on: on.clone(), show_settings: false, show_settings_toggle: None, @@ -184,6 +212,7 @@ impl App { tab_active, data_frequency: Arc::new(AtomicF32::new(0.0)), single_frequency, + dft_num, }; app.update_start_stop(); @@ -193,7 +222,7 @@ impl App { pub fn update_start_stop(&self) { match (self.tab_active, *self.on.lock().unwrap()) { (TabActive::Single, true) => { - if let Err(e) = self.run_impedancemeter_tx.try_send(SingleFrequencySignal::Start(*self.single_frequency.lock().unwrap())) { + if let Err(e) = self.run_impedancemeter_tx.try_send(SingleFrequencySignal::Start(*self.single_frequency.lock().unwrap(), *self.dft_num.lock().unwrap())) { eprintln!("Failed to send start command: {:?}", e); } }, diff --git a/src/bin/main_cli.rs b/src/bin/main_cli.rs index de86652..e648f49 100644 --- a/src/bin/main_cli.rs +++ b/src/bin/main_cli.rs @@ -78,8 +78,8 @@ async fn main() { .subscribe_multi::(8) .await .unwrap(); - client.start_impedancemeter(freq).await.unwrap(); - println!("Started!"); + client.start_impedancemeter(freq, bioz_icd_rs::IcdDftNum::Num2048).await.unwrap(); + println!("Started with dft_num 2048!"); let dur = Duration::from_millis(dur.into()); let start = Instant::now(); @@ -97,8 +97,8 @@ async fn main() { }; - match client.start_impedancemeter(freq).await { - Ok(_) => println!("Started!"), + match client.start_impedancemeter(freq, bioz_icd_rs::IcdDftNum::Num2048).await { + Ok(_) => println!("Started with dft_num 2048!"), Err(e) => println!("Error starting impedancemeter: {:?}", e), }; diff --git a/src/client.rs b/src/client.rs index 003e200..e33f8ee 100644 --- a/src/client.rs +++ b/src/client.rs @@ -9,6 +9,8 @@ use bioz_icd_rs::{ StopImpedanceEndpoint, }; +use crate::icd::IcdDftNum; + #[derive(Debug)] pub struct WorkbookClient { pub client: HostClient, @@ -64,9 +66,10 @@ impl WorkbookClient { pub async fn start_impedancemeter( &self, frequency: u32, + dft_number: IcdDftNum, ) -> Result<(), WorkbookError> { self.client - .send_resp::(&StartImpedance { update_frequency: 60, sinus_frequency: frequency }) + .send_resp::(&StartImpedance { update_frequency: 60, sinus_frequency: frequency, dft_number}) .await?; Ok(()) } diff --git a/src/communication.rs b/src/communication.rs index 0ddab3b..658e6bc 100644 --- a/src/communication.rs +++ b/src/communication.rs @@ -90,11 +90,11 @@ pub async fn communicate_with_hardware( select! { Some(frequency) = run_impedancemeter_rx.recv() => { match frequency { - SingleFrequencySignal::Start(freq) => { - if let Err(e) = workbook_client.start_impedancemeter(freq).await { + SingleFrequencySignal::Start(freq, dft_num) => { + if let Err(e) = workbook_client.start_impedancemeter(freq, dft_num).await { error!("Failed to start impedancemeter: {:?}", e); } else { - settings.frequency = Some(SingleFrequencySignal::Start(freq)); + settings.frequency = Some(SingleFrequencySignal::Start(freq, dft_num)); info!("Impedancemeter started at frequency: {}", freq); } }, diff --git a/src/signals.rs b/src/signals.rs index 1346436..5df4e25 100644 --- a/src/signals.rs +++ b/src/signals.rs @@ -1,5 +1,7 @@ +use crate::icd::IcdDftNum; + #[derive(Copy, Clone)] pub enum SingleFrequencySignal { - Start(u32), + Start(u32, IcdDftNum), // frequency in Hz, DFT number Stop, } \ No newline at end of file From e71b02477a96520ae99b000af1ad226f7d52355b Mon Sep 17 00:00:00 2001 From: Hubald Verzijl Date: Tue, 7 Oct 2025 10:18:04 +0200 Subject: [PATCH 2/4] Added bode plots to GUI. --- .gitignore | 1 + Cargo.lock | 23 ++++- Cargo.toml | 6 +- src/app.rs | 203 ++++++++++++++++++++++++++++++++++++++++--- src/bin/main_cli.rs | 6 +- src/bin/main_gui.rs | 6 +- src/client.rs | 22 +++-- src/communication.rs | 66 ++++++++++---- src/plot.rs | 40 +++++++++ src/signals.rs | 7 +- 10 files changed, 331 insertions(+), 49 deletions(-) diff --git a/.gitignore b/.gitignore index ea8c4bf..212de44 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +.DS_Store \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 1016a9e..ecdd6c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -445,6 +445,7 @@ dependencies = [ "egui_plot", "log", "postcard-rpc", + "postcard-schema", "simple_logger", "tokio", "tokio-serial", @@ -454,6 +455,7 @@ dependencies = [ name = "bioz-icd-rs" version = "0.1.0" dependencies = [ + "heapless 0.9.1", "postcard-rpc", "postcard-schema", "serde", @@ -1515,6 +1517,17 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "heapless" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1edcd5a338e64688fbdcb7531a846cfd3476a54784dcb918a0844682bc7ada5" +dependencies = [ + "hash32 0.3.1", + "serde", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.5.0" @@ -2579,9 +2592,9 @@ dependencies = [ [[package]] name = "postcard-rpc" -version = "0.11.13" +version = "0.11.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1af23d87c9a8308bbfaae655ac770ec10517e6448fa5e0b50838a36e5d860b9" +checksum = "c7e1944dfb9859e440511700c442edce3eacd5862f90f5a9997d004bd3553f3b" dependencies = [ "cobs 0.4.0", "heapless 0.8.0", @@ -2601,10 +2614,12 @@ dependencies = [ [[package]] name = "postcard-schema" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5a13d8b1f8b3473b45c2c779b97c18c260ac6458eb045d4be75df8087784400" +checksum = "9475666d89f42231a0a57da32d5f6ca7f9b5cd4c335ea1fe8f3278215b7a21ff" dependencies = [ + "heapless 0.8.0", + "heapless 0.9.1", "postcard-derive", "serde", ] diff --git a/Cargo.toml b/Cargo.toml index d5cddc8..2b4ae17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,13 +17,17 @@ path = "../bioz-icd-rs" features = ["use-std"] [dependencies.postcard-rpc] -version = "0.11.13" +version = "0.11.15" features = [ "use-std", "raw-nusb", "cobs-serial", ] +[dependencies.postcard-schema] +version = "0.2.5" +features = ["derive", "heapless_v0_8"] + [dependencies.tokio] version = "1.37.0" features = [ diff --git a/src/app.rs b/src/app.rs index 39cc604..f9967c4 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,5 +1,7 @@ use log::info; +use std::f32::consts::PI; +use std::ops::RangeInclusive; use std::sync::{Arc, Mutex}; use std::sync::atomic::{AtomicBool, Ordering}; @@ -7,14 +9,14 @@ use atomic_float::AtomicF32; use tokio::{sync::mpsc::{Sender}}; use eframe::egui::{self, Button, CollapsingHeader, Color32, ComboBox, DragValue, Key, Label, Layout, Modifiers}; -use egui_plot::{Corner, Legend, Line, Plot, PlotPoints, Points, PlotBounds}; +use egui_plot::{Corner, GridInput, GridMark, Legend, Line, Plot, PlotBounds, PlotPoint, PlotPoints, Points}; use egui_dock::{DockArea, DockState, Style}; -use crate::plot::TimeSeriesPlot; +use crate::plot::{TimeSeriesPlot, BodePlot}; -use crate::signals::SingleFrequencySignal; +use crate::signals::StartStopSignal; -use crate::icd::IcdDftNum; +use crate::icd::{IcdDftNum, NumberOfPoints}; const DFTNUM_VARIANTS: [IcdDftNum; 13] = [ IcdDftNum::Num4, IcdDftNum::Num8, IcdDftNum::Num16, IcdDftNum::Num32, @@ -33,11 +35,12 @@ enum TabActive { pub struct App { tree: DockState, tab_viewer: TabViewer, - run_impedancemeter_tx: Sender, + run_impedancemeter_tx: Sender, pub magnitude: Arc>, pub phase: Arc>, pub magnitude_series: Arc>, pub phase_series: Arc>, + pub bode_plot: Arc>, pub connected: Arc, pub on: Arc>, tab_active: TabActive, @@ -51,6 +54,7 @@ struct TabViewer { phase: Arc>, magnitude_series: Arc>, phase_series: Arc>, + bode_plot: Arc>, on: Arc>, single_frequency: Arc>, dft_num: Arc>, @@ -100,7 +104,7 @@ impl TabViewer { let available_height = ui.available_height(); let half_height = available_height / 2.0-2.0; - // Plot pressure + // Plot magnitude ui.allocate_ui_with_layout( egui::vec2(ui.available_width(), half_height), Layout::top_down(egui::Align::Min), @@ -119,7 +123,7 @@ impl TabViewer { }); }, ); - // Plot pressure + // Plot phase ui.allocate_ui_with_layout( egui::vec2(ui.available_width(), half_height), Layout::top_down(egui::Align::Min), @@ -142,7 +146,99 @@ impl TabViewer { } fn multi_tab(&mut self, ui: &mut egui::Ui) { - ui.heading("Bode plots!"); + egui::Frame::default().inner_margin(5).show(ui, |ui| { + let settings = CollapsingHeader::new("Settings") + .open(self.show_settings_toggle) + .show(ui, |ui| { + if let Ok(on) = self.on.lock() { + ui.add_enabled_ui(!*on, |ui| { + ui.horizontal(|ui| { + ui.label("ADC samples per DFT:"); + let mut dft_num = self.dft_num.lock().unwrap(); + let mut index = DFTNUM_VARIANTS.iter().position(|&x| x == *dft_num).unwrap_or(0); + ComboBox::from_id_salt("Dftnum") + .width(75.0) + .show_index(ui, &mut index, DFTNUM_VARIANTS.len(), |i| { + format!("{}", 1 << (2 + i)) // 2^2 = 4, 2^3 = 8, ..., 2^14 = 16384 + }); + let new_value = DFTNUM_VARIANTS[index]; + if *dft_num != new_value { + *dft_num = new_value; + info!("DFTNUM setting changed!"); + }; + }); + }); + } + }); + + self.show_settings_toggle = None; + self.show_settings = !settings.fully_closed(); + + ui.separator(); + + let available_height = ui.available_height(); + let half_height = available_height / 2.0-2.0; + + // Bodeplot magnitude + ui.allocate_ui_with_layout( + egui::vec2(ui.available_width(), half_height), + Layout::top_down(egui::Align::Min), + |ui| { + // Magnitude + let bode_plot = self.bode_plot.lock().unwrap(); + Plot::new("bode_mag") + .legend(Legend::default().position(Corner::LeftTop)) + .y_axis_label("Magnitude [Ω]") + .x_grid_spacer(log10_grid_spacer) + .x_axis_formatter(log10_axis_formatter) + // .auto_bounds([true; 2].into()) + .y_axis_min_width(80.0) + .default_x_bounds(0.0, 200_000_f64.log10()) + .label_formatter(log10x_formatter) + .show(ui, |plot_ui| { + plot_ui.line( + Line::new("Magnitude", bode_plot.plot_magnitudes()) + .color(Color32::LIGHT_BLUE) + ); + plot_ui.points( + Points::new("Magnitude measurement", bode_plot.plot_magnitudes()) + .color(Color32::BLUE) + .radius(1.5) + ); + }); + }, + ); + + // Bodeplot phase + ui.allocate_ui_with_layout( + egui::vec2(ui.available_width(), half_height), + Layout::top_down(egui::Align::Min), + |ui| { + // Phase + let bode_plot = self.bode_plot.lock().unwrap(); + Plot::new("bode_phase") + .legend(Legend::default().position(Corner::LeftTop)) + .y_axis_label("Phase [rad]") + .x_grid_spacer(log10_grid_spacer) + .x_axis_formatter(log10_axis_formatter) + // .auto_bounds([true; 2].into()) + .y_axis_min_width(80.0) + .default_x_bounds(0.0, 200_000_f64.log10()) + .label_formatter(log10x_formatter) + .show(ui, |plot_ui| { + plot_ui.line( + Line::new("Phase", bode_plot.plot_phases()) + .color(Color32::LIGHT_RED) + ); + plot_ui.points( + Points::new("Phase measurement", bode_plot.plot_phases()) + .color(Color32::RED) + .radius(1.5) + ); + }); + }, + ); + }); } fn shortcuts(&mut self, ui: &mut egui::Ui) { @@ -154,6 +250,64 @@ impl TabViewer { } } +fn log10_grid_spacer(input: GridInput) -> Vec { + let base = 10u32; + assert!(base >= 2); + let basef = base as f64; + + let step_size = basef.powi(input.base_step_size.abs().log(basef).ceil() as i32); + let (min, max) = input.bounds; + let mut steps = vec![]; + + for step_size in [step_size, step_size * basef, step_size * basef * basef] { + if step_size == basef.powi(-1) { + // FIXME: float comparison + let first = ((min / (step_size * basef)).floor() as i64) * base as i64; + let last = (max / step_size).ceil() as i64; + + let mut logs = vec![0.0; base as usize - 2]; + for (i, j) in logs.iter_mut().enumerate() { + *j = basef * ((i + 2) as f64).log(basef); + } + + steps.extend((first..last).step_by(base as usize).flat_map(|j| { + logs.iter().map(move |i| GridMark { + value: (j as f64 + i) * step_size, + step_size, + }) + })); + } else { + let first = (min / step_size).ceil() as i64; + let last = (max / step_size).ceil() as i64; + + steps.extend((first..last).map(move |i| GridMark { + value: (i as f64) * step_size, + step_size, + })); + } + } + steps +} + +fn log10_axis_formatter(mark: GridMark, _range: &RangeInclusive) -> String { + let base = 10u32; + let basef = base as f64; + let prec = (-mark.step_size.log10().round()).max(0.0) as usize; + format!("{:.*e}", prec, basef.powf(mark.value)) +} + +fn log10x_formatter(name: &str, value: &PlotPoint) -> String { + let base = 10u32; + let basef = base as f64; + format!( + "{}\nx: {:.d$e}\ny: {:.d$e}", + name, + basef.powf(value.x), + value.y, + d = 3 + ) +} + impl egui_dock::TabViewer for TabViewer { type Tab = String; @@ -174,12 +328,13 @@ impl egui_dock::TabViewer for TabViewer { } impl App { - pub fn new(run_impedancemeter_tx: Sender) -> Self { + pub fn new(run_impedancemeter_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)); let magnitude_series = Arc::new(Mutex::new(TimeSeriesPlot::new())); let phase_series = Arc::new(Mutex::new(TimeSeriesPlot::new())); + let bode_plot = Arc::new(Mutex::new(BodePlot::new())); let single_frequency = Arc::new(Mutex::new(50000)); let dft_num = Arc::new(Mutex::new(IcdDftNum::Num2048)); let on = Arc::new(Mutex::new(true)); @@ -191,6 +346,7 @@ impl App { phase: phase.clone(), magnitude_series: magnitude_series.clone(), phase_series: phase_series.clone(), + bode_plot: bode_plot.clone(), single_frequency: single_frequency.clone(), dft_num: dft_num.clone(), on: on.clone(), @@ -207,6 +363,7 @@ impl App { phase, magnitude_series, phase_series, + bode_plot, connected: Arc::new(AtomicBool::new(false)), on, tab_active, @@ -215,6 +372,25 @@ impl App { dft_num, }; + // For testing purposes, populate the Bode plot with a sample low-pass filter response + let fc = 1000.0; // cutoff frequency in Hz + + let freqs = NumberOfPoints::TwentyEight.values().to_vec(); + let magnitudes = freqs.iter() + .map(|&f| { + 1.0 / (1.0 + (f / fc).powi(2)).sqrt() + }) + .collect::>(); + let phases = freqs.iter() + .map(|&f| { + -(f / fc).atan() * 180.0 / PI + }) + .collect::>(); + + app.bode_plot.lock().unwrap().update_magnitudes(magnitudes); + app.bode_plot.lock().unwrap().update_phases(phases); + + app.update_start_stop(); app } @@ -222,16 +398,17 @@ impl App { pub fn update_start_stop(&self) { match (self.tab_active, *self.on.lock().unwrap()) { (TabActive::Single, true) => { - if let Err(e) = self.run_impedancemeter_tx.try_send(SingleFrequencySignal::Start(*self.single_frequency.lock().unwrap(), *self.dft_num.lock().unwrap())) { + 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); } }, (TabActive::Multi, true) => { - // For future use - info!("Multi frequency mode not implemented yet"); + if let Err(e) = self.run_impedancemeter_tx.try_send(StartStopSignal::StartMulti(*self.dft_num.lock().unwrap(), NumberOfPoints::TwentyEight)) { + eprintln!("Failed to send start command: {:?}", e); + } }, (_, false) => { - if let Err(e) = self.run_impedancemeter_tx.try_send(SingleFrequencySignal::Stop) { + if let Err(e) = self.run_impedancemeter_tx.try_send(StartStopSignal::Stop) { eprintln!("Failed to send stop command: {:?}", e); } }, diff --git a/src/bin/main_cli.rs b/src/bin/main_cli.rs index e648f49..c86c866 100644 --- a/src/bin/main_cli.rs +++ b/src/bin/main_cli.rs @@ -75,10 +75,10 @@ async fn main() { let mut sub = client .client - .subscribe_multi::(8) + .subscribe_multi::(8) .await .unwrap(); - client.start_impedancemeter(freq, bioz_icd_rs::IcdDftNum::Num2048).await.unwrap(); + client.start_impedancemeter_single(freq, bioz_icd_rs::IcdDftNum::Num2048).await.unwrap(); println!("Started with dft_num 2048!"); let dur = Duration::from_millis(dur.into()); @@ -97,7 +97,7 @@ async fn main() { }; - match client.start_impedancemeter(freq, bioz_icd_rs::IcdDftNum::Num2048).await { + match client.start_impedancemeter_single(freq, bioz_icd_rs::IcdDftNum::Num2048).await { Ok(_) => println!("Started with dft_num 2048!"), Err(e) => println!("Error starting impedancemeter: {:?}", e), }; diff --git a/src/bin/main_gui.rs b/src/bin/main_gui.rs index 8e6e3cf..bd81d5c 100644 --- a/src/bin/main_gui.rs +++ b/src/bin/main_gui.rs @@ -8,7 +8,7 @@ use bioz_host_rs::communication::communicate_with_hardware; use tokio::sync::mpsc::{self}; -use bioz_host_rs::signals::SingleFrequencySignal; +use bioz_host_rs::signals::StartStopSignal; fn main() { SimpleLogger::new().init().expect("Failed to initialize logger"); @@ -20,7 +20,7 @@ fn main() { // Enter the runtime so that `tokio::spawn` is available immediately. // let _enter = rt.enter(); - let (run_impedancemeter_tx, run_impedancemeter_rx) = mpsc::channel::(2); + let (run_impedancemeter_tx, run_impedancemeter_rx) = mpsc::channel::(2); let run_impedancemeter_tx_clone = run_impedancemeter_tx.clone(); let app = App::new(run_impedancemeter_tx); @@ -28,6 +28,7 @@ fn main() { let phase_clone = app.phase.clone(); let magnitude_series_clone = app.magnitude_series.clone(); let phase_series_clone = app.phase_series.clone(); + let bode_clone = app.bode_plot.clone(); let connected_clone = app.connected.clone(); let data_frequency_clone = app.data_frequency.clone(); @@ -41,6 +42,7 @@ fn main() { phase_clone, magnitude_series_clone, phase_series_clone, + bode_clone, connected_clone, data_frequency_clone, )); diff --git a/src/client.rs b/src/client.rs index e33f8ee..ef9577a 100644 --- a/src/client.rs +++ b/src/client.rs @@ -5,11 +5,10 @@ use postcard_rpc::{ }; use std::convert::Infallible; use bioz_icd_rs::{ - PingEndpoint, GetUniqueIdEndpoint, SetGreenLedEndpoint, StartImpedance, StartImpedanceEndpoint, - StopImpedanceEndpoint, + GetUniqueIdEndpoint, PingEndpoint, SetGreenLedEndpoint, StartMultiImpedance, StartMultiImpedanceEndpoint, StartSingleImpedance, StartSingleImpedanceEndpoint, StopSingleImpedanceEndpoint }; -use crate::icd::IcdDftNum; +use crate::icd::{IcdDftNum, NumberOfPoints}; #[derive(Debug)] pub struct WorkbookClient { @@ -63,13 +62,24 @@ impl WorkbookClient { Ok(()) } - pub async fn start_impedancemeter( + pub async fn start_impedancemeter_single( &self, frequency: u32, dft_number: IcdDftNum, ) -> Result<(), WorkbookError> { self.client - .send_resp::(&StartImpedance { update_frequency: 60, sinus_frequency: frequency, dft_number}) + .send_resp::(&StartSingleImpedance { update_frequency: 60, sinus_frequency: frequency, dft_number}) + .await?; + Ok(()) + } + + pub async fn start_impedancemeter_multi( + &self, + dft_number: IcdDftNum, + number_of_points: NumberOfPoints, + ) -> Result<(), WorkbookError> { + self.client + .send_resp::(&StartMultiImpedance { dft_number, number_of_points }) .await?; Ok(()) } @@ -77,7 +87,7 @@ impl WorkbookClient { pub async fn stop_impedancemeter(&self) -> Result> { let res = self .client - .send_resp::(&()) + .send_resp::(&()) .await?; Ok(res) } diff --git a/src/communication.rs b/src/communication.rs index 658e6bc..227091a 100644 --- a/src/communication.rs +++ b/src/communication.rs @@ -11,17 +11,18 @@ use std::sync::{Arc, Mutex}; use crate::icd; use crate::client::WorkbookClient; -use crate::plot::TimeSeriesPlot; +use crate::plot::{TimeSeriesPlot, BodePlot}; -use crate::signals::SingleFrequencySignal; +use crate::signals::StartStopSignal; pub async fn communicate_with_hardware( - mut run_impedancemeter_rx: Receiver, - run_impedancemeter_tx: Sender, + mut run_impedancemeter_rx: Receiver, + run_impedancemeter_tx: Sender, magnitude: Arc>, phase: Arc>, magnitude_series: Arc>, phase_series: Arc>, + bode_series: Arc>, connected: Arc, data_frequency: Arc, ) { @@ -37,7 +38,7 @@ pub async fn communicate_with_hardware( #[derive(Default)] struct Settings { - frequency: Option, + frequency: Option, } let mut settings = Settings::default(); @@ -58,19 +59,20 @@ pub async fn communicate_with_hardware( } }; - let mut sub = workbook_client + // Subscribe to SingleImpedanceOutputTopic + let mut single_impedance_sub = workbook_client .client - .subscribe_multi::(8) + .subscribe_multi::(8) .await .unwrap(); let data = (magnitude_series.clone(), phase_series.clone(), magnitude.clone(), phase.clone()); - let data_counter_clone = data_counter_clone.clone(); + let data_counter_clone_single = data_counter_clone.clone(); tokio::spawn(async move { - while let Ok(val) = sub.recv().await { + while let Ok(val) = single_impedance_sub.recv().await { let mut mag_plot = data.0.lock().unwrap(); - let mut phase_plot = data.1.lock().unwrap(); + let mut phase_plot = data.1.lock().unwrap(); let mut mag_val = data.2.lock().unwrap(); let mut phase_val = data.3.lock().unwrap(); @@ -80,29 +82,59 @@ pub async fn communicate_with_hardware( mag_plot.add(val.magnitude as f64); phase_plot.add(val.phase as f64); - data_counter_clone.fetch_add(1, Ordering::Relaxed); + data_counter_clone_single.fetch_add(1, Ordering::Relaxed); } - info!("ImpedanceTopic subscription ended."); + info!("SingleImpedanceOutputTopic subscription ended."); + }); + // Subscribe to MultiImpedanceOutputTopic28 + let mut multi_impedance_28_sub = workbook_client + .client + .subscribe_multi::(8) + .await + .unwrap(); + + let data = bode_series.clone(); + let data_counter_clone_multi = data_counter_clone.clone(); + + tokio::spawn(async move { + while let Ok(val) = multi_impedance_28_sub.recv().await { + let mut bode_plot = data.lock().unwrap(); + + let magnitudes = val.magnitudes.into_iter().collect::>(); + bode_plot.update_magnitudes(magnitudes); + let phases = val.phases.into_iter().collect::>(); + bode_plot.update_phases(phases); + + data_counter_clone_multi.fetch_add(1, Ordering::Relaxed); + } }); loop { select! { Some(frequency) = run_impedancemeter_rx.recv() => { match frequency { - SingleFrequencySignal::Start(freq, dft_num) => { - if let Err(e) = workbook_client.start_impedancemeter(freq, dft_num).await { + StartStopSignal::StartSingle(freq, dft_num) => { + if let Err(e) = workbook_client.start_impedancemeter_single(freq, dft_num).await { error!("Failed to start impedancemeter: {:?}", e); } else { - settings.frequency = Some(SingleFrequencySignal::Start(freq, dft_num)); + settings.frequency = Some(StartStopSignal::StartSingle(freq, dft_num)); info!("Impedancemeter started at frequency: {}", freq); } }, - SingleFrequencySignal::Stop => { + StartStopSignal::StartMulti(dft_num, num_points) => { + if let Err(e) = workbook_client.start_impedancemeter_multi(dft_num, num_points).await { + error!("Failed to start multi-point impedancemeter: {:?}", e); + } else { + settings.frequency = Some(StartStopSignal::StartMulti(dft_num, num_points)); + info!("Multi-point Impedancemeter started."); + } + }, + StartStopSignal::Stop => { if let Err(e) = workbook_client.stop_impedancemeter().await { error!("Failed to stop impedancemeter: {:?}", e); } else { - settings.frequency = Some(SingleFrequencySignal::Stop); + settings.frequency = Some(StartStopSignal::Stop); info!("Impedancemeter stopped."); } }, diff --git a/src/plot.rs b/src/plot.rs index 042cd6a..d137a94 100644 --- a/src/plot.rs +++ b/src/plot.rs @@ -3,6 +3,8 @@ use std::collections::VecDeque; use egui_plot::{PlotPoint, PlotPoints}; +use bioz_icd_rs::NumberOfPoints; + pub struct TimeSeriesPlot { pub values: VecDeque, max_points: usize, @@ -46,4 +48,42 @@ impl TimeSeriesPlot { .map(|i| PlotPoint::new(i, f64::NAN)) .collect(); } +} + +pub struct BodePlot { + pub magnitudes: Vec, + pub phases: Vec, +} + +impl BodePlot { + pub fn new() -> Self { + Self { + magnitudes: Vec::new(), + phases: Vec::new(), + } + } + + pub fn update_magnitudes(&mut self, magnitudes: Vec) { + let freqs = NumberOfPoints::TwentyEight.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(); + } + + pub fn update_phases(&mut self, phases: Vec) { + let freqs = NumberOfPoints::TwentyEight.values().to_vec(); + self.phases = freqs.into_iter().zip(phases.into_iter()).map(|(f, p)| PlotPoint::new(f.log10(), p as f64)).collect(); + } + + pub fn plot_magnitudes(&self) -> PlotPoints { + PlotPoints::Owned(self.magnitudes.clone()) + } + + pub fn plot_phases(&self) -> PlotPoints { + PlotPoints::Owned(self.phases.clone()) + } + + pub fn clear(&mut self) { + self.magnitudes.clear(); + self.phases.clear(); + } } \ No newline at end of file diff --git a/src/signals.rs b/src/signals.rs index 5df4e25..b7fa809 100644 --- a/src/signals.rs +++ b/src/signals.rs @@ -1,7 +1,8 @@ -use crate::icd::IcdDftNum; +use crate::icd::{IcdDftNum, NumberOfPoints}; #[derive(Copy, Clone)] -pub enum SingleFrequencySignal { - Start(u32, IcdDftNum), // frequency in Hz, DFT number +pub enum StartStopSignal { + StartSingle(u32, IcdDftNum), // frequency in Hz, DFT number + StartMulti(IcdDftNum, NumberOfPoints), // DFT number, number of points per measurement Stop, } \ No newline at end of file From 26e9c4dcab9af9c6c54d87a3f19c54b6c8fc68c9 Mon Sep 17 00:00:00 2001 From: Hubald Verzijl Date: Tue, 7 Oct 2025 12:59:28 +0200 Subject: [PATCH 3/4] Show periods per DFT in GUI. --- src/app.rs | 21 +++++++++++++++++++++ src/bin/main_gui.rs | 3 +++ src/client.rs | 8 ++++---- src/communication.rs | 22 ++++++++++++++++------ 4 files changed, 44 insertions(+), 10 deletions(-) diff --git a/src/app.rs b/src/app.rs index f9967c4..0b0c7f2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -47,6 +47,7 @@ pub struct App { pub data_frequency: Arc, pub single_frequency: Arc>, pub dft_num: Arc>, + pub periods_per_dft: Arc>>, } struct TabViewer { @@ -58,6 +59,7 @@ struct TabViewer { on: Arc>, single_frequency: Arc>, dft_num: Arc>, + periods_per_dft: Arc>>, show_settings: bool, show_settings_toggle: Option, } @@ -93,6 +95,22 @@ impl TabViewer { }; }); }); + ui.add_enabled_ui(*on, |ui| { + ui.horizontal(|ui| { + ui.label("Periods per DFT:"); + match (*on, *self.periods_per_dft.lock().unwrap()) { + (true, Some(periods)) => { + ui.add(Label::new(format!("{:.2}", periods))); + }, + (true, None) => { + ui.add(Label::new("N/A")); + }, + (false, _) => { + ui.add(Label::new("Start to determine!")); + } + } + }); + }); } }); @@ -337,6 +355,7 @@ impl App { let bode_plot = Arc::new(Mutex::new(BodePlot::new())); let single_frequency = Arc::new(Mutex::new(50000)); let dft_num = Arc::new(Mutex::new(IcdDftNum::Num2048)); + let periods_per_dft = Arc::new(Mutex::new(None)); let on = Arc::new(Mutex::new(true)); let tab_active = TabActive::Single; @@ -349,6 +368,7 @@ impl App { bode_plot: bode_plot.clone(), single_frequency: single_frequency.clone(), dft_num: dft_num.clone(), + periods_per_dft: periods_per_dft.clone(), on: on.clone(), show_settings: false, show_settings_toggle: None, @@ -370,6 +390,7 @@ impl App { data_frequency: Arc::new(AtomicF32::new(0.0)), single_frequency, dft_num, + periods_per_dft, }; // For testing purposes, populate the Bode plot with a sample low-pass filter response diff --git a/src/bin/main_gui.rs b/src/bin/main_gui.rs index bd81d5c..3c589eb 100644 --- a/src/bin/main_gui.rs +++ b/src/bin/main_gui.rs @@ -33,6 +33,8 @@ fn main() { let data_frequency_clone = app.data_frequency.clone(); + let periods_per_dft = app.periods_per_dft.clone(); + // Execute the runtime in its own thread. std::thread::spawn(move || { rt.block_on(communicate_with_hardware( @@ -45,6 +47,7 @@ fn main() { bode_clone, connected_clone, data_frequency_clone, + periods_per_dft, )); }); diff --git a/src/client.rs b/src/client.rs index ef9577a..3181adc 100644 --- a/src/client.rs +++ b/src/client.rs @@ -5,7 +5,7 @@ use postcard_rpc::{ }; use std::convert::Infallible; use bioz_icd_rs::{ - GetUniqueIdEndpoint, PingEndpoint, SetGreenLedEndpoint, StartMultiImpedance, StartMultiImpedanceEndpoint, StartSingleImpedance, StartSingleImpedanceEndpoint, StopSingleImpedanceEndpoint + GetUniqueIdEndpoint, InitImpedanceResult, PingEndpoint, SetGreenLedEndpoint, StartMultiImpedance, StartMultiImpedanceEndpoint, StartSingleImpedance, StartSingleImpedanceEndpoint, StopSingleImpedanceEndpoint }; use crate::icd::{IcdDftNum, NumberOfPoints}; @@ -66,11 +66,11 @@ impl WorkbookClient { &self, frequency: u32, dft_number: IcdDftNum, - ) -> Result<(), WorkbookError> { - self.client + ) -> Result> { + let response = self.client .send_resp::(&StartSingleImpedance { update_frequency: 60, sinus_frequency: frequency, dft_number}) .await?; - Ok(()) + Ok(response) } pub async fn start_impedancemeter_multi( diff --git a/src/communication.rs b/src/communication.rs index 227091a..5b64a1c 100644 --- a/src/communication.rs +++ b/src/communication.rs @@ -25,6 +25,7 @@ pub async fn communicate_with_hardware( bode_series: Arc>, connected: Arc, data_frequency: Arc, + periods_per_dft: Arc>>, ) { let data_counter = Arc::new(AtomicU32::new(0)); let data_counter_clone = data_counter.clone(); @@ -113,13 +114,22 @@ pub async fn communicate_with_hardware( loop { select! { Some(frequency) = run_impedancemeter_rx.recv() => { - match frequency { + match frequency { StartStopSignal::StartSingle(freq, dft_num) => { - if let Err(e) = workbook_client.start_impedancemeter_single(freq, dft_num).await { - error!("Failed to start impedancemeter: {:?}", e); - } else { - settings.frequency = Some(StartStopSignal::StartSingle(freq, dft_num)); - info!("Impedancemeter started at frequency: {}", freq); + 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)); + *periods_per_dft.lock().unwrap() = Some(periods); + }, + Ok(Err(e)) => { + error!("Failed to init on hardware: {:?}", e); + *periods_per_dft.lock().unwrap() = None; + }, + Err(e) => { + error!("Communication error when starting impedancemeter: {:?}", e); + *periods_per_dft.lock().unwrap() = None; + } } }, StartStopSignal::StartMulti(dft_num, num_points) => { From 66b799445f98d6f8616238684105c9cd26e09fc0 Mon Sep 17 00:00:00 2001 From: Hubald Verzijl Date: Wed, 8 Oct 2025 15:41:22 +0200 Subject: [PATCH 4/4] Included bode plot measurements, including different number of points. --- Cargo.lock | 144 ++++++++++++++++++++++++++++++++++++++++--- Cargo.toml | 1 + src/app.rs | 106 +++++++++++++++++++++++++------ src/bin/main_cli.rs | 2 +- src/bin/main_gui.rs | 2 + src/client.rs | 19 +++--- src/communication.rs | 61 +++++++++++++----- src/plot.rs | 10 +-- src/signals.rs | 6 +- 9 files changed, 290 insertions(+), 61 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ecdd6c5..27b865e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -442,6 +442,7 @@ dependencies = [ "defmt", "eframe", "egui_dock", + "egui_extras", "egui_plot", "log", "postcard-rpc", @@ -892,9 +893,9 @@ dependencies = [ [[package]] name = "ecolor" -version = "0.32.0" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a631732d995184114016fab22fc7e3faf73d6841c2d7650395fe251fbcd9285" +checksum = "94bdf37f8d5bd9aa7f753573fdda9cf7343afa73dd28d7bfe9593bd9798fc07e" dependencies = [ "bytemuck", "emath", @@ -938,9 +939,9 @@ dependencies = [ [[package]] name = "egui" -version = "0.32.0" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8470210c95a42cc985d9ffebfd5067eea55bdb1c3f7611484907db9639675e28" +checksum = "5d5d0306cd61ca75e29682926d71f2390160247f135965242e904a636f51c0dc" dependencies = [ "accesskit", "ahash", @@ -1005,6 +1006,20 @@ dependencies = [ "paste", ] +[[package]] +name = "egui_extras" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dddbceddf39805fc6c62b1f7f9c05e23590b40844dc9ed89c6dc6dbc886e3e3b" +dependencies = [ + "ahash", + "egui", + "enum-map", + "log", + "mime_guess2", + "profiling", +] + [[package]] name = "egui_glow" version = "0.32.0" @@ -1036,9 +1051,9 @@ dependencies = [ [[package]] name = "emath" -version = "0.32.0" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45f057b141e7e46340c321400be74b793543b1b213036f0f989c35d35957c32e" +checksum = "45fd7bc25f769a3c198fe1cf183124bf4de3bd62ef7b4f1eaf6b08711a3af8db" dependencies = [ "bytemuck", ] @@ -1067,6 +1082,26 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" +[[package]] +name = "enum-map" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9" +dependencies = [ + "enum-map-derive", +] + +[[package]] +name = "enum-map-derive" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "enumflags2" version = "0.7.12" @@ -1090,9 +1125,9 @@ dependencies = [ [[package]] name = "epaint" -version = "0.32.0" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94cca02195f0552c17cabdc02f39aa9ab6fbd815dac60ab1cd3d5b0aa6f9551c" +checksum = "63adcea970b7a13094fe97a36ab9307c35a750f9e24bf00bb7ef3de573e0fddb" dependencies = [ "ab_glyph", "ahash", @@ -1108,9 +1143,9 @@ dependencies = [ [[package]] name = "epaint_default_fonts" -version = "0.32.0" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8495e11ed527dff39663b8c36b6c2b2799d7e4287fb90556e455d72eca0b4d3" +checksum = "1537accc50c9cab5a272c39300bdd0dd5dca210f6e5e8d70be048df9596e7ca2" [[package]] name = "equivalent" @@ -1910,6 +1945,24 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess2" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1706dc14a2e140dec0a7a07109d9a3d5890b81e85bd6c60b906b249a77adf0ca" +dependencies = [ + "mime", + "phf", + "phf_shared", + "unicase", +] + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2484,6 +2537,50 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn", + "unicase", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", + "unicase", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -2732,6 +2829,21 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -2996,6 +3108,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "slab" version = "0.4.10" @@ -3470,6 +3588,12 @@ dependencies = [ "thiserror 2.0.12", ] +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + [[package]] name = "unicode-ident" version = "1.0.18" diff --git a/Cargo.toml b/Cargo.toml index 2b4ae17..d619f75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ defmt = { version = "1.0.1" } eframe = { version = "0.32.0"} egui_plot = "0.33.0" egui_dock = "0.17.0" +egui_extras = "0.32.3" log = "0.4.27" simple_logger = "5.0.0" atomic_float = "1.1.0" diff --git a/src/app.rs b/src/app.rs index 0b0c7f2..748d014 100644 --- a/src/app.rs +++ b/src/app.rs @@ -9,14 +9,15 @@ use atomic_float::AtomicF32; use tokio::{sync::mpsc::{Sender}}; use eframe::egui::{self, Button, CollapsingHeader, Color32, ComboBox, DragValue, Key, Label, Layout, Modifiers}; -use egui_plot::{Corner, GridInput, GridMark, Legend, Line, Plot, PlotBounds, PlotPoint, PlotPoints, Points}; +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::icd::{IcdDftNum, NumberOfPoints}; +use crate::icd::{IcdDftNum, MeasurementPointSet}; const DFTNUM_VARIANTS: [IcdDftNum; 13] = [ IcdDftNum::Num4, IcdDftNum::Num8, IcdDftNum::Num16, IcdDftNum::Num32, @@ -25,6 +26,11 @@ const DFTNUM_VARIANTS: [IcdDftNum; 13] = [ IcdDftNum::Num8192, IcdDftNum::Num16384, ]; +const MEASUREMENT_POINTS_VARIANTS: [MeasurementPointSet; 2] = [ + MeasurementPointSet::Eight, + MeasurementPointSet::Eighteen, +]; + #[derive(Clone, Copy,Debug, PartialEq, Eq)] enum TabActive { Single, @@ -47,7 +53,9 @@ pub struct App { pub data_frequency: Arc, pub single_frequency: Arc>, pub dft_num: Arc>, + pub measurement_points: Arc>, pub periods_per_dft: Arc>>, + pub periods_per_dft_multi: Arc, Option>)>>, } struct TabViewer { @@ -59,7 +67,9 @@ struct TabViewer { on: Arc>, single_frequency: Arc>, dft_num: Arc>, + measurement_points: Arc>, periods_per_dft: Arc>>, + periods_per_dft_multi: Arc, Option>)>>, show_settings: bool, show_settings_toggle: Option, } @@ -171,21 +181,76 @@ impl TabViewer { if let Ok(on) = self.on.lock() { ui.add_enabled_ui(!*on, |ui| { ui.horizontal(|ui| { - ui.label("ADC samples per DFT:"); - let mut dft_num = self.dft_num.lock().unwrap(); - let mut index = DFTNUM_VARIANTS.iter().position(|&x| x == *dft_num).unwrap_or(0); - ComboBox::from_id_salt("Dftnum") + ui.label("Measurement Points:"); + let mut measurement_points = self.measurement_points.lock().unwrap(); + let mut index = MEASUREMENT_POINTS_VARIANTS.iter().position(|&x| x == *measurement_points).unwrap_or(0); + ComboBox::from_id_salt("MeasurementPoints") .width(75.0) - .show_index(ui, &mut index, DFTNUM_VARIANTS.len(), |i| { - format!("{}", 1 << (2 + i)) // 2^2 = 4, 2^3 = 8, ..., 2^14 = 16384 + .show_index(ui, &mut index, MEASUREMENT_POINTS_VARIANTS.len(), |i| { + format!("{:?}", MEASUREMENT_POINTS_VARIANTS[i].len()) }); - let new_value = DFTNUM_VARIANTS[index]; - if *dft_num != new_value { - *dft_num = new_value; - info!("DFTNUM setting changed!"); - }; + let new_value = MEASUREMENT_POINTS_VARIANTS[index]; + if *measurement_points != new_value { + *measurement_points = new_value; + info!("Measurement Points setting changed!"); + } }); }); + 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 { + // kHz + if freq % 1_000.0 == 0.0 { + format!("{}k", (freq / 1_000.0) as u64) + } else { + format!("{:.1}k", freq / 1_000.0) + } + } else { + // Hz + if freq % 1.0 == 0.0 { + format!("{}", freq as u64) + } else { + format!("{:.1}", freq) + } + } + } + + TableBuilder::new(ui) + .column(Column::auto()) + .columns(Column::remainder().at_most(30.0), freq.len()) + .header(10.0, |mut header| { + header.col(|ui| { + ui.label("Frequency [Hz]:"); + }); + for freq in &freq { + header.col(|ui| { + ui.label(format_frequency(*freq)); + }); + } + }) + .body(|mut body| { + body.row(5.0, |mut row| { + row.col(|ui| { + ui.label("Periods per DFT:"); + }); + if let Some(periods) = periods_per_dft_vec { + for period in &periods { + row.col(|ui| { + ui.label(format!("{:.1}", period)); + }); + } + } else { + for _ in &freq { + row.col(|ui| { + ui.label("N/A"); + }); + } + } + }); + }); + }); } }); @@ -355,7 +420,9 @@ impl App { let bode_plot = Arc::new(Mutex::new(BodePlot::new())); let single_frequency = Arc::new(Mutex::new(50000)); let dft_num = Arc::new(Mutex::new(IcdDftNum::Num2048)); + let measurement_points = Arc::new(Mutex::new(MeasurementPointSet::Eighteen)); let periods_per_dft = Arc::new(Mutex::new(None)); + let periods_per_dft_multi = Arc::new(Mutex::new((MeasurementPointSet::Eighteen.values().to_vec(), None))); let on = Arc::new(Mutex::new(true)); let tab_active = TabActive::Single; @@ -368,7 +435,9 @@ impl App { bode_plot: bode_plot.clone(), single_frequency: single_frequency.clone(), dft_num: dft_num.clone(), + measurement_points: measurement_points.clone(), periods_per_dft: periods_per_dft.clone(), + periods_per_dft_multi: periods_per_dft_multi.clone(), on: on.clone(), show_settings: false, show_settings_toggle: None, @@ -390,13 +459,15 @@ impl App { data_frequency: Arc::new(AtomicF32::new(0.0)), single_frequency, dft_num, + measurement_points, periods_per_dft, + periods_per_dft_multi, }; // For testing purposes, populate the Bode plot with a sample low-pass filter response let fc = 1000.0; // cutoff frequency in Hz - let freqs = NumberOfPoints::TwentyEight.values().to_vec(); + let freqs = MeasurementPointSet::Eighteen.values().to_vec(); let magnitudes = freqs.iter() .map(|&f| { 1.0 / (1.0 + (f / fc).powi(2)).sqrt() @@ -408,9 +479,8 @@ impl App { }) .collect::>(); - app.bode_plot.lock().unwrap().update_magnitudes(magnitudes); - app.bode_plot.lock().unwrap().update_phases(phases); - + app.bode_plot.lock().unwrap().update_magnitudes(MeasurementPointSet::Eighteen, magnitudes); + app.bode_plot.lock().unwrap().update_phases(MeasurementPointSet::Eighteen, phases); app.update_start_stop(); app @@ -424,7 +494,7 @@ impl App { } }, (TabActive::Multi, true) => { - if let Err(e) = self.run_impedancemeter_tx.try_send(StartStopSignal::StartMulti(*self.dft_num.lock().unwrap(), NumberOfPoints::TwentyEight)) { + if let Err(e) = self.run_impedancemeter_tx.try_send(StartStopSignal::StartMulti(*self.measurement_points.lock().unwrap())) { eprintln!("Failed to send start command: {:?}", e); } }, diff --git a/src/bin/main_cli.rs b/src/bin/main_cli.rs index c86c866..2f91cec 100644 --- a/src/bin/main_cli.rs +++ b/src/bin/main_cli.rs @@ -78,7 +78,7 @@ async fn main() { .subscribe_multi::(8) .await .unwrap(); - client.start_impedancemeter_single(freq, bioz_icd_rs::IcdDftNum::Num2048).await.unwrap(); + client.start_impedancemeter_single(freq, bioz_icd_rs::IcdDftNum::Num2048).await.unwrap().ok(); println!("Started with dft_num 2048!"); let dur = Duration::from_millis(dur.into()); diff --git a/src/bin/main_gui.rs b/src/bin/main_gui.rs index 3c589eb..e9908f6 100644 --- a/src/bin/main_gui.rs +++ b/src/bin/main_gui.rs @@ -34,6 +34,7 @@ fn main() { let data_frequency_clone = app.data_frequency.clone(); let periods_per_dft = app.periods_per_dft.clone(); + let periods_per_dft_multi = app.periods_per_dft_multi.clone(); // Execute the runtime in its own thread. std::thread::spawn(move || { @@ -48,6 +49,7 @@ fn main() { connected_clone, data_frequency_clone, periods_per_dft, + periods_per_dft_multi, )); }); diff --git a/src/client.rs b/src/client.rs index 3181adc..40ecf75 100644 --- a/src/client.rs +++ b/src/client.rs @@ -5,10 +5,10 @@ use postcard_rpc::{ }; use std::convert::Infallible; use bioz_icd_rs::{ - GetUniqueIdEndpoint, InitImpedanceResult, PingEndpoint, SetGreenLedEndpoint, StartMultiImpedance, StartMultiImpedanceEndpoint, StartSingleImpedance, StartSingleImpedanceEndpoint, StopSingleImpedanceEndpoint + GetUniqueIdEndpoint, ImpedanceInitResult, MultiImpedanceInitResult, MultiImpedanceStartRequest, PingEndpoint, SetGreenLedEndpoint, SingleImpedanceStartRequest, StartMultiImpedanceEndpoint, StartSingleImpedanceEndpoint, StopSingleImpedanceEndpoint }; -use crate::icd::{IcdDftNum, NumberOfPoints}; +use crate::icd::{IcdDftNum, MeasurementPointSet}; #[derive(Debug)] pub struct WorkbookClient { @@ -66,22 +66,21 @@ impl WorkbookClient { &self, frequency: u32, dft_number: IcdDftNum, - ) -> Result> { + ) -> Result> { let response = self.client - .send_resp::(&StartSingleImpedance { update_frequency: 60, sinus_frequency: frequency, dft_number}) + .send_resp::(&SingleImpedanceStartRequest { update_frequency: 60, sinus_frequency: frequency, dft_number}) .await?; Ok(response) } pub async fn start_impedancemeter_multi( &self, - dft_number: IcdDftNum, - number_of_points: NumberOfPoints, - ) -> Result<(), WorkbookError> { - self.client - .send_resp::(&StartMultiImpedance { dft_number, number_of_points }) + points: MeasurementPointSet, + ) -> Result> { + let response = self.client + .send_resp::(&MultiImpedanceStartRequest { points }) .await?; - Ok(()) + Ok(response) } pub async fn stop_impedancemeter(&self) -> Result> { diff --git a/src/communication.rs b/src/communication.rs index 5b64a1c..acccaef 100644 --- a/src/communication.rs +++ b/src/communication.rs @@ -8,6 +8,8 @@ use atomic_float::AtomicF32; use std::sync::{Arc, Mutex}; +use bioz_icd_rs::MeasurementPointSet; + use crate::icd; use crate::client::WorkbookClient; @@ -26,6 +28,7 @@ pub async fn communicate_with_hardware( connected: Arc, data_frequency: Arc, periods_per_dft: Arc>>, + periods_per_dft_multi: Arc, Option>)>>, ) { let data_counter = Arc::new(AtomicU32::new(0)); let data_counter_clone = data_counter.clone(); @@ -88,10 +91,10 @@ pub async fn communicate_with_hardware( info!("SingleImpedanceOutputTopic subscription ended."); }); - // Subscribe to MultiImpedanceOutputTopic28 - let mut multi_impedance_28_sub = workbook_client + // Subscribe to MultiImpedanceOutputTopic8 + let mut multi_impedance_sub = workbook_client .client - .subscribe_multi::(8) + .subscribe_multi::(8) .await .unwrap(); @@ -99,14 +102,25 @@ pub async fn communicate_with_hardware( let data_counter_clone_multi = data_counter_clone.clone(); tokio::spawn(async move { - while let Ok(val) = multi_impedance_28_sub.recv().await { + while let Ok(val) = multi_impedance_sub.recv().await { let mut bode_plot = data.lock().unwrap(); - let magnitudes = val.magnitudes.into_iter().collect::>(); - bode_plot.update_magnitudes(magnitudes); - let phases = val.phases.into_iter().collect::>(); - bode_plot.update_phases(phases); + match val.points { + MeasurementPointSet::Eight => { + let magnitudes: Vec = val.magnitudes_8.into_iter().collect(); + let phases: Vec = val.phases_8.into_iter().collect(); + bode_plot.update_magnitudes(MeasurementPointSet::Eight, magnitudes); + bode_plot.update_phases(MeasurementPointSet::Eight, phases); + }, + MeasurementPointSet::Eighteen => { + let magnitudes: Vec = val.magnitudes_18.into_iter().collect(); + let phases: Vec = val.phases_18.into_iter().collect(); + bode_plot.update_magnitudes(MeasurementPointSet::Eighteen, magnitudes); + bode_plot.update_phases(MeasurementPointSet::Eighteen, phases); + }, + } + data_counter_clone_multi.fetch_add(1, Ordering::Relaxed); } }); @@ -132,12 +146,28 @@ pub async fn communicate_with_hardware( } } }, - StartStopSignal::StartMulti(dft_num, num_points) => { - if let Err(e) = workbook_client.start_impedancemeter_multi(dft_num, num_points).await { - error!("Failed to start multi-point impedancemeter: {:?}", e); - } else { - settings.frequency = Some(StartStopSignal::StartMulti(dft_num, num_points)); - info!("Multi-point Impedancemeter started."); + StartStopSignal::StartMulti(num_points) => { + match workbook_client.start_impedancemeter_multi(num_points).await { + Ok(Ok(periods)) => { + settings.frequency = Some(StartStopSignal::StartMulti(num_points)); + info!("Multi-point Impedancemeter started."); + match num_points { + MeasurementPointSet::Eight => { + *periods_per_dft_multi.lock().unwrap() = (num_points.values().iter().copied().collect(), Some(periods.periods_per_dft_8.into_iter().collect())); + }, + MeasurementPointSet::Eighteen => { + *periods_per_dft_multi.lock().unwrap() = (num_points.values().iter().copied().collect(), Some(periods.periods_per_dft_18.into_iter().collect())); + }, + } + }, + Ok(Err(e)) => { + error!("Failed to multi-init on hardware: {:?}", e); + *periods_per_dft_multi.lock().unwrap() = (num_points.values().iter().copied().collect(), None); + }, + Err(e) => { + error!("Communication error when starting impedancemeter: {:?}", e); + *periods_per_dft_multi.lock().unwrap() = (num_points.values().iter().copied().collect(), None); + } } }, StartStopSignal::Stop => { @@ -145,6 +175,9 @@ pub async fn communicate_with_hardware( error!("Failed to stop impedancemeter: {:?}", e); } else { settings.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); info!("Impedancemeter stopped."); } }, diff --git a/src/plot.rs b/src/plot.rs index d137a94..b7a9232 100644 --- a/src/plot.rs +++ b/src/plot.rs @@ -3,7 +3,7 @@ use std::collections::VecDeque; use egui_plot::{PlotPoint, PlotPoints}; -use bioz_icd_rs::NumberOfPoints; +use bioz_icd_rs::MeasurementPointSet; pub struct TimeSeriesPlot { pub values: VecDeque, @@ -63,14 +63,14 @@ impl BodePlot { } } - pub fn update_magnitudes(&mut self, magnitudes: Vec) { - let freqs = NumberOfPoints::TwentyEight.values().to_vec(); + 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(); } - pub fn update_phases(&mut self, phases: Vec) { - let freqs = NumberOfPoints::TwentyEight.values().to_vec(); + 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(); } diff --git a/src/signals.rs b/src/signals.rs index b7fa809..5171af3 100644 --- a/src/signals.rs +++ b/src/signals.rs @@ -1,8 +1,8 @@ -use crate::icd::{IcdDftNum, NumberOfPoints}; +use crate::icd::{IcdDftNum, MeasurementPointSet}; #[derive(Copy, Clone)] pub enum StartStopSignal { StartSingle(u32, IcdDftNum), // frequency in Hz, DFT number - StartMulti(IcdDftNum, NumberOfPoints), // DFT number, number of points per measurement + StartMulti(MeasurementPointSet), // DFT number, number of points per measurement Stop, -} \ No newline at end of file +}