Skip to content

Commit 14191ad

Browse files
authored
Remote overrides (#1131)
* Allow overrides to be enabled via APNs * Update to NS sent notifications * Cleanup prints * Query override history for NS upload * Bump submodule revs * Handle indefinite duration override treatment uploads * Use separate representation for indefinite duration treatments * Bump cartfile rev * Send any override changes when uploading loop status * Bump carthage revisions * Bump carthage revision * Fix typo, and log device token * Carthage dependency revisions
1 parent 3139262 commit 14191ad

14 files changed

+254
-33
lines changed

Cartfile.resolved

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270"
22
github "LoopKit/CGMBLEKit" "7417605dd898bf89378171941126c85d85dc642c"
33
github "LoopKit/G4ShareSpy" "e62d296067180c6659166272ff9cc406f470ec9e"
4-
github "LoopKit/LoopKit" "18a5a04afd310e945ac54f8c43a44838a16503c2"
4+
github "LoopKit/LoopKit" "a08405ec7a4e38fa96e39ae079114db30f393a2a"
55
github "LoopKit/MKRingProgressView" "f548a5c64832be2d37d7c91b5800e284887a2a0a"
66
github "LoopKit/dexcom-share-client-swift" "c4f3d48e56e5b3ad786486ccd1bbc753034096d2"
77
github "i-schuetz/SwiftCharts" "0.6.5"
8-
github "ps2/rileylink_ios" "4b756c126317320209474f34e909ecf254d8a5b9"
8+
github "ps2/rileylink_ios" "51d6a20cc63c0c0b2071a4a78b4065e9a11ba81f"

Loop.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,7 @@
370370
C136AA2423109CC6008A320D /* LoopPlugins.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16DA84122E8E112008624C2 /* LoopPlugins.swift */; };
371371
C13BAD941E8009B000050CB5 /* NumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0B31E45C1BE00FF19A9 /* NumberFormatter.swift */; };
372372
C15713821DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15713811DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift */; };
373+
C165B8CE23302C5D0004112E /* RemoteCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C165B8CD23302C5D0004112E /* RemoteCommand.swift */; };
373374
C16DA84222E8E112008624C2 /* LoopPlugins.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16DA84122E8E112008624C2 /* LoopPlugins.swift */; };
374375
C178249A1E1999FA00D9D25C /* CaseCountable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17824991E1999FA00D9D25C /* CaseCountable.swift */; };
375376
C17824A01E19CF9800D9D25C /* GlucoseThresholdTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C178249F1E19CF9800D9D25C /* GlucoseThresholdTableViewController.swift */; };
@@ -1024,6 +1025,7 @@
10241025
C12CB9B823106A6300F84978 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Intents.strings; sourceTree = "<group>"; };
10251026
C12F21A61DFA79CB00748193 /* recommend_temp_basal_very_low_end_in_range.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = recommend_temp_basal_very_low_end_in_range.json; sourceTree = "<group>"; };
10261027
C15713811DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MealBolusNightscoutTreatment.swift; sourceTree = "<group>"; };
1028+
C165B8CD23302C5D0004112E /* RemoteCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteCommand.swift; sourceTree = "<group>"; };
10271029
C16DA84122E8E112008624C2 /* LoopPlugins.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopPlugins.swift; sourceTree = "<group>"; };
10281030
C17824991E1999FA00D9D25C /* CaseCountable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CaseCountable.swift; sourceTree = "<group>"; };
10291031
C178249F1E19CF9800D9D25C /* GlucoseThresholdTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseThresholdTableViewController.swift; sourceTree = "<group>"; };
@@ -1239,6 +1241,7 @@
12391241
438D42F81D7C88BC003244B0 /* PredictionInputEffect.swift */,
12401242
43441A9B1EDB34810087958C /* StatusExtensionContext+LoopKit.swift */,
12411243
4328E0311CFC068900E199AA /* WatchContext+LoopKit.swift */,
1244+
C165B8CD23302C5D0004112E /* RemoteCommand.swift */,
12421245
);
12431246
path = Models;
12441247
sourceTree = "<group>";
@@ -2604,6 +2607,7 @@
26042607
434FF1EE1CF27EEF000DB779 /* UITableViewCell.swift in Sources */,
26052608
439BED2A1E76093C00B0AED5 /* CGMManager.swift in Sources */,
26062609
C18C8C511D5A351900E043FB /* NightscoutDataManager.swift in Sources */,
2610+
C165B8CE23302C5D0004112E /* RemoteCommand.swift in Sources */,
26072611
438849EA1D297CB6003B3F23 /* NightscoutService.swift in Sources */,
26082612
438172D91F4E9E37003C3328 /* NewPumpEvent.swift in Sources */,
26092613
4389916B1E91B689000EEF90 /* ChartSettings+Loop.swift in Sources */,

Loop/AppDelegate.swift

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,18 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
2626

2727
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
2828
NotificationManager.authorize(delegate: self)
29-
29+
3030
log.info(#function)
3131

3232
AnalyticsManager.shared.application(application, didFinishLaunchingWithOptions: launchOptions)
3333

3434
rootViewController.rootViewController.deviceManager = deviceManager
35+
36+
let notificationOption = launchOptions?[.remoteNotification]
37+
38+
if let notification = notificationOption as? [String: AnyObject] {
39+
deviceManager.handleRemoteNotification(notification)
40+
}
3541

3642
return true
3743
}
@@ -74,6 +80,32 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
7480
return false
7581
}
7682
}
83+
84+
// MARK: - Remote notifications
85+
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
86+
let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
87+
let token = tokenParts.joined()
88+
log.default("RemoteNotifications device token: \(token)")
89+
deviceManager.loopManager.settings.deviceToken = deviceToken
90+
}
91+
92+
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
93+
log.error("Failed to register: \(error)")
94+
}
95+
96+
func application(_ application: UIApplication,
97+
didReceiveRemoteNotification userInfo: [AnyHashable : Any],
98+
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
99+
) {
100+
guard let notification = userInfo as? [String: AnyObject] else {
101+
completionHandler(.failed)
102+
return
103+
}
104+
105+
deviceManager.handleRemoteNotification(notification)
106+
completionHandler(.noData)
107+
}
108+
77109
}
78110

79111

@@ -102,4 +134,5 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
102134
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
103135
completionHandler([.badge, .sound, .alert])
104136
}
137+
105138
}

Loop/Info.plist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
<key>UIBackgroundModes</key>
6363
<array>
6464
<string>bluetooth-central</string>
65+
<string>remote-notification</string>
6566
</array>
6667
<key>UILaunchStoryboardName</key>
6768
<string>LaunchScreen</string>

Loop/Loop.entitlements

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
33
<plist version="1.0">
44
<dict>
5+
<key>aps-environment</key>
6+
<string>development</string>
57
<key>com.apple.developer.healthkit</key>
68
<true/>
79
<key>com.apple.developer.healthkit.access</key>

Loop/Managers/AnalyticsManager.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,8 @@ final class AnalyticsManager: IdentifiableClass {
150150
logEvent("CGM Fetch", outOfSession: true)
151151
}
152152

153-
func loopDidSucceed() {
154-
logEvent("Loop success", outOfSession: true)
153+
func loopDidSucceed(_ duration: TimeInterval) {
154+
logEvent("Loop success", withProperties: ["duration": duration], outOfSession: true)
155155
}
156156

157157
func loopDidError() {

Loop/Managers/DeviceDataManager.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,3 +653,21 @@ extension Notification.Name {
653653
static let PumpEventsAdded = Notification.Name(rawValue: "com.loopKit.notification.PumpEventsAdded")
654654
}
655655

656+
// MARK: - Remote Notification Handling
657+
extension DeviceDataManager {
658+
func handleRemoteNotification(_ notification: [String: AnyObject]) {
659+
660+
if let command = RemoteCommand(notification: notification, allowedPresets: loopManager.settings.overridePresets) {
661+
switch command {
662+
case .temporaryScheduleOverride(let override):
663+
log.default("Enacting remote temporary override: \(override)")
664+
loopManager.settings.scheduleOverride = override
665+
case .cancelTemporaryOverride:
666+
log.default("Canceling temporary override from remote command")
667+
loopManager.settings.scheduleOverride = nil
668+
}
669+
} else {
670+
log.info("Unhandled remote notification: \(notification)")
671+
}
672+
}
673+
}

Loop/Managers/LoopDataManager.swift

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,8 @@ final class LoopDataManager {
216216
private let lockedBasalDeliveryState: Locked<PumpManagerStatus.BasalDeliveryState?>
217217

218218
fileprivate var lastRequestedBolus: DoseEntry?
219+
220+
private var lastLoopStarted: Date?
219221

220222
/// The last date at which a loop completed, from prediction to dose (if dosing is enabled)
221223
var lastLoopCompleted: Date? {
@@ -224,11 +226,6 @@ final class LoopDataManager {
224226
}
225227
set {
226228
lockedLastLoopCompleted.value = newValue
227-
228-
NotificationManager.clearLoopNotRunningNotifications()
229-
NotificationManager.scheduleLoopNotRunningNotifications()
230-
AnalyticsManager.shared.loopDidSucceed()
231-
NotificationCenter.default.post(name: .LoopCompleted, object: self)
232229
}
233230
}
234231
private let lockedLastLoopCompleted: Locked<Date?>
@@ -269,6 +266,15 @@ final class LoopDataManager {
269266
backgroundTask = .invalid
270267
}
271268
}
269+
270+
private func loopDidComplete(date: Date, duration: TimeInterval) {
271+
lastLoopCompleted = date
272+
NotificationManager.clearLoopNotRunningNotifications()
273+
NotificationManager.scheduleLoopNotRunningNotifications()
274+
AnalyticsManager.shared.loopDidSucceed(duration)
275+
NotificationCenter.default.post(name: .LoopCompleted, object: self)
276+
277+
}
272278
}
273279

274280
// MARK: Background task management
@@ -635,6 +641,7 @@ extension LoopDataManager {
635641
NotificationCenter.default.post(name: .LoopRunning, object: self)
636642

637643
self.lastLoopError = nil
644+
let startDate = Date()
638645

639646
do {
640647
try self.update()
@@ -646,7 +653,7 @@ extension LoopDataManager {
646653
if let error = error {
647654
self.logger.error(error)
648655
} else {
649-
self.lastLoopCompleted = Date()
656+
self.loopDidComplete(date: Date(), duration: -startDate.timeIntervalSinceNow)
650657
}
651658
self.logger.default("Loop ended")
652659
self.notify(forChange: .tempBasal)
@@ -655,7 +662,7 @@ extension LoopDataManager {
655662
// Delay the notification until we know the result of the temp basal
656663
return
657664
} else {
658-
self.lastLoopCompleted = Date()
665+
self.loopDidComplete(date: Date(), duration: -startDate.timeIntervalSinceNow)
659666
}
660667
} catch let error {
661668
self.lastLoopError = error

0 commit comments

Comments
 (0)