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
4 changes: 4 additions & 0 deletions Loop.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
4302F4E11D4E9C8900F0FCAF /* TextFieldTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4302F4E01D4E9C8900F0FCAF /* TextFieldTableViewController.swift */; };
4302F4E31D4EA54200F0FCAF /* InsulinDeliveryTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4302F4E21D4EA54200F0FCAF /* InsulinDeliveryTableViewController.swift */; };
4302F4E51D4EA75100F0FCAF /* DoseStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4302F4E41D4EA75100F0FCAF /* DoseStore.swift */; };
43045E581F25AC1700FD9CE1 /* RileyLinkDeviceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43045E571F25AC1700FD9CE1 /* RileyLinkDeviceManager.swift */; };
43076BF31DFDBC4B0012A723 /* it.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 43076BF21DFDBC4B0012A723 /* it.lproj */; };
4309786C1E73D2F500BEBC82 /* it.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 4309786B1E73D2F500BEBC82 /* it.lproj */; };
4309786E1E73DAD100BEBC82 /* CGM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4309786D1E73DAD100BEBC82 /* CGM.swift */; };
Expand Down Expand Up @@ -371,6 +372,7 @@
4302F4E01D4E9C8900F0FCAF /* TextFieldTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldTableViewController.swift; sourceTree = "<group>"; };
4302F4E21D4EA54200F0FCAF /* InsulinDeliveryTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InsulinDeliveryTableViewController.swift; sourceTree = "<group>"; };
4302F4E41D4EA75100F0FCAF /* DoseStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DoseStore.swift; sourceTree = "<group>"; };
43045E571F25AC1700FD9CE1 /* RileyLinkDeviceManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RileyLinkDeviceManager.swift; sourceTree = "<group>"; };
43076BF21DFDBC4B0012A723 /* it.lproj */ = {isa = PBXFileReference; lastKnownFileType = folder; path = it.lproj; sourceTree = "<group>"; };
4309786B1E73D2F500BEBC82 /* it.lproj */ = {isa = PBXFileReference; lastKnownFileType = folder; path = it.lproj; sourceTree = "<group>"; };
4309786D1E73DAD100BEBC82 /* CGM.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGM.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -958,6 +960,7 @@
C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */,
43C094491CACCC73001F6403 /* NotificationManager.swift */,
432E73CA1D24B3D6009AD15D /* RemoteDataManager.swift */,
43045E571F25AC1700FD9CE1 /* RileyLinkDeviceManager.swift */,
430C1ABC1E5568A80067F1AE /* StatusChartsManager+LoopKit.swift */,
4F70C20F1DE8FAC5006380B7 /* StatusExtensionDataManager.swift */,
4328E0341CFC0AE100E199AA /* WatchDataManager.swift */,
Expand Down Expand Up @@ -1533,6 +1536,7 @@
4302F4E11D4E9C8900F0FCAF /* TextFieldTableViewController.swift in Sources */,
435CB6271F37AE5600C320C7 /* WalshInsulinModel.swift in Sources */,
43E344A41B9E1B1C00C85C07 /* NSUserDefaults.swift in Sources */,
43045E581F25AC1700FD9CE1 /* RileyLinkDeviceManager.swift in Sources */,
43F64DD91D9C92C900D24DC6 /* TitleSubtitleTableViewCell.swift in Sources */,
C15713821DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift in Sources */,
435400321C9F745500D5819C /* BolusSuggestionUserInfo.swift in Sources */,
Expand Down
83 changes: 40 additions & 43 deletions Loop/Managers/DeviceDataManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ final class DeviceDataManager {

var latestPumpStatus: RileyLinkKit.PumpStatus?

private(set) var lastError: (date: Date, error: Error)?

fileprivate func setLastError(error: Error) {
DispatchQueue.main.async { // Synchronize writes
self.lastError = (date: Date(), error: error)
// TODO: Notify observers of change
}
}

// Returns a value in the range 0 - 1
var pumpBatteryChargeRemaining: Double? {
get {
Expand Down Expand Up @@ -159,8 +168,7 @@ final class DeviceDataManager {

// Gather PumpStatus from MySentry packet
let pumpStatus: NightscoutUploadKit.PumpStatus?
if let pumpDate = pumpDateComponents.date, let pumpID = pumpID {

if let pumpID = pumpID {
let batteryStatus = BatteryStatus(percent: status.batteryRemainingPercent)
let iobStatus = IOBStatus(timestamp: pumpDate, iob: status.iob)

Expand Down Expand Up @@ -189,7 +197,10 @@ final class DeviceDataManager {
switch result {
case .newData(let values):
self.loopManager.addGlucose(values, from: self.cgmManager?.device)
case .noData, .error:
case .noData:
break
case .error(let error):
self.setLastError(error: error)
break
}
}
Expand Down Expand Up @@ -217,11 +228,16 @@ final class DeviceDataManager {
loopManager.addReservoirValue(units, at: date) { (result) in
switch result {
case .failure(let error):
self.setLastError(error: error)
self.logger.addError(error, fromSource: "DoseStore")
case .success(let (newValue, lastValue, areStoredValuesContinuous)):
// Run a loop as long as we have fresh, reliable pump data.
if self.preferredInsulinDataSource == .pumpHistory || !areStoredValuesContinuous {
self.fetchPumpHistory { (error) in
if let error = error {
self.setLastError(error: error)
}

if error == nil || areStoredValuesContinuous {
self.loopManager.loop()
}
Expand Down Expand Up @@ -258,8 +274,9 @@ final class DeviceDataManager {
/// - Parameters:
/// - completion: A closure called once upon completion
/// - error: An error describing why the fetch and/or store failed
private func fetchPumpHistory(_ completion: @escaping (_ error: Error?) -> Void) {
fileprivate func fetchPumpHistory(_ completion: @escaping (_ error: Error?) -> Void) {
guard let device = rileyLinkManager.firstConnectedDevice else {
completion(LoopError.connectionError)
return
}

Expand All @@ -284,39 +301,6 @@ final class DeviceDataManager {
}
}

/**
Read the pump's current state, including reservoir and clock

- parameter completion: A closure called after the command is complete. This closure takes a single Result argument:
- Success(status, date): The pump status, and the resolved date according to the pump's clock
- Failure(error): An error describing why the command failed
*/
private func readPumpData(_ completion: @escaping (RileyLinkKit.Either<(status: RileyLinkKit.PumpStatus, date: Date), Error>) -> Void) {
guard let device = rileyLinkManager.firstConnectedDevice, let ops = device.ops else {
completion(.failure(LoopError.connectionError))
return
}

ops.readPumpStatus { (result) in
switch result {
case .success(let status):
var clock = status.clock
clock.timeZone = ops.pumpState.timeZone

guard let date = clock.date else {
let errorStr = "Could not interpret pump clock: \(clock)"
self.logger.addError(errorStr, fromSource: "RileyLink")
completion(.failure(LoopError.invalidData(details: errorStr)))
return
}
completion(.success((status: status, date: date)))
case .failure(let error):
self.logger.addError("Failed to fetch pump status: \(error)", fromSource: "RileyLink")
completion(.failure(error))
}
}
}

private func pumpDataIsStale() -> Bool {
// How long should we wait before we poll for new pump data?
let pumpStatusAgeTolerance = rileyLinkManager.idleListeningEnabled ? TimeInterval(minutes: 11) : TimeInterval(minutes: 4)
Expand All @@ -330,6 +314,7 @@ final class DeviceDataManager {
*/
fileprivate func assertCurrentPumpData() {
guard let device = rileyLinkManager.firstConnectedDevice else {
self.setLastError(error: LoopError.connectionError)
return
}

Expand All @@ -339,7 +324,7 @@ final class DeviceDataManager {
return
}

readPumpData { (result) in
rileyLinkManager.readPumpData { (result) in
let nsPumpStatus: NightscoutUploadKit.PumpStatus?
switch result {
case .success(let (status, date)):
Expand All @@ -352,6 +337,8 @@ final class DeviceDataManager {

nsPumpStatus = NightscoutUploadKit.PumpStatus(clock: date, pumpID: status.pumpID, iob: nil, battery: battery, suspended: status.suspended, bolusing: status.bolusing, reservoir: status.reservoir)
case .failure(let error):
self.logger.addError("Failed to fetch pump status: \(error)", fromSource: "RileyLink")
self.setLastError(error: error)
self.troubleshootPumpComms(using: device)
self.nightscoutDataManager.uploadLoopStatus(loopError: error)
nsPumpStatus = nil
Expand Down Expand Up @@ -406,7 +393,7 @@ final class DeviceDataManager {
loopManager.doseStore.lastReservoirVolumeDrop < 0 ||
loopManager.doseStore.lastReservoirValue!.startDate.timeIntervalSinceNow <= TimeInterval(minutes: -6)
{
readPumpData { (result) in
rileyLinkManager.readPumpData { (result) in
switch result {
case .success(let (status, date)):
self.loopManager.addReservoirValue(status.reservoir, at: date) { (result) in
Expand All @@ -425,6 +412,8 @@ final class DeviceDataManager {
default:
notify(error)
}

self.logger.addError("Failed to fetch pump status: \(error)", fromSource: "RileyLink")
}
}
} else {
Expand All @@ -449,6 +438,7 @@ final class DeviceDataManager {
case .failure(let error):
self.logger.addError("Device \(device.name ?? "") auto-tune failed with error: \(error)", fromSource: "RileyLink")
self.rileyLinkManager.deprioritizeDevice(device: device)
self.setLastError(error: error)
}
}
} else {
Expand Down Expand Up @@ -660,7 +650,10 @@ extension DeviceDataManager: CGMManagerDelegate {
loopManager.addGlucose(values, from: manager.device) { _ in
self.assertCurrentPumpData()
}
case .noData, .error:
case .noData:
break
case .error(let error):
self.setLastError(error: error)
self.assertCurrentPumpData()
}
}
Expand Down Expand Up @@ -721,6 +714,12 @@ extension DeviceDataManager: LoopDataManagerDelegate {
value: body.rate,
unit: .unitsPerHour
)))

// 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 }
}
case .failure(let error):
completion(.failure(error))
}
Expand All @@ -736,15 +735,13 @@ extension DeviceDataManager: CustomDebugStringConvertible {
"## DeviceDataManager",
"launchDate: \(launchDate)",
"cgm: \(String(describing: cgm))",
"lastError: \(String(describing: lastError))",
"latestPumpStatusFromMySentry: \(String(describing: latestPumpStatusFromMySentry))",
"pumpState: \(String(reflecting: pumpState))",
"preferredInsulinDataSource: \(preferredInsulinDataSource)",
cgmManager != nil ? String(reflecting: cgmManager!) : "",
String(reflecting: rileyLinkManager),
String(reflecting: statusExtensionManager!),
"",
"## NSUserDefaults",
String(reflecting: UserDefaults.standard.dictionaryRepresentation())
].joined(separator: "\n")
}
}
1 change: 1 addition & 0 deletions Loop/Managers/LoopDataManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1084,6 +1084,7 @@ extension LoopDataManager {

entries.append("insulinOnBoard: \(String(describing: insulinOnBoard))")
entries.append("error: \(String(describing: loopError))")
entries.append("")

self.glucoseStore.generateDiagnosticReport { (report) in
entries.append(report)
Expand Down
42 changes: 42 additions & 0 deletions Loop/Managers/RileyLinkDeviceManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// RileyLinkDeviceManager.swift
// Loop
//
// Copyright © 2017 LoopKit Authors. All rights reserved.
//

import RileyLinkKit


extension RileyLinkDeviceManager {
/// Read the pump's current state, including reservoir and clock
///
/// - Parameter completion: A closure called after the command is complete. This closure takes a single Result argument:
/// - Parameter result:
/// - success(status, date): The pump status, and the resolved date according to the pump's clock
/// - failure(error): An error describing why the command failed
func readPumpData(_ completion: @escaping (_ result: RileyLinkKit.Either<(status: RileyLinkKit.PumpStatus, date: Date), Error>) -> Void) {
guard let device = firstConnectedDevice, let ops = device.ops else {
completion(.failure(LoopError.connectionError))
return
}

ops.readPumpStatus { (result) in
switch result {
case .success(let status):
var clock = status.clock
clock.timeZone = ops.pumpState.timeZone

guard let date = clock.date else {
let errorStr = "Could not interpret pump clock: \(clock)"
completion(.failure(LoopError.invalidData(details: errorStr)))
return
}
completion(.success((status: status, date: date)))
case .failure(let error):
completion(.failure(error))
}
}
}

}
1 change: 1 addition & 0 deletions Loop/View Controllers/CommandResponseViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ extension CommandResponseViewController {
completionHandler([
"Use the Share button above save this diagnostic report to aid investigating your problem. Issues can be filed at https://github.com/LoopKit/Loop/issues.",
"Generated: \(Date())",
"",
String(reflecting: dataManager),
"",
report,
Expand Down
14 changes: 10 additions & 4 deletions Loop/View Controllers/StatusTableViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ final class StatusTableViewController: ChartsTableViewController {
}
}

private var lastLoopError: Error?

private var refreshContext = RefreshContext.all

private var bolusState: BolusState? {
Expand Down Expand Up @@ -163,6 +165,7 @@ final class StatusTableViewController: ChartsTableViewController {

let reloadGroup = DispatchGroup()
var lastLoopCompleted: Date?
var lastLoopError: Error?
var lastReservoirValue: ReservoirValue?
var lastTempBasal: DoseEntry?
var newRecommendedTempBasal: LoopDataManager.TempBasalRecommendation?
Expand Down Expand Up @@ -209,6 +212,7 @@ final class StatusTableViewController: ChartsTableViewController {

lastTempBasal = state.lastTempBasal
lastLoopCompleted = state.lastLoopCompleted
lastLoopError = state.error

if let lastPoint = self.charts.predictedGlucosePoints.last?.y {
self.eventualGlucoseDescription = String(describing: lastPoint)
Expand Down Expand Up @@ -319,6 +323,7 @@ final class StatusTableViewController: ChartsTableViewController {

// Loop completion HUD
self.hudView.loopCompletionHUD.lastLoopCompleted = lastLoopCompleted
self.lastLoopError = lastLoopError

// Net basal rate HUD
let date = lastTempBasal?.startDate ?? Date()
Expand Down Expand Up @@ -814,10 +819,11 @@ final class StatusTableViewController: ChartsTableViewController {
}

@objc private func showLastError(_: Any) {
self.deviceManager.loopManager.getLoopState { (_, state) in
if let error = state.error {
self.presentAlertController(with: error)
}
// First, check whether we have a device error after the most recent completion date
if let deviceError = deviceManager.lastError, deviceError.date > (hudView.loopCompletionHUD.lastLoopCompleted ?? .distantPast) {
self.presentAlertController(with: deviceError.error)
} else if let lastLoopError = lastLoopError {
self.presentAlertController(with: lastLoopError)
}
}

Expand Down