From 03d10ab6788b9d37cea5765028016cc614878c97 Mon Sep 17 00:00:00 2001 From: jess Date: Fri, 3 Apr 2026 02:07:51 -0700 Subject: [PATCH] add auto-mode chlorine flow to desktop --- cue/src/app.rs | 193 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 147 insertions(+), 46 deletions(-) diff --git a/cue/src/app.rs b/cue/src/app.rs index 3468790..2ba5db2 100644 --- a/cue/src/app.rs +++ b/cue/src/app.rs @@ -17,6 +17,13 @@ use crate::protocol::{ use crate::storage::{self, Session, Storage}; use crate::udp::UdpEvent; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ClAutoState { + Idle, + LsvRunning, + MeasureRunning, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum LsvDensityMode { PtsPerMv, @@ -109,6 +116,8 @@ pub enum Message { ClMeasTChanged(String), ClRtiaSelected(LpRtia), StartCl, + StartClAuto, + ClToggleManual, /* pH */ PhStabilizeChanged(String), StartPh, @@ -238,6 +247,9 @@ pub struct App { cl_meas_t: String, cl_rtia: LpRtia, cl_data: text_editor::Content, + cl_manual_peaks: bool, + cl_auto_state: ClAutoState, + cl_auto_potentials: Option, /* pH */ ph_result: Option, @@ -482,6 +494,9 @@ impl App { cl_meas_t: "5000".into(), cl_rtia: LpRtia::R10K, cl_data: text_editor::Content::with_text(&fmt_cl(&[])), + cl_manual_peaks: false, + cl_auto_state: ClAutoState::Idle, + cl_auto_potentials: None, ph_result: None, ph_stabilize: "30".into(), @@ -702,6 +717,27 @@ impl App { } } self.status = st; + + if self.cl_auto_state == ClAutoState::LsvRunning { + let pots = crate::lsv_analysis::derive_cl_potentials(&self.lsv_points); + self.cl_free_v = format!("{:.0}", pots.v_free); + self.cl_total_v = format!("{:.0}", pots.v_total); + self.cl_auto_potentials = Some(pots); + self.cl_auto_state = ClAutoState::MeasureRunning; + + let v_cond = self.cl_cond_v.parse::().unwrap_or(800.0); + let t_cond = self.cl_cond_t.parse::().unwrap_or(2000.0); + let t_dep = self.cl_dep_t.parse::().unwrap_or(5000.0); + let t_meas = self.cl_meas_t.parse::().unwrap_or(5000.0); + self.send_cmd(&protocol::build_sysex_get_temp()); + self.send_cmd(&protocol::build_sysex_start_cl( + v_cond, t_cond, pots.v_free, pots.v_total, t_dep, t_meas, self.cl_rtia, + )); + self.status = format!( + "Auto Cl: measuring (free={:.0}, total={:.0})", + pots.v_free, pots.v_total + ); + } } EisMessage::AmpStart { v_hold } => { self.amp_points.clear(); @@ -744,7 +780,23 @@ impl App { if let Some(sid) = self.current_session { self.save_cl(sid); } - self.status = format!("Chlorine complete: {} points", self.cl_points.len()); + if self.cl_auto_state == ClAutoState::MeasureRunning { + self.cl_auto_state = ClAutoState::Idle; + if let Some(pots) = &self.cl_auto_potentials { + self.status = format!( + "Auto Cl complete: {} pts (free={:.0}{}, total={:.0}{})", + self.cl_points.len(), + pots.v_free, + if pots.v_free_detected { "" } else { " dflt" }, + pots.v_total, + if pots.v_total_detected { "" } else { " dflt" }, + ); + } else { + self.status = format!("Chlorine complete: {} points", self.cl_points.len()); + } + } else { + self.status = format!("Chlorine complete: {} points", self.cl_points.len()); + } } EisMessage::PhResult(r) => { if self.collecting_refs { @@ -932,6 +984,17 @@ impl App { v_cond, t_cond, v_free, v_total, t_dep, t_meas, self.cl_rtia, )); } + Message::StartClAuto => { + self.cl_auto_state = ClAutoState::LsvRunning; + self.cl_auto_potentials = None; + let density = self.lsv_density.parse::().unwrap_or(1.0); + let n = lsv_calc_points(-1100.0, 1100.0, 50.0, density, self.lsv_density_mode); + self.send_cmd(&protocol::build_sysex_start_lsv(-1100.0, 1100.0, 50.0, self.lsv_rtia, n)); + self.status = "Auto Cl: running LSV sweep...".into(); + } + Message::ClToggleManual => { + self.cl_manual_peaks = !self.cl_manual_peaks; + } /* pH */ Message::PhStabilizeChanged(s) => self.ph_stabilize = s, Message::StartPh => { @@ -1650,51 +1713,89 @@ impl App { .align_y(iced::Alignment::End) .into(), - Tab::Chlorine => row![ - button(text("Start LSV").size(13)) - .style(style_action()) - .padding([6, 16]) - .on_press(Message::StartLsv), - button(text(if self.lsv_manual_peaks { "Manual" } else { "Auto" }).size(13)) - .padding([6, 12]) - .on_press(Message::LsvToggleManual), - rule::Rule::vertical(1), - column![ - text("Cond mV").size(12), - text_input("800", &self.cl_cond_v).on_input(Message::ClCondVChanged).width(70), - ].spacing(2), - column![ - text("Cond ms").size(12), - text_input("2000", &self.cl_cond_t).on_input(Message::ClCondTChanged).width(70), - ].spacing(2), - column![ - text("Free mV").size(12), - text_input("100", &self.cl_free_v).on_input(Message::ClFreeVChanged).width(70), - ].spacing(2), - column![ - text("Total mV").size(12), - text_input("-200", &self.cl_total_v).on_input(Message::ClTotalVChanged).width(70), - ].spacing(2), - column![ - text("Settle ms").size(12), - text_input("5000", &self.cl_dep_t).on_input(Message::ClDepTChanged).width(70), - ].spacing(2), - column![ - text("Meas ms").size(12), - text_input("5000", &self.cl_meas_t).on_input(Message::ClMeasTChanged).width(70), - ].spacing(2), - column![ - text("RTIA").size(12), - pick_list(LpRtia::ALL, Some(self.cl_rtia), Message::ClRtiaSelected).width(Length::Shrink), - ].spacing(2), - button(text("Measure").size(13)) - .style(style_action()) - .padding([6, 16]) - .on_press(Message::StartCl), - ] - .spacing(8) - .align_y(iced::Alignment::End) - .into(), + Tab::Chlorine => if self.cl_manual_peaks { + row![ + button(text("Start LSV").size(13)) + .style(style_action()) + .padding([6, 16]) + .on_press(Message::StartLsv), + button(text("Manual").size(13)) + .padding([6, 12]) + .on_press(Message::ClToggleManual), + rule::Rule::vertical(1), + column![ + text("Cond mV").size(12), + text_input("800", &self.cl_cond_v).on_input(Message::ClCondVChanged).width(70), + ].spacing(2), + column![ + text("Cond ms").size(12), + text_input("2000", &self.cl_cond_t).on_input(Message::ClCondTChanged).width(70), + ].spacing(2), + column![ + text("Free mV").size(12), + text_input("100", &self.cl_free_v).on_input(Message::ClFreeVChanged).width(70), + ].spacing(2), + column![ + text("Total mV").size(12), + text_input("-200", &self.cl_total_v).on_input(Message::ClTotalVChanged).width(70), + ].spacing(2), + column![ + text("Settle ms").size(12), + text_input("5000", &self.cl_dep_t).on_input(Message::ClDepTChanged).width(70), + ].spacing(2), + column![ + text("Meas ms").size(12), + text_input("5000", &self.cl_meas_t).on_input(Message::ClMeasTChanged).width(70), + ].spacing(2), + column![ + text("RTIA").size(12), + pick_list(LpRtia::ALL, Some(self.cl_rtia), Message::ClRtiaSelected).width(Length::Shrink), + ].spacing(2), + button(text("Measure").size(13)) + .style(style_action()) + .padding([6, 16]) + .on_press(Message::StartCl), + ] + .spacing(8) + .align_y(iced::Alignment::End) + .into() + } else { + let auto_btn = { + let b = button(text("Start Auto").size(13)) + .style(style_action()) + .padding([6, 16]); + if self.cl_auto_state == ClAutoState::Idle { + b.on_press(Message::StartClAuto) + } else { + b + } + }; + let pot_text = if let Some(pots) = &self.cl_auto_potentials { + format!( + "free={:.0}{} total={:.0}{}", + pots.v_free, + if pots.v_free_detected { "" } else { "?" }, + pots.v_total, + if pots.v_total_detected { "" } else { "?" }, + ) + } else { + String::new() + }; + row![ + auto_btn, + button(text("Auto").size(13)) + .padding([6, 12]) + .on_press(Message::ClToggleManual), + column![ + text("RTIA").size(12), + pick_list(LpRtia::ALL, Some(self.cl_rtia), Message::ClRtiaSelected).width(Length::Shrink), + ].spacing(2), + text(pot_text).size(12), + ] + .spacing(8) + .align_y(iced::Alignment::End) + .into() + }, Tab::Ph => row![ column![