desktop: add pH calibration UI in Calibrate tab

This commit is contained in:
jess 2026-04-02 19:33:04 -07:00
parent bdb72a9917
commit d5e1a7dd0f
1 changed files with 80 additions and 0 deletions

View File

@ -998,6 +998,45 @@ impl App {
self.status = "No valid EIS data for Rs extraction".into(); self.status = "No valid EIS data for Rs extraction".into();
} }
} }
Message::PhCalKnownChanged(s) => { self.ph_cal_known = s; }
Message::PhAddCalPoint => {
if let Some(peak_mv) = crate::lsv_analysis::detect_qhq_peak(&self.lsv_points) {
if let Ok(ph) = self.ph_cal_known.parse::<f32>() {
self.ph_cal_points.push((ph, peak_mv));
self.status = format!("pH cal point: pH={:.2} peak={:.1} mV ({} pts)",
ph, peak_mv, self.ph_cal_points.len());
}
} else {
self.status = "No Q/HQ peak found in LSV data".into();
}
}
Message::PhClearCalPoints => {
self.ph_cal_points.clear();
self.status = "pH cal points cleared".into();
}
Message::PhComputeAndSetCal => {
if self.ph_cal_points.len() < 2 {
self.status = "Need at least 2 calibration points".into();
} else {
let n = self.ph_cal_points.len() as f32;
let mean_ph: f32 = self.ph_cal_points.iter().map(|p| p.0).sum::<f32>() / n;
let mean_v: f32 = self.ph_cal_points.iter().map(|p| p.1).sum::<f32>() / n;
let num: f32 = self.ph_cal_points.iter()
.map(|p| (p.0 - mean_ph) * (p.1 - mean_v)).sum();
let den: f32 = self.ph_cal_points.iter()
.map(|p| (p.0 - mean_ph).powi(2)).sum();
if den.abs() < 1e-12 {
self.status = "Degenerate calibration data".into();
} else {
let slope = num / den;
let offset = mean_v - slope * mean_ph;
self.ph_slope = Some(slope);
self.ph_offset = Some(offset);
self.send_cmd(&protocol::build_sysex_set_ph_cal(slope, offset));
self.status = format!("pH cal set: slope={:.4} offset={:.4}", slope, offset);
}
}
}
Message::ClCalKnownPpmChanged(s) => { self.cl_cal_known_ppm = s; } Message::ClCalKnownPpmChanged(s) => { self.cl_cal_known_ppm = s; }
Message::ClSetFactor => { Message::ClSetFactor => {
let known_ppm = self.cl_cal_known_ppm.parse::<f32>().unwrap_or(0.0); let known_ppm = self.cl_cal_known_ppm.parse::<f32>().unwrap_or(0.0);
@ -1750,6 +1789,47 @@ impl App {
].spacing(10).align_y(iced::Alignment::End) ].spacing(10).align_y(iced::Alignment::End)
); );
/* pH calibration */
results = results.push(iced::widget::horizontal_rule(1));
results = results.push(text("pH Calibration (Q/HQ peak-shift)").size(16));
if let (Some(s), Some(o)) = (self.ph_slope, self.ph_offset) {
results = results.push(text(format!("slope: {:.4} mV/pH offset: {:.4} mV", s, o)).size(14));
if let Some(peak) = crate::lsv_analysis::detect_qhq_peak(&self.lsv_points) {
if s.abs() > 1e-6 {
let ph = (peak - o) / s;
results = results.push(text(format!("Computed pH: {:.2} (peak at {:.1} mV)", ph, peak)).size(14));
}
}
}
results = results.push(
row![
column![
text("Known pH").size(12),
text_input("7.00", &self.ph_cal_known)
.on_input(Message::PhCalKnownChanged).width(80),
].spacing(2),
button(text("Add Point").size(13))
.style(style_action())
.padding([6, 16])
.on_press(Message::PhAddCalPoint),
].spacing(10).align_y(iced::Alignment::End)
);
for (i, (ph, mv)) in self.ph_cal_points.iter().enumerate() {
results = results.push(text(format!(" {}. pH={:.2} peak={:.1} mV", i + 1, ph, mv)).size(13));
}
results = results.push(
row![
button(text("Clear Points").size(13))
.style(style_action())
.padding([6, 16])
.on_press(Message::PhClearCalPoints),
button(text("Compute & Set pH Cal").size(13))
.style(style_action())
.padding([6, 16])
.on_press(Message::PhComputeAndSetCal),
].spacing(10)
);
row![ row![
container(inputs).width(Length::FillPortion(2)), container(inputs).width(Length::FillPortion(2)),
iced::widget::vertical_rule(1), iced::widget::vertical_rule(1),