Explorar o código

Merge branch 'core-data' of github.com:polscm32/Open-iAPS into core-data

dnzxy %!s(int64=2) %!d(string=hai) anos
pai
achega
a529036bf6

+ 4 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -266,6 +266,7 @@
 		53F2382465BF74DB1A967C8B /* PumpConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8630D58BDAD6D9C650B9B39 /* PumpConfigProvider.swift */; };
 		53F2382465BF74DB1A967C8B /* PumpConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8630D58BDAD6D9C650B9B39 /* PumpConfigProvider.swift */; };
 		581516A42BCED84A00BF67D7 /* DebuggingIdentifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581516A32BCED84A00BF67D7 /* DebuggingIdentifiers.swift */; };
 		581516A42BCED84A00BF67D7 /* DebuggingIdentifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581516A32BCED84A00BF67D7 /* DebuggingIdentifiers.swift */; };
 		581516A92BCEEDF800BF67D7 /* NSPredicates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581516A82BCEEDF800BF67D7 /* NSPredicates.swift */; };
 		581516A92BCEEDF800BF67D7 /* NSPredicates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581516A82BCEEDF800BF67D7 /* NSPredicates.swift */; };
+		581AC4392BE22ED10038760C /* JSONConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581AC4382BE22ED10038760C /* JSONConverter.swift */; };
 		58237D9E2BCF0A6B00A47A79 /* PopupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58237D9D2BCF0A6B00A47A79 /* PopupView.swift */; };
 		58237D9E2BCF0A6B00A47A79 /* PopupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58237D9D2BCF0A6B00A47A79 /* PopupView.swift */; };
 		5825D1342BD4058F00F36E9B /* BGaverages+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5825D1062BD4058F00F36E9B /* BGaverages+CoreDataClass.swift */; };
 		5825D1342BD4058F00F36E9B /* BGaverages+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5825D1062BD4058F00F36E9B /* BGaverages+CoreDataClass.swift */; };
 		5825D1352BD4058F00F36E9B /* BGaverages+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5825D1072BD4058F00F36E9B /* BGaverages+CoreDataProperties.swift */; };
 		5825D1352BD4058F00F36E9B /* BGaverages+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5825D1072BD4058F00F36E9B /* BGaverages+CoreDataProperties.swift */; };
@@ -886,6 +887,7 @@
 		505E09DC17A0C3D0AF4B66FE /* ISFEditorStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ISFEditorStateModel.swift; sourceTree = "<group>"; };
 		505E09DC17A0C3D0AF4B66FE /* ISFEditorStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ISFEditorStateModel.swift; sourceTree = "<group>"; };
 		581516A32BCED84A00BF67D7 /* DebuggingIdentifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebuggingIdentifiers.swift; sourceTree = "<group>"; };
 		581516A32BCED84A00BF67D7 /* DebuggingIdentifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebuggingIdentifiers.swift; sourceTree = "<group>"; };
 		581516A82BCEEDF800BF67D7 /* NSPredicates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSPredicates.swift; sourceTree = "<group>"; };
 		581516A82BCEEDF800BF67D7 /* NSPredicates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSPredicates.swift; sourceTree = "<group>"; };
+		581AC4382BE22ED10038760C /* JSONConverter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONConverter.swift; sourceTree = "<group>"; };
 		58237D9D2BCF0A6B00A47A79 /* PopupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupView.swift; sourceTree = "<group>"; };
 		58237D9D2BCF0A6B00A47A79 /* PopupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupView.swift; sourceTree = "<group>"; };
 		5825D1062BD4058F00F36E9B /* BGaverages+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BGaverages+CoreDataClass.swift"; sourceTree = SOURCE_ROOT; };
 		5825D1062BD4058F00F36E9B /* BGaverages+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BGaverages+CoreDataClass.swift"; sourceTree = SOURCE_ROOT; };
 		5825D1072BD4058F00F36E9B /* BGaverages+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BGaverages+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; };
 		5825D1072BD4058F00F36E9B /* BGaverages+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BGaverages+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; };
@@ -2215,6 +2217,7 @@
 				5837A5312BD2E81100A5DC04 /* InsulinStored+helper.swift */,
 				5837A5312BD2E81100A5DC04 /* InsulinStored+helper.swift */,
 				CC76E9502BD4812E008BEB61 /* Forecast+helper.swift */,
 				CC76E9502BD4812E008BEB61 /* Forecast+helper.swift */,
 				5887527B2BD986E1008B081D /* OpenAPSBattery.swift */,
 				5887527B2BD986E1008B081D /* OpenAPSBattery.swift */,
+				581AC4382BE22ED10038760C /* JSONConverter.swift */,
 			);
 			);
 			path = Helper;
 			path = Helper;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
@@ -3104,6 +3107,7 @@
 				5D16287A969E64D18CE40E44 /* PumpConfigStateModel.swift in Sources */,
 				5D16287A969E64D18CE40E44 /* PumpConfigStateModel.swift in Sources */,
 				19D466AA29AA3099004D5F33 /* FPUConfigRootView.swift in Sources */,
 				19D466AA29AA3099004D5F33 /* FPUConfigRootView.swift in Sources */,
 				E974172296125A5AE99E634C /* PumpConfigRootView.swift in Sources */,
 				E974172296125A5AE99E634C /* PumpConfigRootView.swift in Sources */,
+				581AC4392BE22ED10038760C /* JSONConverter.swift in Sources */,
 				CE7CA3522A064973004BE681 /* ListTempPresetsIntent.swift in Sources */,
 				CE7CA3522A064973004BE681 /* ListTempPresetsIntent.swift in Sources */,
 				448B6FCB252BD4796E2960C0 /* PumpSettingsEditorDataFlow.swift in Sources */,
 				448B6FCB252BD4796E2960C0 /* PumpSettingsEditorDataFlow.swift in Sources */,
 				38E44536274E411700EC9A94 /* Disk.swift in Sources */,
 				38E44536274E411700EC9A94 /* Disk.swift in Sources */,

+ 1 - 1
FreeAPS.xcworkspace/xcshareddata/swiftpm/Package.resolved

@@ -39,7 +39,7 @@
       },
       },
       {
       {
         "package": "SwiftCharts",
         "package": "SwiftCharts",
-        "repositoryURL": "https://github.com/ivanschuetz/SwiftCharts.git",
+        "repositoryURL": "https://github.com/ivanschuetz/SwiftCharts",
         "state": {
         "state": {
           "branch": "master",
           "branch": "master",
           "revision": "c354c1945bb35a1f01b665b22474f6db28cba4a2",
           "revision": "c354c1945bb35a1f01b665b22474f6db28cba4a2",

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

@@ -64,7 +64,6 @@ final class BaseAPSManager: APSManager, Injectable {
     @Injected() private var storage: FileStorage!
     @Injected() private var storage: FileStorage!
     @Injected() private var pumpHistoryStorage: PumpHistoryStorage!
     @Injected() private var pumpHistoryStorage: PumpHistoryStorage!
     @Injected() private var alertHistoryStorage: AlertHistoryStorage!
     @Injected() private var alertHistoryStorage: AlertHistoryStorage!
-    @Injected() private var glucoseStorage: GlucoseStorage!
     @Injected() private var tempTargetsStorage: TempTargetsStorage!
     @Injected() private var tempTargetsStorage: TempTargetsStorage!
     @Injected() private var carbsStorage: CarbsStorage!
     @Injected() private var carbsStorage: CarbsStorage!
     @Injected() private var announcementsStorage: AnnouncementsStorage!
     @Injected() private var announcementsStorage: AnnouncementsStorage!

+ 37 - 5
FreeAPS/Sources/APS/FetchGlucoseManager.swift

@@ -32,6 +32,9 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
     private lazy var dexcomSourceG7 = DexcomSourceG7(glucoseStorage: glucoseStorage, glucoseManager: self)
     private lazy var dexcomSourceG7 = DexcomSourceG7(glucoseStorage: glucoseStorage, glucoseManager: self)
     private lazy var simulatorSource = GlucoseSimulatorSource()
     private lazy var simulatorSource = GlucoseSimulatorSource()
 
 
+    // TODO: - test if we need to use the viewContext here
+    private let context = CoreDataStack.shared.backgroundContext
+
     init(resolver: Resolver) {
     init(resolver: Resolver) {
         injectServices(resolver)
         injectServices(resolver)
         updateGlucoseSource()
         updateGlucoseSource()
@@ -96,6 +99,36 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
         .store(in: &lifetime)
         .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]()
+
+            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 glucoseStoreAndHeartDecision(syncDate: Date, glucose: [BloodGlucose], glucoseFromHealth: [BloodGlucose] = []) {
     private func glucoseStoreAndHeartDecision(syncDate: Date, glucose: [BloodGlucose], glucoseFromHealth: [BloodGlucose] = []) {
         let allGlucose = glucose + glucoseFromHealth
         let allGlucose = glucose + glucoseFromHealth
         var filteredByDate: [BloodGlucose] = []
         var filteredByDate: [BloodGlucose] = []
@@ -132,11 +165,10 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
 
 
         // filter the data if it is the case
         // filter the data if it is the case
         if settingsManager.settings.smoothGlucose {
         if settingsManager.settings.smoothGlucose {
-            // limit to 30 minutes of previous BG Data
-            let oldGlucoses = glucoseStorage.recent().filter {
-                $0.dateString.addingTimeInterval(31 * 60) > Date()
-            }
-            var smoothedValues = oldGlucoses + filtered
+            // limited to 30 min of old glucose data
+            let oldGlucoseValues = fetchAndProcessGlucose()
+
+            var smoothedValues = oldGlucoseValues + filtered
             // smooth with 3 repeats
             // smooth with 3 repeats
             for _ in 1 ... 3 {
             for _ in 1 ... 3 {
                 smoothedValues.smoothSavitzkyGolayQuaDratic(withFilterWidth: 3)
                 smoothedValues.smoothSavitzkyGolayQuaDratic(withFilterWidth: 3)

+ 54 - 11
FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift

@@ -11,6 +11,8 @@ final class OpenAPS {
 
 
     let context = CoreDataStack.shared.backgroundContext
     let context = CoreDataStack.shared.backgroundContext
 
 
+    let jsonConverter = JSONConverter()
+
     init(storage: FileStorage) {
     init(storage: FileStorage) {
         self.storage = storage
         self.storage = storage
     }
     }
@@ -92,6 +94,31 @@ 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.predicateFor20MinAgo,
+                ascending: false,
+                fetchLimit: 3
+            )) /// it only returns the last 3 values within the last 20 minutes, that means one reading can not be older than 5 minutes, otherwise the loop will fail
+        } catch {
+            debugPrint("OpenAPS: \(#function) \(DebuggingIdentifiers.failed) failed to fetch glucose with error: \(error)")
+            return []
+        }
+    }
+
+    private func fetchCarbs() -> [MealsStored]? {
+        do {
+            debugPrint("OpenAPS: \(#function) \(DebuggingIdentifiers.succeeded) fetched carbs")
+            return try context.fetch(MealsStored.fetch(NSPredicate.predicateFor30MinAgo, ascending: true))
+        } catch {
+            debugPrint("OpenAPS: \(#function) \(DebuggingIdentifiers.failed) failed to fetch carbs with error: \(error)")
+            return []
+        }
+    }
+
     func determineBasal(currentTemp: TempBasal, clock: Date = Date()) -> Future<Determination?, Never> {
     func determineBasal(currentTemp: TempBasal, clock: Date = Date()) -> Future<Determination?, Never> {
         Future { promise in
         Future { promise in
             self.processQueue.async {
             self.processQueue.async {
@@ -103,20 +130,28 @@ final class OpenAPS {
                 let tempBasal = currentTemp.rawJSON
                 let tempBasal = currentTemp.rawJSON
                 self.storage.save(tempBasal, as: Monitor.tempBasal)
                 self.storage.save(tempBasal, as: Monitor.tempBasal)
 
 
-                // meal
                 let pumpHistory = self.loadFileFromStorage(name: OpenAPS.Monitor.pumpHistory)
                 let pumpHistory = self.loadFileFromStorage(name: OpenAPS.Monitor.pumpHistory)
-                let carbs = self.loadFileFromStorage(name: Monitor.carbHistory)
-                let glucose = self.loadFileFromStorage(name: Monitor.glucose)
+                
+                // carbs
+                let carbs = self.fetchCarbs()
+                let carbsString = self.jsonConverter.convertToJSON(carbs)
+
+                /// glucose
+                let glucose = self.fetchGlucose()
+                let glucoseString = self.jsonConverter.convertToJSON(glucose)
+
+                /// profile
                 let profile = self.loadFileFromStorage(name: Settings.profile)
                 let profile = self.loadFileFromStorage(name: Settings.profile)
                 let basalProfile = self.loadFileFromStorage(name: Settings.basalProfile)
                 let basalProfile = self.loadFileFromStorage(name: Settings.basalProfile)
 
 
+                /// meal
                 let meal = self.meal(
                 let meal = self.meal(
                     pumphistory: pumpHistory,
                     pumphistory: pumpHistory,
                     profile: profile,
                     profile: profile,
                     basalProfile: basalProfile,
                     basalProfile: basalProfile,
                     clock: clock,
                     clock: clock,
-                    carbs: carbs,
-                    glucose: glucose
+                    carbs: carbsString,
+                    glucose: glucoseString
                 )
                 )
 
 
                 self.storage.save(meal, as: Monitor.meal)
                 self.storage.save(meal, as: Monitor.meal)
@@ -141,7 +176,7 @@ final class OpenAPS {
                 let oref2_variables = self.oref2()
                 let oref2_variables = self.oref2()
 
 
                 let orefDetermination = self.determineBasal(
                 let orefDetermination = self.determineBasal(
-                    glucose: glucose,
+                    glucose: glucoseString,
                     currentTemp: tempBasal,
                     currentTemp: tempBasal,
                     iob: iob,
                     iob: iob,
                     profile: profile,
                     profile: profile,
@@ -154,7 +189,7 @@ final class OpenAPS {
                     basalProfile: basalProfile,
                     basalProfile: basalProfile,
                     oref2_variables: oref2_variables
                     oref2_variables: oref2_variables
                 )
                 )
-                debug(.openAPS, "SUGGESTED: \(orefDetermination)")
+                debug(.openAPS, "Determinated: \(orefDetermination)")
 
 
                 if var determination = Determination(from: orefDetermination) {
                 if var determination = Determination(from: orefDetermination) {
                     determination.timestamp = determination.deliverAt ?? clock
                     determination.timestamp = determination.deliverAt ?? clock
@@ -378,12 +413,16 @@ final class OpenAPS {
                 debug(.openAPS, "Start autosens")
                 debug(.openAPS, "Start autosens")
                 let pumpHistory = self.loadFileFromStorage(name: OpenAPS.Monitor.pumpHistory)
                 let pumpHistory = self.loadFileFromStorage(name: OpenAPS.Monitor.pumpHistory)
                 let carbs = self.loadFileFromStorage(name: Monitor.carbHistory)
                 let carbs = self.loadFileFromStorage(name: Monitor.carbHistory)
-                let glucose = self.loadFileFromStorage(name: Monitor.glucose)
+
+                /// glucose
+                let glucose = self.fetchGlucose()
+                let glucoseString = self.jsonConverter.convertToJSON(glucose)
+
                 let profile = self.loadFileFromStorage(name: Settings.profile)
                 let profile = self.loadFileFromStorage(name: Settings.profile)
                 let basalProfile = self.loadFileFromStorage(name: Settings.basalProfile)
                 let basalProfile = self.loadFileFromStorage(name: Settings.basalProfile)
                 let tempTargets = self.loadFileFromStorage(name: Settings.tempTargets)
                 let tempTargets = self.loadFileFromStorage(name: Settings.tempTargets)
                 let autosensResult = self.autosense(
                 let autosensResult = self.autosense(
-                    glucose: glucose,
+                    glucose: glucoseString,
                     pumpHistory: pumpHistory,
                     pumpHistory: pumpHistory,
                     basalprofile: basalProfile,
                     basalprofile: basalProfile,
                     profile: profile,
                     profile: profile,
@@ -408,7 +447,11 @@ final class OpenAPS {
             self.processQueue.async {
             self.processQueue.async {
                 debug(.openAPS, "Start autotune")
                 debug(.openAPS, "Start autotune")
                 let pumpHistory = self.loadFileFromStorage(name: OpenAPS.Monitor.pumpHistory)
                 let pumpHistory = self.loadFileFromStorage(name: OpenAPS.Monitor.pumpHistory)
-                let glucose = self.loadFileFromStorage(name: Monitor.glucose)
+
+                /// glucose
+                let glucose = self.fetchGlucose()
+                let glucoseString = self.jsonConverter.convertToJSON(glucose)
+
                 let profile = self.loadFileFromStorage(name: Settings.profile)
                 let profile = self.loadFileFromStorage(name: Settings.profile)
                 let pumpProfile = self.loadFileFromStorage(name: Settings.pumpProfile)
                 let pumpProfile = self.loadFileFromStorage(name: Settings.pumpProfile)
                 let carbs = self.loadFileFromStorage(name: Monitor.carbHistory)
                 let carbs = self.loadFileFromStorage(name: Monitor.carbHistory)
@@ -416,7 +459,7 @@ final class OpenAPS {
                 let autotunePreppedGlucose = self.autotunePrepare(
                 let autotunePreppedGlucose = self.autotunePrepare(
                     pumphistory: pumpHistory,
                     pumphistory: pumpHistory,
                     profile: profile,
                     profile: profile,
-                    glucose: glucose,
+                    glucose: glucoseString,
                     pumpprofile: pumpProfile,
                     pumpprofile: pumpProfile,
                     carbs: carbs,
                     carbs: carbs,
                     categorizeUamAsBasal: categorizeUamAsBasal,
                     categorizeUamAsBasal: categorizeUamAsBasal,

+ 143 - 67
FreeAPS/Sources/APS/Storage/GlucoseStorage.swift

@@ -8,12 +8,10 @@ import Swinject
 protocol GlucoseStorage {
 protocol GlucoseStorage {
     func storeGlucose(_ glucose: [BloodGlucose])
     func storeGlucose(_ glucose: [BloodGlucose])
     func removeGlucose(ids: [String])
     func removeGlucose(ids: [String])
-    func recent() -> [BloodGlucose]
     func syncDate() -> Date
     func syncDate() -> Date
     func filterTooFrequentGlucose(_ glucose: [BloodGlucose], at: Date) -> [BloodGlucose]
     func filterTooFrequentGlucose(_ glucose: [BloodGlucose], at: Date) -> [BloodGlucose]
     func lastGlucoseDate() -> Date
     func lastGlucoseDate() -> Date
     func isGlucoseFresh() -> Bool
     func isGlucoseFresh() -> Bool
-    func isGlucoseNotFlat() -> Bool
     func nightscoutGlucoseNotUploaded() -> [BloodGlucose]
     func nightscoutGlucoseNotUploaded() -> [BloodGlucose]
     func nightscoutCGMStateNotUploaded() -> [NigtscoutTreatment]
     func nightscoutCGMStateNotUploaded() -> [NigtscoutTreatment]
     func nightscoutManualGlucoseNotUploaded() -> [NigtscoutTreatment]
     func nightscoutManualGlucoseNotUploaded() -> [NigtscoutTreatment]
@@ -50,53 +48,53 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     func storeGlucose(_ glucose: [BloodGlucose]) {
     func storeGlucose(_ glucose: [BloodGlucose]) {
         processQueue.sync {
         processQueue.sync {
             debug(.deviceManager, "start storage glucose")
             debug(.deviceManager, "start storage glucose")
-            let file = OpenAPS.Monitor.glucose
-            self.storage.transaction { storage in
-                storage.append(glucose, to: file, uniqBy: \.dateString)
-
-                let uniqEvents = storage.retrieve(file, as: [BloodGlucose].self)?
-                    .filter { $0.dateString.addingTimeInterval(24.hours.timeInterval) > Date() }
-                    .sorted { $0.dateString > $1.dateString } ?? []
-                let glucose = Array(uniqEvents)
-                storage.save(glucose, as: file)
-
-                DispatchQueue.main.async {
-                    self.broadcaster.notify(GlucoseObserver.self, on: .main) {
-                        $0.glucoseDidUpdate(glucose.reversed())
-                    }
-                }
-
-                // MARK: - Save to CoreData.
-
-                var bg_ = 0
-                var direction = ""
-
-                if glucose.isNotEmpty {
-                    bg_ = glucose[0].glucose ?? 0
-                    direction = glucose[0].direction?.symbol ?? "↔︎"
-                }
+//            let file = OpenAPS.Monitor.glucose
+//            self.storage.transaction { storage in
+//                storage.append(glucose, to: file, uniqBy: \.dateString)
+//
+//                let uniqEvents = storage.retrieve(file, as: [BloodGlucose].self)?
+//                    .filter { $0.dateString.addingTimeInterval(24.hours.timeInterval) > Date() }
+//                    .sorted { $0.dateString > $1.dateString } ?? []
+//                let glucose = Array(uniqEvents)
+//                storage.save(glucose, as: file)
+//
+//                DispatchQueue.main.async {
+//                    self.broadcaster.notify(GlucoseObserver.self, on: .main) {
+//                        $0.glucoseDidUpdate(glucose.reversed())
+//                    }
+//                }
+
+            // MARK: - Save to CoreData.
+
+            var bg_ = 0
+            var direction = ""
+
+            if glucose.isNotEmpty {
+                bg_ = glucose[0].glucose ?? 0
+                direction = glucose[0].direction?.symbol ?? "↔︎"
+            }
 
 
-                if bg_ != 0 {
-                    self.coredataContext.perform {
-                        let newItem = GlucoseStored(context: self.coredataContext)
-                        newItem.id = UUID()
-                        newItem.glucose = Int16(bg_)
-                        newItem.date = Date()
-                        newItem.direction = direction
-
-                        if self.coredataContext.hasChanges {
-                            do {
-                                try self.coredataContext.save()
-                                debugPrint(
-                                    "Glucose Storage: \(CoreDataStack.identifier) \(DebuggingIdentifiers.succeeded) saved glucose to core data"
-                                )
-                            } catch {
-                                debugPrint(
-                                    "Glucose Storage: \(CoreDataStack.identifier) \(DebuggingIdentifiers.failed) failed to save glucose to core data"
-                                )
-                            }
+            if bg_ != 0 {
+                self.coredataContext.perform {
+                    let newItem = GlucoseStored(context: self.coredataContext)
+                    newItem.id = UUID()
+                    newItem.glucose = Int16(bg_)
+                    newItem.date = Date()
+                    newItem.direction = direction
+
+                    if self.coredataContext.hasChanges {
+                        do {
+                            try self.coredataContext.save()
+                            debugPrint(
+                                "Glucose Storage: \(CoreDataStack.identifier) \(DebuggingIdentifiers.succeeded) saved glucose to core data"
+                            )
+                        } catch {
+                            debugPrint(
+                                "Glucose Storage: \(CoreDataStack.identifier) \(DebuggingIdentifiers.failed) failed to save glucose to core data"
+                            )
                         }
                         }
                     }
                     }
+//                    }
                 }
                 }
             }
             }
 
 
@@ -178,20 +176,13 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     }
     }
 
 
     func syncDate() -> Date {
     func syncDate() -> Date {
-        guard let events = storage.retrieve(OpenAPS.Monitor.glucose, as: [BloodGlucose].self),
-              let recent = events.first
-        else {
-            return Date().addingTimeInterval(-1.days.timeInterval)
-        }
-        return recent.dateString
-    }
-
-    func recent() -> [BloodGlucose] {
-        storage.retrieve(OpenAPS.Monitor.glucose, as: [BloodGlucose].self)?.reversed() ?? []
+        //  TODO: - proof logic here!
+        /// previously the Blood Glucose array was retrieved and the date of the first, i.e. oldest value was returned (called recent????)
+        fetchGlucose().last?.date ?? .distantPast
     }
     }
 
 
     func lastGlucoseDate() -> Date {
     func lastGlucoseDate() -> Date {
-        recent().last?.dateString ?? .distantPast
+        fetchGlucose().first?.date ?? .distantPast
     }
     }
 
 
     func isGlucoseFresh() -> Bool {
     func isGlucoseFresh() -> Bool {
@@ -214,17 +205,99 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
         return filtered
         return filtered
     }
     }
 
 
-    func isGlucoseNotFlat() -> Bool {
-        let count = 3 // check last 3 readings
-        let lastReadings = Array(recent().suffix(count))
-        let filtered = lastReadings.compactMap(\.filtered).filter { $0 != 0 }
-        guard lastReadings.count == count, filtered.count == count else { return true }
-        return Array(filtered.uniqued()).count != 1
+    // MARK: - fetching non manual Glucose, manual Glucose and the last glucose value
+
+    // TODO: -optimize this bullshit here...I would love to use the async/await pattern, but its simply not possible because you would need to change all the calls of the following functions and make them async...same shit with the NSAsynchronousFetchRequest
+    /// its all done on a background thread and on a separate queue so hopefully its not too heavy
+    /// also tried this but here again you need to make everything asynchronous...
+    ///  let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
+    /// privateContext.parent = coredataContext /// merges changes to the core data context
+    private func fetchGlucose() -> [GlucoseStored] {
+        do {
+            debugPrint("OpenAPS: \(#function) \(DebuggingIdentifiers.succeeded) fetched glucose")
+            return try coredataContext.fetch(GlucoseStored.fetch(
+                NSPredicate.predicateForOneDayAgo,
+                ascending: false,
+                fetchLimit: 288,
+                batchSize: 50
+            ))
+        } catch {
+            debugPrint("OpenAPS: \(#function) \(DebuggingIdentifiers.failed) failed to fetch glucose")
+            return []
+        }
+    }
+
+    private func fetchLatestGlucose() -> GlucoseStored? {
+        do {
+            debugPrint("Glucose Storage: \(#function) \(DebuggingIdentifiers.succeeded) fetched glucose")
+            return try coredataContext.fetch(GlucoseStored.fetch(
+                NSPredicate.predicateFor20MinAgo,
+                ascending: false,
+                fetchLimit: 1
+            )).first
+        } catch {
+            debugPrint("Glucose Storage: \(#function) \(DebuggingIdentifiers.failed) failed to fetch glucose")
+            return nil
+        }
+    }
+
+    private func fetchAndProcessManualGlucose() -> [BloodGlucose] {
+        do {
+            let fetchedResults = try coredataContext.fetch(GlucoseStored.fetch(
+                NSPredicate.manualGlucose,
+                ascending: false,
+                fetchLimit: 288,
+                batchSize: 50
+            ))
+            debugPrint("Glucose Storage: \(#function) \(DebuggingIdentifiers.succeeded) fetched manual glucose")
+            let glucoseArray = fetchedResults.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: ""
+                )
+            }
+            return glucoseArray
+        } catch {
+            debugPrint("Glucose Storage: \(#function) \(DebuggingIdentifiers.failed) failed to fetch manual glucose")
+            return []
+        }
+    }
+
+    private func fetchAndProcessGlucose() -> [BloodGlucose] {
+        do {
+            let results = try coredataContext.fetch(GlucoseStored.fetch(
+                NSPredicate.predicateForOneDayAgo,
+                ascending: false,
+                fetchLimit: 288,
+                batchSize: 50
+            ))
+
+            debugPrint("Glucose Storage: \(#function) \(DebuggingIdentifiers.succeeded) fetched glucose")
+
+            let glucoseArray = 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: ""
+                )
+            }
+            return glucoseArray
+        } catch {
+            debugPrint("Glucose Storage: \(#function) \(DebuggingIdentifiers.failed) failed to fetch glucose")
+            return []
+        }
     }
     }
 
 
     func nightscoutGlucoseNotUploaded() -> [BloodGlucose] {
     func nightscoutGlucoseNotUploaded() -> [BloodGlucose] {
         let uploaded = storage.retrieve(OpenAPS.Nightscout.uploadedGlucose, as: [BloodGlucose].self) ?? []
         let uploaded = storage.retrieve(OpenAPS.Nightscout.uploadedGlucose, as: [BloodGlucose].self) ?? []
-        let recentGlucose = recent()
+        let recentGlucose = fetchAndProcessGlucose()
 
 
         return Array(Set(recentGlucose).subtracting(Set(uploaded)))
         return Array(Set(recentGlucose).subtracting(Set(uploaded)))
     }
     }
@@ -238,7 +311,8 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     func nightscoutManualGlucoseNotUploaded() -> [NigtscoutTreatment] {
     func nightscoutManualGlucoseNotUploaded() -> [NigtscoutTreatment] {
         let uploaded = (storage.retrieve(OpenAPS.Nightscout.uploadedGlucose, as: [BloodGlucose].self) ?? [])
         let uploaded = (storage.retrieve(OpenAPS.Nightscout.uploadedGlucose, as: [BloodGlucose].self) ?? [])
             .filter({ $0.type == GlucoseType.manual.rawValue })
             .filter({ $0.type == GlucoseType.manual.rawValue })
-        let recent = recent().filter({ $0.type == GlucoseType.manual.rawValue })
+
+        let recent = fetchAndProcessManualGlucose()
         let filtered = Array(Set(recent).subtracting(Set(uploaded)))
         let filtered = Array(Set(recent).subtracting(Set(uploaded)))
         let manualReadings = filtered.map { item -> NigtscoutTreatment in
         let manualReadings = filtered.map { item -> NigtscoutTreatment in
             NigtscoutTreatment(
             NigtscoutTreatment(
@@ -256,8 +330,10 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     }
     }
 
 
     var alarm: GlucoseAlarm? {
     var alarm: GlucoseAlarm? {
-        guard let glucose = recent().last, glucose.dateString.addingTimeInterval(20.minutes.timeInterval) > Date(),
-              let glucoseValue = glucose.glucose else { return nil }
+        /// glucose can not be older than 20 minutes due to the predicate in the fetch request
+        guard let glucose = fetchLatestGlucose() else { return nil }
+
+        let glucoseValue = glucose.glucose
 
 
         if Decimal(glucoseValue) <= settingsManager.settings.lowGlucose {
         if Decimal(glucoseValue) <= settingsManager.settings.lowGlucose {
             return .low
             return .low

+ 24 - 8
FreeAPS/Sources/Modules/Calibrations/CalibrationsStateModel.swift

@@ -15,6 +15,9 @@ extension Calibrations {
 
 
         var units: GlucoseUnits = .mmolL
         var units: GlucoseUnits = .mmolL
 
 
+        // TODO: - test if we need to use the viewContext here
+        private let context = CoreDataStack.shared.backgroundContext
+
         override func subscribe() {
         override func subscribe() {
             units = settingsManager.settings.units
             units = settingsManager.settings.units
             calibrate = calibrationService.calibrate
             calibrate = calibrationService.calibrate
@@ -30,6 +33,20 @@ extension Calibrations {
             }
             }
         }
         }
 
 
+        private func fetchAndProcessGlucose() -> GlucoseStored? {
+            do {
+                debugPrint("Calibrations State Model: \(#function) \(DebuggingIdentifiers.succeeded) fetched glucose")
+                return try context.fetch(GlucoseStored.fetch(
+                    NSPredicate.predicateFor20MinAgo,
+                    ascending: false,
+                    fetchLimit: 1
+                )).first
+            } catch {
+                debugPrint("Calibrations State Model: \(#function) \(DebuggingIdentifiers.failed) failed to fetch glucose")
+                return nil
+            }
+        }
+
         func addCalibration() {
         func addCalibration() {
             defer {
             defer {
                 UIApplication.shared.endEditing()
                 UIApplication.shared.endEditing()
@@ -41,17 +58,16 @@ extension Calibrations {
                 glucose = newCalibration.asMgdL
                 glucose = newCalibration.asMgdL
             }
             }
 
 
-            guard let lastGlucose = glucoseStorage.recent().last,
-                  lastGlucose.dateString.addingTimeInterval(60 * 4.5) > Date(),
-                  let unfiltered = lastGlucose.unfiltered
-            else {
+            if let lastGlucose = fetchAndProcessGlucose() {
+                let unfiltered = lastGlucose.glucose
+
+                let calibration = Calibration(x: Double(unfiltered), y: Double(glucose))
+
+                calibrationService.addCalibration(calibration)
+            } else {
                 info(.service, "Glucose is stale for calibration")
                 info(.service, "Glucose is stale for calibration")
                 return
                 return
             }
             }
-
-            let calibration = Calibration(x: Double(unfiltered), y: Double(glucose))
-
-            calibrationService.addCalibration(calibration)
         }
         }
 
 
         func removeLast() {
         func removeLast() {

+ 0 - 2
FreeAPS/Sources/Modules/DataTable/DataTableDataFlow.swift

@@ -220,8 +220,6 @@ protocol DataTableProvider: Provider {
     func pumpHistory() -> [PumpHistoryEvent]
     func pumpHistory() -> [PumpHistoryEvent]
     func tempTargets() -> [TempTarget]
     func tempTargets() -> [TempTarget]
     func carbs() -> [CarbsEntry]
     func carbs() -> [CarbsEntry]
-    func glucose() -> [BloodGlucose]
     func deleteCarbs(_ treatement: DataTable.Treatment)
     func deleteCarbs(_ treatement: DataTable.Treatment)
     func deleteInsulin(_ treatement: DataTable.Treatment)
     func deleteInsulin(_ treatement: DataTable.Treatment)
-    func deleteGlucose(id: String)
 }
 }

+ 0 - 9
FreeAPS/Sources/Modules/DataTable/DataTableProvider.swift

@@ -42,15 +42,6 @@ extension DataTable {
             }
             }
         }
         }
 
 
-        func glucose() -> [BloodGlucose] {
-            glucoseStorage.recent().sorted { $0.date > $1.date }
-        }
-
-        func deleteGlucose(id: String) {
-            glucoseStorage.removeGlucose(ids: [id])
-            healthkitManager.deleteGlucose(syncID: id)
-        }
-
         func deleteManualGlucose(date: Date?) {
         func deleteManualGlucose(date: Date?) {
             nightscoutManager.deleteManualGlucose(at: date ?? .distantPast)
             nightscoutManager.deleteManualGlucose(at: date ?? .distantPast)
         }
         }

+ 3 - 48
FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift

@@ -31,12 +31,10 @@ extension DataTable {
             maxBolus = provider.pumpSettings().maxBolus
             maxBolus = provider.pumpSettings().maxBolus
             historyLayout = settingsManager.settings.historyLayout
             historyLayout = settingsManager.settings.historyLayout
             setupTreatments()
             setupTreatments()
-            setupGlucose()
             broadcaster.register(SettingsObserver.self, observer: self)
             broadcaster.register(SettingsObserver.self, observer: self)
             broadcaster.register(PumpHistoryObserver.self, observer: self)
             broadcaster.register(PumpHistoryObserver.self, observer: self)
             broadcaster.register(TempTargetsObserver.self, observer: self)
             broadcaster.register(TempTargetsObserver.self, observer: self)
             broadcaster.register(CarbsObserver.self, observer: self)
             broadcaster.register(CarbsObserver.self, observer: self)
-            broadcaster.register(GlucoseObserver.self, observer: self)
             broadcaster.register(SuggestionObserver.self, observer: self)
             broadcaster.register(SuggestionObserver.self, observer: self)
         }
         }
 
 
@@ -154,12 +152,6 @@ extension DataTable {
             }
             }
         }
         }
 
 
-        func setupGlucose() {
-            DispatchQueue.main.async {
-                self.glucose = self.provider.glucose().map(Glucose.init)
-            }
-        }
-
         func invokeCarbDeletionTask(_ treatment: Treatment) {
         func invokeCarbDeletionTask(_ treatment: Treatment) {
             carbEntryDeleted = true
             carbEntryDeleted = true
             waitForSuggestion = true
             waitForSuggestion = true
@@ -195,38 +187,6 @@ extension DataTable {
             }
             }
         }
         }
 
 
-        func deleteGlucose(_ glucose: Glucose) {
-            let id = glucose.id
-            provider.deleteGlucose(id: id)
-
-            let fetchRequest: NSFetchRequest<NSFetchRequestResult>
-            fetchRequest = NSFetchRequest(entityName: "GlucoseStored")
-            fetchRequest.predicate = NSPredicate(format: "id == %@", id)
-            let deleteRequest = NSBatchDeleteRequest(
-                fetchRequest: fetchRequest
-            )
-            deleteRequest.resultType = .resultTypeObjectIDs
-            do {
-                let deleteResult = try coredataContext.execute(deleteRequest) as? NSBatchDeleteResult
-                if let objectIDs = deleteResult?.result as? [NSManagedObjectID] {
-                    NSManagedObjectContext.mergeChanges(
-                        fromRemoteContextSave: [NSDeletedObjectsKey: objectIDs],
-                        into: [coredataContext]
-                    )
-                }
-                debugPrint("Data Table State: \(CoreDataStack.identifier) \(DebuggingIdentifiers.succeeded) deleted glucose")
-            } catch {
-                debugPrint(
-                    "Data Table State: \(CoreDataStack.identifier) \(DebuggingIdentifiers.failed) failed to delete glucose"
-                )
-            }
-
-            // Deletes Manual Glucose
-            if (glucose.glucose.type ?? "") == GlucoseType.manual.rawValue {
-                provider.deleteManualGlucose(date: glucose.glucose.dateString)
-            }
-        }
-
         func addManualGlucose() {
         func addManualGlucose() {
             let glucose = units == .mmolL ? manualGlucose.asMgdL : manualGlucose
             let glucose = units == .mmolL ? manualGlucose.asMgdL : manualGlucose
             let glucoseAsInt = Int(glucose)
             let glucoseAsInt = Int(glucose)
@@ -244,8 +204,8 @@ extension DataTable {
                 glucose: Int(glucose),
                 glucose: Int(glucose),
                 type: GlucoseType.manual.rawValue
                 type: GlucoseType.manual.rawValue
             )
             )
-            provider.glucoseStorage.storeGlucose([saveToJSON])
-            debug(.default, "Manual Glucose saved to glucose.json")
+
+            // TODO: -do we need this?
             // Save to Health
             // Save to Health
             var saveToHealth = [BloodGlucose]()
             var saveToHealth = [BloodGlucose]()
             saveToHealth.append(saveToJSON)
             saveToHealth.append(saveToJSON)
@@ -277,8 +237,7 @@ extension DataTable.StateModel:
     SettingsObserver,
     SettingsObserver,
     PumpHistoryObserver,
     PumpHistoryObserver,
     TempTargetsObserver,
     TempTargetsObserver,
-    CarbsObserver,
-    GlucoseObserver
+    CarbsObserver
 {
 {
     func settingsDidChange(_: FreeAPSSettings) {
     func settingsDidChange(_: FreeAPSSettings) {
         historyLayout = settingsManager.settings.historyLayout
         historyLayout = settingsManager.settings.historyLayout
@@ -296,10 +255,6 @@ extension DataTable.StateModel:
     func carbsDidUpdate(_: [CarbsEntry]) {
     func carbsDidUpdate(_: [CarbsEntry]) {
         setupTreatments()
         setupTreatments()
     }
     }
-
-    func glucoseDidUpdate(_: [BloodGlucose]) {
-        setupGlucose()
-    }
 }
 }
 
 
 extension DataTable.StateModel: SuggestionObserver {
 extension DataTable.StateModel: SuggestionObserver {

+ 58 - 59
FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift

@@ -16,7 +16,16 @@ extension DataTable {
         @State private var showManualGlucose: Bool = false
         @State private var showManualGlucose: Bool = false
         @State private var isAmountUnconfirmed: Bool = true
         @State private var isAmountUnconfirmed: Bool = true
 
 
+        @State private var showAlert = false
+
         @Environment(\.colorScheme) var colorScheme
         @Environment(\.colorScheme) var colorScheme
+        @Environment(\.managedObjectContext) var context
+
+        /// fetch ALL glucose values, i.e. manual and non-manual
+        @FetchRequest(
+            fetchRequest: GlucoseStored.fetch(NSPredicate.predicateForOneDayAgo, ascending: false, fetchLimit: 288),
+            animation: .bouncy
+        ) var glucoseStored: FetchedResults<GlucoseStored>
 
 
         private var insulinFormatter: NumberFormatter {
         private var insulinFormatter: NumberFormatter {
             let formatter = NumberFormatter()
             let formatter = NumberFormatter()
@@ -203,16 +212,50 @@ extension DataTable {
                     Spacer()
                     Spacer()
                     Text("Time").foregroundStyle(.secondary)
                     Text("Time").foregroundStyle(.secondary)
                 }
                 }
-                if !state.glucose.isEmpty {
-                    ForEach(state.glucose) { item in
-                        glucoseView(item, isManual: item.glucose)
-                    }
+                if !glucoseStored.isEmpty {
+                    ForEach(glucoseStored) { glucose in
+                        HStack {
+                            Text(formatGlucose(glucose.glucose, isManual: glucose.isManual))
+
+                            /// check for manual glucose
+                            if glucose.isManual {
+                                Image(systemName: "drop.fill").symbolRenderingMode(.monochrome).foregroundStyle(.red)
+                            } else {
+                                Text("\(glucose.direction ?? "--")")
+                            }
+
+                            Spacer()
+
+                            Text(dateFormatter.string(from: glucose.date ?? Date()))
+                        }
+                    }.onDelete(perform: deleteGlucose)
                 } else {
                 } else {
                     HStack {
                     HStack {
                         Text("No data.")
                         Text("No data.")
                     }
                     }
                 }
                 }
             }.listRowBackground(Color.chart)
             }.listRowBackground(Color.chart)
+                .alert(isPresented: $showAlert) {
+                    Alert(title: Text("Error"), message: Text(alertMessage), dismissButton: .default(Text("OK")))
+                }
+        }
+
+        private func deleteGlucose(at offsets: IndexSet) {
+            for index in offsets {
+                let glucoseToDelete = glucoseStored[index]
+                context.delete(glucoseToDelete)
+            }
+
+            do {
+                try context.save()
+                debugPrint("Data Table Root View: \(#function) \(DebuggingIdentifiers.succeeded) deleted glucose from core data")
+            } catch {
+                debugPrint(
+                    "Data Table Root View: \(#function) \(DebuggingIdentifiers.failed) error while deleting glucose from core data"
+                )
+                alertMessage = "Failed to delete glucose data: \(error.localizedDescription)"
+                showAlert = true
+            }
         }
         }
 
 
         @ViewBuilder private func addGlucoseView() -> some View {
         @ViewBuilder private func addGlucoseView() -> some View {
@@ -398,62 +441,18 @@ extension DataTable {
             }
             }
         }
         }
 
 
-        @ViewBuilder private func glucoseView(_ item: Glucose, isManual: BloodGlucose) -> some View {
-            HStack {
-                Text(item.glucose.glucose.map {
-                    (
-                        isManual.type == GlucoseType.manual.rawValue ?
-                            manualGlucoseFormatter :
-                            glucoseFormatter
-                    )
-                    .string(from: Double(
-                        state.units == .mmolL ? $0.asMmolL : Decimal($0)
-                    ) as NSNumber)!
-                } ?? "--")
-                if isManual.type == GlucoseType.manual.rawValue {
-                    Image(systemName: "drop.fill").symbolRenderingMode(.monochrome).foregroundStyle(.red)
-                } else {
-                    Text(item.glucose.direction?.symbol ?? "--")
-                }
-                Spacer()
+        // MARK: - Format glucose
 
 
-                Text(dateFormatter.string(from: item.glucose.dateString))
-            }
-            .swipeActions {
-                Button(
-                    "Delete",
-                    systemImage: "trash.fill",
-                    role: .none,
-                    action: {
-                        alertGlucoseToDelete = item
-                        let valueText = (
-                            isManual.type == GlucoseType.manual.rawValue ?
-                                manualGlucoseFormatter :
-                                glucoseFormatter
-                        ).string(from: Double(
-                            state.units == .mmolL ? Double(item.glucose.value.asMmolL) : item.glucose.value
-                        ) as NSNumber)! + " " + state.units.rawValue
-                        alertTitle = "Delete Glucose?"
-                        alertMessage = dateFormatter.string(from: item.glucose.dateString) + ", " + valueText
-                        isRemoveHistoryItemAlertPresented = true
-                    }
-                ).tint(.red)
-            }
-            .alert(
-                Text(NSLocalizedString(alertTitle, comment: "")),
-                isPresented: $isRemoveHistoryItemAlertPresented
-            ) {
-                Button("Cancel", role: .cancel) {}
-                Button("Delete", role: .destructive) {
-                    guard let glucoseToDelete = alertGlucoseToDelete else {
-                        print("Cannot unwrap alertTreatmentToDelete!")
-                        return
-                    }
-                    state.deleteGlucose(glucoseToDelete)
-                }
-            } message: {
-                Text("\n" + NSLocalizedString(alertMessage, comment: ""))
-            }
+        private func formatGlucose(_ value: Int16, isManual: Bool) -> String {
+            let formatter = isManual ? manualGlucoseFormatter : glucoseFormatter
+            let formattedValue = formatter.string(from: NSNumber(value: value)) ?? "--"
+
+            return state.units == .mmolL ? convertToMMOL(Double(value)) : formattedValue
+        }
+
+        private func convertToMMOL(_ value: Double) -> String {
+            let mmolValue = value * 0.0555
+            return "\(mmolValue) mmol/L"
         }
         }
     }
     }
 }
 }

+ 0 - 2
FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift

@@ -140,8 +140,6 @@ extension Settings {
                                 .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.announcementsEnacted), from: self)
                                 .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.announcementsEnacted), from: self)
                             Text("Autotune")
                             Text("Autotune")
                                 .navigationLink(to: .configEditor(file: OpenAPS.Settings.autotune), from: self)
                                 .navigationLink(to: .configEditor(file: OpenAPS.Settings.autotune), from: self)
-                            Text("Glucose")
-                                .navigationLink(to: .configEditor(file: OpenAPS.Monitor.glucose), from: self)
                         }
                         }
 
 
                         Group {
                         Group {

+ 28 - 19
FreeAPS/Sources/Services/Calendar/CalendarManager.swift

@@ -7,7 +7,7 @@ protocol CalendarManager {
     func requestAccessIfNeeded() -> AnyPublisher<Bool, Never>
     func requestAccessIfNeeded() -> AnyPublisher<Bool, Never>
     func calendarIDs() -> [String]
     func calendarIDs() -> [String]
     var currentCalendarID: String? { get set }
     var currentCalendarID: String? { get set }
-    func createEvent(for glucose: BloodGlucose?, delta: Int?)
+    func createEvent(for glucose: GlucoseStored, delta: Int)
 }
 }
 
 
 final class BaseCalendarManager: CalendarManager, Injectable {
 final class BaseCalendarManager: CalendarManager, Injectable {
@@ -88,14 +88,14 @@ final class BaseCalendarManager: CalendarManager, Injectable {
         EKEventStore().calendars(for: .event).map(\.title)
         EKEventStore().calendars(for: .event).map(\.title)
     }
     }
 
 
-    func createEvent(for glucose: BloodGlucose?, delta: Int?) {
+    func createEvent(for glucose: GlucoseStored, delta: Int) {
         guard settingsManager.settings.useCalendar else { return }
         guard settingsManager.settings.useCalendar else { return }
 
 
         guard let calendar = currentCalendar else { return }
         guard let calendar = currentCalendar else { return }
 
 
         deleteAllEvents(in: calendar)
         deleteAllEvents(in: calendar)
 
 
-        guard let glucose = glucose, let glucoseValue = glucose.glucose else { return }
+        let glucoseValue = glucose.glucose
 
 
         // create an event now
         // create an event now
         let event = EKEvent(eventStore: eventStore)
         let event = EKEvent(eventStore: eventStore)
@@ -127,15 +127,14 @@ final class BaseCalendarManager: CalendarManager, Injectable {
 
 
         let glucoseText = glucoseFormatter
         let glucoseText = glucoseFormatter
             .string(from: Double(
             .string(from: Double(
-                settingsManager.settings.units == .mmolL ?glucoseValue
+                settingsManager.settings.units == .mmolL ? Int(glucoseValue)
                     .asMmolL : Decimal(glucoseValue)
                     .asMmolL : Decimal(glucoseValue)
             ) as NSNumber)!
             ) as NSNumber)!
-        let directionText = glucose.direction?.symbol ?? "↔︎"
-        let deltaText = delta
-            .map {
-                deltaFormatter
-                    .string(from: Double(settingsManager.settings.units == .mmolL ? $0.asMmolL : Decimal($0)) as NSNumber)!
-            } ?? "--"
+
+        let directionText = glucose.direction ?? "↔︎"
+
+        let deltaValue = settingsManager.settings.units == .mmolL ? Int(delta.asMmolL) : delta
+        let deltaText = deltaFormatter.string(from: NSNumber(value: deltaValue)) ?? "--"
 
 
         let iobText = iobFormatter.string(from: (lastLoop.first?.iob ?? 0) as NSNumber) ?? ""
         let iobText = iobFormatter.string(from: (lastLoop.first?.iob ?? 0) as NSNumber) ?? ""
         let cobText = cobFormatter.string(from: (lastLoop.first?.cob ?? 0) as NSNumber) ?? ""
         let cobText = cobFormatter.string(from: (lastLoop.first?.cob ?? 0) as NSNumber) ?? ""
@@ -230,16 +229,26 @@ final class BaseCalendarManager: CalendarManager, Injectable {
         return formatter
         return formatter
     }
     }
 
 
-    func setupGlucose() {
-        let glucose = glucoseStorage.recent()
-        let recentGlucose = glucose.last
-        let glucoseDelta: Int?
-        if glucose.count >= 2 {
-            glucoseDelta = (recentGlucose?.glucose ?? 0) - (glucose[glucose.count - 2].glucose ?? 0)
-        } else {
-            glucoseDelta = nil
+    private func fetchAndProcessGlucose() -> [GlucoseStored]? {
+        do {
+            debugPrint("Calendar Manager: \(#function) \(DebuggingIdentifiers.succeeded) fetched glucose")
+            return try coredataContext.fetch(GlucoseStored.fetch(
+                NSPredicate.predicateFor20MinAgo,
+                ascending: false,
+                fetchLimit: 3
+            ))
+        } catch {
+            debugPrint("Calendar Manager: \(#function) \(DebuggingIdentifiers.failed) failed to fetch glucose")
+            return []
         }
         }
-        createEvent(for: recentGlucose, delta: glucoseDelta)
+    }
+
+    func setupGlucose() {
+        guard let glucose = fetchAndProcessGlucose(), let lastGlucose = glucose.first, let lastReading = glucose.first?.glucose,
+              let secondLastReading = glucose.dropFirst().first?.glucose else { return }
+
+        let glucoseDelta = lastReading - secondLastReading
+        createEvent(for: lastGlucose, delta: Int(glucoseDelta))
     }
     }
 }
 }
 
 

+ 27 - 7
FreeAPS/Sources/Services/UserNotifiactions/UserNotificationsManager.swift

@@ -52,6 +52,8 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
     private let center = UNUserNotificationCenter.current()
     private let center = UNUserNotificationCenter.current()
     private var lifetime = Lifetime()
     private var lifetime = Lifetime()
 
 
+    private let context = CoreDataStack.shared.viewContext
+
     init(resolver: Resolver) {
     init(resolver: Resolver) {
         super.init()
         super.init()
         center.delegate = self
         center.delegate = self
@@ -182,13 +184,27 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
         }
         }
     }
     }
 
 
+    private func fetchAndProcessGlucose() -> [GlucoseStored]? {
+        do {
+            debugPrint("Calibrations State Model: \(#function) \(DebuggingIdentifiers.succeeded) fetched glucose")
+            return try context.fetch(GlucoseStored.fetch(
+                NSPredicate.predicateFor20MinAgo,
+                ascending: false,
+                fetchLimit: 3
+            ))
+        } catch {
+            debugPrint("Calibrations State Model: \(#function) \(DebuggingIdentifiers.failed) failed to fetch glucose")
+            return []
+        }
+    }
+
     private func sendGlucoseNotification() {
     private func sendGlucoseNotification() {
         addAppBadge(glucose: nil)
         addAppBadge(glucose: nil)
 
 
-        let glucose = glucoseStorage.recent()
-        guard let lastGlucose = glucose.last, let glucoseValue = lastGlucose.glucose else { return }
+        guard let glucose = fetchAndProcessGlucose(), let lastValue = glucose.first, let lastReading = glucose.first?.glucose,
+              let secondLastReading = glucose.dropFirst().first?.glucose else { return }
 
 
-        addAppBadge(glucose: lastGlucose.glucose)
+        addAppBadge(glucose: (glucose.first?.glucose).map { Int($0) })
 
 
         guard glucoseStorage.alarm != nil || settingsManager.settings.glucoseNotificationsAlways else {
         guard glucoseStorage.alarm != nil || settingsManager.settings.glucoseNotificationsAlways else {
             return
             return
@@ -209,8 +225,12 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
                 notificationAlarm = true
                 notificationAlarm = true
             }
             }
 
 
-            let delta = glucose.count >= 2 ? glucoseValue - (glucose[glucose.count - 2].glucose ?? 0) : nil
-            let body = self.glucoseText(glucoseValue: glucoseValue, delta: delta, direction: lastGlucose.direction) + self
+            let delta = glucose.count >= 2 ? lastReading - secondLastReading : nil
+            let body = self.glucoseText(
+                glucoseValue: (glucose.first?.glucose).map { Int($0) } ?? 0,
+                delta: Int(delta ?? 0),
+                direction: lastValue.direction
+            ) + self
                 .infoBody()
                 .infoBody()
 
 
             if self.snoozeUntilDate > Date() {
             if self.snoozeUntilDate > Date() {
@@ -233,14 +253,14 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
         }
         }
     }
     }
 
 
-    private func glucoseText(glucoseValue: Int, delta: Int?, direction: BloodGlucose.Direction?) -> String {
+    private func glucoseText(glucoseValue: Int, delta: Int?, direction: String?) -> String {
         let units = settingsManager.settings.units
         let units = settingsManager.settings.units
         let glucoseText = glucoseFormatter
         let glucoseText = glucoseFormatter
             .string(from: Double(
             .string(from: Double(
                 units == .mmolL ? glucoseValue
                 units == .mmolL ? glucoseValue
                     .asMmolL : Decimal(glucoseValue)
                     .asMmolL : Decimal(glucoseValue)
             ) as NSNumber)! + " " + NSLocalizedString(units.rawValue, comment: "units")
             ) as NSNumber)! + " " + NSLocalizedString(units.rawValue, comment: "units")
-        let directionText = direction?.symbol ?? "↔︎"
+        let directionText = direction ?? "↔︎"
         let deltaText = delta
         let deltaText = delta
             .map {
             .map {
                 self.deltaFormatter
                 self.deltaFormatter

+ 34 - 14
Model/Helper/GlucoseStored+helper.swift

@@ -2,13 +2,21 @@ import CoreData
 import Foundation
 import Foundation
 
 
 extension GlucoseStored {
 extension GlucoseStored {
-    static func fetch(_ predicate: NSPredicate = .all, ascending: Bool, fetchLimit: Int? = nil) -> NSFetchRequest<GlucoseStored> {
+    static func fetch(
+        _ predicate: NSPredicate = .all,
+        ascending: Bool,
+        fetchLimit: Int? = nil,
+        batchSize: Int? = nil
+    ) -> NSFetchRequest<GlucoseStored> {
         let request = GlucoseStored.fetchRequest()
         let request = GlucoseStored.fetchRequest()
         request.sortDescriptors = [NSSortDescriptor(keyPath: \GlucoseStored.date, ascending: ascending)]
         request.sortDescriptors = [NSSortDescriptor(keyPath: \GlucoseStored.date, ascending: ascending)]
         request.predicate = predicate
         request.predicate = predicate
         if let limit = fetchLimit {
         if let limit = fetchLimit {
             request.fetchLimit = limit
             request.fetchLimit = limit
         }
         }
+        if let batchSize = batchSize {
+            request.fetchBatchSize = batchSize
+        }
         return request
         return request
     }
     }
 
 
@@ -20,19 +28,6 @@ extension GlucoseStored {
 
 
         return lastThreeValues.allSatisfy { $0.glucose == firstValue }
         return lastThreeValues.allSatisfy { $0.glucose == firstValue }
     }
     }
-
-//    static func asyncFetch(_ predicate: NSPredicate = NSPredicate(value: true), completion: @escaping (NSAsynchronousFetchResult<GlucoseStored>)->Void) -> NSAsynchronousFetchRequest<GlucoseStored> {
-//           let request: NSFetchRequest<GlucoseStored> = GlucoseStored.fetchRequest()
-//           request.sortDescriptors = [NSSortDescriptor(keyPath: \GlucoseStored.date, ascending: true)]
-//           request.predicate = predicate
-//
-//           // Erstelle einen NSAsynchronousFetchRequest mit einem Completion Handler
-//           let asyncFetchRequest = NSAsynchronousFetchRequest<GlucoseStored>(fetchRequest: request) { result in
-//               completion(result)
-//           }
-//
-//           return asyncFetchRequest
-//       }
 }
 }
 
 
 extension NSPredicate {
 extension NSPredicate {
@@ -50,3 +45,28 @@ extension NSPredicate {
 protocol GlucoseStoredObserver {
 protocol GlucoseStoredObserver {
     func glucoseDidUpdate(_ glucose: [GlucoseStored])
     func glucoseDidUpdate(_ glucose: [GlucoseStored])
 }
 }
+
+extension GlucoseStored: Encodable {
+    enum CodingKeys: String, CodingKey {
+        case date
+        case sgv
+        case glucose
+        case direction
+        case id
+    }
+
+    public func encode(to encoder: Encoder) throws {
+        var container = encoder.container(keyedBy: CodingKeys.self)
+
+        let dateString = String(format: "%.0f", (date?.timeIntervalSince1970 ?? Date().timeIntervalSince1970) * 1000)
+        try container.encode(dateString, forKey: .date)
+        try container.encode(direction, forKey: .direction)
+        try container.encode(id, forKey: .id)
+
+        if isManual {
+            try container.encode(glucose, forKey: .glucose)
+        } else {
+            try container.encode(glucose, forKey: .sgv)
+        }
+    }
+}

+ 21 - 0
Model/Helper/JSONConverter.swift

@@ -0,0 +1,21 @@
+import Foundation
+
+class JSONConverter {
+    /// this is temporarily used to parse the fetched Core Data objects to JSON in order to pass it to DetermineBasal()
+    func convertToJSON<T: Encodable>(_ value: T?) -> String {
+        guard let value = value else { return "" }
+
+        let encoder = JSONEncoder()
+        encoder.keyEncodingStrategy = .useDefaultKeys
+        do {
+            let jsonData = try encoder.encode(value)
+            if let jsonString = String(data: jsonData, encoding: .utf8) {
+                return jsonString
+            }
+        } catch {
+            debugPrint("\(DebuggingIdentifiers.failed) could not convert object to JSON: \(error)")
+        }
+
+        return "could not convert object to JSON"
+    }
+}

+ 32 - 6
Model/Helper/MealsStored+helper.swift

@@ -14,13 +14,39 @@ extension NSPredicate {
 }
 }
 
 
 extension MealsStored {
 extension MealsStored {
-    static func fetch(_ predicate: NSPredicate = .predicateForOneDayAgo) -> NSFetchRequest<MealsStored> {
-        let request = MealsStored.fetchRequest()
-        request.sortDescriptors = [NSSortDescriptor(keyPath: \MealsStored.date, ascending: false)]
-        request.fetchLimit = 100
+    static func fetch(
+        _ predicate: NSPredicate = .predicateForOneDayAgo,
+        fetchLimit: Int = 100,
+        ascending: Bool = false
+    ) -> NSFetchRequest<MealsStored> {
+        let request = MealsStored.fetchRequest() as NSFetchRequest<MealsStored>
+        request.sortDescriptors = [NSSortDescriptor(keyPath: \MealsStored.date, ascending: ascending)]
+        request.fetchLimit = fetchLimit
         request.predicate = predicate
         request.predicate = predicate
-//        request.propertiesToFetch = ["date", "carbs"]
-//        request.resultType = .dictionaryResultType
         return request
         return request
     }
     }
 }
 }
+
+extension MealsStored: Encodable {
+    enum CodingKeys: String, CodingKey {
+        case date
+        case carbs
+        case fat
+        case id
+        case isFPU
+        case note
+        case protein
+    }
+
+    public func encode(to encoder: Encoder) throws {
+        var container = encoder.container(keyedBy: CodingKeys.self)
+
+        try container.encode(date, forKey: .date)
+        try container.encode(carbs, forKey: .carbs)
+        try container.encode(fat, forKey: .fat)
+        try container.encode(isFPU, forKey: .isFPU)
+        try container.encode(note, forKey: .note)
+        try container.encode(protein, forKey: .protein)
+        try container.encode(id, forKey: .id)
+    }
+}

+ 9 - 0
Model/Helper/NSPredicates.swift

@@ -10,6 +10,10 @@ extension Date {
         Calendar.current.date(byAdding: .minute, value: -30, to: Date())!
         Calendar.current.date(byAdding: .minute, value: -30, to: Date())!
     }
     }
 
 
+    static var twentyMinutesAgo: Date {
+        Calendar.current.date(byAdding: .minute, value: -20, to: Date())!
+    }
+
     static var twoHoursAgo: Date {
     static var twoHoursAgo: Date {
         Calendar.current.date(byAdding: .hour, value: -2, to: Date())!
         Calendar.current.date(byAdding: .hour, value: -2, to: Date())!
     }
     }
@@ -46,6 +50,11 @@ extension NSPredicate {
         return NSPredicate(format: "date >= %@", date as NSDate)
         return NSPredicate(format: "date >= %@", date as NSDate)
     }
     }
 
 
+    static var predicateFor20MinAgo: NSPredicate {
+        let date = Date.twentyMinutesAgo
+        return NSPredicate(format: "date >= %@", date as NSDate)
+    }
+
     static var predicateFor30MinAgoForDetermination: NSPredicate {
     static var predicateFor30MinAgoForDetermination: NSPredicate {
         let date = Date.halfHourAgo
         let date = Date.halfHourAgo
         return NSPredicate(format: "deliverAt >= %@", date as NSDate)
         return NSPredicate(format: "deliverAt >= %@", date as NSDate)