Save dac settings to config file

This commit is contained in:
Ruben van de Ven 2025-07-03 11:36:41 +02:00
parent 319062b0b4
commit 13c8628590

View file

@ -19,9 +19,16 @@ use zmq::Socket;
use std::sync::{mpsc, Arc}; use std::sync::{mpsc, Arc};
use std::time::{Instant, Duration}; use std::time::{Instant, Duration};
use std::collections::HashMap; 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; // use egui_dropdown::DropDownBox;
const CONFIG_FILE_PATH: &str = "./config.json";
fn main() { fn main() {
nannou::app(model).update(update).run(); nannou::app(model).update(update).run();
@ -32,12 +39,13 @@ pub struct StreamConfig{
pub config: DacConfig, pub config: DacConfig,
} }
type StreamConfigMap = HashMap<DacId, StreamConfig>; type StreamConfigMap = HashMap<DacId, StreamConfig>;
type StreamMap = HashMap<DacId, laser::FrameStream<LaserModel>>;
struct GuiModel { struct GuiModel {
// A handle to the laser API used for spawning streams and detecting DACs. // A handle to the laser API used for spawning streams and detecting DACs.
laser_api: Arc<laser::Api>, laser_api: Arc<laser::Api>,
// All of the live stream handles. // 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. // A copy of the state that will live on the laser thread so we can present a GUI.
laser_model: LaserModel, laser_model: LaserModel,
// A copy of the laser settings so that we can control them with the GUI. // 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()); // 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 = get_laser_lines(version);
let lines_for_laser: RenderableLines = lines.clone(); 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; let laser_lines: RenderableLines = lines_for_laser;
laser_model.current_lines = laser_lines; laser_model.current_lines = laser_lines;
}); });
@ -180,67 +188,81 @@ fn zmq_receive(model: &mut GuiModel) {
type DacConfigMap = HashMap<DacId, DacConfig>; type DacConfigMap = HashMap<DacId, DacConfig>;
#[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<SavedDacConfig>
}
impl Into<DacConfigMap> 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<P: AsRef<Path>>(path: P) -> std::result::Result<SavedConfig, Box<dyn Error>> {
// 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<P: AsRef<Path>>(path: P, config: SavedConfig) -> std::result::Result<(), Box<dyn Error>> {
// 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. // Some hardcoded config. Not spending time on reading/writing config atm.
fn get_dac_configs() -> DacConfigMap{ fn get_dac_configs() -> DacConfigMap{
match read_config_from_file(CONFIG_FILE_PATH) {
let mut dac_configs: DacConfigMap = HashMap::new(); Err(err) => {
dac_configs.insert( eprintln!("Could not load config {}", err);
DacId::Helios { id: 926298163 }, return HashMap::new()
DacConfig{
name: "Helios#1".into(),
.. DacConfig::default()
}
);
dac_configs.insert(
DacId::EtherDream {
mac_address: [
122,
39,
223,
73,
5,
227,
],
}, },
DacConfig{ Ok(saved_config) => return saved_config.into(),
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
} }
fn model(app: &App) -> GuiModel { fn model(app: &App) -> GuiModel {
@ -388,14 +410,13 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
// First, check for new laser DACs. // First, check for new laser DACs.
for dac in model.dac_rx.try_recv() { for dac in model.dac_rx.try_recv() {
println!("Detected DAC {:?}!", dac.id()); println!("Detected DAC {:?}!", dac.id());
let config = match model.per_laser_config.contains_key(&dac.id()) { if !model.per_laser_config.contains_key(&dac.id()) {
true => &model.per_laser_config[&dac.id()], println!("Found unknown DAC, register with defaults");
false => { model.per_laser_config.insert(dac.id(), DacConfig::default());
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()];
},
};
let stream = model let stream = model
.laser_api .laser_api
.new_frame_stream(model.laser_model.with_config(config), laser_frame_producer) .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() .build()
.expect("failed to establish stream with newly detected DAC"); .expect("failed to establish stream with newly detected DAC");
// dbg!(stream.enable_draw_reorder()); // 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 // Check if any streams have dropped out (e.g network issues, DAC turned off) and attempt to
// start them again. // start them again.
let mut dropped = vec![]; let mut dropped = vec![];
for (dac_id, stream_config) in model.laser_streams.iter() { for (dac_id, stream) in model.laser_streams.iter() {
if stream_config.stream.is_closed() { if stream.is_closed() {
dropped.push(dac_id.clone()); dropped.push(dac_id.clone());
} }
} }
@ -419,15 +445,15 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
// let stream = ; // let stream = ;
let s = model.laser_streams.remove(&dac_id); let s = model.laser_streams.remove(&dac_id);
if model.selected_stream == Some(dac_id){ // if model.selected_stream == Some(dac_id){
model.selected_stream = None; // model.selected_stream = None;
} // }
if let Some(stream_config) = s{ if let Some(stream) = s{
let dac = stream_config.stream let dac = stream
.dac() .dac()
.expect("`dac` returned `None` even though one was specified during stream creation"); .expect("`dac` returned `None` even though one was specified during stream creation");
let res = stream_config.stream let res = stream
.close() .close()
.expect("stream was unexpectedly already closed from another stream handle") .expect("stream was unexpectedly already closed from another stream handle")
.expect("failed to join stream thread"); .expect("failed to join stream thread");
@ -437,14 +463,7 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
// TODO: keeps looping on disconnect. // TODO: keeps looping on disconnect.
println!("attempting to restart stream with DAC {:?}", dac.id()); println!("attempting to restart stream with DAC {:?}", dac.id());
let dac_id = dac.id(); let dac_id = dac.id();
let config = match model.per_laser_config.contains_key(&dac.id()) { let config = &model.per_laser_config[&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()
},
};
match model match model
.laser_api .laser_api
.new_frame_stream(model.laser_model.with_config(config), laser_frame_producer) .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), Err(err) => eprintln!("failed to restart stream: {}", err),
Ok(stream) => { Ok(stream) => {
println!("Reinsert stream. {:?}", dac_id); 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 (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(); ui.separator();
@ -504,7 +529,7 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
{ {
let hz = laser_settings.point_hz; let hz = laser_settings.point_hz;
for (_dac_id, stream) in laser_streams.iter() { for (_dac_id, stream) in laser_streams.iter() {
stream.stream.set_point_hz(hz).ok(); stream.set_point_hz(hz).ok();
} }
} }
if ui if ui
@ -513,7 +538,7 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
{ {
let latency = laser_settings.latency_points; let latency = laser_settings.latency_points;
for (_dac_id, stream) in laser_streams.iter() { for (_dac_id, stream) in laser_streams.iter() {
stream.stream.set_latency_points(latency).ok(); stream.set_latency_points(latency).ok();
} }
} }
if ui if ui
@ -522,7 +547,7 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
{ {
let hz = laser_settings.frame_hz; let hz = laser_settings.frame_hz;
for (_dac_id, stream) in laser_streams.iter() { 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") .checkbox(&mut laser_settings.enable_optimisations, "Optimize Path")
.changed() .changed()
{ {
for (_dac_id, stream_config) in laser_streams.iter() { for (_dac_id, stream) in laser_streams.iter() {
stream_config.stream stream
.enable_optimisations(laser_settings.enable_optimisations) .enable_optimisations(laser_settings.enable_optimisations)
.ok(); .ok();
} }
@ -548,8 +573,8 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
// .checkbox(&mut laser_settings.enable_draw_reorder, "Reorder paths") // .checkbox(&mut laser_settings.enable_draw_reorder, "Reorder paths")
.changed() .changed()
{ {
for (_dac_id, stream_config) in laser_streams.iter() { for (_dac_id, stream) in laser_streams.iter() {
stream_config.stream stream
.enable_draw_reorder(laser_settings.enable_draw_reorder) .enable_draw_reorder(laser_settings.enable_draw_reorder)
.ok(); .ok();
} }
@ -564,7 +589,7 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
{ {
let distance = laser_settings.distance_per_point; let distance = laser_settings.distance_per_point;
for (_dac_id, stream) in laser_streams.iter() { 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 if ui
@ -576,7 +601,7 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
{ {
let delay = laser_settings.blank_delay_points; let delay = laser_settings.blank_delay_points;
for (_dac_id, stream) in laser_streams.iter() { 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); 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); let radians = deg_to_rad(degrees);
laser_settings.radians_per_point = radians; laser_settings.radians_per_point = radians;
for (_dac_id, stream) in laser_streams.iter() { 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.separator();
ui.heading("Laser specific settings"); ui.heading("Laser specific settings");
if laser_streams.is_empty() { if per_laser_config.is_empty() {
ui.label("No dacs available"); ui.label("No dacs available");
} else { } else {
ui.horizontal(|ui| { ui.horizontal_wrapped(|ui| {
for (dac_id, _stream) in laser_streams.iter() { ui.selectable_value(
selected_stream,
None,
""
);
for (dac_id, _config) in per_laser_config.iter() {
ui.selectable_value( ui.selectable_value(
selected_stream, selected_stream,
Some(dac_id.clone()), Some(dac_id.clone()),
@ -615,20 +646,28 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
ui.separator(); ui.separator();
ui.add(egui::Label::new(format!("{:?}", selected_stream_value))); 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") egui::ComboBox::from_label("Source")
.selected_text(format!("{source:?}")) .selected_text(format!("{source:?}"))
.show_ui(ui, |ui| { .show_ui(ui, |ui| {
for source_option in STREAM_SOURCES { for source_option in STREAM_SOURCES {
if ui.selectable_value(source, source_option.clone(), format!("{:?}", &source_option)).clicked() { if ui.selectable_value(source, source_option.clone(), format!("{:?}", &source_option)).clicked() {
if let Some(stream) = selected_laser_stream {
// let source = source_option; // let source = source_option;
stream_config.stream.send(move |laser_model: &mut LaserModel| { stream.send(move |laser_model: &mut LaserModel| {
laser_model.config.source = source_option; laser_model.config.source = source_option;
}).unwrap(); }).unwrap();
}
}; };
} }
@ -636,87 +675,103 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
if ui 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() .changed()
{ {
let factor = stream_config.config.filters.dim.intensity; let factor = selected_config.filters.dim.intensity;
stream_config.stream.send(move |laser_model: &mut LaserModel| { if let Some(stream) = selected_laser_stream {
laser_model.config.filters.dim.intensity = factor; stream.send(move |laser_model: &mut LaserModel| {
}).unwrap(); laser_model.config.filters.dim.intensity = factor;
}).unwrap();
}
} }
if ui 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() .changed()
{ {
let factor = stream_config.config.filters.dim.intensity; let factor = selected_config.filters.dim.intensity;
stream_config.stream.send(move |laser_model: &mut LaserModel| { if let Some(stream) = selected_laser_stream {
laser_model.config.filters.dim.intensity = factor; stream.send(move |laser_model: &mut LaserModel| {
}).unwrap(); laser_model.config.filters.dim.intensity = factor;
}).unwrap();
}
} }
if ui 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() .changed()
{ {
let factor = stream_config.config.filters.scale.factor; let factor = selected_config.filters.scale.factor;
stream_config.stream.send(move |laser_model: &mut LaserModel| { if let Some(stream) = selected_laser_stream {
laser_model.config.filters.scale.factor = factor; stream.send(move |laser_model: &mut LaserModel| {
}).unwrap(); laser_model.config.filters.scale.factor = factor;
}).unwrap();
}
} }
// Pincushion / Pillow / Barrel distortion. Generally, only needed for the x-axis // Pincushion / Pillow / Barrel distortion. Generally, only needed for the x-axis
if ui 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() .changed()
{ {
let factor = stream_config.config.filters.pincushion.k_x; let factor = selected_config.filters.pincushion.k_x;
stream_config.stream.send(move |laser_model: &mut LaserModel| { if let Some(stream) = selected_laser_stream {
laser_model.config.filters.pincushion.k_x = factor; stream.send(move |laser_model: &mut LaserModel| {
}).unwrap(); laser_model.config.filters.pincushion.k_x = factor;
}).unwrap();
}
} }
if ui 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() .changed()
{ {
let factor = stream_config.config.filters.pincushion.k_x2; let factor = selected_config.filters.pincushion.k_x2;
stream_config.stream.send(move |laser_model: &mut LaserModel| { if let Some(stream) = selected_laser_stream {
laser_model.config.filters.pincushion.k_x2 = factor; stream.send(move |laser_model: &mut LaserModel| {
}).unwrap(); laser_model.config.filters.pincushion.k_x2 = factor;
}).unwrap();
}
} }
if ui 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() .changed()
{ {
let factor = stream_config.config.filters.pincushion.k_y; let factor = selected_config.filters.pincushion.k_y;
stream_config.stream.send(move |laser_model: &mut LaserModel| { if let Some(stream) = selected_laser_stream {
laser_model.config.filters.pincushion.k_y = factor; stream.send(move |laser_model: &mut LaserModel| {
}).unwrap(); laser_model.config.filters.pincushion.k_y = factor;
}).unwrap();
}
} }
if ui 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() .changed()
{ {
let factor = stream_config.config.filters.pincushion.k_y2; let factor = selected_config.filters.pincushion.k_y2;
stream_config.stream.send(move |laser_model: &mut LaserModel| { if let Some(stream) = selected_laser_stream {
laser_model.config.filters.pincushion.k_y2 = factor; stream.send(move |laser_model: &mut LaserModel| {
}).unwrap(); laser_model.config.filters.pincushion.k_y2 = factor;
}).unwrap();
}
} }
if ui if ui
.checkbox(&mut stream_config.config.filters.crop.enabled ,"Crop") .checkbox(&mut selected_config.filters.crop.enabled ,"Crop")
.changed() .changed()
{ {
let enabled = stream_config.config.filters.crop.enabled; let enabled = selected_config.filters.crop.enabled;
stream_config.stream.send(move |laser_model: &mut LaserModel| { if let Some(stream) = selected_laser_stream {
laser_model.config.filters.crop.enabled = enabled; stream.send(move |laser_model: &mut LaserModel| {
}).unwrap(); 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()); .wh(win_rect.wh());
}, },
Some(dac_id) => { 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)) draw.text(&format!("{:?}", dac_id))
.h(win_rect.h()) .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. // check which source should be used, and get points accordingly.
// potentially ignoring the points coming from the stream // 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 pointno = points.points.len();
let new_points = stream_config.config.filters.apply(&points); let new_points = config.filters.apply(&points);
// similar to map code: // similar to map code: