Ivan Valkou 4 лет назад
Родитель
Сommit
da8b9f98a1
1 измененных файлов с 104 добавлено и 71 удалено
  1. 104 71
      FreeAPS/Sources/APS/APSManager.swift

+ 104 - 71
FreeAPS/Sources/APS/APSManager.swift

@@ -108,9 +108,7 @@ final class BaseAPSManager: APSManager, Injectable {
         lastLoopDateSubject.send(lastLoopDate)
 
         isLooping
-            .sink { value in
-                self.deviceDataManager.loopInProgress = value
-            }
+            .weakAssign(to: \.deviceDataManager.loopInProgress, on: self)
             .store(in: &lifetime)
     }
 
@@ -147,6 +145,7 @@ final class BaseAPSManager: APSManager, Injectable {
         deviceDataManager.heartbeat(date: date)
     }
 
+    // Loop entry point
     private func loop() {
         guard !isLooping.value else {
             warning(.apsManager, "Already looping, skip")
@@ -156,51 +155,68 @@ final class BaseAPSManager: APSManager, Injectable {
         debug(.apsManager, "Starting loop")
         isLooping.send(true)
         determineBasal()
-            .sink { [weak self] ok in
-                guard let self = self else { return }
+            .replaceEmpty(with: false)
+            .flatMap { [weak self] success -> AnyPublisher<Void, Error> in
+                guard let self = self, success else {
+                    return Fail(error: APSError.apsError(message: "Determine basal failed")).eraseToAnyPublisher()
+                }
+                self.nightscout.uploadStatus()
 
-                if ok {
-                    self.nightscout.uploadStatus()
-                    if self.settings.closedLoop {
-                        self.enactSuggested()
-                    } else {
-                        self.isLooping.send(false)
-                        self.lastLoopDate = Date()
-                    }
+                // Open loop completed
+                guard self.settings.closedLoop else {
+                    return Just(()).setFailureType(to: Error.self).eraseToAnyPublisher()
+                }
+
+                // Closed loop - enact suggested
+                return self.enactSuggested()
+            }
+            .sink { [weak self] completion in
+                guard let self = self else { return }
+                if case let .failure(error) = completion {
+                    self.loopCompleted(error: error)
                 } else {
-                    self.isLooping.send(false)
+                    self.loopCompleted()
                 }
-            }.store(in: &lifetime)
+            } receiveValue: {}
+            .store(in: &lifetime)
     }
 
-    private func verifyStatus() -> Bool {
+    // Loop exit point
+    private func loopCompleted(error: Error? = nil) {
+        isLooping.send(false)
+
+        if let error = error {
+            warning(.apsManager, "Loop failed with error: \(error.localizedDescription)")
+            processError(error)
+        } else {
+            debug(.apsManager, "Loop succeeded")
+            lastLoopDate = Date()
+            lastError.send(nil)
+        }
+
+        reportEnacted(received: error == nil)
+    }
+
+    private func verifyStatus() -> Error? {
         guard let pump = pumpManager else {
-            debug(.apsManager, "Pump is not set")
-            processError(APSError.invalidPumpState(message: "Pump not set"))
-            return false
+            return APSError.invalidPumpState(message: "Pump not set")
         }
         let status = pump.status.pumpStatus
 
         guard !status.bolusing else {
-            debug(.apsManager, "Pump is bolusing")
-            processError(APSError.invalidPumpState(message: "Pump is bolusing"))
-            return false
+            return APSError.invalidPumpState(message: "Pump is bolusing")
         }
 
         guard !status.suspended else {
-            debug(.apsManager, "Pump suspended")
-            processError(APSError.invalidPumpState(message: "Pump suspended"))
-            return false
+            return APSError.invalidPumpState(message: "Pump suspended")
         }
 
         let reservoir = storage.retrieve(OpenAPS.Monitor.reservoir, as: Decimal.self) ?? 100
         guard reservoir > 0 else {
-            debug(.apsManager, "Reservoir is empty")
-            processError(APSError.invalidPumpState(message: "Reservoir is empty"))
-            return false
+            return APSError.invalidPumpState(message: "Reservoir is empty")
         }
 
-        return true
+        return nil
     }
 
     private func autosens() -> AnyPublisher<Bool, Never> {
@@ -301,7 +317,17 @@ final class BaseAPSManager: APSManager, Injectable {
     private var bolusReporter: DoseProgressReporter?
 
     func enactBolus(amount: Double, isSMB: Bool) {
-        guard let pump = pumpManager, verifyStatus() else { return }
+        if let error = verifyStatus() {
+            processError(error)
+            processQueue.async {
+                self.broadcaster.notify(BolusFailureObserver.self, on: self.processQueue) {
+                    $0.bolusDidFail()
+                }
+            }
+            return
+        }
+
+        guard let pump = pumpManager else { return }
 
         let roundedAmout = pump.roundToSupportedBolusVolume(units: amount)
 
@@ -348,7 +374,12 @@ final class BaseAPSManager: APSManager, Injectable {
     }
 
     func enactTempBasal(rate: Double, duration: TimeInterval) {
-        guard let pump = pumpManager, verifyStatus() else { return }
+        if let error = verifyStatus() {
+            processError(error)
+            return
+        }
+
+        guard let pump = pumpManager else { return }
         debug(.apsManager, "Enact temp basal \(rate) - \(duration)")
 
         let roundedAmout = pump.roundToSupportedBasalRate(unitsPerHour: rate)
@@ -402,7 +433,8 @@ final class BaseAPSManager: APSManager, Injectable {
 
         switch action {
         case let .bolus(amount):
-            guard verifyStatus() else {
+            if let error = verifyStatus() {
+                processError(error)
                 return
             }
             let roundedAmount = pump.roundToSupportedBolusVolume(units: Double(amount))
@@ -419,7 +451,8 @@ final class BaseAPSManager: APSManager, Injectable {
         case let .pump(pumpAction):
             switch pumpAction {
             case .suspend:
-                guard verifyStatus(), !pump.status.pumpStatus.suspended else {
+                if let error = verifyStatus() {
+                    processError(error)
                     return
                 }
                 pump.suspendDelivery { error in
@@ -450,7 +483,11 @@ final class BaseAPSManager: APSManager, Injectable {
             debug(.apsManager, "Closed loop \(closedLoop) by Announcement")
             announcementsStorage.storeAnnouncements([announcement], enacted: true)
         case let .tempbasal(rate, duration):
-            guard verifyStatus(), !settings.closedLoop else {
+            if let error = verifyStatus() {
+                processError(error)
+                return
+            }
+            guard !settings.closedLoop else {
                 return
             }
             let roundedRate = pump.roundToSupportedBasalRate(unitsPerHour: Double(rate))
@@ -489,30 +526,27 @@ final class BaseAPSManager: APSManager, Injectable {
         }
     }
 
-    private func enactSuggested() {
+    private func enactSuggested() -> AnyPublisher<Void, Error> {
         guard let suggested = storage.retrieve(OpenAPS.Enact.suggested, as: Suggestion.self) else {
-            isLooping.send(false)
-            warning(.apsManager, "Suggestion not found")
-            processError(APSError.apsError(message: "Suggestion not found"))
-            return
+            return Fail(error: APSError.apsError(message: "Suggestion not found")).eraseToAnyPublisher()
         }
 
         guard Date().timeIntervalSince(suggested.deliverAt ?? .distantPast) < Config.eхpirationInterval else {
-            isLooping.send(false)
-            warning(.apsManager, "Suggestion expired")
-            processError(APSError.apsError(message: "Suggestion expired"))
-            return
+            return Fail(error: APSError.apsError(message: "Suggestion expired")).eraseToAnyPublisher()
         }
 
         guard let pump = pumpManager else {
-            isLooping.send(false)
-            warning(.apsManager, "Pump not set")
-            processError(APSError.invalidPumpState(message: "Pump not set"))
-            return
+            return Fail(error: APSError.apsError(message: "Pump not set")).eraseToAnyPublisher()
         }
 
         let basalPublisher: AnyPublisher<Void, Error> = Deferred { () -> AnyPublisher<Void, Error> in
-            guard let rate = suggested.rate, let duration = suggested.duration, self.verifyStatus() else {
+            if let error = self.verifyStatus() {
+                return Fail(error: error).eraseToAnyPublisher()
+            }
+
+            guard let rate = suggested.rate, let duration = suggested.duration else {
+                // It is OK, no temp required
+                debug(.apsManager, "No temp required")
                 return Just(()).setFailureType(to: Error.self)
                     .eraseToAnyPublisher()
             }
@@ -525,7 +559,12 @@ final class BaseAPSManager: APSManager, Injectable {
         }.eraseToAnyPublisher()
 
         let bolusPublisher: AnyPublisher<Void, Error> = Deferred { () -> AnyPublisher<Void, Error> in
-            guard let units = suggested.units, self.verifyStatus() else {
+            if let error = self.verifyStatus() {
+                return Fail(error: error).eraseToAnyPublisher()
+            }
+            guard let units = suggested.units else {
+                // It is OK, no bolus required
+                debug(.apsManager, "No bolus required")
                 return Just(()).setFailureType(to: Error.self)
                     .eraseToAnyPublisher()
             }
@@ -536,26 +575,11 @@ final class BaseAPSManager: APSManager, Injectable {
             .eraseToAnyPublisher()
         }.eraseToAnyPublisher()
 
-        basalPublisher
-            .flatMap { bolusPublisher }
-            .sink { [weak self] completion in
-                if case let .failure(error) = completion {
-                    warning(.apsManager, "Loop failed with error: \(error.localizedDescription)")
-                    self?.reportEnacted(suggestion: suggested, received: false)
-                    self?.processError(APSError.pumpError(error))
-                } else {
-                    self?.reportEnacted(suggestion: suggested, received: true)
-                }
-                self?.isLooping.send(false)
-            } receiveValue: {
-                debug(.apsManager, "Loop succeeded")
-                self.lastError.send(nil)
-                self.lastLoopDate = Date()
-            }.store(in: &lifetime)
+        return basalPublisher.flatMap { bolusPublisher }.eraseToAnyPublisher()
     }
 
-    private func reportEnacted(suggestion: Suggestion, received: Bool) {
-        if suggestion.deliverAt != nil {
+    private func reportEnacted(received: Bool) {
+        if let suggestion = storage.retrieve(OpenAPS.Enact.suggested, as: Suggestion.self), suggestion.deliverAt != nil {
             var enacted = suggestion
             enacted.timestamp = Date()
             enacted.recieved = received
@@ -602,7 +626,9 @@ private extension PumpManager {
                     promise(.failure(error))
                 }
             }
-        }.eraseToAnyPublisher()
+        }
+        .mapError { APSError.pumpError($0) }
+        .eraseToAnyPublisher()
     }
 
     func enactBolus(units: Double, automatic: Bool) -> AnyPublisher<DoseEntry, Error> {
@@ -617,7 +643,9 @@ private extension PumpManager {
                     promise(.failure(error))
                 }
             }
-        }.eraseToAnyPublisher()
+        }
+        .mapError { APSError.pumpError($0) }
+        .eraseToAnyPublisher()
     }
 
     func cancelBolus() -> AnyPublisher<DoseEntry?, Error> {
@@ -633,6 +661,7 @@ private extension PumpManager {
                 }
             }
         }
+        .mapError { APSError.pumpError($0) }
         .eraseToAnyPublisher()
     }
 
@@ -645,7 +674,9 @@ private extension PumpManager {
                     promise(.success(()))
                 }
             }
-        }.eraseToAnyPublisher()
+        }
+        .mapError { APSError.pumpError($0) }
+        .eraseToAnyPublisher()
     }
 
     func resumeDelivery() -> AnyPublisher<Void, Error> {
@@ -657,7 +688,9 @@ private extension PumpManager {
                     promise(.success(()))
                 }
             }
-        }.eraseToAnyPublisher()
+        }
+        .mapError { APSError.pumpError($0) }
+        .eraseToAnyPublisher()
     }
 }