Skip to content
Merged
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
12 changes: 12 additions & 0 deletions Common/Models/WatchContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ final class WatchContext: RawRepresentable {
var glucose: HKQuantity?
var glucoseTrendRawValue: Int?
var glucoseDate: Date?
var glucoseSyncIdentifier: String?

var predictedGlucose: WatchPredictedGlucose?
var eventualGlucose: HKQuantity? {
Expand Down Expand Up @@ -58,6 +59,7 @@ final class WatchContext: RawRepresentable {

glucoseTrendRawValue = rawValue["gt"] as? Int
glucoseDate = rawValue["gd"] as? Date
glucoseSyncIdentifier = rawValue["gs"] as? String
iob = rawValue["iob"] as? Double
reservoir = rawValue["r"] as? Double
reservoirPercentage = rawValue["rp"] as? Double
Expand Down Expand Up @@ -95,6 +97,7 @@ final class WatchContext: RawRepresentable {

raw["gt"] = glucoseTrendRawValue
raw["gd"] = glucoseDate
raw["gs"] = glucoseSyncIdentifier
raw["iob"] = iob
raw["ld"] = loopLastRunDate
raw["r"] = reservoir
Expand All @@ -117,3 +120,12 @@ extension WatchContext {
}
}
}

extension WatchContext {
var newGlucoseSample: NewGlucoseSample? {
if let quantity = glucose, let date = glucoseDate, let syncIdentifier = glucoseSyncIdentifier {
return NewGlucoseSample(date: date, quantity: quantity, isDisplayOnly: false, syncIdentifier: syncIdentifier, syncVersion: 0)
}
return nil
}
}
3 changes: 2 additions & 1 deletion Loop Status Extension/StatusViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -256,11 +256,12 @@ class StatusViewController: UIViewController, NCWidgetProviding {
return
}

if let lastGlucose = glucose.last {
if let lastGlucose = glucose.last, let recencyInterval = defaults.loopSettings?.inputDataRecencyInterval {
self.hudView.glucoseHUD.setGlucoseQuantity(
lastGlucose.quantity.doubleValue(for: unit),
at: lastGlucose.startDate,
unit: unit,
staleGlucoseAge: recencyInterval,
sensor: context.sensor
)
}
Expand Down
22 changes: 14 additions & 8 deletions Loop.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,8 @@
C17824A61E1AF91F00D9D25C /* BolusRecommendation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17824A41E1AD4D100D9D25C /* BolusRecommendation.swift */; };
C1814B86225E507C008D2D8E /* Sequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1814B85225E507C008D2D8E /* Sequence.swift */; };
C18C8C511D5A351900E043FB /* NightscoutDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */; };
C19E96DF23D275F8003F79B0 /* LoopCompletionFreshness.swift in Sources */ = {isa = PBXBuildFile; fileRef = C19E96DD23D2733F003F79B0 /* LoopCompletionFreshness.swift */; };
C19E96E023D275FA003F79B0 /* LoopCompletionFreshness.swift in Sources */ = {isa = PBXBuildFile; fileRef = C19E96DD23D2733F003F79B0 /* LoopCompletionFreshness.swift */; };
C1A3EED2235233E1007672E3 /* DerivedAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C1A3EED1235233E1007672E3 /* DerivedAssets.xcassets */; };
C1A3EED423523551007672E3 /* DerivedAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C1A3EED323523551007672E3 /* DerivedAssets.xcassets */; };
C1A3EED523535FFF007672E3 /* DefaultAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 894F71E11FFEC4D8007D365C /* DefaultAssets.xcassets */; };
Expand Down Expand Up @@ -1087,6 +1089,7 @@
C18A491422FCC22900FDA733 /* build-derived-watch-assets.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "build-derived-watch-assets.sh"; sourceTree = "<group>"; };
C18A491522FCC22900FDA733 /* copy-plugins.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "copy-plugins.sh"; sourceTree = "<group>"; };
C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NightscoutDataManager.swift; sourceTree = "<group>"; };
C19E96DD23D2733F003F79B0 /* LoopCompletionFreshness.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopCompletionFreshness.swift; sourceTree = "<group>"; };
C1A3EED1235233E1007672E3 /* DerivedAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = DerivedAssets.xcassets; sourceTree = "<group>"; };
C1A3EED323523551007672E3 /* DerivedAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = DerivedAssets.xcassets; sourceTree = "<group>"; };
C1C6591B1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = recommend_temp_basal_dropping_then_rising.json; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1517,6 +1520,7 @@
43C05CB721EBEA54006FB252 /* HKUnit.swift */,
434FF1E91CF26C29000DB779 /* IdentifiableClass.swift */,
430B298C2041F56500BA9F93 /* LoopSettings.swift */,
C19E96DD23D2733F003F79B0 /* LoopCompletionFreshness.swift */,
430B29892041F54A00BA9F93 /* NSUserDefaults.swift */,
431E73471FF95A900069B5F7 /* PersistenceController.swift */,
43D848AF1E7DCBE100DADCBC /* Result.swift */,
Expand Down Expand Up @@ -2790,6 +2794,7 @@
files = (
43C05CB821EBEA54006FB252 /* HKUnit.swift in Sources */,
4345E3F421F036FC009E00E5 /* Result.swift in Sources */,
C19E96E023D275FA003F79B0 /* LoopCompletionFreshness.swift in Sources */,
43D9002021EB209400AF44BF /* NSTimeInterval.swift in Sources */,
43C05CA921EB2B26006FB252 /* PersistenceController.swift in Sources */,
431EA87221EB29150076EC1A /* InsulinModelSettings.swift in Sources */,
Expand Down Expand Up @@ -2842,6 +2847,7 @@
files = (
43C05CB921EBEA54006FB252 /* HKUnit.swift in Sources */,
4345E3F521F036FC009E00E5 /* Result.swift in Sources */,
C19E96DF23D275F8003F79B0 /* LoopCompletionFreshness.swift in Sources */,
43D9FFFB21EAF3D300AF44BF /* NSTimeInterval.swift in Sources */,
43C05CA821EB2B26006FB252 /* PersistenceController.swift in Sources */,
431EA87321EB29160076EC1A /* InsulinModelSettings.swift in Sources */,
Expand Down Expand Up @@ -3531,7 +3537,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = "$(APPICON_NAME)";
CODE_SIGN_ENTITLEMENTS = Loop/Loop.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = UY678SP37Q;
ENABLE_BITCODE = YES;
INFOPLIST_FILE = Loop/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
Expand All @@ -3550,7 +3556,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = "$(APPICON_NAME)";
CODE_SIGN_ENTITLEMENTS = Loop/Loop.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = UY678SP37Q;
ENABLE_BITCODE = YES;
INFOPLIST_FILE = Loop/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
Expand All @@ -3568,7 +3574,7 @@
CODE_SIGN_ENTITLEMENTS = "WatchApp Extension/WatchApp Extension.entitlements";
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer";
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = UY678SP37Q;
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/watchOS";
INFOPLIST_FILE = "WatchApp Extension/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
Expand All @@ -3591,7 +3597,7 @@
CODE_SIGN_ENTITLEMENTS = "WatchApp Extension/WatchApp Extension.entitlements";
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer";
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = UY678SP37Q;
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/watchOS";
INFOPLIST_FILE = "WatchApp Extension/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
Expand All @@ -3612,7 +3618,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = "$(APPICON_NAME)";
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer";
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = UY678SP37Q;
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/watchOS";
IBSC_MODULE = WatchApp_Extension;
INFOPLIST_FILE = WatchApp/Info.plist;
Expand All @@ -3633,7 +3639,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = "$(APPICON_NAME)";
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer";
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = UY678SP37Q;
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/watchOS";
IBSC_MODULE = WatchApp_Extension;
INFOPLIST_FILE = WatchApp/Info.plist;
Expand Down Expand Up @@ -3913,7 +3919,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = UY678SP37Q;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = "Loop Status Extension/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
Expand All @@ -3935,7 +3941,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = UY678SP37Q;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = "Loop Status Extension/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
Expand Down
18 changes: 9 additions & 9 deletions Loop/Managers/LoopDataManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -726,7 +726,7 @@ extension LoopDataManager {
// Fetch glucose effects as far back as we want to make retroactive analysis
var latestGlucoseDate: Date?
updateGroup.enter()
glucoseStore.getCachedGlucoseSamples(start: Date(timeIntervalSinceNow: -settings.recencyInterval)) { (values) in
glucoseStore.getCachedGlucoseSamples(start: Date(timeIntervalSinceNow: -settings.inputDataRecencyInterval)) { (values) in
latestGlucoseDate = values.last?.startDate
updateGroup.leave()
}
Expand Down Expand Up @@ -917,11 +917,11 @@ extension LoopDataManager {
let lastGlucoseDate = glucose.startDate
let now = Date()

guard now.timeIntervalSince(lastGlucoseDate) <= settings.recencyInterval else {
guard now.timeIntervalSince(lastGlucoseDate) <= settings.inputDataRecencyInterval else {
throw LoopError.glucoseTooOld(date: glucose.startDate)
}

guard now.timeIntervalSince(pumpStatusDate) <= settings.recencyInterval else {
guard now.timeIntervalSince(pumpStatusDate) <= settings.inputDataRecencyInterval else {
throw LoopError.pumpDataTooOld(date: pumpStatusDate)
}

Expand Down Expand Up @@ -1015,11 +1015,11 @@ extension LoopDataManager {
let lastGlucoseDate = glucose.startDate
let now = Date()

guard now.timeIntervalSince(lastGlucoseDate) <= settings.recencyInterval else {
guard now.timeIntervalSince(lastGlucoseDate) <= settings.inputDataRecencyInterval else {
throw LoopError.glucoseTooOld(date: glucose.startDate)
}

guard now.timeIntervalSince(pumpStatusDate) <= settings.recencyInterval else {
guard now.timeIntervalSince(pumpStatusDate) <= settings.inputDataRecencyInterval else {
throw LoopError.pumpDataTooOld(date: pumpStatusDate)
}

Expand Down Expand Up @@ -1112,7 +1112,7 @@ extension LoopDataManager {
retrospectiveGlucoseEffect = retrospectiveCorrection.computeEffect(
startingAt: glucose,
retrospectiveGlucoseDiscrepanciesSummed: retrospectiveGlucoseDiscrepanciesSummed,
recencyInterval: settings.recencyInterval,
recencyInterval: settings.inputDataRecencyInterval,
insulinSensitivitySchedule: insulinSensitivitySchedule,
basalRateSchedule: basalRateSchedule,
glucoseCorrectionRangeSchedule: settings.glucoseTargetRangeSchedule,
Expand All @@ -1126,7 +1126,7 @@ extension LoopDataManager {
return retrospectiveCorrection.computeEffect(
startingAt: glucose,
retrospectiveGlucoseDiscrepanciesSummed: retrospectiveGlucoseDiscrepanciesSummed,
recencyInterval: settings.recencyInterval,
recencyInterval: settings.inputDataRecencyInterval,
insulinSensitivitySchedule: insulinSensitivitySchedule,
basalRateSchedule: basalRateSchedule,
glucoseCorrectionRangeSchedule: settings.glucoseTargetRangeSchedule,
Expand Down Expand Up @@ -1155,12 +1155,12 @@ extension LoopDataManager {

let startDate = Date()

guard startDate.timeIntervalSince(glucose.startDate) <= settings.recencyInterval else {
guard startDate.timeIntervalSince(glucose.startDate) <= settings.inputDataRecencyInterval else {
self.predictedGlucose = nil
throw LoopError.glucoseTooOld(date: glucose.startDate)
}

guard startDate.timeIntervalSince(pumpStatusDate) <= settings.recencyInterval else {
guard startDate.timeIntervalSince(pumpStatusDate) <= settings.inputDataRecencyInterval else {
self.predictedGlucose = nil
throw LoopError.pumpDataTooOld(date: pumpStatusDate)
}
Expand Down
12 changes: 12 additions & 0 deletions Loop/Managers/WatchDataManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,18 @@ final class WatchDataManager: NSObject {
if let trend = self.deviceManager.cgmManager?.sensorState?.trendType {
context.glucoseTrendRawValue = trend.rawValue
}

if let glucose = glucose {
updateGroup.enter()
manager.glucoseStore.getCachedGlucoseSamples(start: glucose.startDate) { (samples) in
if let sample = samples.last {
context.glucose = sample.quantity
context.glucoseDate = sample.startDate
context.glucoseSyncIdentifier = sample.syncIdentifier
}
updateGroup.leave()
}
}

updateGroup.enter()
manager.doseStore.insulinOnBoard(at: Date()) { (result) in
Expand Down
1 change: 1 addition & 0 deletions Loop/View Controllers/StatusTableViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@ final class StatusTableViewController: ChartsTableViewController {
hudView.glucoseHUD.setGlucoseQuantity(glucose.quantity.doubleValue(for: unit),
at: glucose.startDate,
unit: unit,
staleGlucoseAge: self.deviceManager.loopManager.settings.inputDataRecencyInterval,
sensor: self.deviceManager.sensorState
)
}
Expand Down
57 changes: 57 additions & 0 deletions LoopCore/LoopCompletionFreshness.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// LoopCompletionFreshness.swift
// Loop
//
// Created by Pete Schwamb on 1/17/20.
// Copyright © 2020 LoopKit Authors. All rights reserved.
//

import Foundation

public enum LoopCompletionFreshness {
case fresh
case aging
case stale
case unknown

public var maxAge: TimeInterval? {
switch self {
case .fresh:
return TimeInterval(minutes: 6)
case .aging:
return TimeInterval(minutes: 16)
case .stale:
return TimeInterval(hours: 12)
case .unknown:
return nil
}
}

public init(age: TimeInterval?) {
guard let age = age else {
self = .unknown
return
}

switch age {
case let t where t <= LoopCompletionFreshness.fresh.maxAge!:
self = .fresh
case let t where t <= LoopCompletionFreshness.aging.maxAge!:
self = .aging
case let t where t <= LoopCompletionFreshness.stale.maxAge!:
self = .stale
default:
self = .unknown
}
}

public init(lastCompletion: Date?, at date: Date = Date()) {
guard let lastCompletion = lastCompletion else {
self = .unknown
return
}

self = LoopCompletionFreshness(age: date.timeIntervalSince(lastCompletion))
}

}
9 changes: 7 additions & 2 deletions LoopCore/LoopSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,13 @@ public struct LoopSettings: Equatable {
/// The interval over which to aggregate changes in glucose for retrospective correction
public let retrospectiveCorrectionGroupingInterval = TimeInterval(minutes: 30)

/// The amount of time since a given date that data should be considered valid
public let recencyInterval = TimeInterval(minutes: 15)
/// The amount of time since a given date that input data should be considered valid
public let inputDataRecencyInterval = TimeInterval(minutes: 15)

/// Loop completion aging category limits
public let completionFreshLimit = TimeInterval(minutes: 6)
public let completionAgingLimit = TimeInterval(minutes: 16)
public let completionStaleLimit = TimeInterval(hours: 12)

public let batteryReplacementDetectionThreshold = 0.5

Expand Down
10 changes: 5 additions & 5 deletions LoopUI/Views/GlucoseHUDView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,27 +91,27 @@ public final class GlucoseHUDView: BaseHUDView {
}
}

public func setGlucoseQuantity(_ glucoseQuantity: Double, at glucoseStartDate: Date, unit: HKUnit, sensor: SensorDisplayable?) {
public func setGlucoseQuantity(_ glucoseQuantity: Double, at glucoseStartDate: Date, unit: HKUnit, staleGlucoseAge: TimeInterval, sensor: SensorDisplayable?) {
var accessibilityStrings = [String]()

let time = timeFormatter.string(from: glucoseStartDate)
caption?.text = time

let sensorDataCurrent = glucoseStartDate.timeIntervalSinceNow > TimeInterval(minutes: -15)
let glucoseValueCurrent = glucoseStartDate.timeIntervalSinceNow > -staleGlucoseAge

let numberFormatter = NumberFormatter.glucoseFormatter(for: unit)
if let valueString = numberFormatter.string(from: glucoseQuantity) {
if sensorDataCurrent {
if glucoseValueCurrent {
glucoseLabel.text = valueString
} else {
glucoseLabel.text = "-"
glucoseLabel.text = "---"
}
accessibilityStrings.append(String(format: LocalizedString("%1$@ at %2$@", comment: "Accessbility format value describing glucose: (1: glucose number)(2: glucose time)"), valueString, time))
}

var unitStrings = [unit.localizedShortUnitString]

if let trend = sensor?.trendType, sensorDataCurrent {
if let trend = sensor?.trendType, glucoseValueCurrent {
unitStrings.append(trend.symbol)
accessibilityStrings.append(trend.localizedDescription)
}
Expand Down
Loading