Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions LoopFollow/Controllers/Nightscout/DeviceStatusLoop.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,12 @@ extension MainViewController {
if let recBolus = lastLoopRecord["recommendedBolus"] as? Double {
infoManager.updateInfoData(type: .recBolus, value: InsulinFormatter.shared.string(recBolus), numericValue: recBolus)
Observable.shared.deviceRecBolus.value = recBolus
let cycleTime = (lastLoopRecord["timestamp"] as? String).flatMap { formatter.date(from: $0)?.timeIntervalSince1970 }
Observable.shared.deviceRecBolusAt.value = cycleTime ?? lastLoopTime
} else {
infoManager.clearInfoData(type: .recBolus)
Observable.shared.deviceRecBolus.value = nil
Observable.shared.deviceRecBolusAt.value = nil
}
if let loopStatus = lastLoopRecord["recommendedTempBasal"] as? [String: AnyObject] {
if let tempBasalString = loopStatus["timestamp"] as? String,
Expand Down
2 changes: 2 additions & 0 deletions LoopFollow/Controllers/Nightscout/DeviceStatusOpenAPS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,11 @@ extension MainViewController {
if let rec = lastLoopRecord["recommendedBolus"] as? Double {
infoManager.updateInfoData(type: .recBolus, value: InsulinFormatter.shared.string(rec))
Observable.shared.deviceRecBolus.value = rec
Observable.shared.deviceRecBolusAt.value = updatedTime ?? Observable.shared.enactedOrSuggested.value
} else {
infoManager.clearInfoData(type: .recBolus)
Observable.shared.deviceRecBolus.value = nil
Observable.shared.deviceRecBolusAt.value = nil
}

// Eventual BG
Expand Down
5 changes: 3 additions & 2 deletions LoopFollow/Remote/LoopAPNS/LoopAPNSBolusView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -307,8 +307,9 @@ struct LoopAPNSBolusView: View {

private func loadRecommendedBolus() {
// Load recommended bolus from Observable
recommendedBolus = Observable.shared.deviceRecBolus.value
lastLoopTime = Observable.shared.alertLastLoopTime.value
let recommended = RecommendedBolus.current()
recommendedBolus = recommended?.units
lastLoopTime = recommended?.at

// Reset warning state when new data is loaded
showOldCalculationWarning = false
Expand Down
28 changes: 28 additions & 0 deletions LoopFollow/Remote/RecommendedBolus.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// LoopFollow
// RecommendedBolus.swift

import Foundation

/// The AID's latest bolus recommendation, offered only while no bolus came
/// after the loop cycle that calculated it. Until the next cycle, an earlier
/// recommendation leaves out that bolus, and using it could double it.
enum RecommendedBolus {
static func current() -> (units: Double, at: TimeInterval)? {
guard let units = Observable.shared.deviceRecBolus.value,
let at = Observable.shared.deviceRecBolusAt.value
else { return nil }
if let lastBolus = lastBolusAt(), lastBolus >= at { return nil }
return (units, at)
}

/// The latest manual bolus in Nightscout, or sent from LoopFollow or the
/// watch, which Nightscout may not show yet. Automatic boluses are left
/// out: the cycle that recommends a bolus can also give one.
static func lastBolusAt() -> TimeInterval? {
let nightscout = MainViewController.shared?.bolusData.map(\.date).max()
let sentBolus = Storage.shared.remoteBolusHistory.value.map(\.date).max()
let sentMeal = Storage.shared.remoteMealHistory.value.filter { $0.bolus > 0 }.map(\.date).max()
let sent = [sentBolus, sentMeal].compactMap { $0?.timeIntervalSince1970 }.max()
return [nightscout, sent].compactMap { $0 }.max()
}
}
14 changes: 6 additions & 8 deletions LoopFollow/Remote/TRC/BolusView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ struct BolusView: View {
@ObservedObject private var maxBolus = Storage.shared.maxBolus
@ObservedObject private var bolusIncrement = Storage.shared.bolusIncrement

@ObservedObject private var deviceRecBolus = Observable.shared.deviceRecBolus
@ObservedObject private var enactedOrSuggested = Observable.shared.enactedOrSuggested
@ObservedObject private var quickPickBoluses = QuickPickBolusesManager.shared

@FocusState private var bolusFieldIsFocused: Bool
Expand Down Expand Up @@ -203,8 +201,8 @@ struct BolusView: View {
title: Text("Old Calculation Warning"),
message: Text(alertMessage ?? ""),
primaryButton: .default(Text("Use Anyway")) {
if let rec = deviceRecBolus.value {
applyRecommendedBolus(rec)
if let rec = RecommendedBolus.current() {
applyRecommendedBolus(rec.units)
}
},
secondaryButton: .cancel()
Expand All @@ -220,10 +218,10 @@ struct BolusView: View {

@ViewBuilder
private func recommendedBlocks(now: Date) -> some View {
if let rec = deviceRecBolus.value,
let t = enactedOrSuggested.value
{
let ageSec = max(0, now.timeIntervalSince1970 - t)
// The TimelineView redraws this every second, so a new loop cycle or bolus shows up here.
if let recommended = RecommendedBolus.current() {
let rec = recommended.units
let ageSec = max(0, now.timeIntervalSince1970 - recommended.at)

if ageSec < 12 * 60 {
let maxU = maxBolus.value.doubleValue(for: .internationalUnit())
Expand Down
3 changes: 3 additions & 0 deletions LoopFollow/Storage/Observable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ class Observable {
var alertLastLoopTime = ObservableValue<TimeInterval?>(default: nil)
var previousAlertLastLoopTime = ObservableValue<TimeInterval?>(default: nil)
var deviceRecBolus = ObservableValue<Double?>(default: nil)
/// When the loop cycle behind `deviceRecBolus` ran. Unlike `alertLastLoopTime`,
/// a failed cycle, which keeps the previous recommendation, does not move it.
var deviceRecBolusAt = ObservableValue<TimeInterval?>(default: nil)
var deviceBatteryLevel = ObservableValue<Double?>(default: nil)
var deviceBatteryIsCharging = ObservableValue<Bool?>(default: nil)
var pumpBatteryLevel = ObservableValue<Double?>(default: nil)
Expand Down
9 changes: 3 additions & 6 deletions LoopFollow/Watch/WatchStateBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,7 @@ enum WatchStateBuilder {
let maxCarbs = Storage.shared.maxCarbs.value.doubleValue(for: .gram())
let mealWithFatProtein = kind == .trio && Storage.shared.mealWithFatProtein.value
let now = Date()

let recommendedAt: TimeInterval? = kind == .trio
? Observable.shared.enactedOrSuggested.value
: Observable.shared.alertLastLoopTime.value
let recommended = RecommendedBolus.current()

return WatchRemoteConfig(
kind: kind,
Expand All @@ -152,8 +149,8 @@ enum WatchStateBuilder {
maxProtein: Storage.shared.maxProtein.value.doubleValue(for: .gram()),
mealWithBolus: kind == .trio && Storage.shared.mealWithBolus.value,
mealWithFatProtein: mealWithFatProtein,
recommendedBolus: Observable.shared.deviceRecBolus.value,
recommendedBolusAt: recommendedAt.map { Date(timeIntervalSince1970: $0) },
recommendedBolus: recommended?.units,
recommendedBolusAt: recommended.map { Date(timeIntervalSince1970: $0.at) },
overridePresets: kind == .trio ? trioOverridePresets() : loopOverridePresets(),
tempTargetPresets: kind == .trio ? tempTargetPresets() : [],
quickPickBoluses: QuickPickBolusesManager.computeQuickPickBoluses(
Expand Down
8 changes: 5 additions & 3 deletions LoopFollowWatch/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ enum WatchRoute: Hashable {
case treatments
case meal
case bolus
case bolusAmount
case bolusPresets
case overrides
case tempTargets
case confirm(CommandRequest)
Expand All @@ -21,6 +21,8 @@ struct CommandRequest: Hashable {
let lines: [String]
let action: WatchCommand.Action
let deliversInsulin: Bool
/// Set when a bolus amount is the AID's recommendation, so it can be checked again before sending.
var recommendation: BolusRecommendation? = nil
}

/// Owns the navigation path so a finished command can return to the glucose page.
Expand Down Expand Up @@ -75,9 +77,9 @@ struct ContentView: View {
case .meal:
MealEntryView(remote: remote)
case .bolus:
BolusPickerView(remote: remote)
case .bolusAmount:
BolusAmountView(remote: remote)
case .bolusPresets:
BolusPresetsView(remote: remote)
case .overrides:
PresetListView(kind: .override, state: state, presets: remote.overridePresets)
case .tempTargets:
Expand Down
Loading