From 565d17b3838dbcd2fd10634effcd0bcebaff18ad Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 21 Jan 2024 15:45:29 +0100 Subject: [PATCH] Switched channels instead of shared state --- hostsoftware/Cargo.toml | 1 + hostsoftware/src/main.rs | 234 +++++++++++++++++++++++---------------- 2 files changed, 138 insertions(+), 97 deletions(-) diff --git a/hostsoftware/Cargo.toml b/hostsoftware/Cargo.toml index 83c26c0..de921a7 100644 --- a/hostsoftware/Cargo.toml +++ b/hostsoftware/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] cheapsdo-protocol = { path = "../protocol" } +crossbeam-channel = "0.5.11" eframe = "0.24.1" egui_plot = "0.24.1" postcard = {version = "1.0.8", features = ["use-std"]} diff --git a/hostsoftware/src/main.rs b/hostsoftware/src/main.rs index 47526e2..dddbbc5 100644 --- a/hostsoftware/src/main.rs +++ b/hostsoftware/src/main.rs @@ -1,12 +1,14 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release use std::io; -use std::sync::{Arc, Mutex}; + +use std::io::Write; use std::time::Duration; -use std::{io::Write, thread}; + +use crossbeam_channel::{select, unbounded, Receiver, Sender}; use eframe::egui; -use egui_plot::{Line, Plot, PlotBounds, PlotPoints}; +use egui_plot::{Line, Plot, PlotPoints}; use cheapsdo_protocol::*; use postcard::{from_bytes_cobs, to_stdvec_cobs}; @@ -21,40 +23,43 @@ fn main() -> Result<(), eframe::Error> { eframe::run_native( "Cheapsdo Control", options, - Box::new(|cc| Box::::default()), + Box::new(|_cc| Box::::default()), ) } -struct SharedState { - device_state: StatusMessage, - average_points: Vec<[f64; 2]>, - measured_points: Vec<[f64; 2]>, - pwm_points: Vec<[f64; 2]>, +#[derive(PartialEq, Debug)] +enum SerialPortCmd { + Disconnect, } -impl Default for SharedState { - fn default() -> Self { - Self { - device_state: StatusMessage::default(), - average_points: Vec::<[f64; 2]>::new(), - measured_points: Vec::<[f64; 2]>::new(), - pwm_points: Vec::<[f64; 2]>::new(), - } - } +#[derive(PartialEq, Debug)] +enum SerialPortData { + DeviceState(StatusMessage), } struct CheapsdoControl { - shared_state: Arc>, serial_device: String, serial_connected: bool, + + data_rx: Option>, + cmd_tx: Option>, + + device_state: StatusMessage, + average_points: Vec<[f64; 2]>, + pwm_points: Vec<[f64; 2]>, } impl Default for CheapsdoControl { fn default() -> Self { Self { - shared_state: Arc::new(Mutex::new(SharedState::default())), serial_device: "".to_owned(), serial_connected: false, + data_rx: None, + cmd_tx: None, + + device_state: StatusMessage::default(), + average_points: Vec::new(), + pwm_points: Vec::new(), } } } @@ -64,6 +69,24 @@ impl eframe::App for CheapsdoControl { let ports = serialport::available_ports().unwrap_or(Vec::::new()); + if let Some(data_rx) = &self.data_rx { + loop { + select! { + recv(data_rx) -> data => { + match data { + Ok(SerialPortData::DeviceState(status_msg)) => { + self.device_state = status_msg.clone(); + self.average_points.push([self.average_points.len() as f64, status_msg.average_frequency]); + self.pwm_points.push([self.pwm_points.len() as f64, status_msg.pwm as f64]); + }, + Err(_) => break, + }; + }, + default => break, + } + } + } + egui::CentralPanel::default().show(ctx, |ui| { ui.horizontal(|ui| { egui::ComboBox::from_label("Select Serialport") @@ -82,101 +105,114 @@ impl eframe::App for CheapsdoControl { .add_enabled(!self.serial_connected, egui::Button::new("Open")) .clicked() { - self.serial_connected = true; - let serial_device = self.serial_device.clone(); - let devicestate = self.shared_state.clone(); let ctx = ctx.clone(); + self.serial_connected = true; + self.average_points.clear(); + self.pwm_points.clear(); + + let (cmd_tx, cmd_rx) = unbounded(); + let (data_tx, data_rx) = unbounded(); + + self.cmd_tx = Some(cmd_tx); + self.data_rx = Some(data_rx); + std::thread::spawn(move || { - poll_device(serial_device, devicestate, ctx); + poll_device(serial_device, cmd_rx, data_tx, ctx); }); } if ui .add_enabled(self.serial_connected, egui::Button::new("Close")) .clicked() - {} + { + if let Some(cmd_tx) = &self.cmd_tx { + cmd_tx.send(SerialPortCmd::Disconnect).unwrap(); + } + self.serial_connected = false; + } }); ui.separator(); - { - let mut state = self.shared_state.lock().unwrap(); - ui.horizontal(|ui| { - ui.label( - egui::RichText::new(format!( - "Measured: {}", - formatters::frequency(state.device_state.measured_frequency) - )) - .family(egui::FontFamily::Monospace) - .size(20.0), - ); + ui.horizontal(|ui| { + ui.label( + egui::RichText::new(format!( + "Measured: {}", + formatters::frequency(self.device_state.measured_frequency) + )) + .family(egui::FontFamily::Monospace) + .size(20.0), + ); - ui.add_space(100.0); + ui.add_space(100.0); - ui.label( - egui::RichText::new(format!( - "Average: {}", - formatters::frequency(state.device_state.average_frequency) - )) - .family(egui::FontFamily::Monospace) - .size(20.0), - ); + ui.label( + egui::RichText::new(format!( + "Average: {}", + formatters::frequency(self.device_state.average_frequency) + )) + .family(egui::FontFamily::Monospace) + .size(20.0), + ); + }); + + ui.add_space(20.0); + + let average_line = Line::new(PlotPoints::new(self.average_points.clone())); + //let measured_line = Line::new(PlotPoints::new(self.measured_points.clone())); + Plot::new("frequency_plot") + .view_aspect(4.0) + .allow_zoom(false) + .allow_scroll(false) + .allow_drag(false) + .allow_boxed_zoom(false) + .y_axis_width(12) + .y_axis_formatter(|val, _, _| formatters::frequency(val)) + .label_formatter(|name, value| { + format!("{}: {}", name, formatters::frequency(value.y)) + }) + .show(ui, |plot_ui| { + plot_ui.set_auto_bounds([true, true].into()); + plot_ui.line(average_line); + //plot_ui.line(measured_line); }); - ui.add_space(20.0); + ui.add_space(20.0); - let average_line = Line::new(PlotPoints::new(state.average_points.clone())); - let measured_line = Line::new(PlotPoints::new(state.measured_points.clone())); - Plot::new("frequency_plot") - .view_aspect(4.0) - .allow_zoom(false) - .allow_scroll(false) - .allow_drag(false) - .allow_boxed_zoom(false) - .y_axis_width(12) - .y_axis_formatter(|val, _, _| formatters::frequency(val)) - .label_formatter(|name, value| { - format!("{}: {}", name, formatters::frequency(value.y)) - }) - .show(ui, |plot_ui| { - plot_ui.set_auto_bounds([true, true].into()); - plot_ui.line(average_line); - //plot_ui.line(measured_line); - }); - - ui.add_space(20.0); - - let pwm_line = Line::new(PlotPoints::new(state.pwm_points.clone())); - Plot::new("pwm_plot") - .view_aspect(4.0) - .allow_zoom(false) - .allow_scroll(false) - .allow_drag(false) - .allow_boxed_zoom(false) - .y_axis_width(12) - .show(ui, |plot_ui| { - plot_ui.set_auto_bounds([true, true].into()); - plot_ui.line(pwm_line); - }); - } + let pwm_line = Line::new(PlotPoints::new(self.pwm_points.clone())); + Plot::new("pwm_plot") + .view_aspect(4.0) + .allow_zoom(false) + .allow_scroll(false) + .allow_drag(false) + .allow_boxed_zoom(false) + .y_axis_width(12) + .show(ui, |plot_ui| { + plot_ui.set_auto_bounds([true, true].into()); + plot_ui.line(pwm_line); + }); }); } } -fn poll_device(port: String, state: Arc>, ctx: egui::Context) { +fn poll_device( + port: String, + cmd_rx: Receiver, + data_tx: Sender, + ctx: egui::Context, +) { let mut port = serialport::new(port, 115_200) .timeout(Duration::from_millis(10)) .open() .expect("Failed to open port"); + let host_msg = HostMessage::RequestStatus; + let msg_bytes = to_stdvec_cobs(&host_msg).unwrap(); + port.write_all(&msg_bytes).unwrap(); + loop { - let host_msg = HostMessage::RequestStatus; - let msg_bytes = to_stdvec_cobs(&host_msg).unwrap(); - - port.write_all(&msg_bytes).unwrap(); - let mut serial_buf: Vec = vec![0; 128]; match port.read(serial_buf.as_mut_slice()) { Ok(t) => { @@ -187,16 +223,10 @@ fn poll_device(port: String, state: Arc>, ctx: egui::Context) match dev_msg { DeviceMessage::Status(status_msg) => { - let mut state = state.lock().unwrap(); - let x = state.average_points.len() as f64; - state.average_points.push([x, status_msg.average_frequency]); - state - .measured_points - .push([x, status_msg.measured_frequency]); - state.pwm_points.push([x, status_msg.pwm as f64]); - - state.device_state = status_msg; - ctx.request_repaint() + data_tx + .send(SerialPortData::DeviceState(status_msg)) + .unwrap(); + ctx.request_repaint(); } } } @@ -204,6 +234,16 @@ fn poll_device(port: String, state: Arc>, ctx: egui::Context) Err(e) => eprintln!("{:?}", e), } - thread::sleep(Duration::from_millis(1000)); + select! { + recv(cmd_rx) -> cmd => match cmd { + Ok(SerialPortCmd::Disconnect) => return, + Err(_) => {}, + }, + default(Duration::from_secs(1)) => { + let host_msg = HostMessage::RequestStatus; + let msg_bytes = to_stdvec_cobs(&host_msg).unwrap(); + port.write_all(&msg_bytes).unwrap(); + }, + } } }