Skip to content

Commit a520f9e

Browse files
authored
Device errors (#563)
* Device errors * Device errors
1 parent e478a7d commit a520f9e

File tree

6 files changed

+98
-47
lines changed

6 files changed

+98
-47
lines changed

Loop.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
4302F4E11D4E9C8900F0FCAF /* TextFieldTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4302F4E01D4E9C8900F0FCAF /* TextFieldTableViewController.swift */; };
1212
4302F4E31D4EA54200F0FCAF /* InsulinDeliveryTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4302F4E21D4EA54200F0FCAF /* InsulinDeliveryTableViewController.swift */; };
1313
4302F4E51D4EA75100F0FCAF /* DoseStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4302F4E41D4EA75100F0FCAF /* DoseStore.swift */; };
14+
43045E581F25AC1700FD9CE1 /* RileyLinkDeviceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43045E571F25AC1700FD9CE1 /* RileyLinkDeviceManager.swift */; };
1415
43076BF31DFDBC4B0012A723 /* it.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 43076BF21DFDBC4B0012A723 /* it.lproj */; };
1516
4309786C1E73D2F500BEBC82 /* it.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 4309786B1E73D2F500BEBC82 /* it.lproj */; };
1617
4309786E1E73DAD100BEBC82 /* CGM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4309786D1E73DAD100BEBC82 /* CGM.swift */; };
@@ -371,6 +372,7 @@
371372
4302F4E01D4E9C8900F0FCAF /* TextFieldTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldTableViewController.swift; sourceTree = "<group>"; };
372373
4302F4E21D4EA54200F0FCAF /* InsulinDeliveryTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InsulinDeliveryTableViewController.swift; sourceTree = "<group>"; };
373374
4302F4E41D4EA75100F0FCAF /* DoseStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DoseStore.swift; sourceTree = "<group>"; };
375+
43045E571F25AC1700FD9CE1 /* RileyLinkDeviceManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RileyLinkDeviceManager.swift; sourceTree = "<group>"; };
374376
43076BF21DFDBC4B0012A723 /* it.lproj */ = {isa = PBXFileReference; lastKnownFileType = folder; path = it.lproj; sourceTree = "<group>"; };
375377
4309786B1E73D2F500BEBC82 /* it.lproj */ = {isa = PBXFileReference; lastKnownFileType = folder; path = it.lproj; sourceTree = "<group>"; };
376378
4309786D1E73DAD100BEBC82 /* CGM.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGM.swift; sourceTree = "<group>"; };
@@ -958,6 +960,7 @@
958960
C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */,
959961
43C094491CACCC73001F6403 /* NotificationManager.swift */,
960962
432E73CA1D24B3D6009AD15D /* RemoteDataManager.swift */,
963+
43045E571F25AC1700FD9CE1 /* RileyLinkDeviceManager.swift */,
961964
430C1ABC1E5568A80067F1AE /* StatusChartsManager+LoopKit.swift */,
962965
4F70C20F1DE8FAC5006380B7 /* StatusExtensionDataManager.swift */,
963966
4328E0341CFC0AE100E199AA /* WatchDataManager.swift */,
@@ -1533,6 +1536,7 @@
15331536
4302F4E11D4E9C8900F0FCAF /* TextFieldTableViewController.swift in Sources */,
15341537
435CB6271F37AE5600C320C7 /* WalshInsulinModel.swift in Sources */,
15351538
43E344A41B9E1B1C00C85C07 /* NSUserDefaults.swift in Sources */,
1539+
43045E581F25AC1700FD9CE1 /* RileyLinkDeviceManager.swift in Sources */,
15361540
43F64DD91D9C92C900D24DC6 /* TitleSubtitleTableViewCell.swift in Sources */,
15371541
C15713821DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift in Sources */,
15381542
435400321C9F745500D5819C /* BolusSuggestionUserInfo.swift in Sources */,

Loop/Managers/DeviceDataManager.swift

Lines changed: 40 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,15 @@ final class DeviceDataManager {
3838

3939
var latestPumpStatus: RileyLinkKit.PumpStatus?
4040

41+
private(set) var lastError: (date: Date, error: Error)?
42+
43+
fileprivate func setLastError(error: Error) {
44+
DispatchQueue.main.async { // Synchronize writes
45+
self.lastError = (date: Date(), error: error)
46+
// TODO: Notify observers of change
47+
}
48+
}
49+
4150
// Returns a value in the range 0 - 1
4251
var pumpBatteryChargeRemaining: Double? {
4352
get {
@@ -159,8 +168,7 @@ final class DeviceDataManager {
159168

160169
// Gather PumpStatus from MySentry packet
161170
let pumpStatus: NightscoutUploadKit.PumpStatus?
162-
if let pumpDate = pumpDateComponents.date, let pumpID = pumpID {
163-
171+
if let pumpID = pumpID {
164172
let batteryStatus = BatteryStatus(percent: status.batteryRemainingPercent)
165173
let iobStatus = IOBStatus(timestamp: pumpDate, iob: status.iob)
166174

@@ -189,7 +197,10 @@ final class DeviceDataManager {
189197
switch result {
190198
case .newData(let values):
191199
self.loopManager.addGlucose(values, from: self.cgmManager?.device)
192-
case .noData, .error:
200+
case .noData:
201+
break
202+
case .error(let error):
203+
self.setLastError(error: error)
193204
break
194205
}
195206
}
@@ -217,11 +228,16 @@ final class DeviceDataManager {
217228
loopManager.addReservoirValue(units, at: date) { (result) in
218229
switch result {
219230
case .failure(let error):
231+
self.setLastError(error: error)
220232
self.logger.addError(error, fromSource: "DoseStore")
221233
case .success(let (newValue, lastValue, areStoredValuesContinuous)):
222234
// Run a loop as long as we have fresh, reliable pump data.
223235
if self.preferredInsulinDataSource == .pumpHistory || !areStoredValuesContinuous {
224236
self.fetchPumpHistory { (error) in
237+
if let error = error {
238+
self.setLastError(error: error)
239+
}
240+
225241
if error == nil || areStoredValuesContinuous {
226242
self.loopManager.loop()
227243
}
@@ -258,8 +274,9 @@ final class DeviceDataManager {
258274
/// - Parameters:
259275
/// - completion: A closure called once upon completion
260276
/// - error: An error describing why the fetch and/or store failed
261-
private func fetchPumpHistory(_ completion: @escaping (_ error: Error?) -> Void) {
277+
fileprivate func fetchPumpHistory(_ completion: @escaping (_ error: Error?) -> Void) {
262278
guard let device = rileyLinkManager.firstConnectedDevice else {
279+
completion(LoopError.connectionError)
263280
return
264281
}
265282

@@ -284,39 +301,6 @@ final class DeviceDataManager {
284301
}
285302
}
286303

287-
/**
288-
Read the pump's current state, including reservoir and clock
289-
290-
- parameter completion: A closure called after the command is complete. This closure takes a single Result argument:
291-
- Success(status, date): The pump status, and the resolved date according to the pump's clock
292-
- Failure(error): An error describing why the command failed
293-
*/
294-
private func readPumpData(_ completion: @escaping (RileyLinkKit.Either<(status: RileyLinkKit.PumpStatus, date: Date), Error>) -> Void) {
295-
guard let device = rileyLinkManager.firstConnectedDevice, let ops = device.ops else {
296-
completion(.failure(LoopError.connectionError))
297-
return
298-
}
299-
300-
ops.readPumpStatus { (result) in
301-
switch result {
302-
case .success(let status):
303-
var clock = status.clock
304-
clock.timeZone = ops.pumpState.timeZone
305-
306-
guard let date = clock.date else {
307-
let errorStr = "Could not interpret pump clock: \(clock)"
308-
self.logger.addError(errorStr, fromSource: "RileyLink")
309-
completion(.failure(LoopError.invalidData(details: errorStr)))
310-
return
311-
}
312-
completion(.success((status: status, date: date)))
313-
case .failure(let error):
314-
self.logger.addError("Failed to fetch pump status: \(error)", fromSource: "RileyLink")
315-
completion(.failure(error))
316-
}
317-
}
318-
}
319-
320304
private func pumpDataIsStale() -> Bool {
321305
// How long should we wait before we poll for new pump data?
322306
let pumpStatusAgeTolerance = rileyLinkManager.idleListeningEnabled ? TimeInterval(minutes: 11) : TimeInterval(minutes: 4)
@@ -330,6 +314,7 @@ final class DeviceDataManager {
330314
*/
331315
fileprivate func assertCurrentPumpData() {
332316
guard let device = rileyLinkManager.firstConnectedDevice else {
317+
self.setLastError(error: LoopError.connectionError)
333318
return
334319
}
335320

@@ -339,7 +324,7 @@ final class DeviceDataManager {
339324
return
340325
}
341326

342-
readPumpData { (result) in
327+
rileyLinkManager.readPumpData { (result) in
343328
let nsPumpStatus: NightscoutUploadKit.PumpStatus?
344329
switch result {
345330
case .success(let (status, date)):
@@ -352,6 +337,8 @@ final class DeviceDataManager {
352337

353338
nsPumpStatus = NightscoutUploadKit.PumpStatus(clock: date, pumpID: status.pumpID, iob: nil, battery: battery, suspended: status.suspended, bolusing: status.bolusing, reservoir: status.reservoir)
354339
case .failure(let error):
340+
self.logger.addError("Failed to fetch pump status: \(error)", fromSource: "RileyLink")
341+
self.setLastError(error: error)
355342
self.troubleshootPumpComms(using: device)
356343
self.nightscoutDataManager.uploadLoopStatus(loopError: error)
357344
nsPumpStatus = nil
@@ -406,7 +393,7 @@ final class DeviceDataManager {
406393
loopManager.doseStore.lastReservoirVolumeDrop < 0 ||
407394
loopManager.doseStore.lastReservoirValue!.startDate.timeIntervalSinceNow <= TimeInterval(minutes: -6)
408395
{
409-
readPumpData { (result) in
396+
rileyLinkManager.readPumpData { (result) in
410397
switch result {
411398
case .success(let (status, date)):
412399
self.loopManager.addReservoirValue(status.reservoir, at: date) { (result) in
@@ -425,6 +412,8 @@ final class DeviceDataManager {
425412
default:
426413
notify(error)
427414
}
415+
416+
self.logger.addError("Failed to fetch pump status: \(error)", fromSource: "RileyLink")
428417
}
429418
}
430419
} else {
@@ -449,6 +438,7 @@ final class DeviceDataManager {
449438
case .failure(let error):
450439
self.logger.addError("Device \(device.name ?? "") auto-tune failed with error: \(error)", fromSource: "RileyLink")
451440
self.rileyLinkManager.deprioritizeDevice(device: device)
441+
self.setLastError(error: error)
452442
}
453443
}
454444
} else {
@@ -660,7 +650,10 @@ extension DeviceDataManager: CGMManagerDelegate {
660650
loopManager.addGlucose(values, from: manager.device) { _ in
661651
self.assertCurrentPumpData()
662652
}
663-
case .noData, .error:
653+
case .noData:
654+
break
655+
case .error(let error):
656+
self.setLastError(error: error)
664657
self.assertCurrentPumpData()
665658
}
666659
}
@@ -721,6 +714,12 @@ extension DeviceDataManager: LoopDataManagerDelegate {
721714
value: body.rate,
722715
unit: .unitsPerHour
723716
)))
717+
718+
// If we haven't fetched history in a while (preferredInsulinDataSource == .reservoir),
719+
// let's try to do so while the pump radio is on.
720+
if self.loopManager.doseStore.lastAddedPumpEvents.timeIntervalSinceNow < .minutes(-4) {
721+
self.fetchPumpHistory { (_) in }
722+
}
724723
case .failure(let error):
725724
completion(.failure(error))
726725
}
@@ -736,15 +735,13 @@ extension DeviceDataManager: CustomDebugStringConvertible {
736735
"## DeviceDataManager",
737736
"launchDate: \(launchDate)",
738737
"cgm: \(String(describing: cgm))",
738+
"lastError: \(String(describing: lastError))",
739739
"latestPumpStatusFromMySentry: \(String(describing: latestPumpStatusFromMySentry))",
740740
"pumpState: \(String(reflecting: pumpState))",
741741
"preferredInsulinDataSource: \(preferredInsulinDataSource)",
742742
cgmManager != nil ? String(reflecting: cgmManager!) : "",
743743
String(reflecting: rileyLinkManager),
744744
String(reflecting: statusExtensionManager!),
745-
"",
746-
"## NSUserDefaults",
747-
String(reflecting: UserDefaults.standard.dictionaryRepresentation())
748745
].joined(separator: "\n")
749746
}
750747
}

Loop/Managers/LoopDataManager.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1084,6 +1084,7 @@ extension LoopDataManager {
10841084

10851085
entries.append("insulinOnBoard: \(String(describing: insulinOnBoard))")
10861086
entries.append("error: \(String(describing: loopError))")
1087+
entries.append("")
10871088

10881089
self.glucoseStore.generateDiagnosticReport { (report) in
10891090
entries.append(report)
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//
2+
// RileyLinkDeviceManager.swift
3+
// Loop
4+
//
5+
// Copyright © 2017 LoopKit Authors. All rights reserved.
6+
//
7+
8+
import RileyLinkKit
9+
10+
11+
extension RileyLinkDeviceManager {
12+
/// Read the pump's current state, including reservoir and clock
13+
///
14+
/// - Parameter completion: A closure called after the command is complete. This closure takes a single Result argument:
15+
/// - Parameter result:
16+
/// - success(status, date): The pump status, and the resolved date according to the pump's clock
17+
/// - failure(error): An error describing why the command failed
18+
func readPumpData(_ completion: @escaping (_ result: RileyLinkKit.Either<(status: RileyLinkKit.PumpStatus, date: Date), Error>) -> Void) {
19+
guard let device = firstConnectedDevice, let ops = device.ops else {
20+
completion(.failure(LoopError.connectionError))
21+
return
22+
}
23+
24+
ops.readPumpStatus { (result) in
25+
switch result {
26+
case .success(let status):
27+
var clock = status.clock
28+
clock.timeZone = ops.pumpState.timeZone
29+
30+
guard let date = clock.date else {
31+
let errorStr = "Could not interpret pump clock: \(clock)"
32+
completion(.failure(LoopError.invalidData(details: errorStr)))
33+
return
34+
}
35+
completion(.success((status: status, date: date)))
36+
case .failure(let error):
37+
completion(.failure(error))
38+
}
39+
}
40+
}
41+
42+
}

Loop/View Controllers/CommandResponseViewController.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ extension CommandResponseViewController {
1818
completionHandler([
1919
"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.",
2020
"Generated: \(Date())",
21+
"",
2122
String(reflecting: dataManager),
2223
"",
2324
report,

Loop/View Controllers/StatusTableViewController.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ final class StatusTableViewController: ChartsTableViewController {
126126
}
127127
}
128128

129+
private var lastLoopError: Error?
130+
129131
private var refreshContext = RefreshContext.all
130132

131133
private var bolusState: BolusState? {
@@ -163,6 +165,7 @@ final class StatusTableViewController: ChartsTableViewController {
163165

164166
let reloadGroup = DispatchGroup()
165167
var lastLoopCompleted: Date?
168+
var lastLoopError: Error?
166169
var lastReservoirValue: ReservoirValue?
167170
var lastTempBasal: DoseEntry?
168171
var newRecommendedTempBasal: LoopDataManager.TempBasalRecommendation?
@@ -209,6 +212,7 @@ final class StatusTableViewController: ChartsTableViewController {
209212

210213
lastTempBasal = state.lastTempBasal
211214
lastLoopCompleted = state.lastLoopCompleted
215+
lastLoopError = state.error
212216

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

320324
// Loop completion HUD
321325
self.hudView.loopCompletionHUD.lastLoopCompleted = lastLoopCompleted
326+
self.lastLoopError = lastLoopError
322327

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

816821
@objc private func showLastError(_: Any) {
817-
self.deviceManager.loopManager.getLoopState { (_, state) in
818-
if let error = state.error {
819-
self.presentAlertController(with: error)
820-
}
822+
// First, check whether we have a device error after the most recent completion date
823+
if let deviceError = deviceManager.lastError, deviceError.date > (hudView.loopCompletionHUD.lastLoopCompleted ?? .distantPast) {
824+
self.presentAlertController(with: deviceError.error)
825+
} else if let lastLoopError = lastLoopError {
826+
self.presentAlertController(with: lastLoopError)
821827
}
822828
}
823829

0 commit comments

Comments
 (0)