Switched channels instead of shared state
This commit is contained in:
parent
6dd49234df
commit
565d17b383
|
@ -7,6 +7,7 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cheapsdo-protocol = { path = "../protocol" }
|
cheapsdo-protocol = { path = "../protocol" }
|
||||||
|
crossbeam-channel = "0.5.11"
|
||||||
eframe = "0.24.1"
|
eframe = "0.24.1"
|
||||||
egui_plot = "0.24.1"
|
egui_plot = "0.24.1"
|
||||||
postcard = {version = "1.0.8", features = ["use-std"]}
|
postcard = {version = "1.0.8", features = ["use-std"]}
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
use std::io::Write;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{io::Write, thread};
|
|
||||||
|
use crossbeam_channel::{select, unbounded, Receiver, Sender};
|
||||||
|
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use egui_plot::{Line, Plot, PlotBounds, PlotPoints};
|
use egui_plot::{Line, Plot, PlotPoints};
|
||||||
|
|
||||||
use cheapsdo_protocol::*;
|
use cheapsdo_protocol::*;
|
||||||
use postcard::{from_bytes_cobs, to_stdvec_cobs};
|
use postcard::{from_bytes_cobs, to_stdvec_cobs};
|
||||||
|
@ -21,40 +23,43 @@ fn main() -> Result<(), eframe::Error> {
|
||||||
eframe::run_native(
|
eframe::run_native(
|
||||||
"Cheapsdo Control",
|
"Cheapsdo Control",
|
||||||
options,
|
options,
|
||||||
Box::new(|cc| Box::<CheapsdoControl>::default()),
|
Box::new(|_cc| Box::<CheapsdoControl>::default()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SharedState {
|
#[derive(PartialEq, Debug)]
|
||||||
device_state: StatusMessage,
|
enum SerialPortCmd {
|
||||||
average_points: Vec<[f64; 2]>,
|
Disconnect,
|
||||||
measured_points: Vec<[f64; 2]>,
|
|
||||||
pwm_points: Vec<[f64; 2]>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SharedState {
|
#[derive(PartialEq, Debug)]
|
||||||
fn default() -> Self {
|
enum SerialPortData {
|
||||||
Self {
|
DeviceState(StatusMessage),
|
||||||
device_state: StatusMessage::default(),
|
|
||||||
average_points: Vec::<[f64; 2]>::new(),
|
|
||||||
measured_points: Vec::<[f64; 2]>::new(),
|
|
||||||
pwm_points: Vec::<[f64; 2]>::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CheapsdoControl {
|
struct CheapsdoControl {
|
||||||
shared_state: Arc<Mutex<SharedState>>,
|
|
||||||
serial_device: String,
|
serial_device: String,
|
||||||
serial_connected: bool,
|
serial_connected: bool,
|
||||||
|
|
||||||
|
data_rx: Option<Receiver<SerialPortData>>,
|
||||||
|
cmd_tx: Option<Sender<SerialPortCmd>>,
|
||||||
|
|
||||||
|
device_state: StatusMessage,
|
||||||
|
average_points: Vec<[f64; 2]>,
|
||||||
|
pwm_points: Vec<[f64; 2]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for CheapsdoControl {
|
impl Default for CheapsdoControl {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
shared_state: Arc::new(Mutex::new(SharedState::default())),
|
|
||||||
serial_device: "".to_owned(),
|
serial_device: "".to_owned(),
|
||||||
serial_connected: false,
|
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 =
|
let ports =
|
||||||
serialport::available_ports().unwrap_or(Vec::<serialport::SerialPortInfo>::new());
|
serialport::available_ports().unwrap_or(Vec::<serialport::SerialPortInfo>::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| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
egui::ComboBox::from_label("Select Serialport")
|
egui::ComboBox::from_label("Select Serialport")
|
||||||
|
@ -82,101 +105,114 @@ impl eframe::App for CheapsdoControl {
|
||||||
.add_enabled(!self.serial_connected, egui::Button::new("Open"))
|
.add_enabled(!self.serial_connected, egui::Button::new("Open"))
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
self.serial_connected = true;
|
|
||||||
|
|
||||||
let serial_device = self.serial_device.clone();
|
let serial_device = self.serial_device.clone();
|
||||||
let devicestate = self.shared_state.clone();
|
|
||||||
let ctx = ctx.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 || {
|
std::thread::spawn(move || {
|
||||||
poll_device(serial_device, devicestate, ctx);
|
poll_device(serial_device, cmd_rx, data_tx, ctx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if ui
|
if ui
|
||||||
.add_enabled(self.serial_connected, egui::Button::new("Close"))
|
.add_enabled(self.serial_connected, egui::Button::new("Close"))
|
||||||
.clicked()
|
.clicked()
|
||||||
{}
|
{
|
||||||
|
if let Some(cmd_tx) = &self.cmd_tx {
|
||||||
|
cmd_tx.send(SerialPortCmd::Disconnect).unwrap();
|
||||||
|
}
|
||||||
|
self.serial_connected = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
{
|
ui.horizontal(|ui| {
|
||||||
let mut state = self.shared_state.lock().unwrap();
|
ui.label(
|
||||||
ui.horizontal(|ui| {
|
egui::RichText::new(format!(
|
||||||
ui.label(
|
"Measured: {}",
|
||||||
egui::RichText::new(format!(
|
formatters::frequency(self.device_state.measured_frequency)
|
||||||
"Measured: {}",
|
))
|
||||||
formatters::frequency(state.device_state.measured_frequency)
|
.family(egui::FontFamily::Monospace)
|
||||||
))
|
.size(20.0),
|
||||||
.family(egui::FontFamily::Monospace)
|
);
|
||||||
.size(20.0),
|
|
||||||
);
|
|
||||||
|
|
||||||
ui.add_space(100.0);
|
ui.add_space(100.0);
|
||||||
|
|
||||||
ui.label(
|
ui.label(
|
||||||
egui::RichText::new(format!(
|
egui::RichText::new(format!(
|
||||||
"Average: {}",
|
"Average: {}",
|
||||||
formatters::frequency(state.device_state.average_frequency)
|
formatters::frequency(self.device_state.average_frequency)
|
||||||
))
|
))
|
||||||
.family(egui::FontFamily::Monospace)
|
.family(egui::FontFamily::Monospace)
|
||||||
.size(20.0),
|
.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 pwm_line = Line::new(PlotPoints::new(self.pwm_points.clone()));
|
||||||
let measured_line = Line::new(PlotPoints::new(state.measured_points.clone()));
|
Plot::new("pwm_plot")
|
||||||
Plot::new("frequency_plot")
|
.view_aspect(4.0)
|
||||||
.view_aspect(4.0)
|
.allow_zoom(false)
|
||||||
.allow_zoom(false)
|
.allow_scroll(false)
|
||||||
.allow_scroll(false)
|
.allow_drag(false)
|
||||||
.allow_drag(false)
|
.allow_boxed_zoom(false)
|
||||||
.allow_boxed_zoom(false)
|
.y_axis_width(12)
|
||||||
.y_axis_width(12)
|
.show(ui, |plot_ui| {
|
||||||
.y_axis_formatter(|val, _, _| formatters::frequency(val))
|
plot_ui.set_auto_bounds([true, true].into());
|
||||||
.label_formatter(|name, value| {
|
plot_ui.line(pwm_line);
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_device(port: String, state: Arc<Mutex<SharedState>>, ctx: egui::Context) {
|
fn poll_device(
|
||||||
|
port: String,
|
||||||
|
cmd_rx: Receiver<SerialPortCmd>,
|
||||||
|
data_tx: Sender<SerialPortData>,
|
||||||
|
ctx: egui::Context,
|
||||||
|
) {
|
||||||
let mut port = serialport::new(port, 115_200)
|
let mut port = serialport::new(port, 115_200)
|
||||||
.timeout(Duration::from_millis(10))
|
.timeout(Duration::from_millis(10))
|
||||||
.open()
|
.open()
|
||||||
.expect("Failed to open port");
|
.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 {
|
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<u8> = vec![0; 128];
|
let mut serial_buf: Vec<u8> = vec![0; 128];
|
||||||
match port.read(serial_buf.as_mut_slice()) {
|
match port.read(serial_buf.as_mut_slice()) {
|
||||||
Ok(t) => {
|
Ok(t) => {
|
||||||
|
@ -187,16 +223,10 @@ fn poll_device(port: String, state: Arc<Mutex<SharedState>>, ctx: egui::Context)
|
||||||
|
|
||||||
match dev_msg {
|
match dev_msg {
|
||||||
DeviceMessage::Status(status_msg) => {
|
DeviceMessage::Status(status_msg) => {
|
||||||
let mut state = state.lock().unwrap();
|
data_tx
|
||||||
let x = state.average_points.len() as f64;
|
.send(SerialPortData::DeviceState(status_msg))
|
||||||
state.average_points.push([x, status_msg.average_frequency]);
|
.unwrap();
|
||||||
state
|
ctx.request_repaint();
|
||||||
.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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -204,6 +234,16 @@ fn poll_device(port: String, state: Arc<Mutex<SharedState>>, ctx: egui::Context)
|
||||||
Err(e) => eprintln!("{:?}", e),
|
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();
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue