From 8de67ca66e05a3dfe90b702263024e83d60176a4 Mon Sep 17 00:00:00 2001 From: jess Date: Thu, 2 Apr 2026 23:10:44 -0700 Subject: [PATCH] 2 iOS: add LSV point density config, remove auto/manual toggle --- cue-ios/CueIOS/AppState.swift | 32 +++++++++++++++++++++++----- cue-ios/CueIOS/Models/Protocol.swift | 3 ++- cue-ios/CueIOS/Views/LSVView.swift | 19 ++++++++--------- 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/cue-ios/CueIOS/AppState.swift b/cue-ios/CueIOS/AppState.swift index 831d9f2..018ce34 100644 --- a/cue-ios/CueIOS/AppState.swift +++ b/cue-ios/CueIOS/AppState.swift @@ -1,6 +1,12 @@ import Foundation import Observation +enum LsvDensityMode: String, CaseIterable, Identifiable { + case ptsPerMv = "pts/mV" + case ptsPerSec = "pts/s" + var id: String { rawValue } +} + enum Tab: String, CaseIterable, Identifiable { case eis = "EIS" case lsv = "LSV" @@ -38,12 +44,13 @@ final class AppState { // LSV var lsvPoints: [LsvPoint] = [] var lsvPeaks: [LsvPeak] = [] - var lsvManualPeaks: Bool = false var lsvTotal: UInt16 = 0 var lsvStartV: String = "0" var lsvStopV: String = "500" var lsvScanRate: String = "50" var lsvRtia: LpRtia = .r10K + var lsvDensityMode: LsvDensityMode = .ptsPerMv + var lsvDensity: String = "1" // Amperometry var ampPoints: [AmpPoint] = [] @@ -171,9 +178,7 @@ final class AppState { case .lsvEnd: saveLsv() - if !lsvManualPeaks { - lsvPeaks = detectLsvPeaks(lsvPoints) - } + lsvPeaks = detectLsvPeaks(lsvPoints) var st = "LSV complete: \(lsvPoints.count) points" if let s = phSlope, let o = phOffset, abs(s) > 1e-6 { if let peak = detectQhqPeak(lsvPoints) { @@ -300,13 +305,30 @@ final class AppState { send(buildSysexStartSweep()) } + func lsvCalcPoints() -> UInt16 { + let vs = Float(lsvStartV) ?? 0 + let ve = Float(lsvStopV) ?? 500 + let sr = Float(lsvScanRate) ?? 50 + let d = Float(lsvDensity) ?? 1 + let range = abs(ve - vs) + let raw: Float + switch lsvDensityMode { + case .ptsPerMv: + raw = range * d + case .ptsPerSec: + raw = abs(sr) < 0.001 ? 2 : (range / abs(sr)) * d + } + return max(2, min(500, UInt16(raw))) + } + func startLSV() { lsvPoints.removeAll() let vs = Float(lsvStartV) ?? 0 let ve = Float(lsvStopV) ?? 500 let sr = Float(lsvScanRate) ?? 50 + let n = lsvCalcPoints() send(buildSysexGetTemp()) - send(buildSysexStartLsv(vStart: vs, vStop: ve, scanRate: sr, lpRtia: lsvRtia)) + send(buildSysexStartLsv(vStart: vs, vStop: ve, scanRate: sr, lpRtia: lsvRtia, numPoints: n)) } func startAmp() { diff --git a/cue-ios/CueIOS/Models/Protocol.swift b/cue-ios/CueIOS/Models/Protocol.swift index 8a97608..ab4581d 100644 --- a/cue-ios/CueIOS/Models/Protocol.swift +++ b/cue-ios/CueIOS/Models/Protocol.swift @@ -307,12 +307,13 @@ func buildSysexGetConfig() -> [UInt8] { [0xF0, sysexMfr, CMD_GET_CONFIG, 0xF7] } -func buildSysexStartLsv(vStart: Float, vStop: Float, scanRate: Float, lpRtia: LpRtia) -> [UInt8] { +func buildSysexStartLsv(vStart: Float, vStop: Float, scanRate: Float, lpRtia: LpRtia, numPoints: UInt16) -> [UInt8] { var sx: [UInt8] = [0xF0, sysexMfr, CMD_START_LSV] sx.append(contentsOf: encodeFloat(vStart)) sx.append(contentsOf: encodeFloat(vStop)) sx.append(contentsOf: encodeFloat(scanRate)) sx.append(lpRtia.rawValue) + sx.append(contentsOf: encodeU16(numPoints)) sx.append(0xF7) return sx } diff --git a/cue-ios/CueIOS/Views/LSVView.swift b/cue-ios/CueIOS/Views/LSVView.swift index 503282b..d2ec8c6 100644 --- a/cue-ios/CueIOS/Views/LSVView.swift +++ b/cue-ios/CueIOS/Views/LSVView.swift @@ -41,18 +41,17 @@ struct LSVView: View { LabeledPicker("RTIA", selection: $state.lsvRtia, items: LpRtia.allCases) { $0.label } .frame(width: 120) + LabeledPicker("Density", selection: $state.lsvDensityMode, items: LsvDensityMode.allCases) { $0.rawValue } + .frame(width: 100) + + LabeledField("Value", text: $state.lsvDensity, width: 60) + + Text("\(state.lsvCalcPoints()) pts") + .font(.caption) + .foregroundStyle(.secondary) + Button("Start LSV") { state.startLSV() } .buttonStyle(ActionButtonStyle(color: .green)) - - Button(state.lsvManualPeaks ? "Manual" : "Auto") { - state.lsvManualPeaks.toggle() - if state.lsvManualPeaks { - state.lsvPeaks.removeAll() - } else { - state.lsvPeaks = detectLsvPeaks(state.lsvPoints) - } - } - .font(.caption) } .padding(.horizontal) .padding(.vertical, 8)