1
Fork 0

added basic control gui

tried fltk, didn't like it, using egui
This commit is contained in:
Andy Killorin 2025-03-06 11:49:33 -05:00
parent 971c57fa13
commit 79b22b3cb7
Signed by: ank
GPG key ID: 23F9463ECB67FE8C
4 changed files with 3178 additions and 23 deletions

3092
interface/Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -13,3 +13,7 @@ serde = "1.0.217"
tokio = { version = "1.43.0", features = ["full"] } tokio = { version = "1.43.0", features = ["full"] }
common = {path = "../common"} common = {path = "../common"}
heapless = "0.7.0" heapless = "0.7.0"
eframe = "0.30"
egui_extras = { version = "0.30", features = ["default", "image"] }
egui-toast = "0.16.0"

59
interface/src/gui.rs Normal file
View file

@ -0,0 +1,59 @@
use common::{ControlPacket, TelemetryPacket};
use eframe::egui::{self, Align2};
use tokio::sync::{mpsc, watch::Receiver};
use egui_toast::{Toast, Toasts};
pub fn gui(data: Receiver<GUIData>, toasts: mpsc::Receiver<Toast>) -> eframe::Result {
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 240.0]),
..Default::default()
};
eframe::run_native(
"Cruise Control Dashboard",
options,
Box::new(|cc| {
// This gives us image support:
egui_extras::install_image_loaders(&cc.egui_ctx);
Ok(Box::new(GUI::with_receivers(data, toasts)))
}),
)
}
#[derive(Default)]
pub struct GUIData {
pub telemetry: Option<TelemetryPacket>,
pub last_command: Option<ControlPacket>,
}
struct GUI {
data: Receiver<GUIData>,
toasts: mpsc::Receiver<Toast>,
}
impl GUI {
fn with_receivers(data: Receiver<GUIData>, toasts: mpsc::Receiver<Toast>) -> Self {
Self {
data,
toasts,
}
}
}
impl eframe::App for GUI {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Cruise Control");
});
let mut toasts = Toasts::new()
.anchor(Align2::RIGHT_BOTTOM, (-10.0, -10.0)) // 10 units from the bottom right corner
.direction(egui::Direction::BottomUp);
while let Ok(toast) = self.toasts.try_recv() {
toasts.add(toast);
}
toasts.show(ctx);
}
}

View file

@ -1,12 +1,17 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
#![feature(iter_collect_into)] #![feature(iter_collect_into)]
use std::{ops::ControlFlow, result, sync::Arc, thread::sleep, time::Duration}; use std::{ops::ControlFlow, result, sync::Arc, thread::{self, sleep}, time::Duration};
use anyhow::{Context, Ok, Result}; use anyhow::{Context, Ok, Result};
use common::{ControlPacket, TelemetryPacket}; use common::{ControlPacket, TelemetryPacket};
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use egui_toast::{Toast, ToastKind};
use gui::{gui, GUIData};
use pitch_detection::{detector::{mcleod::McLeodDetector, PitchDetector}, utils}; use pitch_detection::{detector::{mcleod::McLeodDetector, PitchDetector}, utils};
use rust_music_theory::note::{Note, NoteLetter, Pitch, Tuning}; use rust_music_theory::note::{Note, NoteLetter, Pitch, Tuning};
use tokio::{io::{AsyncReadExt, AsyncWriteExt, BufWriter, WriteHalf}, net::{tcp::{OwnedReadHalf, OwnedWriteHalf}, TcpStream}, spawn, sync::{self, mpsc, RwLock}, time::timeout}; use tokio::{io::{AsyncReadExt, AsyncWriteExt, BufWriter, WriteHalf}, net::{tcp::{OwnedReadHalf, OwnedWriteHalf}, TcpStream}, spawn, sync::{self, mpsc, watch, RwLock}, time::timeout};
mod gui;
fn main() -> Result<()> { fn main() -> Result<()> {
// assumes pulseaudio system with f32 samples and 2204 sample packets // assumes pulseaudio system with f32 samples and 2204 sample packets
@ -45,26 +50,36 @@ fn main() -> Result<()> {
//let latest_telem = Arc::new(RwLock::new(None)); //let latest_telem = Arc::new(RwLock::new(None));
let control = executor.block_on(async { let (toast_sender, toasts) = mpsc::channel(5);
let cruisecontrol = TcpStream::connect("192.168.1.2:1234").await?; let (data_sender, gui_data) =watch::channel(GUIData::default());
println!("connected");
cruisecontrol.set_nodelay(true)?;
let (telem, control) = cruisecontrol.into_split();
tokio::spawn(telemetry_handler(telem));
Ok(control)
})?;
executor.block_on(controller(notes, control)).unwrap(); thread::spawn(move || {
let control = executor.block_on(async {
toast_sender.send(Toast::new().text("connecting to bot").kind(ToastKind::Info)).await.unwrap();
let cruisecontrol = TcpStream::connect("192.168.1.2:1234").await?;
println!("connected");
toast_sender.send(Toast::new().text("connected").kind(ToastKind::Success)).await.unwrap();
cruisecontrol.set_nodelay(true)?;
let (telem, control) = cruisecontrol.into_split();
tokio::spawn(telemetry_handler(telem, data_sender.clone()));
Ok(control)
}).unwrap();
executor.block_on(controller(notes, control)).unwrap()
});
println!("launching gui");
gui(gui_data, toasts).unwrap();
drop(stream); drop(stream);
Ok(()) Ok(())
} }
async fn telemetry_handler(mut telem: OwnedReadHalf) -> Result<()> { async fn telemetry_handler(mut telem: OwnedReadHalf, gui: watch::Sender<GUIData>) -> Result<()> {
let mut buf = vec![0; 2048]; let mut buf = vec![0; 2048];
loop { loop {
let len = telem.read_u32().await.context("bad length")? as usize; let len = telem.read_u32().await.context("bad length")? as usize;
@ -74,6 +89,9 @@ async fn telemetry_handler(mut telem: OwnedReadHalf) -> Result<()> {
println!("telem: {telem:?}"); println!("telem: {telem:?}");
gui.send_modify(|gui| {
gui.telemetry = Some(telem);
});
} }
} }