Skip to content

Commit

Permalink
Sync pump time automatically (LoopKit#721)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
ps2 authored May 10, 2018
1 parent 780ad78 commit 1c44a2b
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 28 deletions.
4 changes: 4 additions & 0 deletions Common/Extensions/NSTimeInterval.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import Foundation


extension TimeInterval {
static func seconds(_ seconds: Double) -> TimeInterval {
return seconds
}

static func minutes(_ minutes: Double) -> TimeInterval {
return TimeInterval(minutes: minutes)
}
Expand Down
30 changes: 25 additions & 5 deletions Loop/Managers/AnalyticsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,19 +62,27 @@ final class AnalyticsManager: IdentifiableClass {
// MARK: - Config Events

func didChangeRileyLinkConnectionState() {
logEvent("RileyLink Connection")
logEvent("RileyLink Connection", outOfSession: true)
}

func transmitterTimeDidDrift(_ drift: TimeInterval) {
logEvent("Transmitter time change", withProperties: ["value" : drift])
logEvent("Transmitter time change", withProperties: ["value" : drift], outOfSession: true)
}

func pumpTimeDidDrift(_ drift: TimeInterval) {
logEvent("Pump time change", withProperties: ["value": drift], outOfSession: true)
}

func punpTimeZoneDidChange() {
logEvent("Pump time zone change", outOfSession: true)
}

func pumpBatteryWasReplaced() {
logEvent("Pump battery replacement")
logEvent("Pump battery replacement", outOfSession: true)
}

func reservoirWasRewound() {
logEvent("Pump reservoir rewind")
logEvent("Pump reservoir rewind", outOfSession: true)
}

func didChangeBasalRateSchedule() {
Expand All @@ -98,7 +106,7 @@ final class AnalyticsManager: IdentifiableClass {
}

func didChangeLoopSettings(from oldValue: LoopSettings, to newValue: LoopSettings) {
logEvent("Loop settings change")
logEvent("Loop settings change", outOfSession: true)

if newValue.maximumBasalRatePerHour != oldValue.maximumBasalRatePerHour {
logEvent("Maximum basal rate change")
Expand All @@ -111,6 +119,14 @@ final class AnalyticsManager: IdentifiableClass {
if newValue.suspendThreshold != oldValue.suspendThreshold {
logEvent("Minimum BG Guard change")
}

if newValue.dosingEnabled != oldValue.dosingEnabled {
logEvent("Closed loop enabled change")
}

if newValue.retrospectiveCorrectionEnabled != oldValue.retrospectiveCorrectionEnabled {
logEvent("Retrospective correction enabled change")
}
}

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

func didFetchNewCGMData() {
logEvent("CGM Fetch", outOfSession: true)
}

func loopDidSucceed() {
logEvent("Loop success", outOfSession: true)
}
Expand Down
58 changes: 44 additions & 14 deletions Loop/Managers/DeviceDataManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ final class DeviceDataManager {
self.lastTimerTick = Date()

self.cgmManager?.fetchNewDataIfNeeded(with: self) { (result) in
if case .newData = result {
AnalyticsManager.shared.didFetchNewCGMData()
}

// TODO: Isolate to queue?
self.cgmManager(self.cgmManager!, didUpdateWith: result)
}
Expand Down Expand Up @@ -487,6 +491,16 @@ final class DeviceDataManager {
return
}

// Check if the clock should be reset
if abs(date.timeIntervalSinceNow) > .seconds(20) {
self.logger.addError("Pump clock is more than 20 seconds off. Resetting.", fromSource: "RileyLink")
AnalyticsManager.shared.pumpTimeDidDrift(date.timeIntervalSinceNow)
try session.setTime { () -> DateComponents in
let calendar = Calendar(identifier: .gregorian)
return calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: Date())
}
}

self.observeBatteryDuring {
self.latestPumpStatus = status
}
Expand Down Expand Up @@ -840,18 +854,6 @@ extension DeviceDataManager: LoopDataManagerDelegate {
return
}

let notify = { (result: Result<DoseEntry>) -> Void in
// If we haven't fetched history in a while (preferredInsulinDataSource == .reservoir),
// let's try to do so while the pump radio is on.
if self.loopManager.doseStore.lastAddedPumpEvents.timeIntervalSinceNow < .minutes(-4) {
self.fetchPumpHistory { (_) in
completion(result)
}
} else {
completion(result)
}
}

pumpOps.runSession(withName: "Set Temp Basal", using: rileyLinkManager.firstConnectedDevice) { (session) in
guard let session = session else {
completion(.failure(LoopError.connectionError))
Expand All @@ -864,15 +866,43 @@ extension DeviceDataManager: LoopDataManagerDelegate {
let now = Date()
let endDate = now.addingTimeInterval(response.timeRemaining)
let startDate = endDate.addingTimeInterval(-basal.recommendation.duration)
notify(.success(DoseEntry(
completion(.success(DoseEntry(
type: .tempBasal,
startDate: startDate,
endDate: endDate,
value: response.rate,
unit: .unitsPerHour
)))

// Continue below
} catch let error {
completion(.failure(error))
return
}

do {
// If we haven't fetched history in a while, our preferredInsulinDataSource is probably .reservoir, so
// let's take advantage of the pump radio being on.
if self.loopManager.doseStore.lastAddedPumpEvents.timeIntervalSinceNow < .minutes(-4) {
let clock = try session.getTime()
// Check if the clock should be reset
if let date = clock.date, abs(date.timeIntervalSinceNow) > .seconds(20) {
self.logger.addError("Pump clock is more than 20 seconds off. Resetting.", fromSource: "RileyLink")
AnalyticsManager.shared.pumpTimeDidDrift(date.timeIntervalSinceNow)
try session.setTime { () -> DateComponents in
let calendar = Calendar(identifier: .gregorian)
return calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: Date())
}
}

self.fetchPumpHistory { (error) in
if let error = error {
self.logger.addError("Post-basal history fetch failed: \(error)", fromSource: "RileyLink")
}
}
}
} catch let error {
notify(.failure(error))
self.logger.addError("Post-basal time sync failed: \(error)", fromSource: "RileyLink")
}
}
}
Expand Down
18 changes: 10 additions & 8 deletions Loop/Managers/LoopDataManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ final class LoopDataManager {
}

static let LoopUpdateContextKey = "com.loudnate.Loop.LoopDataManager.LoopUpdateContext"
static let LastLoopCompletedKey = "com.loopkit.Loop.LoopDataManager.LastLoopCompleted"

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

Expand Down Expand Up @@ -134,13 +135,6 @@ final class LoopDataManager {
}
}

/// Disable any active workout glucose targets
func disableWorkoutMode() {
settings.glucoseTargetRangeSchedule?.clearOverride()

notify(forChange: .preferences)
}

/// The length of time insulin has an effect on blood glucose
var insulinModelSettings: InsulinModelSettings? {
get {
Expand Down Expand Up @@ -596,9 +590,17 @@ final class LoopDataManager {
}

private func notify(forChange context: LoopUpdateContext) {
var userInfo: [String: Any] = [
type(of: self).LoopUpdateContextKey: context.rawValue
]

if let lastLoopCompleted = lastLoopCompleted {
userInfo[type(of: self).LastLoopCompletedKey] = lastLoopCompleted
}

NotificationCenter.default.post(name: .LoopDataUpdated,
object: self,
userInfo: [type(of: self).LoopUpdateContextKey: context.rawValue]
userInfo: userInfo
)
}

Expand Down
5 changes: 5 additions & 0 deletions Loop/View Controllers/StatusTableViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ final class StatusTableViewController: ChartsTableViewController {
notificationObservers += [
notificationCenter.addObserver(forName: .LoopDataUpdated, object: deviceManager.loopManager, queue: nil) { [unowned self] note in
let context = note.userInfo?[LoopDataManager.LoopUpdateContextKey] as! LoopDataManager.LoopUpdateContext.RawValue
let lastLoopCompleted = note.userInfo?[LoopDataManager.LastLoopCompletedKey] as? Date
DispatchQueue.main.async {
switch LoopDataManager.LoopUpdateContext(rawValue: context) {
case .none, .bolus?:
Expand All @@ -58,6 +59,10 @@ final class StatusTableViewController: ChartsTableViewController {
self.refreshContext.formUnion([.glucose, .carbs])
case .tempBasal?:
self.refreshContext.update(with: .insulin)

if let lastLoopCompleted = lastLoopCompleted {
self.hudView?.loopCompletionHUD.lastLoopCompleted = lastLoopCompleted
}
}

self.hudView?.loopCompletionHUD.loopInProgress = false
Expand Down
2 changes: 1 addition & 1 deletion LoopUI/Views/LoopCompletionHUDView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public final class LoopCompletionHUDView: BaseHUDView {
)
updateTimer = timer

RunLoop.main.add(timer, forMode: RunLoopMode.defaultRunLoopMode)
RunLoop.main.add(timer, forMode: .defaultRunLoopMode)
}

private var updateTimer: Timer? {
Expand Down

0 comments on commit 1c44a2b

Please sign in to comment.