Преглед изворни кода

pump status validation and unsuspend if no temp

Ivan Valkou пре 5 година
родитељ
комит
42fd229b9e

+ 4 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -175,6 +175,7 @@
 		38C4D33A25E9A1ED00D30B77 /* NSObject+AssociatedValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38C4D33925E9A1ED00D30B77 /* NSObject+AssociatedValues.swift */; };
 		38D0B3B625EBE24900CB6E88 /* Battery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38D0B3B525EBE24900CB6E88 /* Battery.swift */; };
 		38D0B3D925EC07C400CB6E88 /* CarbsEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38D0B3D825EC07C400CB6E88 /* CarbsEntry.swift */; };
+		38E989DD25F5021400C0CED0 /* PumpStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E989DC25F5021400C0CED0 /* PumpStatus.swift */; };
 		38F3B2EF25ED8E2A005C48AA /* TempTargetsStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38F3B2EE25ED8E2A005C48AA /* TempTargetsStorage.swift */; };
 		38FCF3D625E8FDF40078B0D1 /* MD5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FCF3D525E8FDF40078B0D1 /* MD5.swift */; };
 		38FCF3F925E902C20078B0D1 /* FileStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FCF3F825E902C20078B0D1 /* FileStorageTests.swift */; };
@@ -748,6 +749,7 @@
 		38C4D33925E9A1ED00D30B77 /* NSObject+AssociatedValues.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSObject+AssociatedValues.swift"; sourceTree = "<group>"; };
 		38D0B3B525EBE24900CB6E88 /* Battery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Battery.swift; sourceTree = "<group>"; };
 		38D0B3D825EC07C400CB6E88 /* CarbsEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbsEntry.swift; sourceTree = "<group>"; };
+		38E989DC25F5021400C0CED0 /* PumpStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PumpStatus.swift; sourceTree = "<group>"; };
 		38F3B2EE25ED8E2A005C48AA /* TempTargetsStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTargetsStorage.swift; sourceTree = "<group>"; };
 		38FCF3D525E8FDF40078B0D1 /* MD5.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MD5.swift; sourceTree = "<group>"; };
 		38FCF3ED25E9028E0078B0D1 /* FreeAPSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FreeAPSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -1291,6 +1293,7 @@
 				38A0364125ED069400FCBB52 /* TempBasal.swift */,
 				3871F39B25ED892B0013ECB5 /* TempTarget.swift */,
 				3811DE8E25C9D80400A708ED /* User.swift */,
+				38E989DC25F5021400C0CED0 /* PumpStatus.swift */,
 			);
 			path = Models;
 			sourceTree = "<group>";
@@ -2179,6 +2182,7 @@
 				3811DE3F25C9D4A100A708ED /* SettingsViewModel.swift in Sources */,
 				38B4F3CB25E502E200E76A18 /* WeakObjectSet.swift in Sources */,
 				3811DEB725C9D88300A708ED /* AuthorizationManager.swift in Sources */,
+				38E989DD25F5021400C0CED0 /* PumpStatus.swift in Sources */,
 				3811DF0825CAAA4700A708ED /* ServiceContainer.swift in Sources */,
 				3811DEB025C9D88300A708ED /* BaseKeychain.swift in Sources */,
 				3811DE4D25C9D4B800A708ED /* AuthotizedRootViewModel.swift in Sources */,

+ 5 - 0
FreeAPS/Resources/json/defaults/monitor/status.json

@@ -0,0 +1,5 @@
+{
+  "status": "normal",
+  "bolusing": false,
+  "suspended": false
+}

+ 93 - 13
FreeAPS/Sources/APS/APSManager.swift

@@ -15,6 +15,7 @@ protocol APSManager {
 }
 
 final class BaseAPSManager: APSManager, Injectable {
+    private let processQueue = DispatchQueue(label: "BaseAPSManager.processQueue")
     @Injected() private var storage: FileStorage!
     @Injected() private var pumpHistoryStorage: PumpHistoryStorage!
     @Injected() private var glucoseStorage: GlucoseStorage!
@@ -57,6 +58,7 @@ final class BaseAPSManager: APSManager, Injectable {
             .sink { [weak self] in
                 self?.fetchAndLoop()
             }
+        pumpManager?.addStatusObserver(self, queue: processQueue)
     }
 
     func fetchAndLoop() {
@@ -98,6 +100,17 @@ final class BaseAPSManager: APSManager, Injectable {
         }
     }
 
+    private func verifyStatus() -> Bool {
+        guard let pump = pumpManager else {
+            return false
+        }
+        let status = pump.status.pumpStatus
+
+        guard !status.bolusing, !status.suspended else { return false }
+
+        return true
+    }
+
     private func determineBasal() -> AnyPublisher<Bool, Never> {
         guard let glucose = try? storage.retrieve(OpenAPS.Monitor.glucose, as: [BloodGlucose].self), glucose.count >= 36 else {
             print("Not enough glucose data")
@@ -105,20 +118,31 @@ final class BaseAPSManager: APSManager, Injectable {
         }
 
         let now = Date()
-        guard let temp = currentTemp(date: now) else {
-            return Just(false).eraseToAnyPublisher()
-        }
+        let temp = currentTemp(date: now)
 
-        return openAPS.makeProfiles()
+        let mainPublisher = openAPS.makeProfiles()
             .flatMap { _ in
                 self.openAPS.determineBasal(currentTemp: temp, clock: now)
             }
             .map { true }
             .eraseToAnyPublisher()
+
+        if temp.duration == 0,
+           settings.closedLoop,
+           settingsManager.preferences.unsuspendIfNoTemp,
+           let pump = pumpManager
+        {
+            return pump.resumeDelivery()
+                .flatMap { _ in mainPublisher }
+                .replaceError(with: false)
+                .eraseToAnyPublisher()
+        }
+
+        return mainPublisher
     }
 
     func enactBolus(amount: Double) {
-        guard let pump = pumpManager else { return }
+        guard let pump = pumpManager, verifyStatus() else { return }
 
         let roundedAmout = pump.roundToSupportedBolusVolume(units: amount)
         pump.enactBolus(units: roundedAmout, automatic: false) { result in
@@ -132,13 +156,15 @@ final class BaseAPSManager: APSManager, Injectable {
     }
 
     func enactTempBasal(rate: Double, duration: TimeInterval) {
-        guard let pump = pumpManager else { return }
+        guard let pump = pumpManager, verifyStatus() else { return }
 
         let roundedAmout = pump.roundToSupportedBasalRate(unitsPerHour: rate)
         pump.enactTempBasal(unitsPerHour: roundedAmout, for: duration) { result in
             switch result {
             case .success:
                 print("Temp Basal succeeded")
+                let temp = TempBasal(duration: Int(duration / 60), rate: Decimal(rate), temp: .absolute, updatedAt: Date())
+                try? self.storage.save(temp, as: OpenAPS.Monitor.tempBasal)
             case let .failure(error):
                 print("Temp Basal failed with error: \(error.localizedDescription)")
             }
@@ -160,6 +186,9 @@ final class BaseAPSManager: APSManager, Injectable {
         }
         switch action {
         case let .bolus(amount):
+            guard verifyStatus() else {
+                return
+            }
             pumpManager?.enactBolus(units: Double(amount), automatic: false) { result in
                 switch result {
                 case .success:
@@ -172,6 +201,9 @@ final class BaseAPSManager: APSManager, Injectable {
         case let .pump(pumpAction):
             switch pumpAction {
             case .suspend:
+                guard verifyStatus() else {
+                    return
+                }
                 pumpManager?.suspendDelivery { error in
                     if let error = error {
                         print("Pump not suspended by Announcement: \(error.localizedDescription)")
@@ -195,6 +227,9 @@ final class BaseAPSManager: APSManager, Injectable {
             print("Closed loop \(closedLoop) by Announcement")
             announcementsStorage.storeAnnouncements([announcement], enacted: true)
         case let .tempbasal(rate, duration):
+            guard verifyStatus() else {
+                return
+            }
             pumpManager?.enactTempBasal(unitsPerHour: Double(rate), for: TimeInterval(duration) * 60) { result in
                 switch result {
                 case .success:
@@ -207,20 +242,34 @@ final class BaseAPSManager: APSManager, Injectable {
         }
     }
 
-    private func currentTemp(date: Date) -> TempBasal? {
-        guard let state = pumpManager?.status.basalDeliveryState else { return nil }
+    private func currentTemp(date: Date) -> TempBasal {
+        let defaultTemp = { () -> TempBasal in
+            guard let temp = try? storage.retrieve(OpenAPS.Monitor.tempBasal, as: TempBasal.self) else {
+                return TempBasal(duration: 0, rate: 0, temp: .absolute, updatedAt: Date())
+            }
+            let delta = Int((date.timeIntervalSince1970 - temp.updatedAt.timeIntervalSince1970) / 60)
+            let duration = max(0, temp.duration - delta)
+            return TempBasal(duration: duration, rate: temp.rate, temp: .absolute, updatedAt: date)
+        }()
+
+        guard let state = pumpManager?.status.basalDeliveryState else { return defaultTemp }
         switch state {
         case .active:
-            return TempBasal(duration: 0, rate: 0, temp: .absolute)
+            return TempBasal(duration: 0, rate: 0, temp: .absolute, updatedAt: date)
         case let .tempBasal(dose):
             let rate = Decimal(dose.unitsPerHour)
             let durationMin = max(0, Int((dose.endDate.timeIntervalSince1970 - date.timeIntervalSince1970) / 60))
-            return TempBasal(duration: durationMin, rate: rate, temp: .absolute)
-        default: return nil
+            return TempBasal(duration: durationMin, rate: rate, temp: .absolute, updatedAt: date)
+        default:
+            return defaultTemp
         }
     }
 
     private func enactSuggested() {
+        guard verifyStatus() else {
+            return
+        }
+
         guard let pump = pumpManager,
               let suggested = try? storage.retrieve(
                   OpenAPS.Enact.suggested,
@@ -235,8 +284,12 @@ final class BaseAPSManager: APSManager, Injectable {
                 return Just(()).setFailureType(to: Error.self)
                     .eraseToAnyPublisher()
             }
-            return pump.enactTempBasal(unitsPerHour: Double(rate), for: TimeInterval(duration * 60)).map { _ in () }
-                .eraseToAnyPublisher()
+            return pump.enactTempBasal(unitsPerHour: Double(rate), for: TimeInterval(duration * 60)).map { _ in
+                let temp = TempBasal(duration: duration, rate: rate, temp: .absolute, updatedAt: Date())
+                try? self.storage.save(temp, as: OpenAPS.Monitor.tempBasal)
+                return ()
+            }
+            .eraseToAnyPublisher()
         }()
 
         let bolusPublisher: AnyPublisher<Void, Error> = {
@@ -289,4 +342,31 @@ private extension PumpManager {
             }
         }.eraseToAnyPublisher()
     }
+
+    func resumeDelivery() -> AnyPublisher<Void, Error> {
+        Future { promise in
+            self.resumeDelivery { error in
+                if let error = error {
+                    promise(.failure(error))
+                } else {
+                    promise(.success(()))
+                }
+            }
+        }.eraseToAnyPublisher()
+    }
+}
+
+extension BaseAPSManager: PumpManagerStatusObserver {
+    func pumpManager(_: PumpManager, didUpdate status: PumpManagerStatus, oldStatus _: PumpManagerStatus) {
+        try? storage.save(status.pumpStatus, as: OpenAPS.Monitor.status)
+    }
+}
+
+extension PumpManagerStatus {
+    var pumpStatus: PumpStatus {
+        let bolusing = bolusState != .noBolus
+        let suspended = basalDeliveryState?.isSuspended ?? true
+        let type = suspended ? StatusType.suspended : (bolusing ? .bolusing : .normal)
+        return PumpStatus(status: type, bolusing: bolusing, suspended: suspended)
+    }
 }

+ 11 - 0
FreeAPS/Sources/Models/PumpStatus.swift

@@ -0,0 +1,11 @@
+struct PumpStatus: JSON {
+    let status: StatusType
+    let bolusing: Bool
+    let suspended: Bool
+}
+
+enum StatusType: String, JSON {
+    case normal
+    case suspended
+    case bolusing
+}

+ 1 - 0
FreeAPS/Sources/Models/TempBasal.swift

@@ -4,4 +4,5 @@ struct TempBasal: JSON {
     let duration: Int
     let rate: Decimal
     let temp: TempType
+    let updatedAt: Date
 }

+ 3 - 3
FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorProvider.swift

@@ -2,11 +2,11 @@ import Foundation
 
 extension PreferencesEditor {
     final class Provider: BaseProvider, PreferencesEditorProvider {
+        @Injected() private var settingsManager: SettingsManager!
         private let processQueue = DispatchQueue(label: "PreferencesEditorProvider.processQueue")
+
         var preferences: Preferences {
-            (try? storage.retrieve(OpenAPS.Settings.preferences, as: Preferences.self))
-                ?? Preferences(from: OpenAPS.defaults(for: OpenAPS.Settings.preferences))
-                ?? Preferences()
+            settingsManager.preferences
         }
 
         func savePreferences(_ preferences: Preferences) {

+ 7 - 0
FreeAPS/Sources/Services/SettingsManager/SettingsManager.swift

@@ -3,6 +3,7 @@ import Swinject
 
 protocol SettingsManager {
     var settings: FreeAPSSettings { get set }
+    var preferences: Preferences { get }
 }
 
 protocol SettingsObserver {
@@ -36,4 +37,10 @@ final class BaseSettingsManager: SettingsManager, Injectable {
     private func save() {
         try? storage.save(settings, as: OpenAPS.FreeAPS.settings)
     }
+
+    var preferences: Preferences {
+        (try? storage.retrieve(OpenAPS.Settings.preferences, as: Preferences.self))
+            ?? Preferences(from: OpenAPS.defaults(for: OpenAPS.Settings.preferences))
+            ?? Preferences()
+    }
 }