diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index 75abd492c3..da836e102d 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -299,7 +299,7 @@ final class LoopDataManager { do { try self.update() - completion(.success(try self.recommendBolus())) + completion(.success(self.recommendedBolus?.recommendation)) } catch let error { completion(.failure(error)) } @@ -586,7 +586,7 @@ final class LoopDataManager { if predictedGlucose == nil { do { - try updatePredictedGlucoseAndRecommendedBasal() + try updatePredictedGlucoseAndRecommendedBasalAndBolus() } catch let error { logger.error(error) @@ -717,6 +717,7 @@ final class LoopDataManager { fileprivate var predictedGlucose: [GlucoseValue]? { didSet { recommendedTempBasal = nil + recommendedBolus = nil } } fileprivate var retrospectivePredictedGlucose: [GlucoseValue]? { @@ -726,6 +727,8 @@ final class LoopDataManager { } fileprivate var recommendedTempBasal: (recommendation: TempBasalRecommendation, date: Date)? + fileprivate var recommendedBolus: (recommendation: BolusRecommendation, date: Date)? + fileprivate var carbsOnBoard: CarbValue? fileprivate var lastTempBasal: DoseEntry? @@ -841,7 +844,7 @@ final class LoopDataManager { /// - LoopError.glucoseTooOld /// - LoopError.missingDataError /// - LoopError.pumpDataTooOld - private func updatePredictedGlucoseAndRecommendedBasal() throws { + private func updatePredictedGlucoseAndRecommendedBasalAndBolus() throws { dispatchPrecondition(condition: .onQueue(dataAccessQueue)) guard let glucose = glucoseStore.latestGlucose else { @@ -879,14 +882,25 @@ final class LoopDataManager { let glucoseTargetRange = settings.glucoseTargetRangeSchedule, let insulinSensitivity = insulinSensitivitySchedule, let basalRates = basalRateSchedule, + let maxBolus = settings.maximumBolus, let model = insulinModelSettings?.model else { throw LoopError.configurationError("Check settings") } + + let pendingInsulin = try self.getPendingInsulin() - guard - lastRequestedBolus == nil, // Don't recommend changes if a bolus was just set - let tempBasal = predictedGlucose.recommendedTempBasal( + guard lastRequestedBolus == nil + else { + // Don't recommend changes if a bolus was just requested. + // Sending additional pump commands is not going to be + // successful in any case. + recommendedBolus = nil + recommendedTempBasal = nil + return + } + + let tempBasal = predictedGlucose.recommendedTempBasal( to: glucoseTargetRange, suspendThreshold: settings.suspendThreshold?.quantity, sensitivity: insulinSensitivity, @@ -895,42 +909,13 @@ final class LoopDataManager { maxBasalRate: maxBasal, lastTempBasal: lastTempBasal ) - else { + + if let temp = tempBasal { + recommendedTempBasal = (recommendation: temp, date: startDate) + } else { recommendedTempBasal = nil - return - } - - recommendedTempBasal = (recommendation: tempBasal, date: Date()) - } - - /// - Returns: A bolus recommendation from the current data - /// - Throws: - /// - LoopError.configurationError - /// - LoopError.glucoseTooOld - /// - LoopError.missingDataError - fileprivate func recommendBolus() throws -> BolusRecommendation { - dispatchPrecondition(condition: .onQueue(dataAccessQueue)) - - guard - let predictedGlucose = predictedGlucose, - let maxBolus = settings.maximumBolus, - let glucoseTargetRange = settings.glucoseTargetRangeSchedule, - let insulinSensitivity = insulinSensitivitySchedule, - let model = insulinModelSettings?.model - else { - throw LoopError.configurationError("Check Settings") - } - - guard let glucoseDate = predictedGlucose.first?.startDate else { - throw LoopError.missingDataError(details: "No glucose data found", recovery: "Check your CGM source") } - - guard abs(glucoseDate.timeIntervalSinceNow) <= recencyInterval else { - throw LoopError.glucoseTooOld(date: glucoseDate) - } - - let pendingInsulin = try self.getPendingInsulin() - + let recommendation = predictedGlucose.recommendedBolus( to: glucoseTargetRange, suspendThreshold: settings.suspendThreshold?.quantity, @@ -939,8 +924,7 @@ final class LoopDataManager { pendingInsulin: pendingInsulin, maxBolus: maxBolus ) - - return recommendation + recommendedBolus = (recommendation: recommendation, date: startDate) } /// *This method should only be called from the `dataAccessQueue`* @@ -997,6 +981,8 @@ protocol LoopState { /// The recommended temp basal based on predicted glucose var recommendedTempBasal: (recommendation: TempBasalRecommendation, date: Date)? { get } + var recommendedBolus: (recommendation: BolusRecommendation, date: Date)? { get } + /// The retrospective prediction over a recent period of glucose samples var retrospectivePredictedGlucose: [GlucoseValue]? { get } @@ -1008,15 +994,6 @@ protocol LoopState { /// - Returns: An timeline of predicted glucose values /// - Throws: LoopError.missingDataError if prediction cannot be computed func predictGlucose(using inputs: PredictionInputEffect) throws -> [GlucoseValue] - - /// Calculates a recommended bolus based on predicted glucose - /// - /// - Returns: A bolus recommendation - /// - Throws: An error describing why a bolus couldnʼt be computed - /// - LoopError.configurationError - /// - LoopError.glucoseTooOld - /// - LoopError.missingDataError - func recommendBolus() throws -> BolusRecommendation } @@ -1064,6 +1041,11 @@ extension LoopDataManager { dispatchPrecondition(condition: .onQueue(loopDataManager.dataAccessQueue)) return loopDataManager.recommendedTempBasal } + + var recommendedBolus: (recommendation: BolusRecommendation, date: Date)? { + dispatchPrecondition(condition: .onQueue(loopDataManager.dataAccessQueue)) + return loopDataManager.recommendedBolus + } var retrospectivePredictedGlucose: [GlucoseValue]? { dispatchPrecondition(condition: .onQueue(loopDataManager.dataAccessQueue)) @@ -1073,10 +1055,6 @@ extension LoopDataManager { func predictGlucose(using inputs: PredictionInputEffect) throws -> [GlucoseValue] { return try loopDataManager.predictGlucose(using: inputs) } - - func recommendBolus() throws -> BolusRecommendation { - return try loopDataManager.recommendBolus() - } } /// Executes a closure with access to the current state of the loop. @@ -1118,6 +1096,7 @@ extension LoopDataManager { "predictedGlucose: \(state.predictedGlucose ?? [])", "retrospectivePredictedGlucose: \(state.retrospectivePredictedGlucose ?? [])", "recommendedTempBasal: \(String(describing: state.recommendedTempBasal))", + "recommendedBolus: \(String(describing: state.recommendedBolus))", "lastBolus: \(String(describing: manager.lastRequestedBolus))", "lastGlucoseChange: \(String(describing: manager.lastGlucoseChange))", "retrospectiveGlucoseChange: \(String(describing: manager.retrospectiveGlucoseChange))", diff --git a/Loop/Managers/NightscoutDataManager.swift b/Loop/Managers/NightscoutDataManager.swift index 5e7706cce4..a773507f84 100644 --- a/Loop/Managers/NightscoutDataManager.swift +++ b/Loop/Managers/NightscoutDataManager.swift @@ -43,15 +43,7 @@ final class NightscoutDataManager { var loopError = state.error let recommendedBolus: Double? - do { - recommendedBolus = try state.recommendBolus().amount - } catch let error { - recommendedBolus = nil - - if loopError == nil { - loopError = error - } - } + recommendedBolus = state.recommendedBolus?.recommendation.amount let carbsOnBoard = state.carbsOnBoard let predictedGlucose = state.predictedGlucose diff --git a/Loop/Managers/WatchDataManager.swift b/Loop/Managers/WatchDataManager.swift index 44255cd80f..a20a326050 100644 --- a/Loop/Managers/WatchDataManager.swift +++ b/Loop/Managers/WatchDataManager.swift @@ -125,7 +125,7 @@ final class WatchDataManager: NSObject, WCSessionDelegate { context.reservoir = reservoir?.unitVolume context.loopLastRunDate = state.lastLoopCompleted - context.recommendedBolusDose = try? state.recommendBolus().amount + context.recommendedBolusDose = state.recommendedBolus?.recommendation.amount context.maxBolus = manager.settings.maximumBolus if let glucoseTargetRangeSchedule = manager.settings.glucoseTargetRangeSchedule { diff --git a/Loop/View Controllers/BolusViewController+LoopDataManager.swift b/Loop/View Controllers/BolusViewController+LoopDataManager.swift index 280d333530..84246ef882 100644 --- a/Loop/View Controllers/BolusViewController+LoopDataManager.swift +++ b/Loop/View Controllers/BolusViewController+LoopDataManager.swift @@ -20,7 +20,7 @@ extension BolusViewController { if let recommendation = recommendation { bolusRecommendation = recommendation } else { - bolusRecommendation = try? state.recommendBolus() + bolusRecommendation = state.recommendedBolus?.recommendation } manager.doseStore.insulinOnBoard(at: Date()) { (result) in