From e71b02477a96520ae99b000af1ad226f7d52355b Mon Sep 17 00:00:00 2001 From: Hubald Verzijl Date: Tue, 7 Oct 2025 10:18:04 +0200 Subject: [PATCH] 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