mirror of
https://github.com/hubaldv/bioz-host-rs.git
synced 2026-03-09 23:40:31 +00:00
Implemented custom marker support during logging. Updated buttons.
This commit is contained in:
128
src/app.rs
128
src/app.rs
@@ -12,7 +12,7 @@ use chrono::Local;
|
|||||||
use atomic_float::AtomicF32;
|
use atomic_float::AtomicF32;
|
||||||
use tokio::{sync::mpsc::{Sender}};
|
use tokio::{sync::mpsc::{Sender}};
|
||||||
|
|
||||||
use eframe::egui::{self, Button, CollapsingHeader, Color32, ComboBox, DragValue, Id, Key, Label, Layout, Modifiers, RichText, TextEdit, Widget};
|
use eframe::egui::{self, Button, CollapsingHeader, Color32, ComboBox, DragValue, Id, Key, Label, Layout, Modal, Modifiers, RichText, TextEdit, Widget};
|
||||||
use egui_plot::{Corner, GridInput, GridMark, Legend, Line, Plot, PlotPoint, Points};
|
use egui_plot::{Corner, GridInput, GridMark, Legend, Line, Plot, PlotPoint, Points};
|
||||||
use egui_dock::{DockArea, DockState, Style};
|
use egui_dock::{DockArea, DockState, Style};
|
||||||
use egui_extras::{TableBuilder, Column};
|
use egui_extras::{TableBuilder, Column};
|
||||||
@@ -70,6 +70,8 @@ pub struct App {
|
|||||||
pub periods_per_dft_sweep: Arc<Mutex<(Vec<u32>, Option<Vec<f32>>)>>,
|
pub periods_per_dft_sweep: Arc<Mutex<(Vec<u32>, Option<Vec<f32>>)>>,
|
||||||
pub gui_logging_state: Arc<Mutex<LoggingStates>>,
|
pub gui_logging_state: Arc<Mutex<LoggingStates>>,
|
||||||
log_filename: String,
|
log_filename: String,
|
||||||
|
log_marker_modal: bool,
|
||||||
|
log_marker: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TabViewer {
|
struct TabViewer {
|
||||||
@@ -404,6 +406,7 @@ impl TabViewer {
|
|||||||
ui.label("R: Reset view/plots");
|
ui.label("R: Reset view/plots");
|
||||||
ui.label("S: Toggle settings");
|
ui.label("S: Toggle settings");
|
||||||
ui.label("L: Toggle logging");
|
ui.label("L: Toggle logging");
|
||||||
|
ui.label("A: Add marker (when logging)");
|
||||||
ui.label("CMD-W: Close window");
|
ui.label("CMD-W: Close window");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -542,7 +545,9 @@ impl App {
|
|||||||
periods_per_dft,
|
periods_per_dft,
|
||||||
periods_per_dft_sweep,
|
periods_per_dft_sweep,
|
||||||
gui_logging_state: Arc::new(Mutex::new(LoggingStates::Idle)),
|
gui_logging_state: Arc::new(Mutex::new(LoggingStates::Idle)),
|
||||||
log_filename: format!("log_{}.csv", Local::now().format("%Y%m%d"))
|
log_filename: format!("log_{}_single.csv", Local::now().format("%Y%m%d")),
|
||||||
|
log_marker_modal: false,
|
||||||
|
log_marker: String::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// For testing purposes, populate the Bode plot with a sample low-pass filter response
|
// For testing purposes, populate the Bode plot with a sample low-pass filter response
|
||||||
@@ -608,10 +613,27 @@ impl eframe::App for App {
|
|||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
if ui.add_enabled(connected, toggle_start_stop(&mut *self.on.lock().unwrap())).changed() {
|
let on = *self.on.lock().unwrap();
|
||||||
self.update_start_stop();
|
|
||||||
|
let (color, text) = match on {
|
||||||
|
true => (Color32::DARK_RED, "Stop"),
|
||||||
|
false => (Color32::DARK_GREEN, "Start"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let start_stop_button = Button::new(
|
||||||
|
RichText::new(text)
|
||||||
|
.strong()
|
||||||
|
.color(Color32::WHITE)
|
||||||
|
).fill(color)
|
||||||
|
.corner_radius(5.0)
|
||||||
|
.min_size(egui::vec2(45.0, 0.0))
|
||||||
|
.frame(true);
|
||||||
|
|
||||||
|
if ui.add_enabled(connected, start_stop_button).clicked() {
|
||||||
|
*self.on.lock().unwrap() = !on;
|
||||||
|
self.update_start_stop();
|
||||||
|
}
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
if ui.add_enabled(connected, Button::new("Reset view")).clicked() {
|
if ui.add_enabled(connected, Button::new("Reset view")).clicked() {
|
||||||
@@ -620,33 +642,83 @@ impl eframe::App for App {
|
|||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
|
ui.add_enabled(connected, Label::new("Logging:"));
|
||||||
|
|
||||||
let gui_logging_state = *self.gui_logging_state.lock().unwrap();
|
let gui_logging_state = *self.gui_logging_state.lock().unwrap();
|
||||||
|
|
||||||
let (color, signal) = match gui_logging_state {
|
let mut logging_enabled = !matches!(gui_logging_state, LoggingStates::Idle);
|
||||||
LoggingStates::Idle => (Color32::DARK_RED, LoggingSignal::StartFileLogging(self.log_filename.clone())),
|
|
||||||
LoggingStates::Starting => (Color32::from_rgb(204, 153, 0), LoggingSignal::StopFileLogging),
|
|
||||||
LoggingStates::Logging => (Color32::DARK_GREEN, LoggingSignal::StopFileLogging),
|
|
||||||
};
|
|
||||||
|
|
||||||
let button = Button::new(
|
if ui.add_enabled(connected, toggle_start_stop(&mut logging_enabled)).changed() {
|
||||||
RichText::new("Logging")
|
let signal = match gui_logging_state {
|
||||||
.strong()
|
LoggingStates::Idle => LoggingSignal::StartFileLogging(self.log_filename.clone()),
|
||||||
.color(Color32::WHITE)
|
LoggingStates::Starting | LoggingStates::Logging => LoggingSignal::StopFileLogging,
|
||||||
).fill(color)
|
};
|
||||||
.corner_radius(5.0)
|
|
||||||
.frame(true);
|
|
||||||
|
|
||||||
if ui.add(button).clicked() {
|
|
||||||
self.log_tx.try_send(signal).unwrap_or_else(|e| {
|
self.log_tx.try_send(signal).unwrap_or_else(|e| {
|
||||||
error!("Failed to send logging toggle signal: {:?}", e);
|
error!("Failed to send logging toggle signal: {:?}", e);
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
ui.add_enabled_ui(on && logging_enabled, |ui| {
|
||||||
|
if Button::new("Add marker").corner_radius(5.0).min_size(egui::vec2(20.0, 0.0)).ui(ui).on_hover_text("Add a marker to the current time series plots").clicked() {
|
||||||
|
self.log_marker_modal = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if self.log_marker_modal {
|
||||||
|
if Modal::new(Id::new("modal_marker"))
|
||||||
|
.show(ctx, |ui| {
|
||||||
|
ui.vertical_centered(|ui| {
|
||||||
|
ui.heading("Add marker");
|
||||||
|
|
||||||
|
ui.add_space(16.0);
|
||||||
|
|
||||||
|
// Input field for marker
|
||||||
|
let text_edit_response = TextEdit::singleline(&mut self.log_marker)
|
||||||
|
.desired_width(100.0)
|
||||||
|
.id(Id::new("marker_field"))
|
||||||
|
.hint_text("Marker name")
|
||||||
|
.ui(ui);
|
||||||
|
|
||||||
|
ui.add_space(16.0);
|
||||||
|
|
||||||
|
// Centered Add button
|
||||||
|
let add_clicked = Button::new("Add")
|
||||||
|
.corner_radius(5.0)
|
||||||
|
.min_size(egui::vec2(80.0, 30.0))
|
||||||
|
.ui(ui)
|
||||||
|
.clicked();
|
||||||
|
|
||||||
|
// Check for Enter key in the TextEdit
|
||||||
|
let enter_pressed = text_edit_response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter));
|
||||||
|
|
||||||
|
if add_clicked || enter_pressed {
|
||||||
|
info!("Adding marker: {}", self.log_marker);
|
||||||
|
self.log_tx.try_send(LoggingSignal::AddMarker(self.log_marker.clone())).unwrap_or_else(|e| {
|
||||||
|
error!("Failed to send logging marker signal: {:?}", e);
|
||||||
|
});
|
||||||
|
self.log_marker = String::new();
|
||||||
|
ui.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request focus on the text edit when the modal opens
|
||||||
|
text_edit_response.request_focus();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.should_close()
|
||||||
|
{
|
||||||
|
self.log_marker_modal = false;
|
||||||
|
self.log_marker = String::new();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
ui.add_enabled_ui(gui_logging_state == LoggingStates::Idle, |ui| {
|
ui.add_enabled_ui(gui_logging_state == LoggingStates::Idle, |ui| {
|
||||||
ui.label("Log filename:");
|
ui.label("Log filename:");
|
||||||
TextEdit::singleline(&mut self.log_filename).desired_width(125.0).id(Id::new("file_name_field")).ui(ui);
|
TextEdit::singleline(&mut self.log_filename).desired_width(150.0).id(Id::new("file_name_field")).ui(ui);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Spacer to push the LED to the right
|
// Spacer to push the LED to the right
|
||||||
@@ -691,6 +763,12 @@ impl eframe::App for App {
|
|||||||
self.tab_active = TabActive::Single;
|
self.tab_active = TabActive::Single;
|
||||||
*self.on.lock().unwrap() = false;
|
*self.on.lock().unwrap() = false;
|
||||||
self.update_start_stop();
|
self.update_start_stop();
|
||||||
|
if *self.gui_logging_state.lock().unwrap() == LoggingStates::Logging {
|
||||||
|
self.log_tx.try_send(LoggingSignal::StopFileLogging).unwrap_or_else(|e| {
|
||||||
|
error!("Failed to send logging logging signal: {:?}", e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
self.log_filename = format!("log_{}_single.csv", Local::now().format("%Y%m%d"));
|
||||||
info!("Switched to Single tab");
|
info!("Switched to Single tab");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -699,6 +777,12 @@ impl eframe::App for App {
|
|||||||
self.tab_active = TabActive::Sweep;
|
self.tab_active = TabActive::Sweep;
|
||||||
*self.on.lock().unwrap() = false;
|
*self.on.lock().unwrap() = false;
|
||||||
self.update_start_stop();
|
self.update_start_stop();
|
||||||
|
if *self.gui_logging_state.lock().unwrap() == LoggingStates::Logging {
|
||||||
|
self.log_tx.try_send(LoggingSignal::StopFileLogging).unwrap_or_else(|e| {
|
||||||
|
error!("Failed to send logging logging signal: {:?}", e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
self.log_filename = format!("log_{}_sweep.csv", Local::now().format("%Y%m%d"));
|
||||||
info!("Switched to Sweep tab");
|
info!("Switched to Sweep tab");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -725,7 +809,8 @@ impl eframe::App for App {
|
|||||||
// Check if the file name field is focused
|
// Check if the file name field is focused
|
||||||
// If it is, we don't want to trigger shortcuts
|
// 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")));
|
let file_name_field_is_focused = ctx.memory(|memory| memory.has_focus(Id::new("file_name_field")));
|
||||||
if !file_name_field_is_focused {
|
let marker_field_is_focused = ctx.memory(|memory| memory.has_focus(Id::new("marker_field")));
|
||||||
|
if !file_name_field_is_focused && !marker_field_is_focused {
|
||||||
|
|
||||||
// Space to start/stop measurement
|
// Space to start/stop measurement
|
||||||
if ctx.input(|i| i.key_pressed(Key::Space))
|
if ctx.input(|i| i.key_pressed(Key::Space))
|
||||||
@@ -746,6 +831,11 @@ impl eframe::App for App {
|
|||||||
self.reset_view();
|
self.reset_view();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Toggle marker modal
|
||||||
|
if ctx.input(|i| i.key_pressed(egui::Key::A)) && *self.gui_logging_state.lock().unwrap() == LoggingStates::Logging {
|
||||||
|
self.log_marker_modal = !self.log_marker_modal;
|
||||||
|
}
|
||||||
|
|
||||||
// Enable/disable GUI logging
|
// Enable/disable GUI logging
|
||||||
if ctx.input(|i| i.key_pressed(egui::Key::L)) {
|
if ctx.input(|i| i.key_pressed(egui::Key::L)) {
|
||||||
let gui_logging_enabled = *self.gui_logging_state.lock().unwrap();
|
let gui_logging_enabled = *self.gui_logging_state.lock().unwrap();
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
use eframe::NativeOptions;
|
||||||
|
use eframe::egui::Vec2;
|
||||||
|
|
||||||
use simple_logger::SimpleLogger;
|
use simple_logger::SimpleLogger;
|
||||||
use log::info;
|
use log::info;
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
@@ -69,9 +72,11 @@ async fn main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Run the GUI in the main thread.
|
// Run the GUI in the main thread.
|
||||||
|
let mut native_options = NativeOptions::default();
|
||||||
|
native_options.viewport.inner_size = Some(Vec2::new(850.0, 600.0));
|
||||||
let _ = eframe::run_native(
|
let _ = eframe::run_native(
|
||||||
"Impedance Visualizer [egui + tokio] - Hubald Verzijl - 2025",
|
"Impedance Visualizer [egui + tokio] - Hubald Verzijl - 2025",
|
||||||
eframe::NativeOptions::default(),
|
native_options,
|
||||||
Box::new(|_cc| Ok(Box::new(app))),
|
Box::new(|_cc| Ok(Box::new(app))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -23,6 +23,7 @@ pub async fn log_data(
|
|||||||
gui_logging_state: Arc<Mutex<LoggingStates>>,
|
gui_logging_state: Arc<Mutex<LoggingStates>>,
|
||||||
) {
|
) {
|
||||||
let mut file: Option<File> = None;
|
let mut file: Option<File> = None;
|
||||||
|
let mut logging_marker = String::new();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match data.recv().await {
|
match data.recv().await {
|
||||||
@@ -30,25 +31,40 @@ pub async fn log_data(
|
|||||||
match signal {
|
match signal {
|
||||||
LoggingSignal::SingleImpedance(timestamp, frequency, magnitude, phase) => {
|
LoggingSignal::SingleImpedance(timestamp, frequency, magnitude, phase) => {
|
||||||
if let Some(f) = file.as_mut() {
|
if let Some(f) = file.as_mut() {
|
||||||
let _ = f
|
let _ = f.write_all(format!("{},{},{},{},{}\n",
|
||||||
.write_all(format!("{},{},{:.3},{:.3}\n", timestamp.duration_since(UNIX_EPOCH).unwrap().as_millis(), frequency, magnitude, phase).as_bytes())
|
timestamp.duration_since(UNIX_EPOCH).unwrap().as_millis(),
|
||||||
.await;
|
frequency,
|
||||||
|
magnitude,
|
||||||
|
phase,
|
||||||
|
logging_marker)
|
||||||
|
.as_bytes())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
if logging_marker.len() > 0 {
|
||||||
|
logging_marker.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LoggingSignal::SweepImpedance(timestamp, frequencies, magnitudes, phases) => {
|
LoggingSignal::SweepImpedance(timestamp, frequencies, magnitudes, phases) => {
|
||||||
if let Some(f) = file.as_mut() {
|
if let Some(f) = file.as_mut() {
|
||||||
for i in 0..frequencies.len() {
|
for i in 0..frequencies.len() {
|
||||||
let _ = f.write_all(
|
let _ = f.write_all(
|
||||||
format!("{},{},{},{},{}\n",
|
format!("{},{},{},{},{},{}\n",
|
||||||
timestamp.duration_since(UNIX_EPOCH).unwrap().as_millis(),
|
timestamp.duration_since(UNIX_EPOCH).unwrap().as_millis(),
|
||||||
frequencies[i],
|
frequencies[i],
|
||||||
magnitudes[i],
|
magnitudes[i],
|
||||||
phases[i],
|
phases[i],
|
||||||
i // optional index
|
i, // optional index
|
||||||
|
logging_marker
|
||||||
).as_bytes()
|
).as_bytes()
|
||||||
).await;
|
).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if logging_marker.len() > 0 {
|
||||||
|
logging_marker.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LoggingSignal::AddMarker(marker) => {
|
||||||
|
logging_marker = marker;
|
||||||
}
|
}
|
||||||
LoggingSignal::StartFileLogging(filename) => {
|
LoggingSignal::StartFileLogging(filename) => {
|
||||||
// Update global logging state
|
// Update global logging state
|
||||||
|
|||||||
@@ -14,4 +14,5 @@ pub enum LoggingSignal {
|
|||||||
SweepImpedance(SystemTime, Vec<u32>, Vec<f32>, Vec<f32>), // frequency, magnitude, phase
|
SweepImpedance(SystemTime, Vec<u32>, Vec<f32>, Vec<f32>), // frequency, magnitude, phase
|
||||||
StartFileLogging(String), // e.g. filename
|
StartFileLogging(String), // e.g. filename
|
||||||
StopFileLogging,
|
StopFileLogging,
|
||||||
|
AddMarker(String),
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user