#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release use std::io; use std::io::Write; use std::time::Duration; use crossbeam_channel::{select, unbounded, Receiver, Sender}; use eframe::egui; use egui_plot::{Line, Plot, PlotPoints}; use cheapsdo_protocol::*; use postcard::{from_bytes_cobs, to_stdvec_cobs}; mod formatters; fn main() -> Result<(), eframe::Error> { let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default(), ..Default::default() }; eframe::run_native( "Cheapsdo Control", options, Box::new(|_cc| Box::::default()), ) } #[derive(PartialEq, Debug)] enum SerialPortCmd { Disconnect, } #[derive(PartialEq, Debug)] enum SerialPortData { DeviceState(StatusMessage), } struct CheapsdoControl { 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 { 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(), } } } impl eframe::App for CheapsdoControl { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { 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") .selected_text(self.serial_device.to_owned()) .show_ui(ui, |ui| { for port in ports { ui.selectable_value( &mut self.serial_device, port.port_name.to_owned(), port.port_name, ); } }); if ui .add_enabled( !self.serial_connected && !self.serial_device.is_empty(), egui::Button::new("Open"), ) .clicked() { let serial_device = self.serial_device.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, 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(); 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.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); 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, 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 mut serial_buf: Vec = vec![0; 128]; match port.read(serial_buf.as_mut_slice()) { Ok(t) => { serial_buf.truncate(t); println!("Data: {:?}", serial_buf); let dev_msg = from_bytes_cobs::(&mut serial_buf).unwrap(); match dev_msg { DeviceMessage::Status(status_msg) => { data_tx .send(SerialPortData::DeviceState(status_msg)) .unwrap(); ctx.request_repaint(); } } } Err(ref e) if e.kind() == io::ErrorKind::TimedOut => (), Err(e) => eprintln!("{:?}", e), } 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(); }, } } }