浏览代码

looping not depends on NS fetching for now

Ivan Valkou 5 年之前
父节点
当前提交
3ae87e6f33

+ 14 - 6
FreeAPS.xcodeproj/project.pbxproj

@@ -94,6 +94,8 @@
 		3811DF1025CAAAE200A708ED /* APSManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DF0F25CAAAE200A708ED /* APSManager.swift */; };
 		38192E01261B826A0094D973 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 38192E00261B826A0094D973 /* Alamofire */; };
 		38192E04261B82FA0094D973 /* ReachabilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38192E03261B82FA0094D973 /* ReachabilityManager.swift */; };
+		38192E07261BA9960094D973 /* FetchTreatmentsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38192E06261BA9960094D973 /* FetchTreatmentsManager.swift */; };
+		38192E0D261BAF980094D973 /* ConvenienceExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38192E0C261BAF980094D973 /* ConvenienceExtensions.swift */; };
 		3821ED4C25DD18BA00BC42AD /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3821ED4B25DD18BA00BC42AD /* Constants.swift */; };
 		382C133725F13A1E00715CE1 /* InsulinSensitivities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 382C133625F13A1E00715CE1 /* InsulinSensitivities.swift */; };
 		382C134B25F14E3700715CE1 /* BGTargets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 382C134A25F14E3700715CE1 /* BGTargets.swift */; };
@@ -186,7 +188,7 @@
 		38D0B3B625EBE24900CB6E88 /* Battery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38D0B3B525EBE24900CB6E88 /* Battery.swift */; };
 		38D0B3D925EC07C400CB6E88 /* CarbsEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38D0B3D825EC07C400CB6E88 /* CarbsEntry.swift */; };
 		38DAB280260CBB7F00F74C1A /* PumpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38DAB27F260CBB7F00F74C1A /* PumpView.swift */; };
-		38DAB28A260D349500F74C1A /* GlucoseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38DAB289260D349500F74C1A /* GlucoseManager.swift */; };
+		38DAB28A260D349500F74C1A /* FetchGlucoseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38DAB289260D349500F74C1A /* FetchGlucoseManager.swift */; };
 		38E989DD25F5021400C0CED0 /* PumpStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E989DC25F5021400C0CED0 /* PumpStatus.swift */; };
 		38E98A2325F52C9300C0CED0 /* Signpost.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E98A1B25F52C9300C0CED0 /* Signpost.swift */; };
 		38E98A2425F52C9300C0CED0 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E98A1C25F52C9300C0CED0 /* Logger.swift */; };
@@ -385,6 +387,8 @@
 		3811DF0725CAAA4700A708ED /* ServiceContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceContainer.swift; sourceTree = "<group>"; };
 		3811DF0F25CAAAE200A708ED /* APSManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APSManager.swift; sourceTree = "<group>"; };
 		38192E03261B82FA0094D973 /* ReachabilityManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReachabilityManager.swift; sourceTree = "<group>"; };
+		38192E06261BA9960094D973 /* FetchTreatmentsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchTreatmentsManager.swift; sourceTree = "<group>"; };
+		38192E0C261BAF980094D973 /* ConvenienceExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConvenienceExtensions.swift; sourceTree = "<group>"; };
 		3821ED4B25DD18BA00BC42AD /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
 		382C133625F13A1E00715CE1 /* InsulinSensitivities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsulinSensitivities.swift; sourceTree = "<group>"; };
 		382C134A25F14E3700715CE1 /* BGTargets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGTargets.swift; sourceTree = "<group>"; };
@@ -465,7 +469,7 @@
 		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>"; };
 		38DAB27F260CBB7F00F74C1A /* PumpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PumpView.swift; sourceTree = "<group>"; };
-		38DAB289260D349500F74C1A /* GlucoseManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseManager.swift; sourceTree = "<group>"; };
+		38DAB289260D349500F74C1A /* FetchGlucoseManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchGlucoseManager.swift; sourceTree = "<group>"; };
 		38E989DC25F5021400C0CED0 /* PumpStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PumpStatus.swift; sourceTree = "<group>"; };
 		38E98A1B25F52C9300C0CED0 /* Signpost.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Signpost.swift; sourceTree = "<group>"; };
 		38E98A1C25F52C9300C0CED0 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
@@ -962,7 +966,8 @@
 			children = (
 				3811DF0F25CAAAE200A708ED /* APSManager.swift */,
 				38BF021E25E7F0DE00579895 /* DeviceDataManager.swift */,
-				38DAB289260D349500F74C1A /* GlucoseManager.swift */,
+				38DAB289260D349500F74C1A /* FetchGlucoseManager.swift */,
+				38192E06261BA9960094D973 /* FetchTreatmentsManager.swift */,
 				38A504F625DDA0E200C5B9E8 /* Extensions */,
 				388E5A5825B6F0070019842D /* OpenAPS */,
 				38A0362725ECF05300FCBB52 /* Storage */,
@@ -1073,11 +1078,12 @@
 		388E5A5A25B6F05F0019842D /* Helpers */ = {
 			isa = PBXGroup;
 			children = (
-				389487392614928B004DF424 /* DispatchTimer.swift */,
 				38F37827261260DC009DB701 /* Color+Extensions.swift */,
 				389ECE042601144100D86C4F /* ConcurrentMap.swift */,
+				38192E0C261BAF980094D973 /* ConvenienceExtensions.swift */,
 				3871F39E25ED895A0013ECB5 /* Decimal+Extensions.swift */,
 				38C4D33625E9A1A200D30B77 /* DispatchQueue+Extensions.swift */,
+				389487392614928B004DF424 /* DispatchTimer.swift */,
 				3811DE5425C9D4D500A708ED /* Formatters.swift */,
 				38B4F3AE25E2979F00E76A18 /* IndexedCollection.swift */,
 				389A571F26079BAA00BC102F /* Interpolation.swift */,
@@ -1087,9 +1093,9 @@
 				38E98A2C25F52DC400C0CED0 /* NSLocking+Extensions.swift */,
 				38C4D33925E9A1ED00D30B77 /* NSObject+AssociatedValues.swift */,
 				3811DE5725C9D4D500A708ED /* ProgressBar.swift */,
+				3811DEE325CA063400A708ED /* PropertyWrappers */,
 				3811DE5525C9D4D500A708ED /* Publisher.swift */,
 				38E98A3625F5509500C0CED0 /* String+Extensions.swift */,
-				3811DEE325CA063400A708ED /* PropertyWrappers */,
 			);
 			path = Helpers;
 			sourceTree = "<group>";
@@ -1718,7 +1724,7 @@
 				38FE826A25CC82DB001FF17A /* NetworkService.swift in Sources */,
 				3883581C25EE79BB00E024B2 /* DecimalTextField.swift in Sources */,
 				3811DE6C25C9D62600A708ED /* OnboardingDataFlow.swift in Sources */,
-				38DAB28A260D349500F74C1A /* GlucoseManager.swift in Sources */,
+				38DAB28A260D349500F74C1A /* FetchGlucoseManager.swift in Sources */,
 				3811DE2425C9D48300A708ED /* MainViewModel.swift in Sources */,
 				38F37828261260DC009DB701 /* Color+Extensions.swift in Sources */,
 				3811DE3F25C9D4A100A708ED /* SettingsViewModel.swift in Sources */,
@@ -1769,9 +1775,11 @@
 				38E98A2425F52C9300C0CED0 /* Logger.swift in Sources */,
 				CA370FC152BC98B3D1832968 /* BasalProfileEditorRootView.swift in Sources */,
 				F215CAB49BA4B5A01C3BC6B6 /* ISFEditorBuilder.swift in Sources */,
+				38192E07261BA9960094D973 /* FetchTreatmentsManager.swift in Sources */,
 				6632A0DC746872439A858B44 /* ISFEditorDataFlow.swift in Sources */,
 				DBA5254DBB2586C98F61220C /* ISFEditorProvider.swift in Sources */,
 				1BBB001DAD60F3B8CEA4B1C7 /* ISFEditorViewModel.swift in Sources */,
+				38192E0D261BAF980094D973 /* ConvenienceExtensions.swift in Sources */,
 				88AB39B23C9552BD6E0C9461 /* ISFEditorRootView.swift in Sources */,
 				3BD663A04B4CA5278B0260B4 /* CREditorBuilder.swift in Sources */,
 				A33352ED40476125EBAC6EE0 /* CREditorDataFlow.swift in Sources */,

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

@@ -105,26 +105,22 @@ final class BaseAPSManager: APSManager, Injectable {
     private func loop() {
         debug(.apsManager, "Starting loop")
         isLooping.send(true)
-        Publishers.CombineLatest(
-            nightscout.fetchCarbs(),
-            nightscout.fetchTempTargets()
-        )
-        .flatMap { _ in self.determineBasal() }
-        .sink { _ in } receiveValue: { [weak self] ok in
-            guard let self = self else { return }
-
-            if ok {
-                self.nightscout.uploadStatus()
-                if self.settings.closedLoop {
-                    self.enactSuggested()
+        determineBasal()
+            .sink { _ in } receiveValue: { [weak self] ok in
+                guard let self = self else { return }
+
+                if ok {
+                    self.nightscout.uploadStatus()
+                    if self.settings.closedLoop {
+                        self.enactSuggested()
+                    } else {
+                        self.isLooping.send(false)
+                        self.lastLoopDate.send(Date())
+                    }
                 } else {
                     self.isLooping.send(false)
-                    self.lastLoopDate.send(Date())
                 }
-            } else {
-                self.isLooping.send(false)
-            }
-        }.store(in: &lifetime)
+            }.store(in: &lifetime)
     }
 
     private func verifyStatus() -> Bool {

+ 3 - 3
FreeAPS/Sources/APS/GlucoseManager.swift

@@ -3,9 +3,9 @@ import Foundation
 import SwiftDate
 import Swinject
 
-protocol GlucoseManager {}
+protocol FetchGlucoseManager {}
 
-final class BaseGlucoseManager: GlucoseManager, Injectable {
+final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
     private let processQueue = DispatchQueue(label: "BaseGlucoseManager.processQueue")
     @Injected() var glucoseStorage: GlucoseStorage!
     @Injected() var nightscoutManager: NightscoutManager!
@@ -23,7 +23,7 @@ final class BaseGlucoseManager: GlucoseManager, Injectable {
         timer.publisher
             .receive(on: processQueue)
             .flatMap { date -> AnyPublisher<(Date, Date, [BloodGlucose]), Never> in
-                debug(.nightscout, "Glucose manager heartbeat")
+                debug(.nightscout, "FetchGlucoseManager heartbeat")
                 debug(.nightscout, "Start fetching glucose")
                 return Publishers.CombineLatest3(
                     Just(date),

+ 40 - 0
FreeAPS/Sources/APS/FetchTreatmentsManager.swift

@@ -0,0 +1,40 @@
+import Combine
+import Foundation
+import SwiftDate
+import Swinject
+
+protocol FetchTreatmentsManager {}
+
+final class BaseFetchTreatmentsManager: FetchTreatmentsManager, Injectable {
+    private let processQueue = DispatchQueue(label: "BaseFetchTreatmentsManager.processQueue")
+    @Injected() var nightscoutManager: NightscoutManager!
+    @Injected() var tempTargetsStorage: TempTargetsStorage!
+    @Injected() var carbsStorage: CarbsStorage!
+
+    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<([CarbsEntry], [TempTarget]), Never> in
+                debug(.nightscout, "FetchTreatmentsManager heartbeat")
+                debug(.nightscout, "Start fetching carbs and temptargets")
+                return Publishers.CombineLatest(
+                    self.nightscoutManager.fetchCarbs(),
+                    self.nightscoutManager.fetchTempTargets()
+                ).eraseToAnyPublisher()
+            }
+            .sink { carbs, targets in
+                self.carbsStorage.storeCarbs(carbs)
+                self.tempTargetsStorage.storeTempTargets(targets)
+            }
+            .store(in: &lifetime)
+        timer.resume()
+    }
+}

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

@@ -29,7 +29,8 @@ private extension Swinject.Resolver {
         resolver.resolve(AppearanceManager.self)!.setupGlobalAppearance()
         _ = resolver.resolve(DeviceDataManager.self)!
         _ = resolver.resolve(APSManager.self)!
-        _ = resolver.resolve(GlucoseManager.self)!
+        _ = resolver.resolve(FetchGlucoseManager.self)!
+        _ = resolver.resolve(FetchTreatmentsManager.self)!
     }
 
     var body: some Scene {

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

@@ -7,6 +7,7 @@ enum APSContainer: DependeciesContainer {
     static func register(container: Container) {
         container.register(DeviceDataManager.self) { _ in BaseDeviceDataManager(resolver: resolver) }
         container.register(APSManager.self) { _ in BaseAPSManager(resolver: resolver) }
-        container.register(GlucoseManager.self) { _ in BaseGlucoseManager(resolver: resolver) }
+        container.register(FetchGlucoseManager.self) { _ in BaseFetchGlucoseManager(resolver: resolver) }
+        container.register(FetchTreatmentsManager.self) { _ in BaseFetchTreatmentsManager(resolver: resolver) }
     }
 }

+ 44 - 0
FreeAPS/Sources/Helpers/ConvenienceExtensions.swift

@@ -0,0 +1,44 @@
+import UIKit
+
+protocol Occupiable {
+    var isEmpty: Bool { get }
+    var isNotEmpty: Bool { get }
+
+    var nonEmpty: Self? { get }
+}
+
+// Give a default implementation of isNotEmpty, so conformance only requires one implementation
+extension Occupiable {
+    var isNotEmpty: Bool {
+        !isEmpty
+    }
+
+    var nonEmpty: Self? {
+        isEmpty ? nil : self
+    }
+}
+
+extension String: Occupiable {}
+
+extension Array: Occupiable {}
+extension ArraySlice: Occupiable {}
+extension CGRect: Occupiable {}
+extension Data: Occupiable {}
+extension Dictionary: Occupiable {}
+extension Set: Occupiable {}
+
+// Extend the idea of occupiability to optionals. Specifically, optionals wrapping occupiable things.
+extension Optional where Wrapped: Occupiable {
+    var isNilOrEmpty: Bool {
+        switch self {
+        case .none:
+            return true
+        case let .some(value):
+            return value.isEmpty
+        }
+    }
+
+    var isNotNilNotEmpty: Bool {
+        !isNilOrEmpty
+    }
+}

+ 1 - 1
FreeAPS/Sources/Models/CarbsEntry.swift

@@ -5,7 +5,7 @@ struct CarbsEntry: JSON, Equatable {
     let carbs: Decimal
     let enteredBy: String?
 
-    static let manual = "freeaps-x://manual"
+    static let manual = "freeaps-x"
 }
 
 extension CarbsEntry {

+ 1 - 1
FreeAPS/Sources/Models/NightscoutTreatment.swift

@@ -16,7 +16,7 @@ struct NigtscoutTreatment: JSON, Hashable, Equatable {
     let targetTop: Decimal?
     let targetBottom: Decimal?
 
-    static let local = "freeaps-x://local"
+    static let local = "freeaps-x"
 
     static let empty = NigtscoutTreatment(from: "{}")!
 

+ 1 - 1
FreeAPS/Sources/Models/TempTarget.swift

@@ -10,7 +10,7 @@ struct TempTarget: JSON, Identifiable, Equatable {
     let enteredBy: String?
     let reason: String?
 
-    static let manual = "freeaps-x://manual"
+    static let manual = "freeaps-x"
     static let custom = "Temp target"
     static let cancel = "Cancel"
 

+ 3 - 2
FreeAPS/Sources/Services/Network/NightscoutAPI.swift

@@ -31,7 +31,7 @@ extension NightscoutAPI {
     func checkConnection() -> AnyPublisher<Void, Swift.Error> {
         struct Check: Codable, Equatable {
             var eventType = "Note"
-            var enteredBy = "freeaps-x://"
+            var enteredBy = "freeaps-x"
             var notes = "FreeAPS X connected"
         }
         let check = Check()
@@ -171,7 +171,8 @@ extension NightscoutAPI {
             URLQueryItem(
                 name: "find[enteredBy][$ne]",
                 value: NigtscoutTreatment.local.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)
-            )
+            ),
+            URLQueryItem(name: "find[duration][$exists]", value: "true")
         ]
         if let date = sinceDate {
             let dateItem = URLQueryItem(

+ 8 - 14
FreeAPS/Sources/Services/Network/NightscoutManager.swift

@@ -5,8 +5,8 @@ import UIKit
 
 protocol NightscoutManager {
     func fetchGlucose() -> AnyPublisher<[BloodGlucose], Never>
-    func fetchCarbs() -> AnyPublisher<Void, Never>
-    func fetchTempTargets() -> AnyPublisher<Void, Never>
+    func fetchCarbs() -> AnyPublisher<[CarbsEntry], Never>
+    func fetchTempTargets() -> AnyPublisher<[TempTarget], Never>
     func fetchAnnouncements() -> AnyPublisher<Void, Never>
     func deleteCarbs(at date: Date)
     func uploadStatus()
@@ -91,32 +91,26 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
             .eraseToAnyPublisher()
     }
 
-    func fetchCarbs() -> AnyPublisher<Void, Never> {
+    func fetchCarbs() -> AnyPublisher<[CarbsEntry], Never> {
         guard let nightscout = nightscoutAPI, isNetworkReachable else {
-            return Just(()).eraseToAnyPublisher()
+            return Just([]).eraseToAnyPublisher()
         }
 
         let since = carbsStorage.syncDate()
         return nightscout.fetchCarbs(sinceDate: since)
             .replaceError(with: [])
-            .map {
-                self.carbsStorage.storeCarbs($0)
-                return ()
-            }.eraseToAnyPublisher()
+            .eraseToAnyPublisher()
     }
 
-    func fetchTempTargets() -> AnyPublisher<Void, Never> {
+    func fetchTempTargets() -> AnyPublisher<[TempTarget], Never> {
         guard let nightscout = nightscoutAPI, isNetworkReachable else {
-            return Just(()).eraseToAnyPublisher()
+            return Just([]).eraseToAnyPublisher()
         }
 
         let since = tempTargetsStorage.syncDate()
         return nightscout.fetchTempTargets(sinceDate: since)
             .replaceError(with: [])
-            .map {
-                self.tempTargetsStorage.storeTempTargets($0)
-                return ()
-            }.eraseToAnyPublisher()
+            .eraseToAnyPublisher()
     }
 
     func fetchAnnouncements() -> AnyPublisher<Void, Never> {