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::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<DacId, StreamConfig>;
type StreamMap = HashMap<DacId, laser::FrameStream<LaserModel>>;
struct GuiModel {
// A handle to the laser API used for spawning streams and detecting DACs.
laser_api: Arc<laser::Api>,
// 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<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.
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()
Ok(saved_config) => return saved_config.into(),
}
);
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 {
@ -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.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| {
stream.send(move |laser_model: &mut LaserModel| {
laser_model.config.source = source_option;
}).unwrap();
}
};
}
@ -636,88 +675,104 @@ 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| {
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"))
.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();
}
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.dim.intensity, 0.0..=1.).text("Dimming"))
.changed()
{
let factor = stream_config.config.filters.scale.factor;
stream_config.stream.send(move |laser_model: &mut LaserModel| {
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 selected_config.filters.scale.factor, 0.0..=2.).text("Scale"))
.changed()
{
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| {
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| {
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| {
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| {
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| {
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: