Просмотр исходного кода

Remote announcements refctored

Ivan Valkou 5 лет назад
Родитель
Сommit
29f361dce2

+ 4 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -164,6 +164,7 @@
 		38A0363B25ECF07E00FCBB52 /* GlucoseStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A0363A25ECF07E00FCBB52 /* GlucoseStorage.swift */; };
 		38A0364225ED069400FCBB52 /* TempBasal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A0364125ED069400FCBB52 /* TempBasal.swift */; };
 		38A13D3225E28B4B00EAA382 /* PumpHistoryEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A13D3125E28B4B00EAA382 /* PumpHistoryEvent.swift */; };
+		38A43598262E0E4900E80935 /* FetchAnnouncementsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A43597262E0E4900E80935 /* FetchAnnouncementsManager.swift */; };
 		38A504A425DD9C4000C5B9E8 /* UserDefaultsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A5049125DD9C4000C5B9E8 /* UserDefaultsExtensions.swift */; };
 		38A9260525F012D8009E3739 /* CarbRatios.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A9260425F012D8009E3739 /* CarbRatios.swift */; };
 		38AAF85525FFF846004AF583 /* CurrentGlucoseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38AAF85425FFF846004AF583 /* CurrentGlucoseView.swift */; };
@@ -448,6 +449,7 @@
 		38A0363A25ECF07E00FCBB52 /* GlucoseStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseStorage.swift; sourceTree = "<group>"; };
 		38A0364125ED069400FCBB52 /* TempBasal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempBasal.swift; sourceTree = "<group>"; };
 		38A13D3125E28B4B00EAA382 /* PumpHistoryEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PumpHistoryEvent.swift; sourceTree = "<group>"; };
+		38A43597262E0E4900E80935 /* FetchAnnouncementsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchAnnouncementsManager.swift; sourceTree = "<group>"; };
 		38A5049125DD9C4000C5B9E8 /* UserDefaultsExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDefaultsExtensions.swift; sourceTree = "<group>"; };
 		38A9260425F012D8009E3739 /* CarbRatios.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbRatios.swift; sourceTree = "<group>"; };
 		38AAF85425FFF846004AF583 /* CurrentGlucoseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentGlucoseView.swift; sourceTree = "<group>"; };
@@ -970,6 +972,7 @@
 			children = (
 				3811DF0F25CAAAE200A708ED /* APSManager.swift */,
 				38BF021E25E7F0DE00579895 /* DeviceDataManager.swift */,
+				38A43597262E0E4900E80935 /* FetchAnnouncementsManager.swift */,
 				38DAB289260D349500F74C1A /* FetchGlucoseManager.swift */,
 				38192E06261BA9960094D973 /* FetchTreatmentsManager.swift */,
 				38A504F625DDA0E200C5B9E8 /* Extensions */,
@@ -1758,6 +1761,7 @@
 				38E98A3025F52FF700C0CED0 /* Config.swift in Sources */,
 				BD2B464E0745FBE7B79913F4 /* NightscoutConfigProvider.swift in Sources */,
 				9825E5E923F0B8FA80C8C7C7 /* NightscoutConfigViewModel.swift in Sources */,
+				38A43598262E0E4900E80935 /* FetchAnnouncementsManager.swift in Sources */,
 				642F76A05A4FF530463A9FD0 /* NightscoutConfigRootView.swift in Sources */,
 				3340E0D14D4701342D459C95 /* PumpConfigBuilder.swift in Sources */,
 				AD3D2CD42CD01B9EB8F26522 /* PumpConfigDataFlow.swift in Sources */,

+ 28 - 32
FreeAPS/Sources/APS/APSManager.swift

@@ -24,6 +24,7 @@ protocol APSManager {
     func roundBolus(amount: Decimal) -> Decimal
     var lastError: CurrentValueSubject<Error?, Never> { get }
     func cancelBolus()
+    func enactAnnouncement(_ announcement: Announcement)
 }
 
 enum APSError: LocalizedError {
@@ -110,7 +111,7 @@ final class BaseAPSManager: APSManager, Injectable {
         deviceDataManager.recommendsLoop
             .receive(on: processQueue)
             .sink { [weak self] in
-                self?.fetchAndLoop()
+                self?.loop()
             }
             .store(in: &lifetime)
         pumpManager?.addStatusObserver(self, queue: processQueue)
@@ -139,26 +140,6 @@ final class BaseAPSManager: APSManager, Injectable {
         deviceDataManager.heartbeat(date: date, force: force)
     }
 
-    private func fetchAndLoop() {
-        if settings.allowAnnouncements {
-            nightscout.fetchAnnouncements()
-                .sink { [weak self] in
-                    guard let self = self else { return }
-                    guard self.pumpManager != nil,
-                          let recent = self.announcementsStorage.recent(),
-                          recent.action != nil
-                    else {
-                        self.loop()
-                        return
-                    }
-                    self.enactAnnouncement(recent)
-                }
-                .store(in: &lifetime)
-        } else {
-            loop()
-        }
-    }
-
     private func loop() {
         debug(.apsManager, "Starting loop")
         isLooping.send(true)
@@ -374,46 +355,60 @@ final class BaseAPSManager: APSManager, Injectable {
         openAPS.autotune().eraseToAnyPublisher()
     }
 
-    private func enactAnnouncement(_ announcement: Announcement) {
+    func enactAnnouncement(_ announcement: Announcement) {
         guard let action = announcement.action else {
-            debug(.apsManager, "Invalid Announcement action")
+            warning(.apsManager, "Invalid Announcement action")
+            return
+        }
+
+        guard let pump = pumpManager else {
+            warning(.apsManager, "Pump is not set")
             return
         }
+
+        debug(.apsManager, "Start enact announcement: \(action)")
+
         switch action {
         case let .bolus(amount):
             guard verifyStatus() else {
                 return
             }
-            pumpManager?.enactBolus(units: Double(amount), automatic: false) { result in
+            let roundedAmount = pump.roundToSupportedBolusVolume(units: Double(amount))
+            pump.enactBolus(units: roundedAmount, automatic: false) { result in
                 switch result {
                 case .success:
                     debug(.apsManager, "Announcement Bolus succeeded")
                     self.announcementsStorage.storeAnnouncements([announcement], enacted: true)
                 case let .failure(error):
-                    debug(.apsManager, "Announcement Bolus failed with error: \(error.localizedDescription)")
+                    warning(.apsManager, "Announcement Bolus failed with error: \(error.localizedDescription)")
                 }
             }
         case let .pump(pumpAction):
             switch pumpAction {
             case .suspend:
-                guard verifyStatus() else {
+                guard verifyStatus(), !pump.status.pumpStatus.suspended else {
                     return
                 }
-                pumpManager?.suspendDelivery { error in
+                pump.suspendDelivery { error in
                     if let error = error {
                         debug(.apsManager, "Pump not suspended by Announcement: \(error.localizedDescription)")
                     } else {
                         debug(.apsManager, "Pump suspended by Announcement")
                         self.announcementsStorage.storeAnnouncements([announcement], enacted: true)
+                        self.nightscout.uploadStatus()
                     }
                 }
             case .resume:
-                pumpManager?.resumeDelivery { error in
+                guard pump.status.pumpStatus.suspended else {
+                    return
+                }
+                pump.resumeDelivery { error in
                     if let error = error {
-                        debug(.apsManager, "Pump not resumed by Announcement: \(error.localizedDescription)")
+                        warning(.apsManager, "Pump not resumed by Announcement: \(error.localizedDescription)")
                     } else {
                         debug(.apsManager, "Pump resumed by Announcement")
                         self.announcementsStorage.storeAnnouncements([announcement], enacted: true)
+                        self.nightscout.uploadStatus()
                     }
                 }
             }
@@ -422,16 +417,17 @@ final class BaseAPSManager: APSManager, Injectable {
             debug(.apsManager, "Closed loop \(closedLoop) by Announcement")
             announcementsStorage.storeAnnouncements([announcement], enacted: true)
         case let .tempbasal(rate, duration):
-            guard verifyStatus() else {
+            guard verifyStatus(), !settings.closedLoop else {
                 return
             }
-            pumpManager?.enactTempBasal(unitsPerHour: Double(rate), for: TimeInterval(duration) * 60) { result in
+            let roundedRate = pump.roundToSupportedBasalRate(unitsPerHour: Double(rate))
+            pump.enactTempBasal(unitsPerHour: roundedRate, for: TimeInterval(duration) * 60) { result in
                 switch result {
                 case .success:
                     debug(.apsManager, "Announcement TempBasal succeeded")
                     self.announcementsStorage.storeAnnouncements([announcement], enacted: true)
                 case let .failure(error):
-                    debug(.apsManager, "Announcement TempBasal failed with error: \(error.localizedDescription)")
+                    warning(.apsManager, "Announcement TempBasal failed with error: \(error.localizedDescription)")
                 }
             }
         }

+ 1 - 0
FreeAPS/Sources/APS/DeviceDataManager.swift

@@ -47,6 +47,7 @@ final class BaseDeviceDataManager: DeviceDataManager, Injectable {
     let recommendsLoop = PassthroughSubject<Void, Never>()
     let bolusTrigger = PassthroughSubject<Bool, Never>()
     let errorSubject = PassthroughSubject<Error, Never>()
+    let pumpNewStatus = PassthroughSubject<Void, Never>()
 
     var pumpManager: PumpManagerUI? {
         didSet {

+ 47 - 0
FreeAPS/Sources/APS/FetchAnnouncementsManager.swift

@@ -0,0 +1,47 @@
+import Combine
+import Foundation
+import SwiftDate
+import Swinject
+
+protocol FetchAnnouncementsManager {}
+
+final class BaseFetchAnnouncementsManager: FetchAnnouncementsManager, Injectable {
+    private let processQueue = DispatchQueue(label: "BaseFetchAnnouncementsManager.processQueue")
+    @Injected() var announcementsStorage: AnnouncementsStorage!
+    @Injected() var nightscoutManager: NightscoutManager!
+    @Injected() var apsManager: APSManager!
+    @Injected() var settingsManager: SettingsManager!
+
+    private var lifetime = Set<AnyCancellable>()
+    private let timer = DispatchTimer(timeInterval: 1.minutes.timeInterval)
+
+    init(resolver: Resolver) {
+        injectServices(resolver)
+        subscribe()
+    }
+
+    private func subscribe() {
+        timer.publisher
+            .receive(on: processQueue)
+            .flatMap { _ -> AnyPublisher<[Announcement], Never> in
+                guard self.settingsManager.settings.allowAnnouncements else {
+                    return Just([]).eraseToAnyPublisher()
+                }
+                debug(.nightscout, "FetchAnnouncementsManager heartbeat")
+                debug(.nightscout, "Start fetching announcements")
+                return self.nightscoutManager.fetchAnnouncements()
+            }
+            .sink { announcements in
+                guard announcements.filter({ $0.createdAt > self.announcementsStorage.syncDate() }).isNotEmpty else { return }
+                self.announcementsStorage.storeAnnouncements(announcements, enacted: false)
+                if self.settingsManager.settings.allowAnnouncements, let recent = self.announcementsStorage.recent(),
+                   recent.action != nil
+                {
+                    debug(.nightscout, "New announcements found")
+                    self.apsManager.enactAnnouncement(recent)
+                }
+            }
+            .store(in: &lifetime)
+        timer.resume()
+    }
+}

+ 4 - 4
FreeAPS/Sources/APS/Storage/AnnouncementsStorage.swift

@@ -34,12 +34,12 @@ final class BaseAnnouncementsStorage: AnnouncementsStorage, Injectable {
     }
 
     func syncDate() -> Date {
-        guard let events = storage.retrieve(OpenAPS.FreeAPS.announcements, as: [Announcement].self),
-              let recent = events.filter({ $0.enteredBy != Announcement.remote }).first
+        guard let events = storage.retrieve(OpenAPS.FreeAPS.announcementsEnacted, as: [Announcement].self),
+              let recentEnacted = events.filter({ $0.enteredBy == Announcement.remote }).first
         else {
-            return Date().addingTimeInterval(-1.days.timeInterval)
+            return Date().addingTimeInterval(-Config.recentInterval)
         }
-        return recent.createdAt.addingTimeInterval(-6.minutes.timeInterval)
+        return recentEnacted.createdAt.addingTimeInterval(Config.recentInterval)
     }
 
     func recent() -> Announcement? {

+ 1 - 0
FreeAPS/Sources/Application/FreeAPSApp.swift

@@ -33,6 +33,7 @@ private extension Swinject.Resolver {
         _ = resolver.resolve(APSManager.self)!
         _ = resolver.resolve(FetchGlucoseManager.self)!
         _ = resolver.resolve(FetchTreatmentsManager.self)!
+        _ = resolver.resolve(FetchAnnouncementsManager.self)!
     }
 
     init() {

+ 1 - 0
FreeAPS/Sources/Containers/APSContainer.swift

@@ -9,5 +9,6 @@ enum APSContainer: DependeciesContainer {
         container.register(APSManager.self) { _ in BaseAPSManager(resolver: resolver) }
         container.register(FetchGlucoseManager.self) { _ in BaseFetchGlucoseManager(resolver: resolver) }
         container.register(FetchTreatmentsManager.self) { _ in BaseFetchTreatmentsManager(resolver: resolver) }
+        container.register(FetchAnnouncementsManager.self) { _ in BaseFetchAnnouncementsManager(resolver: resolver) }
     }
 }

+ 4 - 7
FreeAPS/Sources/Services/Network/NightscoutManager.swift

@@ -7,7 +7,7 @@ protocol NightscoutManager {
     func fetchGlucose() -> AnyPublisher<[BloodGlucose], Never>
     func fetchCarbs() -> AnyPublisher<[CarbsEntry], Never>
     func fetchTempTargets() -> AnyPublisher<[TempTarget], Never>
-    func fetchAnnouncements() -> AnyPublisher<Void, Never>
+    func fetchAnnouncements() -> AnyPublisher<[Announcement], Never>
     func deleteCarbs(at date: Date)
     func uploadStatus()
     var cgmURL: URL? { get }
@@ -113,18 +113,15 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
             .eraseToAnyPublisher()
     }
 
-    func fetchAnnouncements() -> AnyPublisher<Void, Never> {
+    func fetchAnnouncements() -> AnyPublisher<[Announcement], Never> {
         guard let nightscout = nightscoutAPI, isNetworkReachable else {
-            return Just(()).eraseToAnyPublisher()
+            return Just([]).eraseToAnyPublisher()
         }
 
         let since = announcementsStorage.syncDate()
         return nightscout.fetchAnnouncement(sinceDate: since)
             .replaceError(with: [])
-            .map {
-                self.announcementsStorage.storeAnnouncements($0, enacted: false)
-                return ()
-            }.eraseToAnyPublisher()
+            .eraseToAnyPublisher()
     }
 
     func deleteCarbs(at date: Date) {