Quellcode durchsuchen

Bolus from watch (#326)

Use same calculator on Watch as on iPhone app.
Jon B Mårtensson vor 2 Jahren
Ursprung
Commit
f781fec66e

+ 1 - 0
Core_Data.xcdatamodeld/Core_Data.xcdatamodel/contents

@@ -129,6 +129,7 @@
     </entity>
     <entity name="Readings" representedClassName="Readings" syncable="YES" codeGenerationType="class">
         <attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
+        <attribute name="direction" optional="YES" attributeType="String"/>
         <attribute name="glucose" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
         <attribute name="id" optional="YES" attributeType="String"/>
     </entity>

+ 4 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -23,6 +23,7 @@
 		1927C8E62744606D00347C69 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1927C8E82744606D00347C69 /* InfoPlist.strings */; };
 		1935364028496F7D001E0B16 /* Oref2_variables.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1935363F28496F7D001E0B16 /* Oref2_variables.swift */; };
 		193F6CDD2A512C8F001240FD /* Loops.swift in Sources */ = {isa = PBXBuildFile; fileRef = 193F6CDC2A512C8F001240FD /* Loops.swift */; };
+		1956FB212AFF79E200C7B4FF /* CoreDataStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1956FB202AFF79E200C7B4FF /* CoreDataStorage.swift */; };
 		195D80B42AF6973A00D25097 /* DynamicRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 195D80B32AF6973A00D25097 /* DynamicRootView.swift */; };
 		195D80B72AF697B800D25097 /* DynamicDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 195D80B62AF697B800D25097 /* DynamicDataFlow.swift */; };
 		195D80B92AF697F700D25097 /* DynamicProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 195D80B82AF697F700D25097 /* DynamicProvider.swift */; };
@@ -532,6 +533,7 @@
 		1927C8FE274489BA00347C69 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/InfoPlist.strings; sourceTree = "<group>"; };
 		1935363F28496F7D001E0B16 /* Oref2_variables.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Oref2_variables.swift; sourceTree = "<group>"; };
 		193F6CDC2A512C8F001240FD /* Loops.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Loops.swift; sourceTree = "<group>"; };
+		1956FB202AFF79E200C7B4FF /* CoreDataStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataStorage.swift; sourceTree = "<group>"; };
 		195D80B32AF6973A00D25097 /* DynamicRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicRootView.swift; sourceTree = "<group>"; };
 		195D80B62AF697B800D25097 /* DynamicDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicDataFlow.swift; sourceTree = "<group>"; };
 		195D80B82AF697F700D25097 /* DynamicProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicProvider.swift; sourceTree = "<group>"; };
@@ -1706,6 +1708,7 @@
 				38FCF3FC25E997A80078B0D1 /* PumpHistoryStorage.swift */,
 				38F3B2EE25ED8E2A005C48AA /* TempTargetsStorage.swift */,
 				CE82E02428E867BA00473A9C /* AlertStorage.swift */,
+				1956FB202AFF79E200C7B4FF /* CoreDataStorage.swift */,
 			);
 			path = Storage;
 			sourceTree = "<group>";
@@ -2905,6 +2908,7 @@
 				E25073BC86C11C3D6A42F5AC /* CalibrationsStateModel.swift in Sources */,
 				BA90041DC8991147E5C8C3AA /* CalibrationsRootView.swift in Sources */,
 				E3A08AAE59538BC8A8ABE477 /* NotificationsConfigDataFlow.swift in Sources */,
+				1956FB212AFF79E200C7B4FF /* CoreDataStorage.swift in Sources */,
 				0F7A65FBD2CD8D6477ED4539 /* NotificationsConfigProvider.swift in Sources */,
 				3171D2818C7C72CD1584BB5E /* NotificationsConfigStateModel.swift in Sources */,
 				CD78BB94E43B249D60CC1A1B /* NotificationsConfigRootView.swift in Sources */,

+ 34 - 0
FreeAPS/Sources/APS/Storage/CoreDataStorage.swift

@@ -0,0 +1,34 @@
+import CoreData
+import Foundation
+import SwiftDate
+import Swinject
+
+final class CoreDataStorage {
+    let coredataContext = CoreDataStack.shared.persistentContainer.viewContext // newBackgroundContext()
+
+    func fetchGlucose(interval: NSDate) -> [Readings] {
+        var fetchGlucose = [Readings]()
+        coredataContext.performAndWait {
+            let requestReadings = Readings.fetchRequest() as NSFetchRequest<Readings>
+            let sort = NSSortDescriptor(key: "date", ascending: false)
+            requestReadings.sortDescriptors = [sort]
+            requestReadings.predicate = NSPredicate(
+                format: "glucose > 0 AND date > %@", interval
+            )
+            try? fetchGlucose = self.coredataContext.fetch(requestReadings)
+        }
+        return fetchGlucose
+    }
+
+    func fetchLatestOverride() -> [Override] {
+        var overrideArray = [Override]()
+        coredataContext.performAndWait {
+            let requestOverrides = Override.fetchRequest() as NSFetchRequest<Override>
+            let sortOverride = NSSortDescriptor(key: "date", ascending: false)
+            requestOverrides.sortDescriptors = [sortOverride]
+            requestOverrides.fetchLimit = 1
+            try? overrideArray = self.coredataContext.fetch(requestOverrides)
+        }
+        return overrideArray
+    }
+}

+ 3 - 0
FreeAPS/Sources/APS/Storage/GlucoseStorage.swift

@@ -71,11 +71,13 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
                 var bg_ = 0
                 var bgDate = Date()
                 var id = ""
+                var direction = ""
 
                 if glucose.isNotEmpty {
                     bg_ = glucose[0].glucose ?? 0
                     bgDate = glucose[0].dateString
                     id = glucose[0].id
+                    direction = glucose[0].direction?.symbol ?? "↔︎"
                 }
 
                 if bg_ != 0 {
@@ -84,6 +86,7 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
                         dataForForStats.date = bgDate
                         dataForForStats.glucose = Int16(bg_)
                         dataForForStats.id = id
+                        dataForForStats.direction = direction
                         try? self.coredataContext.save()
                     }
                 }

+ 1 - 0
FreeAPS/Sources/Models/DateFilter.swift

@@ -2,6 +2,7 @@
 import Foundation
 
 struct DateFilter {
+    var twoHours = Date().addingTimeInterval(-2.hours.timeInterval) as NSDate
     var today = Calendar.current.startOfDay(for: Date()) as NSDate
     var day = Date().addingTimeInterval(-24.hours.timeInterval) as NSDate
     var week = Date().addingTimeInterval(-7.days.timeInterval) as NSDate

+ 2 - 14
FreeAPS/Sources/Modules/Bolus/BolusProvider.swift

@@ -1,8 +1,6 @@
-import CoreData
-
 extension Bolus {
     final class Provider: BaseProvider, BolusProvider {
-        let coredataContext = CoreDataStack.shared.persistentContainer.viewContext
+        let coreDataStorage = CoreDataStorage()
 
         var suggestion: Suggestion? {
             storage.retrieve(OpenAPS.Enact.suggested, as: Suggestion.self)
@@ -15,17 +13,7 @@ extension Bolus {
         }
 
         func fetchGlucose() -> [Readings] {
-            var fetchGlucose = [Readings]()
-            coredataContext.performAndWait {
-                let requestReadings = Readings.fetchRequest() as NSFetchRequest<Readings>
-                let sort = NSSortDescriptor(key: "date", ascending: true)
-                requestReadings.sortDescriptors = [sort]
-                requestReadings.predicate = NSPredicate(
-                    format: "glucose > 0 AND date > %@",
-                    Date().addingTimeInterval(-1.hours.timeInterval) as NSDate
-                )
-                try? fetchGlucose = self.coredataContext.fetch(requestReadings)
-            }
+            let fetchGlucose = coreDataStorage.fetchGlucose(interval: DateFilter().twoHours)
             return fetchGlucose
         }
     }

+ 2 - 3
FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift

@@ -100,15 +100,14 @@ extension Bolus {
         func getDeltaBG() {
             let glucose = provider.fetchGlucose()
             guard glucose.count >= 3 else { return }
-            let lastGlucose = glucose.last?.glucose ?? 0
-            let thirdLastGlucose = glucose[glucose.count - 3]
+            let lastGlucose = glucose.first?.glucose ?? 0
+            let thirdLastGlucose = glucose[2]
             let delta = Decimal(lastGlucose) - Decimal(thirdLastGlucose.glucose)
             deltaBG = delta
         }
 
         // CALCULATIONS FOR THE BOLUS CALCULATOR
         func calculateInsulin() -> Decimal {
-            // for mmol conversion
             var conversion: Decimal = 1.0
             if units == .mmolL {
                 conversion = 0.0555

+ 87 - 28
FreeAPS/Sources/Services/WatchManager/WatchManager.swift

@@ -1,4 +1,3 @@
-import CoreData
 import Foundation
 import Swinject
 import WatchConnectivity
@@ -12,14 +11,13 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable {
 
     @Injected() private var broadcaster: Broadcaster!
     @Injected() private var settingsManager: SettingsManager!
-    @Injected() private var glucoseStorage: GlucoseStorage!
     @Injected() private var apsManager: APSManager!
     @Injected() private var storage: FileStorage!
     @Injected() private var carbsStorage: CarbsStorage!
     @Injected() private var tempTargetsStorage: TempTargetsStorage!
     @Injected() private var garmin: GarminManager!
 
-    let coredataContext = CoreDataStack.shared.persistentContainer.viewContext // newBackgroundContext()
+    let coreDataStorage = CoreDataStorage()
 
     private var lifetime = Lifetime()
 
@@ -57,12 +55,13 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable {
 
     private func configureState() {
         processQueue.async {
-            let glucoseValues = self.glucoseText()
+            let readings = self.coreDataStorage.fetchGlucose(interval: DateFilter().twoHours)
+            let glucoseValues = self.glucoseText(readings)
             self.state.glucose = glucoseValues.glucose
             self.state.trend = glucoseValues.trend
             self.state.delta = glucoseValues.delta
-            self.state.trendRaw = self.glucoseStorage.recent().last?.direction?.rawValue
-            self.state.glucoseDate = self.glucoseStorage.recent().last?.dateString
+            self.state.trendRaw = readings.first?.direction ?? "↔︎"
+            self.state.glucoseDate = readings.first?.date ?? .distantPast
             self.state.glucoseDateInterval = self.state.glucoseDate.map { UInt64($0.timeIntervalSince1970) }
             self.state.lastLoopDate = self.enactedSuggestion?.recieved == true ? self.enactedSuggestion?.deliverAt : self
                 .apsManager.lastLoopDate
@@ -76,16 +75,26 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable {
             self.state.carbsRequired = self.suggestion?.carbsReq
 
             var insulinRequired = self.suggestion?.insulinReq ?? 0
+
             var double: Decimal = 2
-            if (self.suggestion?.cob ?? 0) > 0 {
-                if self.suggestion?.manualBolusErrorString == 0 {
-                    insulinRequired = self.suggestion?.insulinForManualBolus ?? 0
-                    double = 1
-                }
+            if self.suggestion?.manualBolusErrorString == 0 {
+                insulinRequired = self.suggestion?.insulinForManualBolus ?? 0
+                double = 1
             }
 
-            self.state.bolusRecommended = self.apsManager
-                .roundBolus(amount: max(insulinRequired * (self.settingsManager.settings.insulinReqPercentage / 100) * double, 0))
+            self.state.useNewCalc = self.settingsManager.settings.useCalc
+
+            if !(self.state.useNewCalc ?? false) {
+                self.state.bolusRecommended = self.apsManager
+                    .roundBolus(amount: max(
+                        insulinRequired * (self.settingsManager.settings.insulinReqPercentage / 100) * double,
+                        0
+                    ))
+            } else {
+                let recommended = self.newBolusCalc(delta: readings, suggestion: self.suggestion)
+                self.state.bolusRecommended = self.apsManager
+                    .roundBolus(amount: max(recommended, 0))
+            }
 
             self.state.iob = self.suggestion?.iob
             self.state.cob = self.suggestion?.cob
@@ -113,12 +122,7 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable {
 
             self.state.isf = self.suggestion?.isf
 
-            var overrideArray = [Override]()
-            let requestOverrides = Override.fetchRequest() as NSFetchRequest<Override>
-            let sortOverride = NSSortDescriptor(key: "date", ascending: false)
-            requestOverrides.sortDescriptors = [sortOverride]
-            requestOverrides.fetchLimit = 1
-            try? overrideArray = self.coredataContext.fetch(requestOverrides)
+            let overrideArray = self.coreDataStorage.fetchLatestOverride()
 
             if overrideArray.first?.enabled ?? false {
                 let percentString = "\((overrideArray.first?.percentage ?? 100).formatted(.number)) %"
@@ -147,26 +151,25 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable {
         }
     }
 
-    private func glucoseText() -> (glucose: String, trend: String, delta: String) {
-        let glucose = glucoseStorage.recent()
+    private func glucoseText(_ glucose: [Readings]) -> (glucose: String, trend: String, delta: String) {
+        let glucoseValue = glucose.first?.glucose ?? 0
 
-        guard let lastGlucose = glucose.last, let glucoseValue = lastGlucose.glucose else { return ("--", "--", "--") }
+        guard !glucose.isEmpty else { return ("--", "--", "--") }
 
-        let delta = glucose.count >= 2 ? glucoseValue - (glucose[glucose.count - 2].glucose ?? 0) : nil
+        let delta = glucose.count >= 2 ? glucoseValue - glucose[1].glucose : nil
 
         let units = settingsManager.settings.units
         let glucoseText = glucoseFormatter
             .string(from: Double(
-                units == .mmolL ? glucoseValue
-                    .asMmolL : Decimal(glucoseValue)
+                units == .mmolL ? Decimal(glucoseValue).asMmolL : Decimal(glucoseValue)
             ) as NSNumber)!
-        let directionText = lastGlucose.direction?.symbol ?? "↔︎"
+
+        let directionText = glucose.first?.direction ?? "↔︎"
         let deltaText = delta
             .map {
                 self.deltaFormatter
                     .string(from: Double(
-                        units == .mmolL ? $0
-                            .asMmolL : Decimal($0)
+                        units == .mmolL ? Decimal($0).asMmolL : Decimal($0)
                     ) as NSNumber)!
             } ?? "--"
 
@@ -200,6 +203,62 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable {
         )!
     }
 
+    private func newBolusCalc(delta: [Readings], suggestion _: Suggestion?) -> Decimal {
+        var conversion: Decimal = 1
+        // Settings
+        if settingsManager.settings.units == .mmolL {
+            conversion = 0.0555
+        }
+        let isf = state.isf ?? 0
+        let target = suggestion?.current_target ?? 0
+        let carbratio = suggestion?.carbRatio ?? 0
+        let bg = delta.first?.glucose ?? 0
+        let cob = state.cob ?? 0
+        let iob = state.iob ?? 0
+        let useFattyMealCorrectionFactor = settingsManager.settings.fattyMeals
+        let fattyMealFactor = settingsManager.settings.fattyMealFactor
+        let maxBolus = settingsManager.pumpSettings.maxBolus
+        var insulinCalculated: Decimal = 0
+        // insulin needed for the current blood glucose
+        let targetDifference = (Decimal(bg) - target) * conversion
+        let targetDifferenceInsulin = targetDifference / isf
+        // more or less insulin because of bg trend in the last 15 minutes
+        var bgDelta: Int = 0
+        if delta.count >= 3 {
+            bgDelta = Int((delta.first?.glucose ?? 0) - delta[2].glucose)
+        }
+        let fifteenMinInsulin = (Decimal(bgDelta) * conversion) / isf
+        // determine whole COB for which we want to dose insulin for and then determine insulin for wholeCOB
+        let wholeCobInsulin = cob / carbratio
+        // determine how much the calculator reduces/ increases the bolus because of IOB
+        let iobInsulinReduction = (-1) * iob
+        // adding everything together
+        // add a calc for the case that no fifteenMinInsulin is available
+        var wholeCalc: Decimal = 0
+        if bgDelta != 0 {
+            wholeCalc = (targetDifferenceInsulin + iobInsulinReduction + wholeCobInsulin + fifteenMinInsulin)
+        } else {
+            // add (rare) case that no glucose value is available -> maybe display warning?
+            // if no bg is available, ?? sets its value to 0
+            if bg == 0 {
+                wholeCalc = (iobInsulinReduction + wholeCobInsulin)
+            } else {
+                wholeCalc = (targetDifferenceInsulin + iobInsulinReduction + wholeCobInsulin)
+            }
+        }
+        // apply custom factor at the end of the calculations
+        let result = wholeCalc * settingsManager.settings.overrideFactor
+        // apply custom factor if fatty meal toggle in bolus calc config settings is on and the box for fatty meals is checked (in RootView)
+        if useFattyMealCorrectionFactor {
+            insulinCalculated = result * fattyMealFactor
+        } else {
+            insulinCalculated = result
+        }
+        // Not 0 or over maxBolus
+        insulinCalculated = max(min(insulinCalculated, maxBolus), 0)
+        return insulinCalculated
+    }
+
     private var glucoseFormatter: NumberFormatter {
         let formatter = NumberFormatter()
         formatter.numberStyle = .decimal

+ 1 - 0
FreeAPSWatch WatchKit Extension/DataFlow.swift

@@ -22,6 +22,7 @@ struct WatchState: Codable {
     var eventualBGRaw: String?
     var displayOnWatch: AwConfig?
     var displayFatAndProteinOnWatch: Bool?
+    var useNewCalc: Bool?
     var isf: Decimal?
     var override: String?
 }

+ 2 - 0
FreeAPSWatch WatchKit Extension/WatchStateModel.swift

@@ -34,6 +34,7 @@ class WatchStateModel: NSObject, ObservableObject {
     @Published var isBolusViewActive = false
     @Published var displayOnWatch: AwConfig = .BGTarget
     @Published var displayFatAndProteinOnWatch = false
+    @Published var useNewCalc = false
     @Published var eventualBG = ""
     @Published var isConfirmationViewActive = false {
         didSet {
@@ -174,6 +175,7 @@ class WatchStateModel: NSObject, ObservableObject {
         eventualBG = state.eventualBG ?? ""
         displayOnWatch = state.displayOnWatch ?? .BGTarget
         displayFatAndProteinOnWatch = state.displayFatAndProteinOnWatch ?? false
+        useNewCalc = state.useNewCalc ?? false
         isf = state.isf
         override = state.override
     }