@@ -25,21 +25,27 @@ enum State<T> {
2525
2626
2727class 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
0 commit comments