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

use last 72 glucose values for autosense, refactoring....wip

polscm32 2 лет назад
Родитель
Сommit
57dc1b2e16

+ 5 - 5
FreeAPS/Sources/APS/APSManager.swift

@@ -727,7 +727,7 @@ final class BaseAPSManager: APSManager, Injectable {
                 determinationUpdated.received = received
 
                 do {
-                    try CoreDataStack.shared.backgroundContext.saveContext()
+                    try CoreDataStack.shared.saveContext()
                     debugPrint("Update successful in reportEnacted() \(DebuggingIdentifiers.succeeded)")
                 } catch {
                     debugPrint(
@@ -745,7 +745,7 @@ final class BaseAPSManager: APSManager, Injectable {
             saveLastLoop.timestamp = (determination.timestamp ?? .distantPast) as Date
 
             do {
-                try CoreDataStack.shared.backgroundContext.saveContext()
+                try CoreDataStack.shared.saveContext()
             } catch {
                 print(error.localizedDescription)
             }
@@ -1235,7 +1235,7 @@ final class BaseAPSManager: APSManager, Injectable {
                 saveStatsCoreData.lastrun = Date()
 
                 do {
-                    try CoreDataStack.shared.backgroundContext.saveContext()
+                    try CoreDataStack.shared.saveContext()
                 } catch {
                     print(error.localizedDescription)
                 }
@@ -1254,7 +1254,7 @@ final class BaseAPSManager: APSManager, Injectable {
             nLS.interval = loopStatRecord.interval ?? 0.0
 
             do {
-                try CoreDataStack.shared.backgroundContext.saveContext()
+                try CoreDataStack.shared.saveContext()
             } catch {
                 print(error.localizedDescription)
             }
@@ -1396,7 +1396,7 @@ extension BaseAPSManager: PumpManagerStatusObserver {
             batteryToStore.status = percent > 10 ? "normal" : "low"
             batteryToStore.display = status.pumpBatteryChargeRemaining != nil
             do {
-                try CoreDataStack.shared.backgroundContext.saveContext()
+                try CoreDataStack.shared.saveContext()
             } catch {
                 print(error.localizedDescription)
             }

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

@@ -357,7 +357,7 @@ extension BaseDeviceDataManager: PumpManagerDelegate {
             batteryToStore.display = status.pumpBatteryChargeRemaining != nil
 
             do {
-                try CoreDataStack.shared.backgroundContext.saveContext()
+                try CoreDataStack.shared.saveContext()
             } catch {
                 print(error.localizedDescription)
             }

+ 21 - 27
FreeAPS/Sources/APS/FetchGlucoseManager.swift

@@ -99,33 +99,27 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
         .store(in: &lifetime)
     }
 
-    private func fetchAndProcessGlucose() -> [BloodGlucose] {
-        do {
-            let results = try context.fetch(GlucoseStored.fetch(
-                NSPredicate.predicateFor30MinAgo,
-                ascending: false,
-                fetchLimit: 6
-            ))
-            debugPrint("Fetch Glucose Manager: \(#function) \(DebuggingIdentifiers.succeeded) fetched glucose")
-
-            var glucoseArray = [BloodGlucose]()
+    private func fetchGlucose() -> [GlucoseStored] {
+        CoreDataStack.shared.fetchEntities(
+            ofType: GlucoseStored.self,
+            predicate: NSPredicate.predicateFor30MinAgo,
+            key: "date",
+            ascending: false,
+            fetchLimit: 6
+        )
+    }
 
-            for result in results {
-                // TODO: - when parsing the CD object to JSON we currently don't have a direction
-                let glucose = BloodGlucose(
-                    date: Decimal(result.date?.timeIntervalSince1970 ?? Date().timeIntervalSince1970) * 1000,
-                    dateString: result.date ?? Date(),
-                    unfiltered: Decimal(result.glucose),
-                    filtered: Decimal(result.glucose),
-                    noise: nil,
-                    type: ""
-                )
-                glucoseArray.append(glucose)
-            }
-            return glucoseArray
-        } catch {
-            debugPrint("Fetch Glucose Manager: \(#function) \(DebuggingIdentifiers.failed) failed to fetch glucose")
-            return []
+    private func processGlucose() -> [BloodGlucose] {
+        let results = fetchGlucose()
+        return results.map { result in
+            BloodGlucose(
+                date: Decimal(result.date?.timeIntervalSince1970 ?? Date().timeIntervalSince1970) * 1000,
+                dateString: result.date ?? Date(),
+                unfiltered: Decimal(result.glucose),
+                filtered: Decimal(result.glucose),
+                noise: nil,
+                type: ""
+            )
         }
     }
 
@@ -166,7 +160,7 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
         // filter the data if it is the case
         if settingsManager.settings.smoothGlucose {
             // limited to 30 min of old glucose data
-            let oldGlucoseValues = fetchAndProcessGlucose()
+            let oldGlucoseValues = processGlucose()
 
             var smoothedValues = oldGlucoseValues + filtered
             // smooth with 3 repeats

+ 65 - 58
FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift

@@ -83,7 +83,7 @@ final class OpenAPS {
 
     func attemptToSaveContext() {
         do {
-            try CoreDataStack.shared.backgroundContext.saveContext()
+            try CoreDataStack.shared.saveContext()
         } catch {
             print(error.localizedDescription)
         }
@@ -91,61 +91,67 @@ final class OpenAPS {
 
     // fetch glucose to pass it to the meal function and to determine basal
     private func fetchGlucose() -> [GlucoseStored]? {
-        do {
-            debugPrint("OpenAPS: \(#function) \(DebuggingIdentifiers.succeeded) fetched glucose")
-            return try context.fetch(GlucoseStored.fetch(
-                NSPredicate.predicateForOneHourAgo,
-                ascending: false,
-                fetchLimit: 6
-            ))
-        } catch {
-            debugPrint("OpenAPS: \(#function) \(DebuggingIdentifiers.failed) failed to fetch glucose with error: \(error)")
-            return []
-        }
+        CoreDataStack.shared.fetchEntities(
+            ofType: GlucoseStored.self,
+            predicate: NSPredicate.predicateForSixHoursAgo,
+            key: "date",
+            ascending: false,
+            fetchLimit: 72,
+            batchSize: 24
+        )
     }
 
     private func fetchCarbs() -> [CarbEntryStored]? {
-        do {
-            debugPrint("OpenAPS: \(#function) \(DebuggingIdentifiers.succeeded) fetched carbs")
-            return try context.fetch(CarbEntryStored.fetch(NSPredicate.predicateForOneDayAgo, ascending: false))
-        } catch {
-            debugPrint("OpenAPS: \(#function) \(DebuggingIdentifiers.failed) failed to fetch carbs with error: \(error)")
-            return []
-        }
+        CoreDataStack.shared.fetchEntities(
+            ofType: CarbEntryStored.self,
+            predicate: NSPredicate.predicateForOneDayAgo,
+            key: "date",
+            ascending: false
+        )
     }
 
-    private func fetchPumpHistory() -> [PumpEventStored]? {
-        do {
-            debugPrint("OpenAPS: \(#function) \(DebuggingIdentifiers.succeeded) fetched pump history")
-
-            return try context
-                .fetch(PumpEventStored.fetch(NSPredicate.pumpHistoryLast24h, ascending: false))
-        } catch {
-            debugPrint(
-                "OpenAPS: \(#function) \(DebuggingIdentifiers.failed) error while fetching pumphistory for determine basal with error: \(error)"
-            )
-            return []
-        }
+    private func fetchPumpHistoryObjectIDs() -> [NSManagedObjectID]? {
+        let results = CoreDataStack.shared.fetchEntities(
+            ofType: PumpEventStored.self,
+            predicate: NSPredicate.pumpHistoryLast24h,
+            key: "timestamp",
+            ascending: false,
+            batchSize: 50
+        )
+        return results.map(\.objectID)
     }
 
-    private func parsePumpHistory(_ pumpHistory: [PumpEventStored]) -> String {
-        guard !pumpHistory.isEmpty else { return "{}" }
-
-        let dtos: [PumpEventDTO] = pumpHistory.flatMap { event -> [PumpEventDTO] in
-            var eventDTOs: [PumpEventDTO] = []
-            if let bolusDTO = event.toBolusDTOEnum() {
-                eventDTOs.append(bolusDTO)
-            }
-            if let tempBasalDTO = event.toTempBasalDTOEnum() {
-                eventDTOs.append(tempBasalDTO)
-            }
-            if let tempBasalDurationDTO = event.toTempBasalDurationDTOEnum() {
-                eventDTOs.append(tempBasalDurationDTO)
+    private func parsePumpHistory(_ pumpHistoryObjectIDs: [NSManagedObjectID]) -> String {
+        // Return an empty JSON object if the list of object IDs is empty
+        guard !pumpHistoryObjectIDs.isEmpty else { return "{}" }
+
+        // Execute all operations on the background context
+        let jsonResult = CoreDataStack.shared.backgroundContext.performAndWait {
+            // Load the pump events from the object IDs
+            let pumpHistory: [PumpEventStored] = pumpHistoryObjectIDs
+                .compactMap { CoreDataStack.shared.backgroundContext.object(with: $0) as? PumpEventStored }
+
+            // Create the DTOs
+            let dtos: [PumpEventDTO] = pumpHistory.flatMap { event -> [PumpEventDTO] in
+                var eventDTOs: [PumpEventDTO] = []
+                if let bolusDTO = event.toBolusDTOEnum() {
+                    eventDTOs.append(bolusDTO)
+                }
+                if let tempBasalDTO = event.toTempBasalDTOEnum() {
+                    eventDTOs.append(tempBasalDTO)
+                }
+                if let tempBasalDurationDTO = event.toTempBasalDurationDTOEnum() {
+                    eventDTOs.append(tempBasalDurationDTO)
+                }
+                return eventDTOs
             }
-            return eventDTOs
+
+            // Convert the DTOs to JSON
+            return jsonConverter.convertToJSON(dtos)
         }
 
-        return jsonConverter.convertToJSON(dtos)
+        // Return the JSON result
+        return jsonResult
     }
 
     func determineBasal(currentTemp: TempBasal, clock: Date = Date()) -> Future<Determination?, Never> {
@@ -154,13 +160,14 @@ final class OpenAPS {
                 debug(.openAPS, "Start determineBasal")
                 // clock
                 self.storage.save(clock, as: Monitor.clock)
+                let pass = self.loadFileFromStorage(name: Monitor.clock)
 
                 // temp_basal
                 let tempBasal = currentTemp.rawJSON
                 self.storage.save(tempBasal, as: Monitor.tempBasal)
 
-                let pumpHistory = self.fetchPumpHistory()
-                let pumpHistoryJSON = self.parsePumpHistory(pumpHistory ?? [])
+                let pumpHistoryObjectIDs = self.fetchPumpHistoryObjectIDs() ?? []
+                let pumpHistoryJSON = self.parsePumpHistory(pumpHistoryObjectIDs)
 
                 // carbs
                 let carbs = self.fetchCarbs()
@@ -179,7 +186,7 @@ final class OpenAPS {
                     pumphistory: pumpHistoryJSON,
                     profile: profile,
                     basalProfile: basalProfile,
-                    clock: clock,
+                    clock: pass,
                     carbs: carbsString,
                     glucose: glucoseString
                 )
@@ -190,7 +197,7 @@ final class OpenAPS {
                 let iob = self.iob(
                     pumphistory: pumpHistoryJSON,
                     profile: profile,
-                    clock: clock,
+                    clock: pass,
                     autosens: autosens.isEmpty ? .null : autosens
                 )
 
@@ -234,7 +241,7 @@ final class OpenAPS {
                             saveToTDD.timestamp = determination.timestamp ?? Date()
                             saveToTDD.tdd = (determination.tdd ?? 0) as NSDecimalNumber?
                             do {
-                                try CoreDataStack.shared.backgroundContext.saveContext()
+                                try CoreDataStack.shared.saveContext()
                             } catch {
                                 print(error.localizedDescription)
                             }
@@ -242,7 +249,7 @@ final class OpenAPS {
                             let saveTarget = Target(context: self.context)
                             saveTarget.current = (determination.current_target ?? 100) as NSDecimalNumber?
                             do {
-                                try CoreDataStack.shared.backgroundContext.saveContext()
+                                try CoreDataStack.shared.saveContext()
                             } catch {
                                 print(error.localizedDescription)
                             }
@@ -347,7 +354,7 @@ final class OpenAPS {
                     saveToCoreData.indefinite = false
                     saveToCoreData.percentage = 100
                     do {
-                        try CoreDataStack.shared.backgroundContext.saveContext()
+                        try CoreDataStack.shared.saveContext()
                     } catch {
                         print(error.localizedDescription)
                     }
@@ -448,8 +455,8 @@ final class OpenAPS {
                 debug(.openAPS, "Start autosens")
 
                 // pump history
-                let pumpHistory = self.fetchPumpHistory()
-                let pumpHistoryJSON = self.parsePumpHistory(pumpHistory ?? [])
+                let pumpHistoryObjectIDs = self.fetchPumpHistoryObjectIDs() ?? []
+                let pumpHistoryJSON = self.parsePumpHistory(pumpHistoryObjectIDs)
 
                 // carbs
                 let carbs = self.fetchCarbs()
@@ -489,8 +496,8 @@ final class OpenAPS {
                 debug(.openAPS, "Start autotune")
 
                 // pump history
-                let pumpHistory = self.fetchPumpHistory()
-                let pumpHistoryJSON = self.parsePumpHistory(pumpHistory ?? [])
+                let pumpHistoryObjectIDs = self.fetchPumpHistoryObjectIDs() ?? []
+                let pumpHistoryJSON = self.parsePumpHistory(pumpHistoryObjectIDs)
 
                 /// glucose
                 let glucose = self.fetchGlucose()
@@ -825,7 +832,7 @@ final class OpenAPS {
             }
 
             do {
-                try CoreDataStack.shared.backgroundContext.saveContext()
+                try CoreDataStack.shared.saveContext()
             } catch {
                 print(error.localizedDescription)
             }

+ 1 - 1
FreeAPS/Sources/APS/Storage/CarbsStorage.swift

@@ -153,7 +153,7 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
             newItem.id = UUID()
             newItem.isFPU = false
             do {
-                try CoreDataStack.shared.backgroundContext.saveContext()
+                try CoreDataStack.shared.saveContext()
             } catch {
                 print(error.localizedDescription)
             }

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

@@ -57,7 +57,7 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
                         // TODO: - do we need duration here?
 
                         do {
-                            try CoreDataStack.shared.backgroundContext.saveContext()
+                            try CoreDataStack.shared.saveContext()
                         } catch {
                             print(error.localizedDescription)
                         }
@@ -102,7 +102,7 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
                         newTempBasal.tempType = TempType.absolute.rawValue
 
                         do {
-                            try CoreDataStack.shared.backgroundContext.saveContext()
+                            try CoreDataStack.shared.saveContext()
                         } catch {
                             print(error.localizedDescription)
                         }
@@ -141,7 +141,7 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
                         newPumpEvent.type = PumpEvent.pumpSuspend.rawValue
 
                         do {
-                            try CoreDataStack.shared.backgroundContext.saveContext()
+                            try CoreDataStack.shared.saveContext()
                         } catch {
                             print(error.localizedDescription)
                         }
@@ -168,7 +168,7 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
                         newPumpEvent.type = PumpEvent.pumpResume.rawValue
 
                         do {
-                            try CoreDataStack.shared.backgroundContext.saveContext()
+                            try CoreDataStack.shared.saveContext()
                         } catch {
                             print(error.localizedDescription)
                         }

+ 7 - 7
FreeAPS/Sources/Modules/AddTempTarget/AddTempTargetStateModel.swift

@@ -47,7 +47,7 @@ extension AddTempTarget {
                     saveToCoreData.startDate = Date()
 
                     do {
-                        try CoreDataStack.shared.backgroundContext.saveContext()
+                        try CoreDataStack.shared.saveContext()
                     } catch {
                         print(error.localizedDescription)
                     }
@@ -59,7 +59,7 @@ extension AddTempTarget {
                     saveToCoreData.active = false
                     saveToCoreData.date = Date()
                     do {
-                        try CoreDataStack.shared.backgroundContext.saveContext()
+                        try CoreDataStack.shared.saveContext()
                     } catch {
                         print(error.localizedDescription)
                     }
@@ -94,7 +94,7 @@ extension AddTempTarget {
                 saveToCoreData.active = false
                 saveToCoreData.date = Date()
                 do {
-                    try CoreDataStack.shared.backgroundContext.saveContext()
+                    try CoreDataStack.shared.saveContext()
                 } catch {
                     print(error.localizedDescription)
                 }
@@ -103,7 +103,7 @@ extension AddTempTarget {
                 setHBT.enabled = false
                 setHBT.date = Date()
                 do {
-                    try CoreDataStack.shared.backgroundContext.saveContext()
+                    try CoreDataStack.shared.saveContext()
                 } catch {
                     print(error.localizedDescription)
                 }
@@ -151,7 +151,7 @@ extension AddTempTarget {
                     saveToCoreData.date = Date()
                     saveToCoreData.duration = duration as NSDecimalNumber
                     do {
-                        try CoreDataStack.shared.backgroundContext.saveContext()
+                        try CoreDataStack.shared.saveContext()
                     } catch {
                         print(error.localizedDescription)
                     }
@@ -184,7 +184,7 @@ extension AddTempTarget {
                         saveToCoreData.duration = whichID?.duration ?? 0
 
                         do {
-                            try CoreDataStack.shared.backgroundContext.saveContext()
+                            try CoreDataStack.shared.saveContext()
                         } catch {
                             print(error.localizedDescription)
                         }
@@ -193,7 +193,7 @@ extension AddTempTarget {
                         saveToCoreData.active = false
                         saveToCoreData.date = Date()
                         do {
-                            try CoreDataStack.shared.backgroundContext.saveContext()
+                            try CoreDataStack.shared.saveContext()
                         } catch {
                             print(error.localizedDescription)
                         }

+ 45 - 29
FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift

@@ -98,12 +98,15 @@ extension Bolus {
         let now = Date.now
 
         let context = CoreDataStack.shared.viewContext
+        let backgroundContext = CoreDataStack.shared.backgroundContext
 
         typealias PumpEvent = PumpEventStored.EventType
 
         override func subscribe() {
-            fetchGlucose()
-            fetchDetermination()
+            Task {
+                await updateGlucose()
+                await updateDetermination()
+            }
             setupInsulinRequired()
             broadcaster.register(DeterminationObserver.self, observer: self)
             broadcaster.register(BolusFailureObserver.self, observer: self)
@@ -178,40 +181,53 @@ extension Bolus {
 
         // MARK: - Glucose
 
-        private func fetchGlucose() {
-            let fetchRequest: NSFetchRequest<GlucoseStored> = GlucoseStored.fetchRequest()
-            fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \GlucoseStored.date, ascending: false)]
-            fetchRequest.predicate = NSPredicate.predicateFor30MinAgo
-            fetchRequest.fetchLimit = 3
-            do {
-                glucoseFromPersistence = try context.fetch(fetchRequest)
+        private func fetchGlucose() async -> [GlucoseStored] {
+            await withCheckedContinuation { continuation in
+                backgroundContext.perform {
+                    let results = CoreDataStack.shared.fetchEntities(
+                        ofType: GlucoseStored.self,
+                        predicate: NSPredicate.predicateFor30MinAgo,
+                        key: "date",
+                        ascending: false,
+                        fetchLimit: 3
+                    )
+                    continuation.resume(returning: results)
+                }
+            }
+        }
 
+        private func updateGlucose() async {
+            let results = await fetchGlucose()
+            await MainActor.run {
+                glucoseFromPersistence = results
                 let lastGlucose = glucoseFromPersistence.first?.glucose ?? 0
                 let thirdLastGlucose = glucoseFromPersistence.last?.glucose ?? 0
                 let delta = Decimal(lastGlucose) - Decimal(thirdLastGlucose)
 
                 currentBG = Decimal(lastGlucose)
                 deltaBG = delta
-                debugPrint(
-                    "Bolus State: \(#function) \(CoreDataStack.identifier) \(DebuggingIdentifiers.succeeded) fetched glucose"
-                )
-            } catch {
-                debugPrint(
-                    "Bolus State: \(#function) \(CoreDataStack.identifier) \(DebuggingIdentifiers.failed) failed to fetch glucose"
-                )
             }
         }
 
-        private func fetchDetermination() {
-            do {
-                determination = try context.fetch(OrefDetermination.fetch(NSPredicate.enactedDetermination))
-                debugPrint(
-                    "Bolus State: \(#function) \(CoreDataStack.identifier) \(DebuggingIdentifiers.succeeded) fetched determinations"
-                )
-            } catch {
-                debugPrint(
-                    "Bolus State: \(#function) \(CoreDataStack.identifier) \(DebuggingIdentifiers.failed) failed to fetch determinations"
-                )
+        private func fetchDetermination() async -> [OrefDetermination] {
+            await withCheckedContinuation { continuation in
+                backgroundContext.perform {
+                    let results = CoreDataStack.shared.fetchEntities(
+                        ofType: OrefDetermination.self,
+                        predicate: NSPredicate.enactedDetermination,
+                        key: "deliverAt",
+                        ascending: false,
+                        fetchLimit: 1
+                    )
+                    continuation.resume(returning: results)
+                }
+            }
+        }
+
+        private func updateDetermination() async {
+            let results = await fetchDetermination()
+            await MainActor.run {
+                determination = results
             }
         }
 
@@ -374,7 +390,7 @@ extension Bolus {
                 newBolusEntry.isSMB = false
 
                 do {
-                    try CoreDataStack.shared.viewContext.saveContext()
+                    try CoreDataStack.shared.saveContext(useViewContext: true)
                 } catch {
                     print(error.localizedDescription)
                 }
@@ -444,7 +460,7 @@ extension Bolus {
                 newBolusEntry.isSMB = false
 
                 do {
-                    try CoreDataStack.shared.viewContext.saveContext()
+                    try CoreDataStack.shared.saveContext(useViewContext: true)
                 } catch {
                     print(error.localizedDescription)
                 }
@@ -491,7 +507,7 @@ extension Bolus {
                 try? context.delete(selection!)
 
                 do {
-                    try CoreDataStack.shared.viewContext.saveContext()
+                    try CoreDataStack.shared.saveContext()
                 } catch {
                     print(error.localizedDescription)
                 }

+ 1 - 1
FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift

@@ -180,7 +180,7 @@ extension DataTable {
                 newItem.isManual = true
 
                 do {
-                    try CoreDataStack.shared.viewContext.saveContext()
+                    try CoreDataStack.shared.saveContext(useViewContext: true)
                 } catch {
                     print(error.localizedDescription)
                 }

+ 1 - 1
FreeAPS/Sources/Modules/Home/HomeStateModel.swift

@@ -349,7 +349,7 @@ extension Home {
                 profiles.date = Date()
 
                 do {
-                    try CoreDataStack.shared.backgroundContext.saveContext()
+                    try CoreDataStack.shared.saveContext()
                 } catch {
                     print(error.localizedDescription)
                 }

+ 1 - 1
FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift

@@ -153,7 +153,7 @@ struct MainChartView: View {
                         updateStartEndMarkers()
                         yAxisChartData()
                         scroller.scrollTo("MainChart", anchor: .trailing)
-                    }.onChange(of: state.glucoseFromPersistence.map(\.id)) { _ in
+                    }.onChange(of: state.glucoseFromPersistence) { _ in
                         updateStartEndMarkers()
                         yAxisChartData()
                         scroller.scrollTo("MainChart", anchor: .trailing)

+ 7 - 6
FreeAPS/Sources/Modules/Home/View/Header/CurrentGlucoseView.swift

@@ -7,6 +7,7 @@ struct CurrentGlucoseView: View {
     @Binding var alarm: GlucoseAlarm?
     @Binding var lowGlucose: Decimal
     @Binding var highGlucose: Decimal
+    @Binding var glucoseFromPersistence: [GlucoseStored]
 
     @State private var rotationDegrees: Double = 0.0
     @State private var angularGradient = AngularGradient(colors: [
@@ -20,12 +21,12 @@ struct CurrentGlucoseView: View {
 
     @Environment(\.colorScheme) var colorScheme
 
-    @FetchRequest(
-        entity: GlucoseStored.entity(),
-        sortDescriptors: [NSSortDescriptor(keyPath: \GlucoseStored.date, ascending: false)],
-        predicate: NSPredicate.predicateFor30MinAgo,
-        animation: Animation.bouncy
-    ) var glucoseFromPersistence: FetchedResults<GlucoseStored>
+//    @FetchRequest(
+//        entity: GlucoseStored.entity(),
+//        sortDescriptors: [NSSortDescriptor(keyPath: \GlucoseStored.date, ascending: false)],
+//        predicate: NSPredicate.predicateFor30MinAgo,
+//        animation: Animation.bouncy
+//    ) var glucoseFromPersistence: FetchedResults<GlucoseStored>
 
     private var glucoseFormatter: NumberFormatter {
         let formatter = NumberFormatter()

+ 4 - 8
FreeAPS/Sources/Modules/Home/View/Header/LoopView.swift

@@ -8,11 +8,7 @@ struct LoopView: View {
         static let lag: TimeInterval = 30
     }
 
-    @FetchRequest(
-        fetchRequest: OrefDetermination
-            .fetch(NSPredicate.enactedDetermination)
-    ) var determination: FetchedResults<OrefDetermination>
-
+    @Binding var determination: OrefDetermination?
     @Binding var closedLoop: Bool
     @Binding var timerDate: Date
     @Binding var isLooping: Bool
@@ -40,7 +36,7 @@ struct LoopView: View {
                 Text("looping")
             } else if manualTempBasal {
                 Text("Manual")
-            } else if determination.first?
+            } else if determination?
                 .deliverAt !=
                 nil
             {
@@ -64,7 +60,7 @@ struct LoopView: View {
     }
 
     private var color: Color {
-        guard determination.first?.deliverAt != nil
+        guard determination?.timestamp != nil
         else {
             // previously the .timestamp property was used here because this only gets updated when the reportenacted function in the aps manager gets called
             return .secondary
@@ -79,7 +75,7 @@ struct LoopView: View {
         let delta = timerDate.timeIntervalSince(lastLoopDate) - Config.lag
 
         if delta <= 5.minutes.timeInterval {
-            guard determination.first?.deliverAt != nil else {
+            guard determination?.timestamp != nil else {
                 return .loopYellow
             }
             return .loopGreen

+ 58 - 40
FreeAPS/Sources/Modules/Home/View/HomeRootView.swift

@@ -42,17 +42,12 @@ extension Home {
             sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)]
         ) var fetchedPercent: FetchedResults<Override>
 
-        @FetchRequest(
-            entity: OrefDetermination.entity(),
-            sortDescriptors: [NSSortDescriptor(key: "deliverAt", ascending: false)],
-            predicate: NSPredicate.predicateFor30MinAgoForDetermination,
-            animation: Animation.bouncy
-        ) var determination: FetchedResults<OrefDetermination>
-
-        @FetchRequest(
-            fetchRequest: OrefDetermination.fetch(NSPredicate.enactedDetermination),
-            animation: Animation.bouncy
-        ) var enactedDeterminations: FetchedResults<OrefDetermination>
+//        @FetchRequest(
+//            entity: OrefDetermination.entity(),
+//            sortDescriptors: [NSSortDescriptor(key: "deliverAt", ascending: false)],
+//            predicate: NSPredicate.predicateFor30MinAgoForDetermination,
+//            animation: Animation.bouncy
+//        ) var determination: FetchedResults<OrefDetermination>
 
         @FetchRequest(
             entity: OverridePresets.entity(),
@@ -71,13 +66,6 @@ extension Home {
             sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)]
         ) var enactedSliderTT: FetchedResults<TempTargetsSlider>
 
-        @FetchRequest(
-            entity: GlucoseStored.entity(),
-            sortDescriptors: [NSSortDescriptor(keyPath: \GlucoseStored.date, ascending: true)],
-            predicate: NSPredicate.predicateFor30MinAgo,
-            animation: Animation.bouncy
-        ) var glucoseFromPersistence: FetchedResults<GlucoseStored>
-
         var bolusProgressFormatter: NumberFormatter {
             let formatter = NumberFormatter()
             formatter.numberStyle = .decimal
@@ -157,13 +145,32 @@ extension Home {
             }
         }
 
+        private var determination: OrefDetermination? {
+            guard let determinationObjectID = state.determinationsFromPersistence.first else {
+                return nil
+            }
+            return CoreDataStack.shared.backgroundContext.object(with: determinationObjectID) as? OrefDetermination
+        }
+
+        private var determinationAsBinding: Binding<OrefDetermination?> {
+            Binding<OrefDetermination?>(
+                get: {
+                    guard let determinationObjectID = state.determinationsFromPersistence.first else {
+                        return nil
+                    }
+                    return CoreDataStack.shared.backgroundContext.object(with: determinationObjectID) as? OrefDetermination
+                }, set: { _ in }
+            )
+        }
+
         var glucoseView: some View {
             CurrentGlucoseView(
                 timerDate: $state.timerDate,
                 units: $state.units,
                 alarm: $state.alarm,
                 lowGlucose: $state.lowGlucose,
-                highGlucose: $state.highGlucose
+                highGlucose: $state.highGlucose,
+                glucoseFromPersistence: $state.glucoseFromPersistence
             ).scaleEffect(0.9)
                 .onTapGesture {
                     if state.alarm == nil {
@@ -431,6 +438,7 @@ extension Home {
             VStack(alignment: .leading, spacing: 20) {
                 /// Loop view at bottomLeading
                 LoopView(
+                    determination: determinationAsBinding,
                     closedLoop: $state.closedLoop,
                     timerDate: $state.timerDate,
                     isLooping: $state.isLooping,
@@ -446,7 +454,7 @@ extension Home {
                 }
                 /// eventualBG string at bottomTrailing
 
-                if let eventualBG = determination.first?.eventualBG {
+                if let eventualBG = determination?.eventualBG {
                     let bg = eventualBG as Decimal
                     HStack {
                         Image(systemName: "arrow.right.circle")
@@ -494,7 +502,7 @@ extension Home {
                         .font(.system(size: 16))
                         .foregroundColor(Color.insulin)
                     Text(
-                        (numberFormatter.string(from: (determination.first?.iob ?? 0) as NSNumber) ?? "0") +
+                        (numberFormatter.string(from: (determination?.iob ?? 0) as NSNumber) ?? "0") +
                             NSLocalizedString(" U", comment: "Insulin unit")
                     )
                     .font(.system(size: 16, weight: .bold, design: .rounded))
@@ -507,7 +515,7 @@ extension Home {
                         .font(.system(size: 16))
                         .foregroundColor(.loopYellow)
                     Text(
-                        (numberFormatter.string(from: (determination.first?.cob ?? 0) as NSNumber) ?? "0") +
+                        (numberFormatter.string(from: (determination?.cob ?? 0) as NSNumber) ?? "0") +
                             NSLocalizedString(" g", comment: "gram of carbs")
                     )
                     .font(.system(size: 16, weight: .bold, design: .rounded))
@@ -530,7 +538,7 @@ extension Home {
                 if !state.tins {
                     Spacer()
                     Text(
-                        "TDD: " + (numberFormatter.string(from: (determination.first?.totalDailyDose ?? 0) as NSNumber) ?? "0") +
+                        "TDD: " + (numberFormatter.string(from: (determination?.totalDailyDose ?? 0) as NSNumber) ?? "0") +
                             NSLocalizedString(" U", comment: "Insulin unit")
                     )
                     .font(.system(size: 16, weight: .bold, design: .rounded))
@@ -814,7 +822,7 @@ extension Home {
             ZStack(alignment: .bottom) {
                 TabView {
                     let carbsRequiredBadge: String? = {
-                        guard let carbsRequired = determination.first?.carbsRequired as? Decimal else { return nil }
+                        guard let carbsRequired = determination?.carbsRequired as? Decimal else { return nil }
                         if carbsRequired > state.settingsManager.settings.carbsRequiredThreshold {
                             let numberAsNSNumber = NSDecimalNumber(decimal: carbsRequired)
                             let formattedNumber = numberFormatter.string(from: numberAsNSNumber) ?? ""
@@ -877,13 +885,21 @@ extension Home {
             VStack(alignment: .leading, spacing: 4) {
                 Text(statusTitle).font(.headline).foregroundColor(.white)
                     .padding(.bottom, 4)
-                if let determination = enactedDeterminations.first {
-                    TagCloudView(tags: determination.reasonParts).animation(.none, value: false)
-
-                    Text(determination.reasonConclusion.capitalizingFirstLetter()).font(.caption).foregroundColor(.white)
+                if let determinationObjectID = state.determinationsFromPersistence.first {
+                    if let determination = CoreDataStack.shared.backgroundContext
+                        .object(with: determinationObjectID) as? OrefDetermination
+                    {
+                        if determination.glucose == 400 {
+                            Text("Invalid CGM reading (HIGH).").font(.callout).bold().foregroundColor(.loopRed).padding(.top, 8)
+                            Text("SMBs and High Temps Disabled.").font(.caption).foregroundColor(.white).padding(.bottom, 4)
+                        } else {
+                            TagCloudView(tags: determination.reasonParts).animation(.none, value: false)
 
-                } else {
-                    Text("No determination found").font(.body).foregroundColor(.white)
+                            Text(determination.reasonConclusion.capitalizingFirstLetter()).font(.caption).foregroundColor(.white)
+                        }
+                    } else {
+                        Text("No determination found").font(.body).foregroundColor(.white)
+                    }
                 }
 
                 if let errorMessage = state.errorMessage, let date = state.errorDate {
@@ -893,24 +909,26 @@ extension Home {
                         .padding(.bottom, 4)
                         .padding(.top, 8)
                     Text(errorMessage).font(.caption).foregroundColor(.loopRed)
-                } else if let determination = determination.first, (determination.glucose ?? 100) == 400 {
-                    Text("Invalid CGM reading (HIGH).").font(.callout).bold().foregroundColor(.loopRed).padding(.top, 8)
-                    Text("SMBs and High Temps Disabled.").font(.caption).foregroundColor(.white).padding(.bottom, 4)
                 }
             }
         }
 
         private func setStatusTitle() {
-            guard let determination = determination.first else {
+            if let determinationObjectID = state.determinationsFromPersistence.first {
+                if let determination = CoreDataStack.shared.backgroundContext
+                    .object(with: determinationObjectID) as? OrefDetermination
+                {
+                    let dateFormatter = DateFormatter()
+                    dateFormatter.timeStyle = .short
+                    statusTitle = NSLocalizedString("Oref Determination enacted at", comment: "Headline in enacted pop up") +
+                        " " +
+                        dateFormatter
+                        .string(from: determination.deliverAt ?? Date())
+                }
+            } else {
                 statusTitle = "No Oref determination"
                 return
             }
-
-            let dateFormatter = DateFormatter()
-            dateFormatter.timeStyle = .short
-            statusTitle = NSLocalizedString("Oref Determination enacted at", comment: "Headline in enacted pop up") + " " +
-                dateFormatter
-                .string(from: determination.deliverAt ?? Date())
         }
     }
 }

+ 1 - 1
FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigStateModel.swift

@@ -318,7 +318,7 @@ extension NightscoutConfig {
                 saveToCoreData.error = string
 
                 do {
-                    try CoreDataStack.shared.backgroundContext.saveContext()
+                    try CoreDataStack.shared.saveContext()
                 } catch {
                     print(error.localizedDescription)
                 }

+ 10 - 10
FreeAPS/Sources/Modules/OverrideProfilesConfig/OverrideProfilesStateModel.swift

@@ -96,7 +96,7 @@ extension OverrideProfilesConfig {
                     saveOverride.uamMinutes = uamMinutes as NSDecimalNumber
                 }
                 do {
-                    try CoreDataStack.shared.backgroundContext.saveContext()
+                    try CoreDataStack.shared.saveContext()
                 } catch {
                     print(error.localizedDescription)
                 }
@@ -141,7 +141,7 @@ extension OverrideProfilesConfig {
                     saveOverride.uamMinutes = uamMinutes as NSDecimalNumber
                 }
                 do {
-                    try CoreDataStack.shared.backgroundContext.saveContext()
+                    try CoreDataStack.shared.saveContext()
                 } catch {
                     print(error.localizedDescription)
                 }
@@ -185,7 +185,7 @@ extension OverrideProfilesConfig {
                     saveOverride.uamMinutes = (profile.uamMinutes ?? 0) as NSDecimalNumber
                 }
                 do {
-                    try CoreDataStack.shared.backgroundContext.saveContext()
+                    try CoreDataStack.shared.saveContext()
                 } catch {
                     print(error.localizedDescription)
                 }
@@ -275,7 +275,7 @@ extension OverrideProfilesConfig {
                 profiles.enabled = false
                 profiles.date = Date()
                 do {
-                    try CoreDataStack.shared.backgroundContext.saveContext()
+                    try CoreDataStack.shared.saveContext()
                 } catch {
                     print(error.localizedDescription)
                 }
@@ -311,7 +311,7 @@ extension OverrideProfilesConfig {
                     saveToCoreData.active = false
                     saveToCoreData.date = Date()
                     do {
-                        try CoreDataStack.shared.backgroundContext.saveContext()
+                        try CoreDataStack.shared.saveContext()
                     } catch {
                         print(error.localizedDescription)
                     }
@@ -346,7 +346,7 @@ extension OverrideProfilesConfig {
                 saveToCoreData.active = false
                 saveToCoreData.date = Date()
                 do {
-                    try CoreDataStack.shared.backgroundContext.saveContext()
+                    try CoreDataStack.shared.saveContext()
                 } catch {
                     print(error.localizedDescription)
                 }
@@ -355,7 +355,7 @@ extension OverrideProfilesConfig {
                 setHBT.enabled = false
                 setHBT.date = Date()
                 do {
-                    try CoreDataStack.shared.backgroundContext.saveContext()
+                    try CoreDataStack.shared.saveContext()
                 } catch {
                     print(error.localizedDescription)
                 }
@@ -403,7 +403,7 @@ extension OverrideProfilesConfig {
                     saveToCoreData.date = Date()
                     saveToCoreData.duration = durationTT as NSDecimalNumber
                     do {
-                        try CoreDataStack.shared.backgroundContext.saveContext()
+                        try CoreDataStack.shared.saveContext()
                     } catch {
                         print(error.localizedDescription)
                     }
@@ -436,7 +436,7 @@ extension OverrideProfilesConfig {
                         saveToCoreData.duration = whichID?.duration ?? 0
 
                         do {
-                            try CoreDataStack.shared.backgroundContext.saveContext()
+                            try CoreDataStack.shared.saveContext()
                         } catch {
                             print(error.localizedDescription)
                         }
@@ -445,7 +445,7 @@ extension OverrideProfilesConfig {
                         saveToCoreData.active = false
                         saveToCoreData.date = Date()
                         do {
-                            try CoreDataStack.shared.backgroundContext.saveContext()
+                            try CoreDataStack.shared.saveContext()
                         } catch {
                             print(error.localizedDescription)
                         }

+ 9 - 16
FreeAPS/Sources/Services/Calendar/CalendarManager.swift

@@ -229,25 +229,18 @@ final class BaseCalendarManager: CalendarManager, Injectable {
         return formatter
     }
 
-    private func fetchAndProcessGlucose() -> [GlucoseStored]? {
-        var result: [GlucoseStored]?
-        coredataContext.perform {
-            do {
-                debugPrint("Calendar Manager: \(#function) \(DebuggingIdentifiers.succeeded) fetched glucose")
-                result = try self.coredataContext.fetch(GlucoseStored.fetch(
-                    NSPredicate.predicateFor20MinAgo,
-                    ascending: false,
-                    fetchLimit: 3
-                ))
-            } catch {
-                debugPrint("Calendar Manager: \(#function) \(DebuggingIdentifiers.failed) failed to fetch glucose")
-            }
-        }
-        return result
+    private func fetchGlucose() -> [GlucoseStored]? {
+        CoreDataStack.shared.fetchEntities(
+            ofType: GlucoseStored.self,
+            predicate: NSPredicate.predicateFor30MinAgo,
+            key: "date",
+            ascending: false,
+            fetchLimit: 4
+        )
     }
 
     func setupGlucose() {
-        guard let glucose = fetchAndProcessGlucose(), glucose.count >= 2 else {
+        guard let glucose = fetchGlucose(), glucose.count >= 2 else {
             debugPrint("Not enough glucose data available")
             return
         }

+ 18 - 13
FreeAPS/Sources/Shortcuts/State/StateIntentRequest.swift

@@ -58,8 +58,13 @@ enum StateIntentError: Error {
 
     func getLastGlucose() throws -> (dateGlucose: Date, glucose: String, trend: String, delta: String) {
         do {
-            let results = try moc.fetch(GlucoseStored.fetch(NSPredicate.predicateFor30MinAgo, ascending: false, fetchLimit: 2))
-            debugPrint("StateIntentRequest: \(#function) \(DebuggingIdentifiers.succeeded) fetched latest glucose")
+            let results = CoreDataStack.shared.fetchEntities(
+                ofType: GlucoseStored.self,
+                predicate: NSPredicate.predicateFor30MinAgo,
+                key: "date",
+                ascending: false,
+                fetchLimit: 2
+            )
 
             guard let lastValue = results.first else { throw StateIntentError.NoBG }
 
@@ -93,17 +98,17 @@ enum StateIntentError: Error {
     }
 
     func getIobAndCob() throws -> (iob: Double, cob: Double) {
-        do {
-            let results = try moc.fetch(OrefDetermination.fetch(NSPredicate.enactedDetermination))
-            let iobAsDouble = Double(truncating: (results.first?.iob ?? 0.0) as NSNumber)
-            let cobAsDouble = Double(truncating: (results.first?.cob ?? 0) as NSNumber)
-            debugPrint("StateIntentRequest: \(#function) \(DebuggingIdentifiers.succeeded) fetched latest cob and iob")
-
-            return (iobAsDouble, cobAsDouble)
-        } catch {
-            debugPrint("StateIntentRequest: \(#function) \(DebuggingIdentifiers.failed) failed to fetch latest cob and iob")
-            return (0.0, 0.0)
-        }
+        let results = CoreDataStack.shared.fetchEntities(
+            ofType: OrefDetermination.self,
+            predicate: NSPredicate.enactedDetermination,
+            key: "deliverAt",
+            ascending: false,
+            fetchLimit: 1
+        )
+        let iobAsDouble = Double(truncating: (results.first?.iob ?? 0.0) as NSNumber)
+        let cobAsDouble = Double(truncating: (results.first?.cob ?? 0) as NSNumber)
+
+        return (iobAsDouble, cobAsDouble)
     }
 
     private var glucoseFormatter: NumberFormatter {

+ 2 - 2
FreeAPS/Sources/Shortcuts/TempPresets/TempPresetsIntentRequest.swift

@@ -57,7 +57,7 @@ import Foundation
                 saveToCoreData.duration = whichID?.duration ?? 0
 
                 do {
-                    try CoreDataStack.shared.backgroundContext.saveContext()
+                    try CoreDataStack.shared.saveContext()
                 } catch {
                     print(error.localizedDescription)
                 }
@@ -66,7 +66,7 @@ import Foundation
                 saveToCoreData.active = false
                 saveToCoreData.date = Date()
                 do {
-                    try CoreDataStack.shared.backgroundContext.saveContext()
+                    try CoreDataStack.shared.saveContext()
                 } catch {
                     print(error.localizedDescription)
                 }

+ 19 - 15
Model/CoreDataStack.swift

@@ -34,6 +34,7 @@ class CoreDataStack: ObservableObject {
         return viewContext
     }()
 
+    // fetch on the thread of the backgroundContext
     func fetchEntities<T: NSManagedObject>(
         ofType type: T.Type,
         predicate: NSPredicate,
@@ -68,7 +69,7 @@ class CoreDataStack: ObservableObject {
             do {
                 debugPrint("Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.succeeded)")
                 result = try self.backgroundContext.fetch(request)
-            } catch {
+            } catch let error as NSError {
                 debugPrint(
                     "Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.failed) \(error)"
                 )
@@ -76,21 +77,24 @@ class CoreDataStack: ObservableObject {
         }
         return result ?? []
     }
-}
 
-extension NSManagedObjectContext {
-    func saveContext(callingFunction: String = #function, callingClass: String = #fileID) throws {
-        if hasChanges {
-            do {
-                try save()
-                debugPrint(
-                    "Saving to Core Data successful in \(callingFunction) in \(callingClass): \(DebuggingIdentifiers.succeeded)"
-                )
-            } catch let error as NSError {
-                debugPrint(
-                    "Saving to Core Data failed in \(callingFunction) in \(callingClass): \(DebuggingIdentifiers.failed) with error \(error), \(error.userInfo)"
-                )
-                throw error
+    // save on the thread of the backgroundContext
+    func saveContext(useViewContext: Bool = false, callingFunction: String = #function, callingClass: String = #fileID) throws {
+        let contextToUse = useViewContext ? viewContext : backgroundContext
+
+        try contextToUse.performAndWait {
+            if contextToUse.hasChanges {
+                do {
+                    try self.backgroundContext.save()
+                    debugPrint(
+                        "Saving to Core Data successful in \(callingFunction) in \(callingClass): \(DebuggingIdentifiers.succeeded)"
+                    )
+                } catch let error as NSError {
+                    debugPrint(
+                        "Saving to Core Data failed in \(callingFunction) in \(callingClass): \(DebuggingIdentifiers.failed) with error \(error), \(error.userInfo)"
+                    )
+                    throw error
+                }
             }
         }
     }