Skip to content

Commit 1c44a2b

Browse files
authored
Sync pump time automatically (#721)
* Sync pump time automatically * Automatically set pump time if off by 20 seconds or more * Ensure LoopStateView is up-to-date even when the app is running in the background * Add didChangeGlucoseTargetRangeSchedule to analytics manager
1 parent 780ad78 commit 1c44a2b

File tree

6 files changed

+89
-28
lines changed

6 files changed

+89
-28
lines changed

Common/Extensions/NSTimeInterval.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ import Foundation
1010

1111

1212
extension TimeInterval {
13+
static func seconds(_ seconds: Double) -> TimeInterval {
14+
return seconds
15+
}
16+
1317
static func minutes(_ minutes: Double) -> TimeInterval {
1418
return TimeInterval(minutes: minutes)
1519
}

Loop/Managers/AnalyticsManager.swift

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,19 +62,27 @@ final class AnalyticsManager: IdentifiableClass {
6262
// MARK: - Config Events
6363

6464
func didChangeRileyLinkConnectionState() {
65-
logEvent("RileyLink Connection")
65+
logEvent("RileyLink Connection", outOfSession: true)
6666
}
6767

6868
func transmitterTimeDidDrift(_ drift: TimeInterval) {
69-
logEvent("Transmitter time change", withProperties: ["value" : drift])
69+
logEvent("Transmitter time change", withProperties: ["value" : drift], outOfSession: true)
70+
}
71+
72+
func pumpTimeDidDrift(_ drift: TimeInterval) {
73+
logEvent("Pump time change", withProperties: ["value": drift], outOfSession: true)
74+
}
75+
76+
func punpTimeZoneDidChange() {
77+
logEvent("Pump time zone change", outOfSession: true)
7078
}
7179

7280
func pumpBatteryWasReplaced() {
73-
logEvent("Pump battery replacement")
81+
logEvent("Pump battery replacement", outOfSession: true)
7482
}
7583

7684
func reservoirWasRewound() {
77-
logEvent("Pump reservoir rewind")
85+
logEvent("Pump reservoir rewind", outOfSession: true)
7886
}
7987

8088
func didChangeBasalRateSchedule() {
@@ -98,7 +106,7 @@ final class AnalyticsManager: IdentifiableClass {
98106
}
99107

100108
func didChangeLoopSettings(from oldValue: LoopSettings, to newValue: LoopSettings) {
101-
logEvent("Loop settings change")
109+
logEvent("Loop settings change", outOfSession: true)
102110

103111
if newValue.maximumBasalRatePerHour != oldValue.maximumBasalRatePerHour {
104112
logEvent("Maximum basal rate change")
@@ -111,6 +119,14 @@ final class AnalyticsManager: IdentifiableClass {
111119
if newValue.suspendThreshold != oldValue.suspendThreshold {
112120
logEvent("Minimum BG Guard change")
113121
}
122+
123+
if newValue.dosingEnabled != oldValue.dosingEnabled {
124+
logEvent("Closed loop enabled change")
125+
}
126+
127+
if newValue.retrospectiveCorrectionEnabled != oldValue.retrospectiveCorrectionEnabled {
128+
logEvent("Retrospective correction enabled change")
129+
}
114130
}
115131

116132
// MARK: - Loop Events
@@ -127,6 +143,10 @@ final class AnalyticsManager: IdentifiableClass {
127143
logEvent("Bolus set", withProperties: ["source" : "Watch"], outOfSession: true)
128144
}
129145

146+
func didFetchNewCGMData() {
147+
logEvent("CGM Fetch", outOfSession: true)
148+
}
149+
130150
func loopDidSucceed() {
131151
logEvent("Loop success", outOfSession: true)
132152
}

Loop/Managers/DeviceDataManager.swift

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ final class DeviceDataManager {
154154
self.lastTimerTick = Date()
155155

156156
self.cgmManager?.fetchNewDataIfNeeded(with: self) { (result) in
157+
if case .newData = result {
158+
AnalyticsManager.shared.didFetchNewCGMData()
159+
}
160+
157161
// TODO: Isolate to queue?
158162
self.cgmManager(self.cgmManager!, didUpdateWith: result)
159163
}
@@ -487,6 +491,16 @@ final class DeviceDataManager {
487491
return
488492
}
489493

494+
// Check if the clock should be reset
495+
if abs(date.timeIntervalSinceNow) > .seconds(20) {
496+
self.logger.addError("Pump clock is more than 20 seconds off. Resetting.", fromSource: "RileyLink")
497+
AnalyticsManager.shared.pumpTimeDidDrift(date.timeIntervalSinceNow)
498+
try session.setTime { () -> DateComponents in
499+
let calendar = Calendar(identifier: .gregorian)
500+
return calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: Date())
501+
}
502+
}
503+
490504
self.observeBatteryDuring {
491505
self.latestPumpStatus = status
492506
}
@@ -840,18 +854,6 @@ extension DeviceDataManager: LoopDataManagerDelegate {
840854
return
841855
}
842856

843-
let notify = { (result: Result<DoseEntry>) -> Void in
844-
// If we haven't fetched history in a while (preferredInsulinDataSource == .reservoir),
845-
// let's try to do so while the pump radio is on.
846-
if self.loopManager.doseStore.lastAddedPumpEvents.timeIntervalSinceNow < .minutes(-4) {
847-
self.fetchPumpHistory { (_) in
848-
completion(result)
849-
}
850-
} else {
851-
completion(result)
852-
}
853-
}
854-
855857
pumpOps.runSession(withName: "Set Temp Basal", using: rileyLinkManager.firstConnectedDevice) { (session) in
856858
guard let session = session else {
857859
completion(.failure(LoopError.connectionError))
@@ -864,15 +866,43 @@ extension DeviceDataManager: LoopDataManagerDelegate {
864866
let now = Date()
865867
let endDate = now.addingTimeInterval(response.timeRemaining)
866868
let startDate = endDate.addingTimeInterval(-basal.recommendation.duration)
867-
notify(.success(DoseEntry(
869+
completion(.success(DoseEntry(
868870
type: .tempBasal,
869871
startDate: startDate,
870872
endDate: endDate,
871873
value: response.rate,
872874
unit: .unitsPerHour
873875
)))
876+
877+
// Continue below
878+
} catch let error {
879+
completion(.failure(error))
880+
return
881+
}
882+
883+
do {
884+
// If we haven't fetched history in a while, our preferredInsulinDataSource is probably .reservoir, so
885+
// let's take advantage of the pump radio being on.
886+
if self.loopManager.doseStore.lastAddedPumpEvents.timeIntervalSinceNow < .minutes(-4) {
887+
let clock = try session.getTime()
888+
// Check if the clock should be reset
889+
if let date = clock.date, abs(date.timeIntervalSinceNow) > .seconds(20) {
890+
self.logger.addError("Pump clock is more than 20 seconds off. Resetting.", fromSource: "RileyLink")
891+
AnalyticsManager.shared.pumpTimeDidDrift(date.timeIntervalSinceNow)
892+
try session.setTime { () -> DateComponents in
893+
let calendar = Calendar(identifier: .gregorian)
894+
return calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: Date())
895+
}
896+
}
897+
898+
self.fetchPumpHistory { (error) in
899+
if let error = error {
900+
self.logger.addError("Post-basal history fetch failed: \(error)", fromSource: "RileyLink")
901+
}
902+
}
903+
}
874904
} catch let error {
875-
notify(.failure(error))
905+
self.logger.addError("Post-basal time sync failed: \(error)", fromSource: "RileyLink")
876906
}
877907
}
878908
}

Loop/Managers/LoopDataManager.swift

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ final class LoopDataManager {
2424
}
2525

2626
static let LoopUpdateContextKey = "com.loudnate.Loop.LoopDataManager.LoopUpdateContext"
27+
static let LastLoopCompletedKey = "com.loopkit.Loop.LoopDataManager.LastLoopCompleted"
2728

2829
fileprivate typealias GlucoseChange = (start: GlucoseValue, end: GlucoseValue)
2930

@@ -134,13 +135,6 @@ final class LoopDataManager {
134135
}
135136
}
136137

137-
/// Disable any active workout glucose targets
138-
func disableWorkoutMode() {
139-
settings.glucoseTargetRangeSchedule?.clearOverride()
140-
141-
notify(forChange: .preferences)
142-
}
143-
144138
/// The length of time insulin has an effect on blood glucose
145139
var insulinModelSettings: InsulinModelSettings? {
146140
get {
@@ -596,9 +590,17 @@ final class LoopDataManager {
596590
}
597591

598592
private func notify(forChange context: LoopUpdateContext) {
593+
var userInfo: [String: Any] = [
594+
type(of: self).LoopUpdateContextKey: context.rawValue
595+
]
596+
597+
if let lastLoopCompleted = lastLoopCompleted {
598+
userInfo[type(of: self).LastLoopCompletedKey] = lastLoopCompleted
599+
}
600+
599601
NotificationCenter.default.post(name: .LoopDataUpdated,
600602
object: self,
601-
userInfo: [type(of: self).LoopUpdateContextKey: context.rawValue]
603+
userInfo: userInfo
602604
)
603605
}
604606

Loop/View Controllers/StatusTableViewController.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ final class StatusTableViewController: ChartsTableViewController {
4646
notificationObservers += [
4747
notificationCenter.addObserver(forName: .LoopDataUpdated, object: deviceManager.loopManager, queue: nil) { [unowned self] note in
4848
let context = note.userInfo?[LoopDataManager.LoopUpdateContextKey] as! LoopDataManager.LoopUpdateContext.RawValue
49+
let lastLoopCompleted = note.userInfo?[LoopDataManager.LastLoopCompletedKey] as? Date
4950
DispatchQueue.main.async {
5051
switch LoopDataManager.LoopUpdateContext(rawValue: context) {
5152
case .none, .bolus?:
@@ -58,6 +59,10 @@ final class StatusTableViewController: ChartsTableViewController {
5859
self.refreshContext.formUnion([.glucose, .carbs])
5960
case .tempBasal?:
6061
self.refreshContext.update(with: .insulin)
62+
63+
if let lastLoopCompleted = lastLoopCompleted {
64+
self.hudView?.loopCompletionHUD.lastLoopCompleted = lastLoopCompleted
65+
}
6166
}
6267

6368
self.hudView?.loopCompletionHUD.loopInProgress = false

LoopUI/Views/LoopCompletionHUDView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public final class LoopCompletionHUDView: BaseHUDView {
9595
)
9696
updateTimer = timer
9797

98-
RunLoop.main.add(timer, forMode: RunLoopMode.defaultRunLoopMode)
98+
RunLoop.main.add(timer, forMode: .defaultRunLoopMode)
9999
}
100100

101101
private var updateTimer: Timer? {

0 commit comments

Comments
 (0)