Skip to content

Commit d416c6d

Browse files
committed
Comments and refactoring.
Preparing for #1
1 parent cafc149 commit d416c6d

File tree

4 files changed

+127
-90
lines changed

4 files changed

+127
-90
lines changed

Loop/Managers/DeviceDataManager.swift

Lines changed: 113 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,27 @@ enum State<T> {
2525

2626

2727
class DeviceDataManager: NSObject, CarbStoreDelegate, TransmitterDelegate, WCSessionDelegate {
28+
/// Notification posted by the instance when new glucose data was processed
2829
static let GlucoseUpdatedNotification = "com.loudnate.Naterade.notification.GlucoseUpdated"
30+
31+
/// Notification posted by the instance when new pump data was processed
2932
static let PumpStatusUpdatedNotification = "com.loudnate.Naterade.notification.PumpStatusUpdated"
3033

3134
enum Error: ErrorType {
3235
case ValueError(String)
3336
}
3437

35-
// MARK: - Observed state
38+
// MARK: - Utilities
3639

3740
lazy var logger = DiagnosticLogger()
3841

42+
/// Manages all the RileyLinks
3943
let rileyLinkManager: RileyLinkDeviceManager
4044

41-
let shareClient: ShareClient?
45+
/// The share server client
46+
private let shareClient: ShareClient?
4247

48+
/// The G5 transmitter object
4349
var transmitter: Transmitter? {
4450
switch transmitterState {
4551
case .Ready(let transmitter):
@@ -51,27 +57,27 @@ class DeviceDataManager: NSObject, CarbStoreDelegate, TransmitterDelegate, WCSes
5157

5258
// MARK: - RileyLink
5359

54-
var rileyLinkManagerObserver: AnyObject? {
60+
private var rileyLinkManagerObserver: AnyObject? {
5561
willSet {
5662
if let observer = rileyLinkManagerObserver {
5763
NSNotificationCenter.defaultCenter().removeObserver(observer)
5864
}
5965
}
6066
}
6167

62-
var rileyLinkDevicePacketObserver: AnyObject? {
68+
private var rileyLinkDevicePacketObserver: AnyObject? {
6369
willSet {
6470
if let observer = rileyLinkDevicePacketObserver {
6571
NSNotificationCenter.defaultCenter().removeObserver(observer)
6672
}
6773
}
6874
}
6975

70-
func receivedRileyLinkManagerNotification(note: NSNotification) {
76+
private func receivedRileyLinkManagerNotification(note: NSNotification) {
7177
NSNotificationCenter.defaultCenter().postNotificationName(note.name, object: self, userInfo: note.userInfo)
7278
}
7379

74-
func receivedRileyLinkPacketNotification(note: NSNotification) {
80+
private func receivedRileyLinkPacketNotification(note: NSNotification) {
7581
if let
7682
device = note.object as? RileyLinkDevice,
7783
data = note.userInfo?[RileyLinkDevice.IdleMessageDataKey] as? NSData,
@@ -82,18 +88,10 @@ class DeviceDataManager: NSObject, CarbStoreDelegate, TransmitterDelegate, WCSes
8288
switch message.messageBody {
8389
case let body as MySentryPumpStatusMessageBody:
8490
updatePumpStatus(body, fromDevice: device)
85-
case is MySentryAlertMessageBody:
86-
break
87-
// TODO: de-dupe
88-
// logger?.addMessage(body.dictionaryRepresentation, toCollection: "sentryAlert")
89-
case is MySentryAlertClearedMessageBody:
90-
break
91-
// TODO: de-dupe
92-
// logger?.addMessage(body.dictionaryRepresentation, toCollection: "sentryAlert")
93-
case let body as UnknownMessageBody:
94-
logger?.addMessage(body.dictionaryRepresentation, toCollection: "sentryOther")
95-
default:
91+
case is MySentryAlertMessageBody, is MySentryAlertClearedMessageBody:
9692
break
93+
case let body:
94+
logger?.addMessage(["messageType": Int(message.messageType.rawValue), "messageBody": body.txData.hexadecimalString], toCollection: "sentryOther")
9795
}
9896
default:
9997
break
@@ -117,74 +115,79 @@ class DeviceDataManager: NSObject, CarbStoreDelegate, TransmitterDelegate, WCSes
117115
AnalyticsManager.didChangeRileyLinkConnectionState()
118116
}
119117

118+
// MARK: Pump data
119+
120+
var latestPumpStatus: MySentryPumpStatusMessageBody?
121+
122+
var latestReservoirValue: ReservoirValue?
123+
124+
/**
125+
Handles receiving a MySentry status message, which are only posted by MM x23 pumps.
126+
127+
This message has two important pieces of info about the pump: reservoir volume and battery.
128+
129+
Because the RileyLink must actively listen for these packets, they are not the most reliable heartbeat. However, we can still use them to assert glucose data is current.
130+
131+
- parameter status: The status message body
132+
- parameter device: The RileyLink that received the message
133+
*/
120134
private func updatePumpStatus(status: MySentryPumpStatusMessageBody, fromDevice device: RileyLinkDevice) {
121135
status.pumpDateComponents.timeZone = pumpState?.timeZone
122136

123-
if status != latestPumpStatus, let pumpDate = status.pumpDateComponents.date {
124-
latestPumpStatus = status
137+
// The pump sends the same message 3x, so ignore it if we've already seen it.
138+
guard status != latestPumpStatus, let pumpDate = status.pumpDateComponents.date else {
139+
return
140+
}
125141

126-
doseStore.addReservoirValue(status.reservoirRemainingUnits, atDate: pumpDate) { (newValue, previousValue, error) -> Void in
127-
if let error = error {
128-
self.logger?.addError(error, fromSource: "DoseStore")
129-
} else if self.latestGlucoseMessageDate == nil,
130-
let shareClient = self.shareClient,
131-
glucoseStore = self.glucoseStore,
132-
lastGlucose = glucoseStore.latestGlucose
133-
where lastGlucose.startDate.timeIntervalSinceNow < -NSTimeInterval(minutes: 5)
134-
{
135-
// Load glucose from Share if our xDripG5 connection hasn't started
136-
shareClient.fetchLast(1) { (error, glucose) in
137-
if let error = error {
138-
self.logger?.addError(error, fromSource: "ShareClient")
139-
}
140-
141-
guard let glucose = glucose?.first where lastGlucose.startDate.timeIntervalSinceDate(glucose.startDate) < -NSTimeInterval(minutes: 1) else {
142-
NSNotificationCenter.defaultCenter().postNotificationName(self.dynamicType.PumpStatusUpdatedNotification, object: self)
143-
return
144-
}
145-
146-
glucoseStore.addGlucose(glucose.quantity, date: glucose.startDate, displayOnly: false, device: nil) { (_, value, error) -> Void in
147-
if let error = error {
148-
self.logger?.addError(error, fromSource: "GlucoseStore")
149-
}
150-
151-
NSNotificationCenter.defaultCenter().postNotificationName(self.dynamicType.GlucoseUpdatedNotification, object: self)
152-
NSNotificationCenter.defaultCenter().postNotificationName(self.dynamicType.PumpStatusUpdatedNotification, object: self)
153-
}
154-
}
155-
} else {
156-
NSNotificationCenter.defaultCenter().postNotificationName(self.dynamicType.PumpStatusUpdatedNotification, object: self)
157-
}
142+
latestPumpStatus = status
158143

159-
if let newVolume = newValue?.unitVolume, previousVolume = previousValue?.unitVolume {
160-
self.checkPumpReservoirForAmount(newVolume, previousAmount: previousVolume, timeLeft: NSTimeInterval(minutes: Double(status.reservoirRemainingMinutes)))
161-
}
162-
}
144+
backfillGlucoseFromShareIfNeeded()
163145

164-
if status.batteryRemainingPercent == 0 {
165-
NotificationManager.sendPumpBatteryLowNotification()
166-
}
146+
updateReservoirVolume(status.reservoirRemainingUnits, atDate: pumpDate, withTimeLeft: NSTimeInterval(minutes: Double(status.reservoirRemainingMinutes)))
147+
148+
// Check for an empty battery. Sentry packets are still broadcast for a few hours after this value reaches 0.
149+
if status.batteryRemainingPercent == 0 {
150+
NotificationManager.sendPumpBatteryLowNotification()
167151
}
168152
}
169153

170-
private func checkPumpReservoirForAmount(newAmount: Double, previousAmount: Double, timeLeft: NSTimeInterval) {
154+
/**
155+
Store a new reservoir volume and notify observers of new pump data.
171156

172-
guard newAmount > 0 else {
173-
NotificationManager.sendPumpReservoirEmptyNotification()
174-
return
175-
}
157+
- parameter units: The number of units remaining
158+
- parameter date: The date the reservoir was read
159+
- parameter timeLeft: The approximate time before the reservoir is empty
160+
*/
161+
private func updateReservoirVolume(units: Double, atDate date: NSDate, withTimeLeft timeLeft: NSTimeInterval?) {
162+
doseStore.addReservoirValue(units, atDate: date) { (newValue, previousValue, error) -> Void in
163+
if let error = error {
164+
self.logger?.addError(error, fromSource: "DoseStore")
165+
return
166+
}
176167

177-
let warningThresholds: [Double] = [10, 20, 30]
168+
self.latestReservoirValue = newValue
178169

179-
for threshold in warningThresholds {
180-
if newAmount <= threshold && previousAmount > threshold {
181-
NotificationManager.sendPumpReservoirLowNotificationForAmount(newAmount, andTimeRemaining: timeLeft)
182-
return
170+
NSNotificationCenter.defaultCenter().postNotificationName(self.dynamicType.PumpStatusUpdatedNotification, object: self)
171+
172+
// Send notifications for low reservoir if necessary
173+
if let newVolume = newValue?.unitVolume, previousVolume = previousValue?.unitVolume {
174+
guard newVolume > 0 else {
175+
NotificationManager.sendPumpReservoirEmptyNotification()
176+
return
177+
}
178+
179+
let warningThresholds: [Double] = [10, 20, 30]
180+
181+
for threshold in warningThresholds {
182+
if newVolume <= threshold && previousVolume > threshold {
183+
NotificationManager.sendPumpReservoirLowNotificationForAmount(newVolume, andTimeRemaining: timeLeft)
184+
}
185+
}
183186
}
184187
}
185188
}
186189

187-
// MARK: - Transmitter
190+
// MARK: - G5 Transmitter
188191

189192
// MARK: TransmitterDelegate
190193

@@ -195,7 +198,7 @@ class DeviceDataManager: NSObject, CarbStoreDelegate, TransmitterDelegate, WCSes
195198
], toCollection: "g5"
196199
)
197200

198-
self.rileyLinkManager.firstConnectedDevice?.assertIdleListening()
201+
rileyLinkManager.firstConnectedDevice?.assertIdleListening()
199202
}
200203

201204
func transmitter(transmitter: Transmitter, didReadGlucose glucose: GlucoseRxMessage) {
@@ -223,12 +226,12 @@ class DeviceDataManager: NSObject, CarbStoreDelegate, TransmitterDelegate, WCSes
223226
}
224227
}
225228

226-
self.rileyLinkManager.firstConnectedDevice?.assertIdleListening()
229+
rileyLinkManager.firstConnectedDevice?.assertIdleListening()
227230
}
228231

229-
// MARK: - Managed state
232+
// MARK: G5 data
230233

231-
var transmitterStartTime: NSTimeInterval? = NSUserDefaults.standardUserDefaults().transmitterStartTime {
234+
private var transmitterStartTime: NSTimeInterval? = NSUserDefaults.standardUserDefaults().transmitterStartTime {
232235
didSet {
233236
if oldValue != transmitterStartTime {
234237
NSUserDefaults.standardUserDefaults().transmitterStartTime = transmitterStartTime
@@ -250,9 +253,42 @@ class DeviceDataManager: NSObject, CarbStoreDelegate, TransmitterDelegate, WCSes
250253
return NSDate(timeIntervalSince1970: startTime).dateByAddingTimeInterval(NSTimeInterval(glucose.timestamp))
251254
}
252255

253-
var latestPumpStatus: MySentryPumpStatusMessageBody?
256+
/**
257+
Attempts to backfill glucose data from the share servers if the G5 connection hasn't been established.
258+
*/
259+
private func backfillGlucoseFromShareIfNeeded() {
260+
if self.latestGlucoseMessageDate == nil,
261+
let shareClient = self.shareClient, glucoseStore = self.glucoseStore
262+
{
263+
// Load glucose from Share if our xDripG5 connection hasn't started
264+
shareClient.fetchLast(1) { (error, glucose) in
265+
if let error = error {
266+
self.logger?.addError(error, fromSource: "ShareClient")
267+
}
268+
269+
guard let glucose = glucose?.first else {
270+
return
271+
}
272+
273+
// Ignore glucose values that are less than a minute newer than our previous value
274+
if let latestGlucose = glucoseStore.latestGlucose where latestGlucose.startDate.timeIntervalSinceDate(glucose.startDate) > -NSTimeInterval(minutes: 1) {
275+
return
276+
}
277+
278+
glucoseStore.addGlucose(glucose.quantity, date: glucose.startDate, displayOnly: false, device: nil) { (_, value, error) -> Void in
279+
if let error = error {
280+
self.logger?.addError(error, fromSource: "GlucoseStore")
281+
}
282+
283+
NSNotificationCenter.defaultCenter().postNotificationName(self.dynamicType.GlucoseUpdatedNotification, object: self)
284+
}
285+
}
286+
}
287+
}
288+
289+
// MARK: - Configuration
254290

255-
var transmitterState: State<Transmitter> = .NeedsConfiguration {
291+
private var transmitterState: State<Transmitter> = .NeedsConfiguration {
256292
didSet {
257293
switch transmitterState {
258294
case .Ready(let transmitter):
@@ -263,7 +299,7 @@ class DeviceDataManager: NSObject, CarbStoreDelegate, TransmitterDelegate, WCSes
263299
}
264300
}
265301

266-
var connectedPeripheralIDs: Set<String> = Set(NSUserDefaults.standardUserDefaults().connectedPeripheralIDs) {
302+
private var connectedPeripheralIDs: Set<String> = Set(NSUserDefaults.standardUserDefaults().connectedPeripheralIDs) {
267303
didSet {
268304
NSUserDefaults.standardUserDefaults().connectedPeripheralIDs = Array(connectedPeripheralIDs)
269305
}
@@ -312,7 +348,7 @@ class DeviceDataManager: NSObject, CarbStoreDelegate, TransmitterDelegate, WCSes
312348
}
313349
}
314350

315-
func pumpStateValuesDidChange(note: NSNotification) {
351+
@objc private func pumpStateValuesDidChange(note: NSNotification) {
316352
switch note.userInfo?[PumpState.PropertyKey] as? String {
317353
case "timeZone"?:
318354
NSUserDefaults.standardUserDefaults().pumpTimeZone = pumpState?.timeZone

Loop/Managers/LoopDataManager.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ class LoopDataManager {
5757
self.glucoseMomentumEffect = nil
5858
self.notify()
5959

60-
// Try to troubleshoot communications errors
61-
if let pumpStatusDate = self.deviceDataManager.latestPumpStatus?.pumpDateComponents.date where pumpStatusDate.timeIntervalSinceNow < NSTimeInterval(minutes: -15),
60+
// Try to troubleshoot communications errors with the pump
61+
if let pumpStatusDate = self.deviceDataManager.latestReservoirValue?.startDate where pumpStatusDate.timeIntervalSinceNow < NSTimeInterval(minutes: -15),
6262
let device = self.deviceDataManager.rileyLinkManager.firstConnectedDevice where device.lastTuned?.timeIntervalSinceNow < NSTimeInterval(minutes: -15) {
6363
device.tunePumpWithResultHandler { (result) in
6464
switch result {
@@ -77,7 +77,9 @@ class LoopDataManager {
7777
NSNotificationCenter.defaultCenter().postNotificationName(self.dynamicType.LoopRunningNotification, object: self)
7878

7979
// Sentry packets are sent in groups of 3, 5s apart. Wait 11s to avoid conflicting comms.
80-
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(11 * NSEC_PER_SEC)), self.dataAccessQueue) {
80+
let waitTime = self.deviceDataManager.latestPumpStatus != nil ? Int64(11 * NSEC_PER_SEC) : 0
81+
82+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, waitTime), self.dataAccessQueue) {
8183
self.waitingForSentryPackets = false
8284
self.insulinEffect = nil
8385
self.loop()
@@ -314,7 +316,7 @@ class LoopDataManager {
314316
private func updatePredictedGlucoseAndRecommendedBasal() throws {
315317
guard let
316318
glucose = self.deviceDataManager.glucoseStore?.latestGlucose,
317-
pumpStatusDate = self.deviceDataManager.latestPumpStatus?.pumpDateComponents.date
319+
pumpStatusDate = self.deviceDataManager.latestReservoirValue?.startDate
318320
else
319321
{
320322
self.predictedGlucose = nil

Loop/Managers/NotificationManager.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ struct NotificationManager {
130130
UIApplication.sharedApplication().presentLocalNotificationNow(notification)
131131
}
132132

133-
static func sendPumpReservoirLowNotificationForAmount(units: Double, andTimeRemaining remaining: NSTimeInterval) {
133+
static func sendPumpReservoirLowNotificationForAmount(units: Double, andTimeRemaining remaining: NSTimeInterval?) {
134134
let notification = UILocalNotification()
135135

136136
notification.alertTitle = NSLocalizedString("Pump Reservoir Low", comment: "The notification title for a low pump reservoir")
@@ -144,12 +144,12 @@ struct NotificationManager {
144144
intervalFormatter.includesApproximationPhrase = true
145145
intervalFormatter.includesTimeRemainingPhrase = true
146146

147-
guard let timeString = intervalFormatter.stringFromTimeInterval(remaining) else {
148-
return
147+
if let remaining = remaining, timeString = intervalFormatter.stringFromTimeInterval(remaining) {
148+
notification.alertBody = String(format: NSLocalizedString("%1$@ U left: %2$@", comment: "Low reservoir alert with time remaining format string. (1: Number of units remaining)(2: approximate time remaining)"), unitsString, timeString)
149+
} else {
150+
notification.alertBody = String(format: NSLocalizedString("%1$@ U left", comment: "Low reservoir alert format string. (1: Number of units remaining)"), unitsString)
149151
}
150152

151-
notification.alertBody = NSLocalizedString("\(unitsString) U left: \(timeString)", comment: "")
152-
153153
notification.soundName = UILocalNotificationDefaultSoundName
154154
notification.category = Category.PumpReservoirLow.rawValue
155155

Loop/View Controllers/StatusTableViewController.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,9 @@ class StatusTableViewController: UITableViewController, UIGestureRecognizerDeleg
194194
}
195195
}
196196

197+
reservoirVolume = dataManager.latestReservoirValue?.unitVolume
198+
197199
if let status = dataManager.latestPumpStatus {
198-
reservoirVolume = status.reservoirRemainingUnits
199200
reservoirLevel = Double(status.reservoirRemainingPercent) / 100
200201
batteryLevel = Double(status.batteryRemainingPercent) / 100
201202
}
@@ -484,9 +485,7 @@ class StatusTableViewController: UITableViewController, UIGestureRecognizerDeleg
484485
case .Date:
485486
cell.textLabel?.text = NSLocalizedString("Last Sensor", comment: "The title of the cell containing the last updated sensor date")
486487

487-
if let glucose = dataManager.latestGlucoseMessage, startTime = dataManager.transmitterStartTime {
488-
let date = NSDate(timeIntervalSince1970: startTime).dateByAddingTimeInterval(NSTimeInterval(glucose.timestamp))
489-
488+
if let date = dataManager.latestGlucoseMessageDate {
490489
cell.detailTextLabel?.text = dateFormatter.stringFromDate(date)
491490
} else {
492491
cell.detailTextLabel?.text = emptyValueString

0 commit comments

Comments
 (0)