From 13c8628590afba2e0edf261d2d52cda6d5c3b314 Mon Sep 17 00:00:00 2001 From: Ruben van de Ven Date: Thu, 3 Jul 2025 11:36:41 +0200 Subject: [PATCH] Save dac settings to config file --- src/bin/render_lines_gui.rs | 354 +++++++++++++++++++++--------------- 1 file changed, 205 insertions(+), 149 deletions(-) diff --git a/src/bin/render_lines_gui.rs b/src/bin/render_lines_gui.rs index 9c389a9..9024002 100644 --- a/src/bin/render_lines_gui.rs +++ b/src/bin/render_lines_gui.rs @@ -19,9 +19,16 @@ use zmq::Socket; use std::sync::{mpsc, Arc}; use std::time::{Instant, Duration}; use std::collections::HashMap; +use serde::{Serialize,Deserialize}; + +use std::error::Error; +use std::fs::File; +use std::io::BufReader; +use std::path::Path; // use egui_dropdown::DropDownBox; +const CONFIG_FILE_PATH: &str = "./config.json"; fn main() { nannou::app(model).update(update).run(); @@ -32,12 +39,13 @@ pub struct StreamConfig{ pub config: DacConfig, } type StreamConfigMap = HashMap; +type StreamMap = HashMap>; struct GuiModel { // A handle to the laser API used for spawning streams and detecting DACs. laser_api: Arc, // All of the live stream handles. - laser_streams: StreamConfigMap, + laser_streams: StreamMap, // A copy of the state that will live on the laser thread so we can present a GUI. laser_model: LaserModel, // A copy of the laser settings so that we can control them with the GUI. @@ -159,10 +167,10 @@ fn zmq_receive(model: &mut GuiModel) { // println!("receive {}", lines.lines.len()); - for (_dac, stream_config) in (&model.laser_streams).into_iter() { + for (_dac, stream) in (&model.laser_streams).into_iter() { // let lines = get_laser_lines(version); let lines_for_laser: RenderableLines = lines.clone(); - let sending = stream_config.stream.send(move |laser_model: &mut LaserModel| { + let sending = stream.send(move |laser_model: &mut LaserModel| { let laser_lines: RenderableLines = lines_for_laser; laser_model.current_lines = laser_lines; }); @@ -180,67 +188,81 @@ fn zmq_receive(model: &mut GuiModel) { type DacConfigMap = HashMap; + +#[derive(Debug, Serialize, Deserialize)] +#[serde(remote = "DacId")] +pub enum DacIdSerializable { + EtherDream { mac_address: [u8; 6] }, + Helios { id: u32 }, +} + + +#[derive(Debug, Serialize, Deserialize)] +pub struct SavedDacConfig{ + #[serde(with = "DacIdSerializable")] + dac_id: DacId, + config: DacConfig, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct SavedConfig { + dacs: Vec +} + +impl Into for SavedConfig { + fn into(self) -> DacConfigMap { + let mut configs = HashMap::new(); + for dac in self.dacs { + configs.insert(dac.dac_id, dac.config); + } + configs + } +} + +impl From<&mut DacConfigMap> for SavedConfig { + fn from(per_laser_config: &mut DacConfigMap) -> SavedConfig { + let mut dacs = Vec::new(); + for (dac_id, config) in per_laser_config.into_iter() { + dacs.push(SavedDacConfig{ + dac_id: dac_id.clone(), config: config.clone() + }); + } + SavedConfig { + dacs + } + } +} + + +fn read_config_from_file>(path: P) -> std::result::Result> { + // Open the file in read-only mode with buffer. + let file = File::open(path)?; + let reader = BufReader::new(file); + + // Read the JSON contents of the file as an instance of `DacConfig`. + let u = serde_json::from_reader(reader)?; + + Ok(u) +} + +fn save_config_file>(path: P, config: SavedConfig) -> std::result::Result<(), Box> { + // Open the file in read-only mode with buffer. + let mut file = File::create(path)?; + + serde_json::to_writer(&mut file, &config)?; + Ok(()) +} + + // Some hardcoded config. Not spending time on reading/writing config atm. fn get_dac_configs() -> DacConfigMap{ - - let mut dac_configs: DacConfigMap = HashMap::new(); - dac_configs.insert( - DacId::Helios { id: 926298163 }, - DacConfig{ - name: "Helios#1".into(), - .. DacConfig::default() - } - ); - dac_configs.insert( - DacId::EtherDream { - mac_address: [ - 122, - 39, - 223, - 73, - 5, - 227, - ], + match read_config_from_file(CONFIG_FILE_PATH) { + Err(err) => { + eprintln!("Could not load config {}", err); + return HashMap::new() }, - DacConfig{ - name: "ED - 192.168.8.101".into(), - .. DacConfig::default() - } - ); - dac_configs.insert( - DacId::EtherDream { - mac_address: [ - 98, - 120, - 178, - 228, - 198, - 175, - ], - }, - DacConfig{ - name: "ED - 192.168.9.101".into(), - .. DacConfig::default() - // filters: PointFilters::default(), - } - ); - dac_configs.insert( - DacId::EtherDream { - mac_address: [ - 18, - 52, - 86, - 120, - 154, - 188, - ], - }, - DacConfig{ - name: "Emulator".into(), - .. DacConfig::default() - } - ); - dac_configs + Ok(saved_config) => return saved_config.into(), + } } fn model(app: &App) -> GuiModel { @@ -388,14 +410,13 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) { // First, check for new laser DACs. for dac in model.dac_rx.try_recv() { println!("Detected DAC {:?}!", dac.id()); - let config = match model.per_laser_config.contains_key(&dac.id()) { - true => &model.per_laser_config[&dac.id()], - false => { - println!("Found unknown DAC, try to register it in get_dac_configs()"); - dbg!(&dac.id()); - &DacConfig::default() - }, - }; + if !model.per_laser_config.contains_key(&dac.id()) { + println!("Found unknown DAC, register with defaults"); + model.per_laser_config.insert(dac.id(), DacConfig::default()); + + } + let config = &model.per_laser_config[&dac.id()]; + let stream = model .laser_api .new_frame_stream(model.laser_model.with_config(config), laser_frame_producer) @@ -403,14 +424,19 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) { .build() .expect("failed to establish stream with newly detected DAC"); // dbg!(stream.enable_draw_reorder()); - model.laser_streams.insert(dac.id(), StreamConfig{ stream, config: config.clone() }); + model.laser_streams.insert(dac.id(), stream); + + // match save_config_file("./config.json", model.per_laser_config.clone().into()) { + // Err(err) => eprintln!("failed saving config: {}", err), + // Ok(_) => println!("Saved config"), + // }; } // Check if any streams have dropped out (e.g network issues, DAC turned off) and attempt to // start them again. let mut dropped = vec![]; - for (dac_id, stream_config) in model.laser_streams.iter() { - if stream_config.stream.is_closed() { + for (dac_id, stream) in model.laser_streams.iter() { + if stream.is_closed() { dropped.push(dac_id.clone()); } } @@ -419,15 +445,15 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) { // let stream = ; let s = model.laser_streams.remove(&dac_id); - if model.selected_stream == Some(dac_id){ - model.selected_stream = None; - } + // if model.selected_stream == Some(dac_id){ + // model.selected_stream = None; + // } - if let Some(stream_config) = s{ - let dac = stream_config.stream + if let Some(stream) = s{ + let dac = stream .dac() .expect("`dac` returned `None` even though one was specified during stream creation"); - let res = stream_config.stream + let res = stream .close() .expect("stream was unexpectedly already closed from another stream handle") .expect("failed to join stream thread"); @@ -437,14 +463,7 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) { // TODO: keeps looping on disconnect. println!("attempting to restart stream with DAC {:?}", dac.id()); let dac_id = dac.id(); - let config = match model.per_laser_config.contains_key(&dac.id()) { - true => &model.per_laser_config[&dac.id()], - false => { - println!("Found unknown DAC, try to register it in get_dac_configs()"); - dbg!(&dac.id()); - &DacConfig::default() - }, - }; + let config = &model.per_laser_config[&dac.id()]; match model .laser_api .new_frame_stream(model.laser_model.with_config(config), laser_frame_producer) @@ -454,9 +473,10 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) { Err(err) => eprintln!("failed to restart stream: {}", err), Ok(stream) => { println!("Reinsert stream. {:?}", dac_id); - model.laser_streams.insert(dac_id, StreamConfig{stream, config: stream_config.config}); + model.laser_streams.insert(dac_id, stream); }, } + } } @@ -489,7 +509,12 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) { (grid_w - gap_space) / n_options as f32 } - ui.heading("Laser Points"); + ui.horizontal(|ui| { + ui.heading("Laser Points"); + if ui.button("💾").clicked() { + save_config_file(CONFIG_FILE_PATH, per_laser_config.into()); + } + }); ui.separator(); @@ -504,7 +529,7 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) { { let hz = laser_settings.point_hz; for (_dac_id, stream) in laser_streams.iter() { - stream.stream.set_point_hz(hz).ok(); + stream.set_point_hz(hz).ok(); } } if ui @@ -513,7 +538,7 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) { { let latency = laser_settings.latency_points; for (_dac_id, stream) in laser_streams.iter() { - stream.stream.set_latency_points(latency).ok(); + stream.set_latency_points(latency).ok(); } } if ui @@ -522,7 +547,7 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) { { let hz = laser_settings.frame_hz; for (_dac_id, stream) in laser_streams.iter() { - stream.stream.set_frame_hz(hz).ok(); + stream.set_frame_hz(hz).ok(); } } @@ -534,8 +559,8 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) { .checkbox(&mut laser_settings.enable_optimisations, "Optimize Path") .changed() { - for (_dac_id, stream_config) in laser_streams.iter() { - stream_config.stream + for (_dac_id, stream) in laser_streams.iter() { + stream .enable_optimisations(laser_settings.enable_optimisations) .ok(); } @@ -548,8 +573,8 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) { // .checkbox(&mut laser_settings.enable_draw_reorder, "Reorder paths") .changed() { - for (_dac_id, stream_config) in laser_streams.iter() { - stream_config.stream + for (_dac_id, stream) in laser_streams.iter() { + stream .enable_draw_reorder(laser_settings.enable_draw_reorder) .ok(); } @@ -564,7 +589,7 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) { { let distance = laser_settings.distance_per_point; for (_dac_id, stream) in laser_streams.iter() { - stream.stream.set_distance_per_point(distance).ok(); + stream.set_distance_per_point(distance).ok(); } } if ui @@ -576,7 +601,7 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) { { let delay = laser_settings.blank_delay_points; for (_dac_id, stream) in laser_streams.iter() { - stream.stream.set_blank_delay_points(delay).ok(); + stream.set_blank_delay_points(delay).ok(); } } let mut degrees = rad_to_deg(laser_settings.radians_per_point); @@ -589,18 +614,24 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) { let radians = deg_to_rad(degrees); laser_settings.radians_per_point = radians; for (_dac_id, stream) in laser_streams.iter() { - stream.stream.set_radians_per_point(radians).ok(); + stream.set_radians_per_point(radians).ok(); } } ui.separator(); ui.heading("Laser specific settings"); - if laser_streams.is_empty() { + if per_laser_config.is_empty() { ui.label("No dacs available"); } else { - ui.horizontal(|ui| { - for (dac_id, _stream) in laser_streams.iter() { + ui.horizontal_wrapped(|ui| { + ui.selectable_value( + selected_stream, + None, + "⊗" + ); + + for (dac_id, _config) in per_laser_config.iter() { ui.selectable_value( selected_stream, Some(dac_id.clone()), @@ -615,20 +646,28 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) { ui.separator(); ui.add(egui::Label::new(format!("{:?}", selected_stream_value))); - let stream_config: &mut StreamConfig = laser_streams.get_mut(&selected_stream_value).expect("Selected stream not found in configs"); + let selected_config: &mut DacConfig = per_laser_config.get_mut(&selected_stream_value).unwrap(); + + ui.add(egui::TextEdit::singleline(&mut selected_config.name)); + + let selected_laser_stream = laser_streams.get(&selected_stream_value); + + // let stream_config: &mut StreamConfig = laser_streams.get_mut(&selected_stream_value).expect("Selected stream not found in configs"); - let source = &mut stream_config.config.source; + let source = &mut selected_config.source; egui::ComboBox::from_label("Source") .selected_text(format!("{source:?}")) .show_ui(ui, |ui| { for source_option in STREAM_SOURCES { if ui.selectable_value(source, source_option.clone(), format!("{:?}", &source_option)).clicked() { + if let Some(stream) = selected_laser_stream { // let source = source_option; - stream_config.stream.send(move |laser_model: &mut LaserModel| { - laser_model.config.source = source_option; - }).unwrap(); + stream.send(move |laser_model: &mut LaserModel| { + laser_model.config.source = source_option; + }).unwrap(); + } }; } @@ -636,87 +675,103 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) { if ui - .add(egui::Slider::new(&mut stream_config.config.filters.dim.intensity, 0.0..=1.).text("Dimming")) + .add(egui::Slider::new(&mut selected_config.filters.dim.intensity, 0.0..=1.).text("Dimming")) .changed() { - let factor = stream_config.config.filters.dim.intensity; - stream_config.stream.send(move |laser_model: &mut LaserModel| { - laser_model.config.filters.dim.intensity = factor; - }).unwrap(); + let factor = selected_config.filters.dim.intensity; + if let Some(stream) = selected_laser_stream { + stream.send(move |laser_model: &mut LaserModel| { + laser_model.config.filters.dim.intensity = factor; + }).unwrap(); + } } if ui - .add(egui::Slider::new(&mut stream_config.config.filters.dim.intensity, 0.0..=1.).text("Dimming")) + .add(egui::Slider::new(&mut selected_config.filters.dim.intensity, 0.0..=1.).text("Dimming")) .changed() { - let factor = stream_config.config.filters.dim.intensity; - stream_config.stream.send(move |laser_model: &mut LaserModel| { - laser_model.config.filters.dim.intensity = factor; - }).unwrap(); + let factor = selected_config.filters.dim.intensity; + if let Some(stream) = selected_laser_stream { + stream.send(move |laser_model: &mut LaserModel| { + laser_model.config.filters.dim.intensity = factor; + }).unwrap(); + } } if ui - .add(egui::Slider::new(&mut stream_config.config.filters.scale.factor, 0.0..=2.).text("Scale")) + .add(egui::Slider::new(&mut selected_config.filters.scale.factor, 0.0..=2.).text("Scale")) .changed() { - let factor = stream_config.config.filters.scale.factor; - stream_config.stream.send(move |laser_model: &mut LaserModel| { - laser_model.config.filters.scale.factor = factor; - }).unwrap(); + let factor = selected_config.filters.scale.factor; + if let Some(stream) = selected_laser_stream { + stream.send(move |laser_model: &mut LaserModel| { + laser_model.config.filters.scale.factor = factor; + }).unwrap(); + } } // Pincushion / Pillow / Barrel distortion. Generally, only needed for the x-axis if ui - .add(egui::Slider::new(&mut stream_config.config.filters.pincushion.k_x, -0.5..=0.5).text("Pincushion x")) + .add(egui::Slider::new(&mut selected_config.filters.pincushion.k_x, -0.5..=0.5).text("Pincushion x")) .changed() { - let factor = stream_config.config.filters.pincushion.k_x; - stream_config.stream.send(move |laser_model: &mut LaserModel| { - laser_model.config.filters.pincushion.k_x = factor; - }).unwrap(); + let factor = selected_config.filters.pincushion.k_x; + if let Some(stream) = selected_laser_stream { + stream.send(move |laser_model: &mut LaserModel| { + laser_model.config.filters.pincushion.k_x = factor; + }).unwrap(); + } } if ui - .add(egui::Slider::new(&mut stream_config.config.filters.pincushion.k_x2, -0.2..=0.2).text("Higher order pincushion x")) + .add(egui::Slider::new(&mut selected_config.filters.pincushion.k_x2, -0.2..=0.2).text("Higher order pincushion x")) .changed() { - let factor = stream_config.config.filters.pincushion.k_x2; - stream_config.stream.send(move |laser_model: &mut LaserModel| { - laser_model.config.filters.pincushion.k_x2 = factor; - }).unwrap(); + let factor = selected_config.filters.pincushion.k_x2; + if let Some(stream) = selected_laser_stream { + stream.send(move |laser_model: &mut LaserModel| { + laser_model.config.filters.pincushion.k_x2 = factor; + }).unwrap(); + } } if ui - .add(egui::Slider::new(&mut stream_config.config.filters.pincushion.k_y, -0.5..=0.5).text("Pincushion y")) + .add(egui::Slider::new(&mut selected_config.filters.pincushion.k_y, -0.5..=0.5).text("Pincushion y")) .changed() { - let factor = stream_config.config.filters.pincushion.k_y; - stream_config.stream.send(move |laser_model: &mut LaserModel| { - laser_model.config.filters.pincushion.k_y = factor; - }).unwrap(); + let factor = selected_config.filters.pincushion.k_y; + if let Some(stream) = selected_laser_stream { + stream.send(move |laser_model: &mut LaserModel| { + laser_model.config.filters.pincushion.k_y = factor; + }).unwrap(); + } } if ui - .add(egui::Slider::new(&mut stream_config.config.filters.pincushion.k_y2, -0.2..=0.2).text("Higher order pincushion y")) + .add(egui::Slider::new(&mut selected_config.filters.pincushion.k_y2, -0.2..=0.2).text("Higher order pincushion y")) .changed() { - let factor = stream_config.config.filters.pincushion.k_y2; - stream_config.stream.send(move |laser_model: &mut LaserModel| { - laser_model.config.filters.pincushion.k_y2 = factor; - }).unwrap(); + let factor = selected_config.filters.pincushion.k_y2; + if let Some(stream) = selected_laser_stream { + stream.send(move |laser_model: &mut LaserModel| { + laser_model.config.filters.pincushion.k_y2 = factor; + }).unwrap(); + } } if ui - .checkbox(&mut stream_config.config.filters.crop.enabled ,"Crop") + .checkbox(&mut selected_config.filters.crop.enabled ,"Crop") .changed() { - let enabled = stream_config.config.filters.crop.enabled; - stream_config.stream.send(move |laser_model: &mut LaserModel| { - laser_model.config.filters.crop.enabled = enabled; - }).unwrap(); + let enabled = selected_config.filters.crop.enabled; + if let Some(stream) = selected_laser_stream { + stream.send(move |laser_model: &mut LaserModel| { + laser_model.config.filters.crop.enabled = enabled; + }).unwrap(); + } } @@ -809,7 +864,8 @@ fn view_laser_preview(app: &App, model: &GuiModel, frame: Frame) { .wh(win_rect.wh()); }, Some(dac_id) => { - let stream_config: & StreamConfig = model.laser_streams.get(&dac_id).expect("Selected stream not found in configs"); + // let stream = model.laser_streams.get(&dac_id); //.expect("Selected stream not found in configs"); + let config = model.per_laser_config.get(&dac_id).expect("Selected stream not found in configs"); draw.text(&format!("{:?}", dac_id)) .h(win_rect.h()) @@ -825,11 +881,11 @@ fn view_laser_preview(app: &App, model: &GuiModel, frame: Frame) { // check which source should be used, and get points accordingly. // potentially ignoring the points coming from the stream - let points = stream_config.config.source.get_shape(current_points); + let points = config.source.get_shape(current_points); let pointno = points.points.len(); - let new_points = stream_config.config.filters.apply(&points); + let new_points = config.filters.apply(&points); // similar to map code: