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
20 changes: 20 additions & 0 deletions Loop.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,11 @@
4F7E8AC520E2AB9600AEA65E /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F7E8AC420E2AB9600AEA65E /* Date.swift */; };
4FC8C8011DEB93E400A1452E /* NSUserDefaults+StatusExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FC8C8001DEB93E400A1452E /* NSUserDefaults+StatusExtension.swift */; };
4FDDD23720DC51DF00D04B16 /* LoopDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FDDD23620DC51DF00D04B16 /* LoopDataManager.swift */; };
D7A3000100000000000A0002 /* WatchCGMHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7A3000100000000000A0001 /* WatchCGMHost.swift */; };
D7A3000100000000000A0004 /* WatchDeviceManagers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7A3000100000000000A0003 /* WatchDeviceManagers.swift */; };
101AA7370B0E499FBD88BB9F /* G7SensorKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = F8760AF31E3C43F6AFBA1BE7 /* G7SensorKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
921B7FAE002E40EFAB3F50C6 /* G7SensorKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8760AF31E3C43F6AFBA1BE7 /* G7SensorKit.framework */; };
8B5CF0144D79477AACBE4A82 /* LoopKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4344628320A7A3BE00C4BE6F /* LoopKit.framework */; };
4FF4D0F81E1725B000846527 /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 434F54561D287FDB002A9274 /* NibLoadable.swift */; };
5021CA8C264146C9DFF33E9D /* PluginPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE102E23D8C1C1291EF66387 /* PluginPickerView.swift */; };
7D23667D21250C7E0028B67D /* LocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D23667C21250C7E0028B67D /* LocalizedString.swift */; };
Expand Down Expand Up @@ -467,6 +472,7 @@
C129BF4A2B2791EE00DF15CB /* TemporaryPresetsManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C129BF492B2791EE00DF15CB /* TemporaryPresetsManagerTests.swift */; };
C129D3BF2B8697F100FEA6A9 /* TempBasalRecommendationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C129D3BE2B8697F100FEA6A9 /* TempBasalRecommendationTests.swift */; };
C12D72402E4FBC5F00BD628A /* WatchActionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12D723F2E4FBC5D00BD628A /* WatchActionsView.swift */; };
D7A3000100000000000A0006 /* WatchSensorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7A3000100000000000A0005 /* WatchSensorView.swift */; };
C13072BA2A76AF31009A7C58 /* live_capture_predicted_glucose.json in Resources */ = {isa = PBXBuildFile; fileRef = C13072B92A76AF31009A7C58 /* live_capture_predicted_glucose.json */; };
C13255D6223E7BE2008AF50C /* BolusProgressTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C1F8B1DB223862D500DD66CF /* BolusProgressTableViewCell.xib */; };
C138C6F82C1B8A2C00F08F1A /* GlucoseCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = C138C6F72C1B8A2C00F08F1A /* GlucoseCondition.swift */; };
Expand Down Expand Up @@ -759,6 +765,7 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
101AA7370B0E499FBD88BB9F /* G7SensorKit.framework in Embed Frameworks */,
43C05CB221EBD88A006FB252 /* LoopCore.framework in Embed Frameworks */,
C1275E1C2E82269A0013B99D /* LoopKit.framework in Embed Frameworks */,
);
Expand Down Expand Up @@ -1025,6 +1032,9 @@
4F7E8AC620E2AC0300AEA65E /* WatchPredictedGlucose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchPredictedGlucose.swift; sourceTree = "<group>"; };
4FC8C8001DEB93E400A1452E /* NSUserDefaults+StatusExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSUserDefaults+StatusExtension.swift"; sourceTree = "<group>"; };
4FDDD23620DC51DF00D04B16 /* LoopDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopDataManager.swift; sourceTree = "<group>"; };
D7A3000100000000000A0001 /* WatchCGMHost.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchCGMHost.swift; sourceTree = "<group>"; };
D7A3000100000000000A0003 /* WatchDeviceManagers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchDeviceManagers.swift; sourceTree = "<group>"; };
F8760AF31E3C43F6AFBA1BE7 /* G7SensorKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = G7SensorKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
4FF4D0FF1E18374700846527 /* WatchContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WatchContext.swift; sourceTree = "<group>"; };
5181B60CBF3CB2DA537F0475 /* Loop/Resources/Sounds/simple.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Loop/Resources/Sounds/simple.caf; sourceTree = SOURCE_ROOT; };
62F7CB5D5DC337E705032AC7 /* Loop/Resources/Sounds/dings.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Loop/Resources/Sounds/dings.caf; sourceTree = SOURCE_ROOT; };
Expand Down Expand Up @@ -1300,6 +1310,7 @@
C12CB9B623106A6200F84978 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Intents.strings; sourceTree = "<group>"; };
C12CB9B823106A6300F84978 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Intents.strings; sourceTree = "<group>"; };
C12D723F2E4FBC5D00BD628A /* WatchActionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchActionsView.swift; sourceTree = "<group>"; };
D7A3000100000000000A0005 /* WatchSensorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchSensorView.swift; sourceTree = "<group>"; };
C13072B92A76AF31009A7C58 /* live_capture_predicted_glucose.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = live_capture_predicted_glucose.json; sourceTree = "<group>"; };
C138C6F72C1B8A2C00F08F1A /* GlucoseCondition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseCondition.swift; sourceTree = "<group>"; };
C13DA2AF24F6C7690098BB29 /* UIViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1479,6 +1490,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
921B7FAE002E40EFAB3F50C6 /* G7SensorKit.framework in Frameworks */,
8B5CF0144D79477AACBE4A82 /* LoopKit.framework in Frameworks */,
C1E2773E224177C000354103 /* ClockKit.framework in Frameworks */,
4344628220A7A37F00C4BE6F /* CoreBluetooth.framework in Frameworks */,
43D9002F21EB234400AF44BF /* LoopCore.framework in Frameworks */,
Expand Down Expand Up @@ -2213,6 +2226,8 @@
isa = PBXGroup;
children = (
4FDDD23620DC51DF00D04B16 /* LoopDataManager.swift */,
D7A3000100000000000A0001 /* WatchCGMHost.swift */,
D7A3000100000000000A0003 /* WatchDeviceManagers.swift */,
898ECA62218ABD21001E9D35 /* ComplicationChartManager.swift */,
);
path = Managers;
Expand Down Expand Up @@ -2462,6 +2477,7 @@
894F6DD8243C060600CCE676 /* ScalablePositionedText.swift */,
89A605EA243288E4009C1096 /* TopDownTriangle.swift */,
C12D723F2E4FBC5D00BD628A /* WatchActionsView.swift */,
D7A3000100000000000A0005 /* WatchSensorView.swift */,
);
path = Views;
sourceTree = "<group>";
Expand Down Expand Up @@ -2583,6 +2599,7 @@
43D9002C21EB225D00AF44BF /* HealthKit.framework */,
43F5C2C81B929C09003EB13D /* HealthKit.framework */,
4344628320A7A3BE00C4BE6F /* LoopKit.framework */,
F8760AF31E3C43F6AFBA1BE7 /* G7SensorKit.framework */,
437AFEE6203688CF008C4892 /* LoopKitUI.framework */,
892A5D5A222F0D7C008961AB /* LoopTestingKit.framework */,
C1E2774722433D7A00354103 /* MKRingProgressView.framework */,
Expand Down Expand Up @@ -3701,6 +3718,8 @@
895788AF242E69A2002CB114 /* BolusInput.swift in Sources */,
894F6DDB243C07CF00CCE676 /* GramLabel.swift in Sources */,
4FDDD23720DC51DF00D04B16 /* LoopDataManager.swift in Sources */,
D7A3000100000000000A0002 /* WatchCGMHost.swift in Sources */,
D7A3000100000000000A0004 /* WatchDeviceManagers.swift in Sources */,
89E267FD2292456700A3F2AF /* FeatureFlags.swift in Sources */,
898ECA60218ABD17001E9D35 /* GlucoseChartScaler.swift in Sources */,
894F6DD9243C060600CCE676 /* ScalablePositionedText.swift in Sources */,
Expand All @@ -3710,6 +3729,7 @@
C10C57FC2E70B8B900A4825C /* EnvironmentValues+GlucoseDisplayUnit.swift in Sources */,
4372E492213D956C0068E043 /* GlucoseRangeSchedule.swift in Sources */,
C12D72402E4FBC5F00BD628A /* WatchActionsView.swift in Sources */,
D7A3000100000000000A0006 /* WatchSensorView.swift in Sources */,
A9347F3324E7522900C99C34 /* WatchHistoricalCarbs.swift in Sources */,
895788AD242E69A2002CB114 /* AbsorptionTimeSelection.swift in Sources */,
89A605EF2432925D009C1096 /* CompletionCheckmark.swift in Sources */,
Expand Down
3 changes: 3 additions & 0 deletions Loop/Managers/WatchDataManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,9 @@ final class WatchDataManager: NSObject {
dosingDecision.carbsOnBoard = carbsOnBoard

context.cgmManagerState = self.deviceManager.cgmManager?.rawValue
if let sharing = self.deviceManager.cgmManager as? DeviceConfigurationSharing, sharing.isConfigurationSharingEnabled {
context.cgmConfiguration = sharing.exportConfiguration().rawValue
}

let settings = self.settingsManager.loopSettings

Expand Down
5 changes: 5 additions & 0 deletions LoopCore/Models/WatchContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ public final class WatchContext: RawRepresentable {

public var cgmManagerState: CGMManager.RawStateValue?

/// The CGM's `SharedDeviceConfiguration`, for a watch that reads the sensor itself.
public var cgmConfiguration: SharedDeviceConfiguration.RawValue?

public var isClosedLoop: Bool?
public var deviceIssue: Bool?
public var isOnboardingCompleted: Bool?
Expand Down Expand Up @@ -178,6 +181,7 @@ public final class WatchContext: RawRepresentable {
cob = rawValue["cob"] as? Double

cgmManagerState = rawValue["cgmManagerState"] as? CGMManager.RawStateValue
cgmConfiguration = rawValue["cgmConfiguration"] as? SharedDeviceConfiguration.RawValue

if let rawValue = rawValue["pg"] as? WatchPredictedGlucose.RawValue {
predictedGlucose = WatchPredictedGlucose(rawValue: rawValue)
Expand All @@ -198,6 +202,7 @@ public final class WatchContext: RawRepresentable {
raw["oc"] = isOnboardingCompleted

raw["cgmManagerState"] = cgmManagerState
raw["cgmConfiguration"] = cgmConfiguration

raw["cob"] = cob

Expand Down
8 changes: 4 additions & 4 deletions WatchApp Extension/ComplicationController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ final class ComplicationController: NSObject, CLKComplicationDataSource {
// MARK: - Timeline Configuration

func getTimelineStartDate(for complication: CLKComplication, withHandler handler: @escaping (Date?) -> Void) {
if let date = LoopDataManager.shared.activeContext?.glucoseDate {
if let date = LoopDataManager.shared.glucoseDisplayContext?.glucoseDate {
handler(date)
} else {
handler(nil)
}
}

func getTimelineEndDate(for complication: CLKComplication, withHandler handler: @escaping (Date?) -> Void) {
if let date = LoopDataManager.shared.activeContext?.glucoseDate {
if let date = LoopDataManager.shared.glucoseDisplayContext?.glucoseDate {
handler(date)
} else {
handler(nil)
Expand Down Expand Up @@ -97,7 +97,7 @@ final class ComplicationController: NSObject, CLKComplicationDataSource {

self.log.default("Updating current complication timeline entry")

if let context = LoopDataManager.shared.activeContext,
if let context = LoopDataManager.shared.glucoseDisplayContext,
let template = CLKComplicationTemplate.templateForFamily(complication.family,
from: context,
at: timelineDate,
Expand All @@ -123,7 +123,7 @@ final class ComplicationController: NSObject, CLKComplicationDataSource {
updateChartManagerIfNeeded {
let entries: [CLKComplicationTimelineEntry]?

guard let context = LoopDataManager.shared.activeContext,
guard let context = LoopDataManager.shared.glucoseDisplayContext,
let glucoseDate = context.glucoseDate else
{
handler(nil)
Expand Down
54 changes: 52 additions & 2 deletions WatchApp Extension/ExtensionDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ class ExtensionDelegate: NSObject, WKApplicationDelegate {
}
})

// At launch, so a relaunch for the CGM's Bluetooth link finds its central.
_ = WatchCGMHost.shared

session.activate()
}

Expand Down Expand Up @@ -93,20 +96,31 @@ class ExtensionDelegate: NSObject, WKApplicationDelegate {
// glucose we're missing.
loopManager.requestContextUpdate()
loopManager.requestGlucoseBackfillIfNecessary()
WatchCGMHost.shared.recheckAcquisition()
}

func applicationWillResignActive() {
}

// Presumably the main thread?
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
// watchOS can deliver a Bluetooth wake on CoreBluetooth's queue; everything below is main-only.
guard Thread.isMainThread else {
DispatchQueue.main.async { self.handle(backgroundTasks) }
return
}
loopManager.requestGlucoseBackfillIfNecessary()

for task in backgroundTasks {
switch task {
case is WKApplicationRefreshBackgroundTask:
log.default("Processing WKApplicationRefreshBackgroundTask")
break
WatchCGMHost.shared.recheckAcquisition()
case let task as WKBluetoothAlertRefreshBackgroundTask:
// A connect the system held for the CGM relaunched or woke the app: keep it running
// through the reading rather than completing the task at once.
log.default("Processing WKBluetoothAlertRefreshBackgroundTask")
holdBluetoothTask(task)
continue
case let task as WKSnapshotRefreshBackgroundTask:
log.default("Processing WKSnapshotRefreshBackgroundTask")
task.setTaskCompleted(restoredDefaultState: false, estimatedSnapshotExpiration: Date(timeIntervalSinceNow: TimeInterval(minutes: 5)), userInfo: nil)
Expand Down Expand Up @@ -138,6 +152,42 @@ class ExtensionDelegate: NSObject, WKApplicationDelegate {

private var pendingConnectivityTasks: [WKWatchConnectivityRefreshBackgroundTask] = []

/// The one Bluetooth wake task held at a time, and every task completed, kept so a task handed over
/// again (WatchKit does, even in a later wake) is never completed twice, which throws.
private var heldBluetoothTask: WKBluetoothAlertRefreshBackgroundTask?
private var completedBluetoothTasks: [WKBluetoothAlertRefreshBackgroundTask] = []

/// Holds a Bluetooth wake 25 s, through the CGM's reading, or until the system expires it. Later
/// deliveries while one is held are completed on arrival.
private func holdBluetoothTask(_ task: WKBluetoothAlertRefreshBackgroundTask) {
dispatchPrecondition(condition: .onQueue(.main))
guard task !== heldBluetoothTask else { return }
guard heldBluetoothTask == nil else {
completeBluetoothTask(task)
return
}
heldBluetoothTask = task
task.expirationHandler = { [weak self] in
DispatchQueue.main.async { self?.completeBluetoothTask(task) }
}
DispatchQueue.main.asyncAfter(deadline: .now() + 25) { [weak self] in
self?.completeBluetoothTask(task)
}
}

private func completeBluetoothTask(_ task: WKBluetoothAlertRefreshBackgroundTask) {
dispatchPrecondition(condition: .onQueue(.main))
if task === heldBluetoothTask {
heldBluetoothTask = nil
}
guard !completedBluetoothTasks.contains(where: { $0 === task }) else { return }
completedBluetoothTasks.append(task)
if completedBluetoothTasks.count > 256 {
completedBluetoothTasks.removeFirst(64)
}
task.setTaskCompletedWithSnapshot(false)
}

private func completePendingConnectivityTasksIfNeeded() {
if WCSession.default.activationState == .activated && !WCSession.default.hasContentPending {
pendingConnectivityTasks.forEach { (task) in
Expand Down
Loading