iOS: add pH calibration UI in Calibrate tab

This commit is contained in:
jess 2026-04-02 19:34:30 -07:00
parent 818c4ff7a2
commit 1441c5ec42
1 changed files with 75 additions and 0 deletions

View File

@ -15,6 +15,7 @@ struct CalibrateView: View {
resultsSection resultsSection
cellConstantSection cellConstantSection
chlorineCalSection chlorineCalSection
phCalibrationSection
} }
.navigationTitle("Calibrate") .navigationTitle("Calibrate")
} }
@ -158,6 +159,80 @@ struct CalibrateView: View {
} }
} }
// MARK: - pH calibration
private var phCalibrationSection: some View {
Section("pH Calibration (Q/HQ peak-shift)") {
if let s = state.phSlope, let o = state.phOffset {
Text(String(format: "slope: %.4f mV/pH offset: %.4f mV", s, o))
if let peak = detectQhqPeak(state.lsvPoints) {
if abs(s) > 1e-6 {
let ph = (Double(peak) - o) / s
Text(String(format: "Computed pH: %.2f (peak at %.1f mV)", ph, peak))
}
}
}
HStack {
Text("Known pH")
Spacer()
TextField("7.00", text: $state.phCalKnown)
.multilineTextAlignment(.trailing)
.frame(width: 80)
#if os(iOS)
.keyboardType(.decimalPad)
#endif
}
Button("Add Calibration Point") {
guard let peak = detectQhqPeak(state.lsvPoints) else {
state.status = "No Q/HQ peak found in LSV data"
return
}
guard let ph = Double(state.phCalKnown) else { return }
state.phCalPoints.append((ph: ph, mV: Double(peak)))
state.status = String(format: "pH cal point: pH=%.2f peak=%.1f mV (%d pts)",
ph, peak, state.phCalPoints.count)
}
.disabled(state.lsvPoints.isEmpty)
ForEach(Array(state.phCalPoints.enumerated()), id: \.offset) { i, pt in
Text(String(format: "%d. pH=%.2f peak=%.1f mV", i + 1, pt.ph, pt.mV))
.font(.caption)
}
Button("Clear Points") {
state.phCalPoints.removeAll()
state.status = "pH cal points cleared"
}
.disabled(state.phCalPoints.isEmpty)
Button("Compute & Set pH Cal") {
let pts = state.phCalPoints
guard pts.count >= 2 else {
state.status = "Need at least 2 calibration points"
return
}
let n = Double(pts.count)
let meanPh = pts.map(\.ph).reduce(0, +) / n
let meanV = pts.map(\.mV).reduce(0, +) / n
let num = pts.map { ($0.ph - meanPh) * ($0.mV - meanV) }.reduce(0, +)
let den = pts.map { ($0.ph - meanPh) * ($0.ph - meanPh) }.reduce(0, +)
guard abs(den) > 1e-12 else {
state.status = "Degenerate calibration data"
return
}
let slope = num / den
let offset = meanV - slope * meanPh
state.phSlope = slope
state.phOffset = offset
state.send(buildSysexSetPhCal(Float(slope), Float(offset)))
state.status = String(format: "pH cal set: slope=%.4f offset=%.4f", slope, offset)
}
.disabled(state.phCalPoints.count < 2)
}
}
// MARK: - Calculations // MARK: - Calculations
private func saltGrams(volumeGal: Double, ppm: Double) -> Double { private func saltGrams(volumeGal: Double, ppm: Double) -> Double {