Bladeren bron

Enacted/Suggested for Trio

Jonas Björkert 1 jaar geleden
bovenliggende
commit
f89a5f54e4

+ 2 - 8
LoopFollow/Controllers/Nightscout/DeviceStatus.swift

@@ -11,14 +11,8 @@ import UIKit
 import Charts
 
 extension MainViewController {
-    // NS Device Status Web Call
     func webLoadNSDeviceStatus() {
-        let count = ObservableUserDefaults.shared.device.value == "Trio" && Observable.shared.isLastDeviceStatusSuggested.value ? "5" : "1"
-        if count != "1" {
-            LogManager.shared.log(category: .deviceStatus, message: "Fetching \(count) device status records")
-        }
-
-        let parameters: [String: String] = ["count": count]
+        let parameters: [String: String] = ["count": "1"]
         NightscoutUtils.executeDynamicRequest(eventType: .deviceStatus, parameters: parameters) { result in
             switch result {
             case .success(let json):
@@ -146,7 +140,7 @@ extension MainViewController {
 
         // OpenAPS - handle new data
         if let lastLoopRecord = lastDeviceStatus?["openaps"] as! [String : AnyObject]? {
-            DeviceStatusOpenAPS(formatter: formatter, lastDeviceStatus: lastDeviceStatus, lastLoopRecord: lastLoopRecord, jsonDeviceStatus: jsonDeviceStatus)
+            DeviceStatusOpenAPS(formatter: formatter, lastDeviceStatus: lastDeviceStatus, lastLoopRecord: lastLoopRecord)
         }
 
         // Start the timer based on the timestamp

+ 183 - 213
LoopFollow/Controllers/Nightscout/DeviceStatusOpenAPS.swift

@@ -8,259 +8,229 @@ import UIKit
 import HealthKit
 
 extension MainViewController {
-    func DeviceStatusOpenAPS(formatter: ISO8601DateFormatter, lastDeviceStatus: [String: AnyObject]?, lastLoopRecord: [String: AnyObject], jsonDeviceStatus: [[String:AnyObject]]) {
-        if let createdAtString = lastDeviceStatus?["created_at"] as? String,
-           let lastLoopTime = formatter.date(from: createdAtString)?.timeIntervalSince1970 {
-            ObservableUserDefaults.shared.device.value = lastDeviceStatus?["device"] as? String ?? ""
-            if lastLoopRecord["failureReason"] != nil {
-                LoopStatusLabel.text = "X"
-                latestLoopStatusString = "X"
+    func DeviceStatusOpenAPS(formatter: ISO8601DateFormatter, lastDeviceStatus: [String: AnyObject]?, lastLoopRecord: [String: AnyObject]) {
+        ObservableUserDefaults.shared.device.value = lastDeviceStatus?["device"] as? String ?? ""
+        if lastLoopRecord["failureReason"] != nil {
+            LoopStatusLabel.text = "X"
+            latestLoopStatusString = "X"
+            evaluateNotLooping(lastLoopTime: UserDefaultsRepository.alertLastLoopTime.value)
+        } else {
+            guard let enactedOrSuggested = lastLoopRecord["suggested"] as? [String: AnyObject] ?? lastLoopRecord["enacted"] as? [String: AnyObject] else {
+                LoopStatusLabel.text = "↻"
+                latestLoopStatusString = "↻"
                 evaluateNotLooping(lastLoopTime: UserDefaultsRepository.alertLastLoopTime.value)
-            } else {
-                guard let enactedOrSuggested = lastLoopRecord["enacted"] as? [String: AnyObject] ?? lastLoopRecord["suggested"] as? [String: AnyObject] else {
-                    LoopStatusLabel.text = "↻"
-                    latestLoopStatusString = "↻"
-                    evaluateNotLooping(lastLoopTime: UserDefaultsRepository.alertLastLoopTime.value)
-                    return
-                }
-
-                var wasEnacted : Bool
-                if let enacted = lastLoopRecord["enacted"] as? [String: AnyObject] {
-                    wasEnacted = NSDictionary(dictionary: enacted).isEqual(to: enactedOrSuggested)
-                } else {
-                    wasEnacted = false
-                }
-
-                Observable.shared.isLastDeviceStatusSuggested.value = !wasEnacted
+                return
+            }
 
-                if wasEnacted {
+            var wasEnacted : Bool
+            if let enacted = lastLoopRecord["enacted"] as? [String: AnyObject] {
+                wasEnacted = true
+                if let timestampString = lastDeviceStatus?["timestamp"] as? String,
+                   let lastLoopTime = formatter.date(from: timestampString)?.timeIntervalSince1970 {
                     UserDefaultsRepository.alertLastLoopTime.value = lastLoopTime
                     LogManager.shared.log(category: .deviceStatus, message: "New LastLoopTime: \(lastLoopTime)", isDebug: true)
-
-                    evaluateNotLooping(lastLoopTime: UserDefaultsRepository.alertLastLoopTime.value)
-                } else {
-                    LogManager.shared.log(category: .deviceStatus, message: "Last devicestatus was not enacted")
-                    findFallbackEnactedAndSetLoopTime(in: jsonDeviceStatus, formatter: formatter)
                 }
+            } else {
+                wasEnacted = false
+                LogManager.shared.log(category: .deviceStatus, message: "Last devicestatus is missing enacted")
+            }
+            evaluateNotLooping(lastLoopTime: UserDefaultsRepository.alertLastLoopTime.value)
 
-                if let timestamp = enactedOrSuggested["timestamp"] as? String,
-                   let enactedTime = formatter.date(from: timestamp)?.timeIntervalSince1970 {
-                    let formattedTime = Localizer.formatTimestampToLocalString(enactedTime)
-                    infoManager.updateInfoData(type: .updated, value: formattedTime)
-                }
 
-                // ISF
-                let profileISF = profileManager.currentISF()
-                var enactedISF: HKQuantity?
-                if let enactedISFValue = enactedOrSuggested["ISF"] as? Double {
-                    var determinedISFUnit: HKUnit = .milligramsPerDeciliter
-                    if enactedISFValue < 15 {
-                        determinedISFUnit = .millimolesPerLiter
-                    }
-                    enactedISF = HKQuantity(unit: determinedISFUnit, doubleValue: enactedISFValue)
-                }
-                if let profileISF = profileISF, let enactedISF = enactedISF, profileISF != enactedISF {
-                    infoManager.updateInfoData(type: .isf, firstValue: profileISF, secondValue: enactedISF, separator: .arrow)
-                } else if let profileISF = profileISF {
-                    infoManager.updateInfoData(type: .isf, value: profileISF)
+            var updatedTime: TimeInterval?
+
+            if let timestamp = enactedOrSuggested["timestamp"] as? String,
+               let parsedTime = formatter.date(from: timestamp)?.timeIntervalSince1970 {
+                updatedTime = parsedTime
+                let formattedTime = Localizer.formatTimestampToLocalString(parsedTime)
+                infoManager.updateInfoData(type: .updated, value: formattedTime)
+            }
+
+            // ISF
+            let profileISF = profileManager.currentISF()
+            var enactedISF: HKQuantity?
+            if let enactedISFValue = enactedOrSuggested["ISF"] as? Double {
+                var determinedISFUnit: HKUnit = .milligramsPerDeciliter
+                if enactedISFValue < 15 {
+                    determinedISFUnit = .millimolesPerLiter
                 }
+                enactedISF = HKQuantity(unit: determinedISFUnit, doubleValue: enactedISFValue)
+            }
+            if let profileISF = profileISF, let enactedISF = enactedISF, profileISF != enactedISF {
+                infoManager.updateInfoData(type: .isf, firstValue: profileISF, secondValue: enactedISF, separator: .arrow)
+            } else if let profileISF = profileISF {
+                infoManager.updateInfoData(type: .isf, value: profileISF)
+            }
 
-                // Carb Ratio (CR)
-                let profileCR = profileManager.currentCarbRatio()
-                var enactedCR: Double?
-                if let reasonString = enactedOrSuggested["reason"] as? String {
-                    let pattern = "CR: (\\d+(?:\\.\\d+)?)"
-                    if let regex = try? NSRegularExpression(pattern: pattern) {
-                        let nsString = reasonString as NSString
-                        if let match = regex.firstMatch(in: reasonString, range: NSRange(location: 0, length: nsString.length)) {
-                            let crString = nsString.substring(with: match.range(at: 1))
-                            enactedCR = Double(crString)
-                        }
+            // Carb Ratio (CR)
+            let profileCR = profileManager.currentCarbRatio()
+            var enactedCR: Double?
+            if let reasonString = enactedOrSuggested["reason"] as? String {
+                let pattern = "CR: (\\d+(?:\\.\\d+)?)"
+                if let regex = try? NSRegularExpression(pattern: pattern) {
+                    let nsString = reasonString as NSString
+                    if let match = regex.firstMatch(in: reasonString, range: NSRange(location: 0, length: nsString.length)) {
+                        let crString = nsString.substring(with: match.range(at: 1))
+                        enactedCR = Double(crString)
                     }
                 }
+            }
 
-                if let profileCR = profileCR, let enactedCR = enactedCR, profileCR != enactedCR {
-                    infoManager.updateInfoData(type: .carbRatio, value: profileCR, enactedValue: enactedCR, separator: .arrow)
-                } else if let profileCR = profileCR {
-                    infoManager.updateInfoData(type: .carbRatio, value: profileCR)
-                }
+            if let profileCR = profileCR, let enactedCR = enactedCR, profileCR != enactedCR {
+                infoManager.updateInfoData(type: .carbRatio, value: profileCR, enactedValue: enactedCR, separator: .arrow)
+            } else if let profileCR = profileCR {
+                infoManager.updateInfoData(type: .carbRatio, value: profileCR)
+            }
 
-                // IOB
-                if let iobMetric = InsulinMetric(from: lastLoopRecord["iob"], key: "iob") {
-                    infoManager.updateInfoData(type: .iob, value: iobMetric)
-                    latestIOB = iobMetric
-                }
+            // IOB
+            if let iobMetric = InsulinMetric(from: lastLoopRecord["iob"], key: "iob") {
+                infoManager.updateInfoData(type: .iob, value: iobMetric)
+                latestIOB = iobMetric
+            }
 
-                // COB
-                if let cobMetric = CarbMetric(from: enactedOrSuggested, key: "COB") {
-                    infoManager.updateInfoData(type: .cob, value: cobMetric)
-                    latestCOB = cobMetric
-                } else if let reasonString = enactedOrSuggested["reason"] as? String {
-                    // Fallback: Extract COB from reason string
-                    let cobPattern = "COB: (\\d+(?:\\.\\d+)?)"
-                    if let cobRegex = try? NSRegularExpression(pattern: cobPattern),
-                       let cobMatch = cobRegex.firstMatch(in: reasonString, range: NSRange(location: 0, length: reasonString.utf16.count)) {
-                        let cobValueString = (reasonString as NSString).substring(with: cobMatch.range(at: 1))
-                        if let cobValue = Double(cobValueString) {
-                            let tempDict: [String: AnyObject] = ["COB": cobValue as AnyObject]
-                            if let fallbackCobMetric = CarbMetric(from: tempDict, key: "COB") {
-                                infoManager.updateInfoData(type: .cob, value: fallbackCobMetric)
-                                latestCOB = fallbackCobMetric
-                            } else {
-                                print("Failed to create CarbMetric from extracted COB value: \(cobValue)")
-                            }
+            // COB
+            if let cobMetric = CarbMetric(from: enactedOrSuggested, key: "COB") {
+                infoManager.updateInfoData(type: .cob, value: cobMetric)
+                latestCOB = cobMetric
+            } else if let reasonString = enactedOrSuggested["reason"] as? String {
+                // Fallback: Extract COB from reason string
+                let cobPattern = "COB: (\\d+(?:\\.\\d+)?)"
+                if let cobRegex = try? NSRegularExpression(pattern: cobPattern),
+                   let cobMatch = cobRegex.firstMatch(in: reasonString, range: NSRange(location: 0, length: reasonString.utf16.count)) {
+                    let cobValueString = (reasonString as NSString).substring(with: cobMatch.range(at: 1))
+                    if let cobValue = Double(cobValueString) {
+                        let tempDict: [String: AnyObject] = ["COB": cobValue as AnyObject]
+                        if let fallbackCobMetric = CarbMetric(from: tempDict, key: "COB") {
+                            infoManager.updateInfoData(type: .cob, value: fallbackCobMetric)
+                            latestCOB = fallbackCobMetric
                         } else {
-                            print("Invalid COB value extracted from reason string: \(cobValueString)")
+                            print("Failed to create CarbMetric from extracted COB value: \(cobValue)")
                         }
                     } else {
-                        print("COB pattern not found in reason string.")
+                        print("Invalid COB value extracted from reason string: \(cobValueString)")
                     }
-                }
-
-                // Insulin Required
-                if let insulinReqMetric = InsulinMetric(from: enactedOrSuggested, key: "insulinReq") {
-                    infoManager.updateInfoData(type: .recBolus, value: insulinReqMetric)
-                    UserDefaultsRepository.deviceRecBolus.value = insulinReqMetric.value
                 } else {
-                    UserDefaultsRepository.deviceRecBolus.value = 0
-                }
-
-                // Autosens
-                if let sens = enactedOrSuggested["sensitivityRatio"] as? Double {
-                    let formattedSens = String(format: "%.0f", sens * 100.0) + "%"
-                    infoManager.updateInfoData(type: .autosens, value: formattedSens)
-                }
-
-                // Eventual BG
-                if let eventualBGValue = enactedOrSuggested["eventualBG"] as? Double {
-                    let eventualBGQuantity = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: eventualBGValue)
-                    PredictionLabel.text = Localizer.formatQuantity(eventualBGQuantity)
+                    print("COB pattern not found in reason string.")
                 }
+            }
 
-                // Target
-                let profileTargetHigh = profileManager.currentTargetHigh()
-                var enactedTarget: HKQuantity?
-                if let enactedTargetValue = enactedOrSuggested["current_target"] as? Double {
-                    var targetUnit = HKUnit.milligramsPerDeciliter
-                    if enactedTargetValue < 40 {
-                        targetUnit = .millimolesPerLiter
-                    }
-                    enactedTarget = HKQuantity(unit: targetUnit, doubleValue: enactedTargetValue)
-                }
+            // Insulin Required
+            if let insulinReqMetric = InsulinMetric(from: enactedOrSuggested, key: "insulinReq") {
+                infoManager.updateInfoData(type: .recBolus, value: insulinReqMetric)
+                UserDefaultsRepository.deviceRecBolus.value = insulinReqMetric.value
+            } else {
+                UserDefaultsRepository.deviceRecBolus.value = 0
+            }
 
-                if let profileTargetHigh = profileTargetHigh, let enactedTarget = enactedTarget {
-                    let profileTargetHighFormatted = Localizer.formatQuantity(profileTargetHigh)
-                    let enactedTargetFormatted = Localizer.formatQuantity(enactedTarget)
+            // Autosens
+            if let sens = enactedOrSuggested["sensitivityRatio"] as? Double {
+                let formattedSens = String(format: "%.0f", sens * 100.0) + "%"
+                infoManager.updateInfoData(type: .autosens, value: formattedSens)
+            }
 
-                    // Compare formatted values to avoid issues with minor floating-point differences
-                    // Profile target could be in another unit than enacted target
-                    if profileTargetHighFormatted != enactedTargetFormatted {
-                        infoManager.updateInfoData(type: .target, firstValue: profileTargetHigh, secondValue: enactedTarget, separator: .arrow)
-                    } else {
-                        infoManager.updateInfoData(type: .target, value: profileTargetHigh)
-                    }
-                }
+            // Eventual BG
+            if let eventualBGValue = enactedOrSuggested["eventualBG"] as? Double {
+                let eventualBGQuantity = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: eventualBGValue)
+                PredictionLabel.text = Localizer.formatQuantity(eventualBGQuantity)
+            }
 
-                // TDD
-                if let tddMetric = InsulinMetric(from: enactedOrSuggested, key: "TDD") {
-                    infoManager.updateInfoData(type: .tdd, value: tddMetric)
+            // Target
+            let profileTargetHigh = profileManager.currentTargetHigh()
+            var enactedTarget: HKQuantity?
+            if let enactedTargetValue = enactedOrSuggested["current_target"] as? Double {
+                var targetUnit = HKUnit.milligramsPerDeciliter
+                if enactedTargetValue < 40 {
+                    targetUnit = .millimolesPerLiter
                 }
+                enactedTarget = HKQuantity(unit: targetUnit, doubleValue: enactedTargetValue)
+            }
 
-                let predictioncolor = UIColor.systemGray
-                PredictionLabel.textColor = predictioncolor
-                topPredictionBG = UserDefaultsRepository.minBGScale.value
-                if let predbgdata = enactedOrSuggested["predBGs"] as? [String: AnyObject] {
-                    let predictionTypes: [(type: String, colorName: String, dataIndex: Int)] = [
-                        ("ZT", "ZT", 12),
-                        ("IOB", "Insulin", 13),
-                        ("COB", "LoopYellow", 14),
-                        ("UAM", "UAM", 15)
-                    ]
-
-                    var minPredBG = Double.infinity
-                    var maxPredBG = -Double.infinity
+            if let profileTargetHigh = profileTargetHigh, let enactedTarget = enactedTarget {
+                let profileTargetHighFormatted = Localizer.formatQuantity(profileTargetHigh)
+                let enactedTargetFormatted = Localizer.formatQuantity(enactedTarget)
 
-                    for (type, colorName, dataIndex) in predictionTypes {
-                        var predictionData = [ShareGlucoseData]()
-                        if let graphdata = predbgdata[type] as? [Double] {
-                            var predictionTime = lastLoopTime
-                            let toLoad = Int(UserDefaultsRepository.predictionToLoad.value * 12)
+                // Compare formatted values to avoid issues with minor floating-point differences
+                // Profile target could be in another unit than enacted target
+                if profileTargetHighFormatted != enactedTargetFormatted {
+                    infoManager.updateInfoData(type: .target, firstValue: profileTargetHigh, secondValue: enactedTarget, separator: .arrow)
+                } else {
+                    infoManager.updateInfoData(type: .target, value: profileTargetHigh)
+                }
+            }
 
-                            for i in 0...toLoad {
-                                if i < graphdata.count {
-                                    let predictionValue = graphdata[i]
-                                    minPredBG = min(minPredBG, predictionValue)
-                                    maxPredBG = max(maxPredBG, predictionValue)
+            // TDD
+            if let tddMetric = InsulinMetric(from: enactedOrSuggested, key: "TDD") {
+                infoManager.updateInfoData(type: .tdd, value: tddMetric)
+            }
 
-                                    let prediction = ShareGlucoseData(sgv: Int(round(predictionValue)), date: predictionTime, direction: "flat")
-                                    predictionData.append(prediction)
-                                    predictionTime += 300
-                                }
+            let predictioncolor = UIColor.systemGray
+            PredictionLabel.textColor = predictioncolor
+            topPredictionBG = UserDefaultsRepository.minBGScale.value
+            if let predbgdata = enactedOrSuggested["predBGs"] as? [String: AnyObject] {
+                let predictionTypes: [(type: String, colorName: String, dataIndex: Int)] = [
+                    ("ZT", "ZT", 12),
+                    ("IOB", "Insulin", 13),
+                    ("COB", "LoopYellow", 14),
+                    ("UAM", "UAM", 15)
+                ]
+
+                var minPredBG = Double.infinity
+                var maxPredBG = -Double.infinity
+
+                for (type, colorName, dataIndex) in predictionTypes {
+                    var predictionData = [ShareGlucoseData]()
+                    if let graphdata = predbgdata[type] as? [Double] {
+                        var predictionTime = updatedTime ?? Date().timeIntervalSince1970
+                        let toLoad = Int(UserDefaultsRepository.predictionToLoad.value * 12)
+
+                        for i in 0...toLoad {
+                            if i < graphdata.count {
+                                let predictionValue = graphdata[i]
+                                minPredBG = min(minPredBG, predictionValue)
+                                maxPredBG = max(maxPredBG, predictionValue)
+
+                                let prediction = ShareGlucoseData(sgv: Int(round(predictionValue)), date: predictionTime, direction: "flat")
+                                predictionData.append(prediction)
+                                predictionTime += 300
                             }
                         }
-
-                        let color = UIColor(named: colorName) ?? UIColor.systemPurple
-                        updatePredictionGraphGeneric(
-                            dataIndex: dataIndex,
-                            predictionData: predictionData,
-                            chartLabel: type,
-                            color: color
-                        )
                     }
 
-                    if minPredBG != Double.infinity && maxPredBG != -Double.infinity {
-                        let value = "\(Localizer.toDisplayUnits(String(minPredBG)))/\(Localizer.toDisplayUnits(String(maxPredBG)))"
-                        infoManager.updateInfoData(type: .minMax, value: value)
-                    } else {
-                        infoManager.updateInfoData(type: .minMax, value: "N/A")
-                    }
+                    let color = UIColor(named: colorName) ?? UIColor.systemPurple
+                    updatePredictionGraphGeneric(
+                        dataIndex: dataIndex,
+                        predictionData: predictionData,
+                        chartLabel: type,
+                        color: color
+                    )
                 }
 
-                if let loopStatus = lastLoopRecord["recommendedTempBasal"] as? [String: AnyObject] {
-                    if let tempBasalTime = formatter.date(from: (loopStatus["timestamp"] as! String))?.timeIntervalSince1970 {
-                        var lastBGTime = lastLoopTime
-                        if bgData.count > 0 {
-                            lastBGTime = bgData[bgData.count - 1].date
-                        }
-                        if tempBasalTime > lastBGTime && !wasEnacted {
-                            LoopStatusLabel.text = "⏀"
-                            latestLoopStatusString = "⏀"
-                        } else {
-                            LoopStatusLabel.text = "↻"
-                            latestLoopStatusString = "↻"
-                        }
-                    }
+                if minPredBG != Double.infinity && maxPredBG != -Double.infinity {
+                    let value = "\(Localizer.toDisplayUnits(String(minPredBG)))/\(Localizer.toDisplayUnits(String(maxPredBG)))"
+                    infoManager.updateInfoData(type: .minMax, value: value)
                 } else {
-                    LoopStatusLabel.text = "↻"
-                    latestLoopStatusString = "↻"
+                    infoManager.updateInfoData(type: .minMax, value: "N/A")
                 }
             }
-        }
-    }
 
-    private func findFallbackEnactedAndSetLoopTime(
-        in allDeviceStatuses: [[String: AnyObject]],
-        formatter: ISO8601DateFormatter
-    ) {
-        for i in 1 ..< allDeviceStatuses.count {
-            let ds = allDeviceStatuses[i]
-            guard
-                let openaps = ds["openaps"] as? [String: AnyObject],
-                openaps["failureReason"] == nil,
-                let enacted = openaps["enacted"] as? [String: AnyObject],
-                let dateString = ds["created_at"] as? String,
-                let dateTime = formatter.date(from: dateString)?.timeIntervalSince1970
-            else {
-                continue
+            if let loopStatus = lastLoopRecord["recommendedTempBasal"] as? [String: AnyObject] {
+                if let tempBasalTime = formatter.date(from: (loopStatus["timestamp"] as! String))?.timeIntervalSince1970 {
+                    var lastBGTime = updatedTime ?? Date().timeIntervalSince1970
+                    if bgData.count > 0 {
+                        lastBGTime = bgData[bgData.count - 1].date
+                    }
+                    if tempBasalTime > lastBGTime && !wasEnacted {
+                        LoopStatusLabel.text = "⏀"
+                        latestLoopStatusString = "⏀"
+                    } else {
+                        LoopStatusLabel.text = "↻"
+                        latestLoopStatusString = "↻"
+                    }
+                }
+            } else {
+                LoopStatusLabel.text = "↻"
+                latestLoopStatusString = "↻"
             }
-
-            UserDefaultsRepository.alertLastLoopTime.value = dateTime
-            LogManager.shared.log(category: .deviceStatus, message: "Found older enacted. Setting lastLoopTime to \(dateTime)", isDebug: true)
-
-            evaluateNotLooping(lastLoopTime: dateTime)
-            return
         }
-
-        LogManager.shared.log(category: .deviceStatus, message: "No older record was enacted!")
     }
 }

+ 0 - 1
LoopFollow/Storage/Observable.swift

@@ -14,7 +14,6 @@ class Observable {
 
     var tempTarget = ObservableValue<HKQuantity?>(default: nil)
     var override = ObservableValue<String?>(default: nil)
-    var isLastDeviceStatusSuggested = ObservableValue<Bool>(default: false)
 
     private init() {}
 }