EIS-BLE-S3/cue-ios/CueIOS/AppState.swift

276 lines
8.4 KiB
Swift

import Foundation
import Observation
enum Tab: String, CaseIterable, Identifiable {
case eis = "EIS"
case lsv = "LSV"
case amp = "Amperometry"
case chlorine = "Chlorine"
case ph = "pH"
case sessions = "Sessions"
var id: String { rawValue }
}
// MARK: - App State
@Observable
final class AppState {
var tab: Tab = .eis
var status: String = "Disconnected"
var bleConnected: Bool = false
var tempC: Float = 25.0
// EIS
var eisPoints: [EisPoint] = []
var sweepTotal: UInt16 = 0
var freqStart: String = "1000"
var freqStop: String = "200000"
var ppd: String = "10"
var rtia: Rtia = .r5K
var rcal: Rcal = .r3K
var electrode: Electrode = .fourWire
// LSV
var lsvPoints: [LsvPoint] = []
var lsvTotal: UInt16 = 0
var lsvStartV: String = "0"
var lsvStopV: String = "500"
var lsvScanRate: String = "50"
var lsvRtia: LpRtia = .r10K
// Amperometry
var ampPoints: [AmpPoint] = []
var ampTotal: UInt16 = 0
var ampRunning: Bool = false
var ampVHold: String = "200"
var ampInterval: String = "100"
var ampDuration: String = "60"
var ampRtia: LpRtia = .r10K
// Chlorine
var clPoints: [ClPoint] = []
var clResult: ClResult? = nil
var clTotal: UInt16 = 0
var clCondV: String = "800"
var clCondT: String = "2000"
var clFreeV: String = "100"
var clTotalV: String = "-200"
var clDepT: String = "5000"
var clMeasT: String = "5000"
var clRtia: LpRtia = .r10K
// pH
var phResult: PhResult? = nil
var phStabilize: String = "30"
// Reference baselines
var eisRef: [EisPoint]? = nil
var lsvRef: [LsvPoint]? = nil
var ampRef: [AmpPoint]? = nil
var clRef: (points: [ClPoint], result: ClResult)? = nil
var phRef: PhResult? = nil
// Device reference collection
var collectingRefs: Bool = false
var hasDeviceRefs: Bool = false
// Clean
var cleanV: String = "1200"
var cleanDur: String = "30"
// MARK: - Actions
func applyEISSettings() {
let fs = Float(freqStart) ?? 1000
let fe = Float(freqStop) ?? 200000
let p = UInt16(ppd) ?? 10
status = "Applying: \(fs)-\(fe) Hz, \(p) PPD"
}
func startSweep() {
eisPoints.removeAll()
status = "Starting sweep..."
}
func startLSV() {
lsvPoints.removeAll()
let vs = Float(lsvStartV) ?? 0
let ve = Float(lsvStopV) ?? 500
status = "Starting LSV: \(vs)-\(ve) mV"
}
func startAmp() {
ampPoints.removeAll()
ampRunning = true
status = "Starting amperometry..."
}
func stopAmp() {
ampRunning = false
status = "Stopping amperometry..."
}
func startChlorine() {
clPoints.removeAll()
clResult = nil
status = "Starting chlorine measurement..."
}
func startPh() {
phResult = nil
status = "Starting pH measurement..."
}
func setReference() {
switch tab {
case .eis where !eisPoints.isEmpty:
eisRef = eisPoints
status = "EIS reference set (\(eisPoints.count) pts)"
case .lsv where !lsvPoints.isEmpty:
lsvRef = lsvPoints
status = "LSV reference set (\(lsvPoints.count) pts)"
case .amp where !ampPoints.isEmpty:
ampRef = ampPoints
status = "Amp reference set (\(ampPoints.count) pts)"
case .chlorine where !clPoints.isEmpty:
if let r = clResult {
clRef = (clPoints, r)
status = "Chlorine reference set"
}
case .ph:
if let r = phResult {
phRef = r
status = String(format: "pH reference set (%.2f)", r.ph)
}
default:
break
}
}
func clearReference() {
switch tab {
case .eis: eisRef = nil; status = "EIS reference cleared"
case .lsv: lsvRef = nil; status = "LSV reference cleared"
case .amp: ampRef = nil; status = "Amp reference cleared"
case .chlorine: clRef = nil; status = "Chlorine reference cleared"
case .ph: phRef = nil; status = "pH reference cleared"
case .sessions: break
}
}
func collectRefs() {
collectingRefs = true
status = "Starting reference collection..."
}
func clearRefs() {
collectingRefs = false
hasDeviceRefs = false
eisRef = nil
lsvRef = nil
ampRef = nil
clRef = nil
phRef = nil
status = "Refs cleared"
}
func startClean() {
let v = Float(cleanV) ?? 1200
let d = Float(cleanDur) ?? 30
status = String(format: "Cleaning: %.0f mV for %.0fs", v, d)
}
var hasCurrentRef: Bool {
switch tab {
case .eis: eisRef != nil
case .lsv: lsvRef != nil
case .amp: ampRef != nil
case .chlorine: clRef != nil
case .ph: phRef != nil
case .sessions: false
}
}
var hasCurrentData: Bool {
switch tab {
case .eis: !eisPoints.isEmpty
case .lsv: !lsvPoints.isEmpty
case .amp: !ampPoints.isEmpty
case .chlorine: clResult != nil
case .ph: phResult != nil
case .sessions: false
}
}
// MARK: - Measurement loading
func loadMeasurement(_ measurement: Measurement) {
guard let id = measurement.id,
let type = MeasurementType(rawValue: measurement.type) else { return }
do {
switch type {
case .eis:
eisPoints = try Storage.shared.fetchTypedPoints(measurementId: id, as: EisPoint.self)
tab = .eis
status = "Loaded EIS (\(eisPoints.count) pts)"
case .lsv:
lsvPoints = try Storage.shared.fetchTypedPoints(measurementId: id, as: LsvPoint.self)
tab = .lsv
status = "Loaded LSV (\(lsvPoints.count) pts)"
case .amp:
ampPoints = try Storage.shared.fetchTypedPoints(measurementId: id, as: AmpPoint.self)
tab = .amp
status = "Loaded Amp (\(ampPoints.count) pts)"
case .chlorine:
clPoints = try Storage.shared.fetchTypedPoints(measurementId: id, as: ClPoint.self)
if let summary = measurement.resultSummary {
clResult = try JSONDecoder().decode(ClResult.self, from: summary)
}
tab = .chlorine
status = "Loaded Chlorine (\(clPoints.count) pts)"
case .ph:
if let summary = measurement.resultSummary {
phResult = try JSONDecoder().decode(PhResult.self, from: summary)
}
tab = .ph
status = "Loaded pH result"
}
} catch {
status = "Load failed: \(error.localizedDescription)"
}
}
func loadAsReference(_ measurement: Measurement) {
guard let id = measurement.id,
let type = MeasurementType(rawValue: measurement.type) else { return }
do {
switch type {
case .eis:
eisRef = try Storage.shared.fetchTypedPoints(measurementId: id, as: EisPoint.self)
status = "EIS reference loaded (\(eisRef?.count ?? 0) pts)"
case .lsv:
lsvRef = try Storage.shared.fetchTypedPoints(measurementId: id, as: LsvPoint.self)
status = "LSV reference loaded (\(lsvRef?.count ?? 0) pts)"
case .amp:
ampRef = try Storage.shared.fetchTypedPoints(measurementId: id, as: AmpPoint.self)
status = "Amp reference loaded (\(ampRef?.count ?? 0) pts)"
case .chlorine:
let pts = try Storage.shared.fetchTypedPoints(measurementId: id, as: ClPoint.self)
if let summary = measurement.resultSummary {
let result = try JSONDecoder().decode(ClResult.self, from: summary)
clRef = (pts, result)
status = "Chlorine reference loaded"
}
case .ph:
if let summary = measurement.resultSummary {
phRef = try JSONDecoder().decode(PhResult.self, from: summary)
status = String(format: "pH reference loaded (%.2f)", phRef?.ph ?? 0)
}
}
} catch {
status = "Reference load failed: \(error.localizedDescription)"
}
}
}