desktop: add pH calibration UI in Calibrate tab
This commit is contained in:
parent
bdb72a9917
commit
d5e1a7dd0f
|
|
@ -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),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue