|
@@ -1,537 +0,0 @@
|
|
|
-import Combine
|
|
|
|
|
-import CoreData
|
|
|
|
|
-import Foundation
|
|
|
|
|
-import Swinject
|
|
|
|
|
-import WatchConnectivity
|
|
|
|
|
-
|
|
|
|
|
-protocol WatchManager {}
|
|
|
|
|
-
|
|
|
|
|
-final class BaseWatchManager: NSObject, WatchManager, Injectable {
|
|
|
|
|
- private let session: WCSession
|
|
|
|
|
- private var state = WatchState()
|
|
|
|
|
- private let processQueue = DispatchQueue(label: "BaseWatchManager.processQueue")
|
|
|
|
|
-
|
|
|
|
|
- @Injected() private var broadcaster: Broadcaster!
|
|
|
|
|
- @Injected() private var settingsManager: SettingsManager!
|
|
|
|
|
- @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!
|
|
|
|
|
- @Injected() private var glucoseStorage: GlucoseStorage!
|
|
|
|
|
-
|
|
|
|
|
- private var glucoseFormatter: NumberFormatter {
|
|
|
|
|
- let formatter = NumberFormatter()
|
|
|
|
|
- formatter.numberStyle = .decimal
|
|
|
|
|
- formatter.maximumFractionDigits = 0
|
|
|
|
|
- if settingsManager.settings.units == .mmolL {
|
|
|
|
|
- formatter.minimumFractionDigits = 1
|
|
|
|
|
- formatter.maximumFractionDigits = 1
|
|
|
|
|
- }
|
|
|
|
|
- formatter.roundingMode = .halfUp
|
|
|
|
|
- return formatter
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private var eventualFormatter: NumberFormatter {
|
|
|
|
|
- let formatter = NumberFormatter()
|
|
|
|
|
- formatter.numberStyle = .decimal
|
|
|
|
|
- formatter.maximumFractionDigits = 1
|
|
|
|
|
- return formatter
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private var deltaFormatter: NumberFormatter {
|
|
|
|
|
- let formatter = NumberFormatter()
|
|
|
|
|
- formatter.numberStyle = .decimal
|
|
|
|
|
- formatter.maximumFractionDigits = settingsManager.settings.units == .mmolL ? 1 : 0
|
|
|
|
|
- formatter.positivePrefix = "+"
|
|
|
|
|
- formatter.negativePrefix = "-"
|
|
|
|
|
- return formatter
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private var targetFormatter: NumberFormatter {
|
|
|
|
|
- let formatter = NumberFormatter()
|
|
|
|
|
- formatter.numberStyle = .decimal
|
|
|
|
|
- formatter.maximumFractionDigits = 1
|
|
|
|
|
- return formatter
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let context = CoreDataStack.shared.newTaskContext()
|
|
|
|
|
- let viewContext = CoreDataStack.shared.persistentContainer.viewContext
|
|
|
|
|
-
|
|
|
|
|
- private var coreDataPublisher: AnyPublisher<Set<NSManagedObject>, Never>?
|
|
|
|
|
- private var subscriptions = Set<AnyCancellable>()
|
|
|
|
|
-
|
|
|
|
|
- private var lifetime = Lifetime()
|
|
|
|
|
-
|
|
|
|
|
- init(resolver: Resolver, session: WCSession = .default) {
|
|
|
|
|
- self.session = session
|
|
|
|
|
- super.init()
|
|
|
|
|
- injectServices(resolver)
|
|
|
|
|
- registerHandlers()
|
|
|
|
|
- registerSubscribers()
|
|
|
|
|
-
|
|
|
|
|
- coreDataPublisher =
|
|
|
|
|
- changedObjectsOnManagedObjectContextDidSavePublisher()
|
|
|
|
|
- .receive(on: DispatchQueue.global(qos: .background))
|
|
|
|
|
- .share()
|
|
|
|
|
- .eraseToAnyPublisher()
|
|
|
|
|
-
|
|
|
|
|
- Task {
|
|
|
|
|
- await configureState()
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if WCSession.isSupported() {
|
|
|
|
|
- session.delegate = self
|
|
|
|
|
- session.activate()
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- broadcaster.register(SettingsObserver.self, observer: self)
|
|
|
|
|
- broadcaster.register(PumpHistoryObserver.self, observer: self)
|
|
|
|
|
- broadcaster.register(PumpSettingsObserver.self, observer: self)
|
|
|
|
|
- broadcaster.register(BasalProfileObserver.self, observer: self)
|
|
|
|
|
- broadcaster.register(TempTargetsObserver.self, observer: self)
|
|
|
|
|
- broadcaster.register(CarbsObserver.self, observer: self)
|
|
|
|
|
- broadcaster.register(PumpBatteryObserver.self, observer: self)
|
|
|
|
|
- broadcaster.register(PumpReservoirObserver.self, observer: self)
|
|
|
|
|
- garmin.stateRequet = { [weak self] () -> Data in
|
|
|
|
|
- guard let self = self, let data = try? JSONEncoder().encode(self.state) else {
|
|
|
|
|
- warning(.service, "Cannot encode watch state")
|
|
|
|
|
- return Data()
|
|
|
|
|
- }
|
|
|
|
|
- return data
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private func registerSubscribers() {
|
|
|
|
|
- glucoseStorage.updatePublisher
|
|
|
|
|
- .receive(on: DispatchQueue.global(qos: .background))
|
|
|
|
|
- .sink { [weak self] _ in
|
|
|
|
|
- guard let self = self else { return }
|
|
|
|
|
- Task {
|
|
|
|
|
- await self.configureState()
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- .store(in: &subscriptions)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private func registerHandlers() {
|
|
|
|
|
- coreDataPublisher?.filterByEntityName("OrefDetermination").sink { [weak self] _ in
|
|
|
|
|
- guard let self = self else { return }
|
|
|
|
|
- Task {
|
|
|
|
|
- await self.configureState()
|
|
|
|
|
- }
|
|
|
|
|
- }.store(in: &subscriptions)
|
|
|
|
|
-
|
|
|
|
|
- coreDataPublisher?.filterByEntityName("OverrideStored").sink { [weak self] _ in
|
|
|
|
|
- guard let self = self else { return }
|
|
|
|
|
- Task {
|
|
|
|
|
- await self.configureState()
|
|
|
|
|
- }
|
|
|
|
|
- }.store(in: &subscriptions)
|
|
|
|
|
-
|
|
|
|
|
- // Observes Deletion of Glucose Objects
|
|
|
|
|
- coreDataPublisher?.filterByEntityName("GlucoseStored").sink { [weak self] _ in
|
|
|
|
|
- guard let self = self else { return }
|
|
|
|
|
- Task {
|
|
|
|
|
- await self.configureState()
|
|
|
|
|
- }
|
|
|
|
|
- }.store(in: &subscriptions)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private func fetchlastDetermination() async -> [NSManagedObjectID] {
|
|
|
|
|
- let results = await CoreDataStack.shared.fetchEntitiesAsync(
|
|
|
|
|
- ofType: OrefDetermination.self,
|
|
|
|
|
- onContext: context,
|
|
|
|
|
- predicate: NSPredicate.enactedDetermination,
|
|
|
|
|
- key: "timestamp",
|
|
|
|
|
- ascending: false,
|
|
|
|
|
- fetchLimit: 1
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- return await context.perform {
|
|
|
|
|
- guard let fetchedResults = results as? [OrefDetermination] else { return [] }
|
|
|
|
|
-
|
|
|
|
|
- return fetchedResults.map(\.objectID)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private func fetchLatestOverride() async -> NSManagedObjectID? {
|
|
|
|
|
- let results = await CoreDataStack.shared.fetchEntitiesAsync(
|
|
|
|
|
- ofType: OverrideStored.self,
|
|
|
|
|
- onContext: context,
|
|
|
|
|
- predicate: NSPredicate.predicateForOneDayAgo,
|
|
|
|
|
- key: "date",
|
|
|
|
|
- ascending: false,
|
|
|
|
|
- fetchLimit: 1,
|
|
|
|
|
- propertiesToFetch: ["enabled", "percentage", "objectID"]
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- return await context.perform {
|
|
|
|
|
- guard let fetchedResults = results as? [[String: Any]] else { return nil }
|
|
|
|
|
-
|
|
|
|
|
- return fetchedResults.compactMap { $0["objectID"] as? NSManagedObjectID }.first
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private func fetchGlucose() async -> [NSManagedObjectID] {
|
|
|
|
|
- let results = await CoreDataStack.shared.fetchEntitiesAsync(
|
|
|
|
|
- ofType: GlucoseStored.self,
|
|
|
|
|
- onContext: context,
|
|
|
|
|
- predicate: NSPredicate.predicateFor120MinAgo,
|
|
|
|
|
- key: "date",
|
|
|
|
|
- ascending: false,
|
|
|
|
|
- fetchLimit: 24,
|
|
|
|
|
- batchSize: 12
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- return await context.perform {
|
|
|
|
|
- guard let glucoseResults = results as? [GlucoseStored] else {
|
|
|
|
|
- return []
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return glucoseResults.map(\.objectID)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- @MainActor private func configureState() async {
|
|
|
|
|
- let glucoseValuesIds = await fetchGlucose()
|
|
|
|
|
- async let getLatestDeterminationIds = fetchlastDetermination()
|
|
|
|
|
- async let getlatestOverrideId = fetchLatestOverride()
|
|
|
|
|
-
|
|
|
|
|
- let latestOverrideId = await getlatestOverrideId
|
|
|
|
|
-
|
|
|
|
|
- guard let lastDeterminationId = await getLatestDeterminationIds.first else {
|
|
|
|
|
- debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to get last Determination")
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- do {
|
|
|
|
|
- let glucoseValues: [GlucoseStored] = await CoreDataStack.shared
|
|
|
|
|
- .getNSManagedObject(with: glucoseValuesIds, context: viewContext)
|
|
|
|
|
- let lastDetermination = try viewContext.existingObject(with: lastDeterminationId) as? OrefDetermination
|
|
|
|
|
- let recommendedInsulin = await newBolusCalc(
|
|
|
|
|
- glucoseIds: glucoseValuesIds,
|
|
|
|
|
- determinationId: lastDeterminationId
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- var latestOverride: OverrideStored?
|
|
|
|
|
- if let id = latestOverrideId {
|
|
|
|
|
- latestOverride = try viewContext.existingObject(with: id) as? OverrideStored
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- await MainActor.run { [weak self] in
|
|
|
|
|
- guard let self = self else { return }
|
|
|
|
|
-
|
|
|
|
|
- if let firstGlucoseValue = glucoseValues.first {
|
|
|
|
|
- let value = self.settingsManager.settings.units == .mgdL
|
|
|
|
|
- ? Decimal(firstGlucoseValue.glucose)
|
|
|
|
|
- : Decimal(firstGlucoseValue.glucose).asMmolL
|
|
|
|
|
-
|
|
|
|
|
- self.state.glucose = self.glucoseFormatter.string(from: value as NSNumber)
|
|
|
|
|
- self.state.trend = firstGlucoseValue.directionEnum?.symbol
|
|
|
|
|
-
|
|
|
|
|
- let delta = glucoseValues.count >= 2
|
|
|
|
|
- ? Decimal(firstGlucoseValue.glucose) - Decimal(glucoseValues.dropFirst().first?.glucose ?? 0)
|
|
|
|
|
- : 0
|
|
|
|
|
- let deltaConverted = self.settingsManager.settings.units == .mgdL ? delta : delta.asMmolL
|
|
|
|
|
- self.state.delta = self.deltaFormatter.string(from: deltaConverted as NSNumber)
|
|
|
|
|
- self.state.trendRaw = firstGlucoseValue.direction
|
|
|
|
|
- self.state.glucoseDate = firstGlucoseValue.date
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- self.state.lastLoopDate = lastDetermination?.timestamp
|
|
|
|
|
- self.state.lastLoopDateInterval = self.state.lastLoopDate.map {
|
|
|
|
|
- guard $0.timeIntervalSince1970 > 0 else { return 0 }
|
|
|
|
|
- return UInt64($0.timeIntervalSince1970)
|
|
|
|
|
- }
|
|
|
|
|
- self.state.bolusIncrement = self.settingsManager.preferences.bolusIncrement
|
|
|
|
|
- self.state.maxCOB = self.settingsManager.preferences.maxCOB
|
|
|
|
|
- self.state.maxBolus = self.settingsManager.pumpSettings.maxBolus
|
|
|
|
|
- self.state.carbsRequired = lastDetermination?.carbsRequired as? Decimal
|
|
|
|
|
- self.state.bolusRecommended = self.apsManager
|
|
|
|
|
- .roundBolus(amount: max(recommendedInsulin, 0))
|
|
|
|
|
- self.state.displayOnWatch = self.settingsManager.settings.displayOnWatch
|
|
|
|
|
- self.state.displayFatAndProteinOnWatch = self.settingsManager.settings.displayFatAndProteinOnWatch
|
|
|
|
|
- self.state.confirmBolusFaster = self.settingsManager.settings.confirmBolusFaster
|
|
|
|
|
-
|
|
|
|
|
- self.state.iob = lastDetermination?.iob as? Decimal
|
|
|
|
|
- if let cobValue = lastDetermination?.cob {
|
|
|
|
|
- self.state.cob = Decimal(cobValue)
|
|
|
|
|
- } else {
|
|
|
|
|
- self.state.cob = 0
|
|
|
|
|
- }
|
|
|
|
|
- self.state.tempTargets = self.tempTargetsStorage.presets()
|
|
|
|
|
- .map { target -> TempTargetWatchPreset in
|
|
|
|
|
- let untilDate = self.tempTargetsStorage.current().flatMap { currentTarget -> Date? in
|
|
|
|
|
- guard currentTarget.id == target.id else { return nil }
|
|
|
|
|
- let date = currentTarget.createdAt.addingTimeInterval(TimeInterval(currentTarget.duration * 60))
|
|
|
|
|
- return date > Date() ? date : nil
|
|
|
|
|
- }
|
|
|
|
|
- return TempTargetWatchPreset(
|
|
|
|
|
- name: target.displayName,
|
|
|
|
|
- id: target.id,
|
|
|
|
|
- description: self.descriptionForTarget(target),
|
|
|
|
|
- until: untilDate
|
|
|
|
|
- )
|
|
|
|
|
- }
|
|
|
|
|
- self.state.displayOnWatch = self.settingsManager.settings.displayOnWatch
|
|
|
|
|
- self.state.displayFatAndProteinOnWatch = self.settingsManager.settings.displayFatAndProteinOnWatch
|
|
|
|
|
- self.state.confirmBolusFaster = self.settingsManager.settings.confirmBolusFaster
|
|
|
|
|
-
|
|
|
|
|
- if let eventualBG = self.settingsManager.settings.units == .mgdL ? lastDetermination?
|
|
|
|
|
- .eventualBG : lastDetermination?
|
|
|
|
|
- .eventualBG?.decimalValue.asMmolL as NSDecimalNumber?
|
|
|
|
|
- {
|
|
|
|
|
- let eventualBGAsString = self.eventualFormatter.string(from: eventualBG)
|
|
|
|
|
- self.state.eventualBG = eventualBGAsString.map { "⇢ " + $0 }
|
|
|
|
|
- self.state.eventualBGRaw = eventualBGAsString
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- self.state.isf = lastDetermination?.insulinSensitivity as? Decimal
|
|
|
|
|
-
|
|
|
|
|
- if let latestOverride = latestOverride {
|
|
|
|
|
- if latestOverride.enabled {
|
|
|
|
|
- let percentString = "\(latestOverride.percentage.formatted(.number)) %"
|
|
|
|
|
- self.state.override = percentString
|
|
|
|
|
- } else {
|
|
|
|
|
- self.state.override = "100 %"
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- self.sendState()
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- } catch let error as NSError {
|
|
|
|
|
- debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to configure state with error: \(error)")
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private func sendState() {
|
|
|
|
|
- guard let data = try? JSONEncoder().encode(state) else {
|
|
|
|
|
- warning(.service, "Cannot encode watch state")
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- garmin.sendState(data)
|
|
|
|
|
-
|
|
|
|
|
- guard session.isReachable else { return }
|
|
|
|
|
- session.sendMessageData(data, replyHandler: nil) { error in
|
|
|
|
|
- warning(.service, "Cannot send message to watch", error: error)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private func descriptionForTarget(_ target: TempTarget) -> String {
|
|
|
|
|
- let units = settingsManager.settings.units
|
|
|
|
|
-
|
|
|
|
|
- var low = target.targetBottom
|
|
|
|
|
- var high = target.targetTop
|
|
|
|
|
- if units == .mmolL {
|
|
|
|
|
- low = low?.asMmolL
|
|
|
|
|
- high = high?.asMmolL
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let description =
|
|
|
|
|
- "\(targetFormatter.string(from: (low ?? 0) as NSNumber)!) - \(targetFormatter.string(from: (high ?? 0) as NSNumber)!)" +
|
|
|
|
|
- " for \(targetFormatter.string(from: target.duration as NSNumber)!) min"
|
|
|
|
|
-
|
|
|
|
|
- return description
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private func newBolusCalc(glucoseIds: [NSManagedObjectID], determinationId: NSManagedObjectID) async -> Decimal {
|
|
|
|
|
- await context.perform {
|
|
|
|
|
- let glucoseObjects = glucoseIds.compactMap { self.context.object(with: $0) as? GlucoseStored }
|
|
|
|
|
- guard let determination = self.context.object(with: determinationId) as? OrefDetermination else {
|
|
|
|
|
- print("Failed to fetch determination")
|
|
|
|
|
- return 0
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- guard let firstGlucose = glucoseObjects.first else {
|
|
|
|
|
- return 0 // If there's no glucose data, exit the block
|
|
|
|
|
- }
|
|
|
|
|
- let bg = firstGlucose.glucose // Make sure to provide a fallback value for glucose
|
|
|
|
|
-
|
|
|
|
|
- // Calculations related to glucose data
|
|
|
|
|
- var bgDelta: Int = 0
|
|
|
|
|
- if glucoseObjects.count >= 3 {
|
|
|
|
|
- bgDelta = Int(firstGlucose.glucose) - Int(glucoseObjects[2].glucose)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let conversion: Decimal = self.settingsManager.settings.units == .mmolL ? 0.0555 : 1
|
|
|
|
|
- let isf = self.state.isf ?? 0
|
|
|
|
|
- let target = determination.currentTarget as? Decimal ?? 100
|
|
|
|
|
- let carbratio = determination.carbRatio as? Decimal ?? 10
|
|
|
|
|
- let cob = self.state.cob ?? 0
|
|
|
|
|
- let iob = self.state.iob ?? 0
|
|
|
|
|
- let fattyMealFactor = self.settingsManager.settings.fattyMealFactor
|
|
|
|
|
-
|
|
|
|
|
- // Complete bolus calculation logic
|
|
|
|
|
- let targetDifference = Decimal(bg) - target
|
|
|
|
|
- let targetDifferenceInsulin = targetDifference * conversion / isf
|
|
|
|
|
- let fifteenMinInsulin = Decimal(bgDelta) * conversion / isf
|
|
|
|
|
- let wholeCobInsulin = cob / carbratio
|
|
|
|
|
- let iobInsulinReduction = -iob
|
|
|
|
|
- let wholeCalc = targetDifferenceInsulin + iobInsulinReduction + wholeCobInsulin + fifteenMinInsulin
|
|
|
|
|
-
|
|
|
|
|
- let result = wholeCalc * self.settingsManager.settings.overrideFactor
|
|
|
|
|
- var insulinCalculated: Decimal
|
|
|
|
|
- if self.settingsManager.settings.fattyMeals {
|
|
|
|
|
- insulinCalculated = result * fattyMealFactor
|
|
|
|
|
- } else {
|
|
|
|
|
- insulinCalculated = result
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Ensure the calculated insulin amount does not exceed the maximum bolus and is not below zero
|
|
|
|
|
- insulinCalculated = max(min(insulinCalculated, self.settingsManager.pumpSettings.maxBolus), 0)
|
|
|
|
|
- return insulinCalculated // Return the calculated insulin outside of the performAndWait block
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-extension BaseWatchManager: WCSessionDelegate {
|
|
|
|
|
- func sessionDidBecomeInactive(_: WCSession) {}
|
|
|
|
|
-
|
|
|
|
|
- func sessionDidDeactivate(_: WCSession) {}
|
|
|
|
|
-
|
|
|
|
|
- func session(_: WCSession, activationDidCompleteWith state: WCSessionActivationState, error _: Error?) {
|
|
|
|
|
- debug(.service, "WCSession is activated: \(state == .activated)")
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- func session(_: WCSession, didReceiveMessage message: [String: Any]) {
|
|
|
|
|
- debug(.service, "WCSession got message: \(message)")
|
|
|
|
|
-
|
|
|
|
|
- if let stateRequest = message["stateRequest"] as? Bool, stateRequest {
|
|
|
|
|
- processQueue.async {
|
|
|
|
|
- self.sendState()
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- func session(_: WCSession, didReceiveMessage message: [String: Any], replyHandler: @escaping ([String: Any]) -> Void) {
|
|
|
|
|
- debug(.service, "WCSession got message with reply handler: \(message)")
|
|
|
|
|
-
|
|
|
|
|
- if let carbs = message["carbs"] as? Double,
|
|
|
|
|
- let fat = message["fat"] as? Double,
|
|
|
|
|
- let protein = message["protein"] as? Double,
|
|
|
|
|
- carbs > 0 || fat > 0 || protein > 0
|
|
|
|
|
- {
|
|
|
|
|
- Task {
|
|
|
|
|
- await carbsStorage.storeCarbs(
|
|
|
|
|
- [CarbsEntry(
|
|
|
|
|
- id: UUID().uuidString,
|
|
|
|
|
- createdAt: Date(),
|
|
|
|
|
- actualDate: nil,
|
|
|
|
|
- carbs: Decimal(carbs),
|
|
|
|
|
- fat: Decimal(fat),
|
|
|
|
|
- protein: Decimal(protein),
|
|
|
|
|
- note: message["note"] as? String,
|
|
|
|
|
- enteredBy: CarbsEntry.local,
|
|
|
|
|
- isFPU: false,
|
|
|
|
|
- fpuID: nil
|
|
|
|
|
- )],
|
|
|
|
|
- areFetchedFromRemote: false
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- _ = await apsManager.determineBasal()
|
|
|
|
|
- replyHandler(["confirmation": true])
|
|
|
|
|
- }
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if let tempTargetID = message["tempTarget"] as? String {
|
|
|
|
|
- Task {
|
|
|
|
|
- if var preset = tempTargetsStorage.presets().first(where: { $0.id == tempTargetID }) {
|
|
|
|
|
- preset.createdAt = Date()
|
|
|
|
|
- await tempTargetsStorage.storeTempTarget(tempTarget: preset)
|
|
|
|
|
- replyHandler(["confirmation": true])
|
|
|
|
|
- } else if tempTargetID == "cancel" {
|
|
|
|
|
- let entry = TempTarget(
|
|
|
|
|
- name: TempTarget.cancel,
|
|
|
|
|
- createdAt: Date(),
|
|
|
|
|
- targetTop: 0,
|
|
|
|
|
- targetBottom: 0,
|
|
|
|
|
- duration: 0,
|
|
|
|
|
- enteredBy: TempTarget.local,
|
|
|
|
|
- reason: TempTarget.cancel,
|
|
|
|
|
- isPreset: false,
|
|
|
|
|
- enabled: false,
|
|
|
|
|
- halfBasalTarget: 160
|
|
|
|
|
- )
|
|
|
|
|
- await tempTargetsStorage.storeTempTarget(tempTarget: entry)
|
|
|
|
|
- replyHandler(["confirmation": true])
|
|
|
|
|
- } else {
|
|
|
|
|
- replyHandler(["confirmation": false])
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if let bolus = message["bolus"] as? Double, bolus > 0 {
|
|
|
|
|
- Task {
|
|
|
|
|
- await apsManager.enactBolus(amount: bolus, isSMB: false)
|
|
|
|
|
- replyHandler(["confirmation": true])
|
|
|
|
|
- }
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- replyHandler(["confirmation": false])
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- func session(_: WCSession, didReceiveMessageData _: Data) {}
|
|
|
|
|
-
|
|
|
|
|
- func sessionReachabilityDidChange(_ session: WCSession) {
|
|
|
|
|
- if session.isReachable {
|
|
|
|
|
- processQueue.async {
|
|
|
|
|
- self.sendState()
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-extension BaseWatchManager:
|
|
|
|
|
- SettingsObserver,
|
|
|
|
|
- PumpHistoryObserver,
|
|
|
|
|
- PumpSettingsObserver,
|
|
|
|
|
- BasalProfileObserver,
|
|
|
|
|
- TempTargetsObserver,
|
|
|
|
|
- CarbsObserver,
|
|
|
|
|
- PumpBatteryObserver,
|
|
|
|
|
- PumpReservoirObserver
|
|
|
|
|
-{
|
|
|
|
|
- func settingsDidChange(_: FreeAPSSettings) {
|
|
|
|
|
- Task {
|
|
|
|
|
- await configureState()
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- func pumpHistoryDidUpdate(_: [PumpHistoryEvent]) {
|
|
|
|
|
- // TODO:
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- func pumpSettingsDidChange(_: PumpSettings) {
|
|
|
|
|
- Task {
|
|
|
|
|
- await configureState()
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- func basalProfileDidChange(_: [BasalProfileEntry]) {
|
|
|
|
|
- // TODO:
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- func tempTargetsDidUpdate(_: [TempTarget]) {
|
|
|
|
|
- Task {
|
|
|
|
|
- await configureState()
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- func carbsDidUpdate(_: [CarbsEntry]) {
|
|
|
|
|
- // TODO:
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- func pumpBatteryDidChange(_: Battery) {
|
|
|
|
|
- // TODO:
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- func pumpReservoirDidChange(_: Decimal) {
|
|
|
|
|
- // TODO:
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|