Skip to content

Commit

Permalink
Bolus from watch (nightscout#326)
Browse files Browse the repository at this point in the history
Use same calculator on Watch as on iPhone app.
  • Loading branch information
Jon-b-m authored and MikePlante1 committed Mar 12, 2024
1 parent 59af5be commit a1c1160
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 28 deletions.
1 change: 1 addition & 0 deletions Core_Data.xcdatamodeld/Core_Data.xcdatamodel/contents
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,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>
Expand Down
4 changes: 4 additions & 0 deletions FreeAPS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,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 */; };
1967DFBE29D052C200759F30 /* Icons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1967DFBD29D052C200759F30 /* Icons.swift */; };
1967DFC029D053AC00759F30 /* IconSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1967DFBF29D053AC00759F30 /* IconSelection.swift */; };
1967DFC229D053D300759F30 /* IconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1967DFC129D053D300759F30 /* IconImage.swift */; };
Expand Down Expand Up @@ -550,6 +551,7 @@
193F1E3B2B44C14800525770 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/InfoPlist.strings; sourceTree = "<group>"; };
193F1E3C2B44C14800525770 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; 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>"; };
1967DFBD29D052C200759F30 /* Icons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Icons.swift; sourceTree = "<group>"; };
1967DFBF29D053AC00759F30 /* IconSelection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconSelection.swift; sourceTree = "<group>"; };
1967DFC129D053D300759F30 /* IconImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconImage.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1716,6 +1718,7 @@
38FCF3FC25E997A80078B0D1 /* PumpHistoryStorage.swift */,
38F3B2EE25ED8E2A005C48AA /* TempTargetsStorage.swift */,
CE82E02428E867BA00473A9C /* AlertStorage.swift */,
1956FB202AFF79E200C7B4FF /* CoreDataStorage.swift */,
);
path = Storage;
sourceTree = "<group>";
Expand Down Expand Up @@ -2913,6 +2916,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 */,
Expand Down
34 changes: 34 additions & 0 deletions FreeAPS/Sources/APS/Storage/CoreDataStorage.swift
Original file line number Diff line number Diff line change
@@ -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 changes: 3 additions & 0 deletions FreeAPS/Sources/APS/Storage/GlucoseStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,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 {
Expand All @@ -74,6 +76,7 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
dataForForStats.date = bgDate
dataForForStats.glucose = Int16(bg_)
dataForForStats.id = id
dataForForStats.direction = direction
try? self.coredataContext.save()
}
}
Expand Down
1 change: 1 addition & 0 deletions FreeAPS/Sources/Models/DateFilter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
115 changes: 87 additions & 28 deletions FreeAPS/Sources/Services/WatchManager/WatchManager.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import CoreData
import Foundation
import Swinject
import WatchConnectivity
Expand All @@ -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()

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)) %"
Expand Down Expand Up @@ -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)!
} ?? "--"

Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions FreeAPSWatch WatchKit Extension/DataFlow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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?
}
Expand Down
2 changes: 2 additions & 0 deletions FreeAPSWatch WatchKit Extension/WatchStateModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
Expand Down

0 comments on commit a1c1160

Please sign in to comment.