diff --git a/MinimedKit/Info.plist b/MinimedKit/Info.plist index 425ef70fb..5d7827c27 100644 --- a/MinimedKit/Info.plist +++ b/MinimedKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.7.1 + 0.8.0 CFBundleSignature ???? CFBundleVersion diff --git a/MinimedKit/MessageType.swift b/MinimedKit/MessageType.swift index a4df84169..7d4915860 100644 --- a/MinimedKit/MessageType.swift +++ b/MinimedKit/MessageType.swift @@ -28,6 +28,7 @@ public enum MessageType: UInt8 { case GetPumpModel = 0x8d case ReadTempBasal = 0x98 case ReadSettings = 0xc0 + case ReadPumpStatus = 0xce var bodyType: MessageBody.Type { switch self { @@ -59,6 +60,8 @@ public enum MessageType: UInt8 { return GetBatteryCarelinkMessageBody.self case .ReadRemainingInsulin: return ReadRemainingInsulinMessageBody.self + case .ReadPumpStatus: + return ReadPumpStatusMessageBody.self default: return UnknownMessageBody.self } diff --git a/MinimedKit/Messages/GetBatteryCarelinkMessageBody.swift b/MinimedKit/Messages/GetBatteryCarelinkMessageBody.swift index 681633019..a8dd6f8ff 100644 --- a/MinimedKit/Messages/GetBatteryCarelinkMessageBody.swift +++ b/MinimedKit/Messages/GetBatteryCarelinkMessageBody.swift @@ -8,25 +8,37 @@ import Foundation +public enum BatteryStatus { + case Low + case Normal + case Unknown(rawVal: UInt8) + + init(statusByte: UInt8) { + switch statusByte { + case 1: + self = .Low + case 0: + self = .Normal + default: + self = .Unknown(rawVal: statusByte) + } + } +} + public class GetBatteryCarelinkMessageBody: CarelinkLongMessageBody { - public let status: String + public let status: BatteryStatus public let volts: Double public required init?(rxData: NSData) { guard rxData.length == self.dynamicType.length else { - status = "" volts = 0 + status = .Unknown(rawVal: 0) super.init(rxData: rxData) return nil } volts = Double(Int(rxData[2] as UInt8) << 8 + Int(rxData[3] as UInt8)) / 100.0 - - if rxData[1] as UInt8 > 0 { - status = "Low" - } else { - status = "Normal" - } + status = BatteryStatus(statusByte: rxData[1] as UInt8) super.init(rxData: rxData) } diff --git a/MinimedKit/Messages/ReadPumpStatusMessageBody.swift b/MinimedKit/Messages/ReadPumpStatusMessageBody.swift new file mode 100644 index 000000000..7ab72f975 --- /dev/null +++ b/MinimedKit/Messages/ReadPumpStatusMessageBody.swift @@ -0,0 +1,27 @@ +// +// ReadPumpStatusMessageBody.swift +// RileyLink +// +// Created by Pete Schwamb on 7/31/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import Foundation + +public class ReadPumpStatusMessageBody: CarelinkLongMessageBody { + + public let bolusing: Bool + public let suspended: Bool + + public required init?(rxData: NSData) { + guard rxData.length == self.dynamicType.length else { + return nil + } + + bolusing = (rxData[2] as UInt8) > 0 + suspended = (rxData[3] as UInt8) > 0 + + super.init(rxData: rxData) + } + +} diff --git a/MinimedKit/Messages/ReadRemainingInsulinMessageBody.swift b/MinimedKit/Messages/ReadRemainingInsulinMessageBody.swift index 35af7dff3..f5ec46ae6 100644 --- a/MinimedKit/Messages/ReadRemainingInsulinMessageBody.swift +++ b/MinimedKit/Messages/ReadRemainingInsulinMessageBody.swift @@ -6,7 +6,7 @@ // Copyright © 2016 Pete Schwamb. All rights reserved. // -import UIKit +import Foundation public class ReadRemainingInsulinMessageBody: CarelinkLongMessageBody { diff --git a/MinimedKit/PumpEventType.swift b/MinimedKit/PumpEventType.swift index 722b72c48..5c7579d96 100644 --- a/MinimedKit/PumpEventType.swift +++ b/MinimedKit/PumpEventType.swift @@ -69,7 +69,7 @@ public enum PumpEventType: UInt8 { case DeleteOtherDeviceID = 0x82 case ChangeCaptureEventEnable = 0x83 - var eventType: PumpEvent.Type { + public var eventType: PumpEvent.Type { switch self { case .BolusNormal: return BolusNormalPumpEvent.self diff --git a/MinimedKit/PumpEvents/ChangeTimePumpEvent.swift b/MinimedKit/PumpEvents/ChangeTimePumpEvent.swift index 8126dbf5a..99ebb210a 100644 --- a/MinimedKit/PumpEvents/ChangeTimePumpEvent.swift +++ b/MinimedKit/PumpEvents/ChangeTimePumpEvent.swift @@ -30,7 +30,7 @@ public struct ChangeTimePumpEvent: TimestampedPumpEvent { oldTimestamp = NSDateComponents(pumpEventData: availableData, offset: 2) timestamp = NSDateComponents(pumpEventData: availableData, offset: 9) } - + public var dictionaryRepresentation: [String: AnyObject] { return [ "_type": "ChangeTime", diff --git a/MinimedKit/PumpEvents/PumpAlarmPumpEvent.swift b/MinimedKit/PumpEvents/PumpAlarmPumpEvent.swift index b4d47f5e7..72b87b9fd 100644 --- a/MinimedKit/PumpEvents/PumpAlarmPumpEvent.swift +++ b/MinimedKit/PumpEvents/PumpAlarmPumpEvent.swift @@ -8,12 +8,41 @@ import Foundation +public enum PumpAlarmType { + case BatteryOutLimitExceeded + case NoDelivery + case BatteryDepleted + case DeviceReset + case ReprogramError + case EmptyReservoir + case UnknownType(rawType: UInt8) + + init(rawType: UInt8) { + switch rawType { + case 3: + self = .BatteryOutLimitExceeded + case 4: + self = .NoDelivery + case 5: + self = .BatteryDepleted + case 16: + self = .DeviceReset + case 61: + self = .ReprogramError + case 62: + self = .EmptyReservoir + default: + self = .UnknownType(rawType: rawType) + } + } +} + public struct PumpAlarmPumpEvent: TimestampedPumpEvent { public let length: Int public let rawData: NSData public let timestamp: NSDateComponents - let rawType: Int - + public let alarmType: PumpAlarmType + public init?(availableData: NSData, pumpModel: PumpModel) { length = 9 @@ -23,14 +52,16 @@ public struct PumpAlarmPumpEvent: TimestampedPumpEvent { rawData = availableData[0..CFBundlePackageType BNDL CFBundleShortVersionString - 0.7.1 + 0.8.0 CFBundleSignature ???? CFBundleVersion diff --git a/NightscoutUploadKit/DeviceStatus/BatteryStatus.swift b/NightscoutUploadKit/DeviceStatus/BatteryStatus.swift new file mode 100644 index 000000000..3cfda095f --- /dev/null +++ b/NightscoutUploadKit/DeviceStatus/BatteryStatus.swift @@ -0,0 +1,43 @@ +// +// BatteryStatus.swift +// RileyLink +// +// Created by Pete Schwamb on 7/28/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import Foundation + +public enum BatteryIndicator: String { + case Low = "low" + case Normal = "normal" +} + +public struct BatteryStatus { + let percent: Int? + let voltage: Double? + let status: BatteryIndicator? + + public init(percent: Int? = nil, voltage: Double? = nil, status: BatteryIndicator? = nil) { + self.percent = percent + self.voltage = voltage + self.status = status + } + + public var dictionaryRepresentation: [String: AnyObject] { + var rval = [String: AnyObject]() + + if let percent = percent { + rval["percent"] = percent + } + if let voltage = voltage { + rval["voltage"] = voltage + } + + if let status = status { + rval["status"] = status.rawValue + } + + return rval + } +} \ No newline at end of file diff --git a/NightscoutUploadKit/DeviceStatus/COBStatus.swift b/NightscoutUploadKit/DeviceStatus/COBStatus.swift new file mode 100644 index 000000000..496b9c99d --- /dev/null +++ b/NightscoutUploadKit/DeviceStatus/COBStatus.swift @@ -0,0 +1,30 @@ +// +// COBStatus.swift +// RileyLink +// +// Created by Pete Schwamb on 8/2/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import Foundation + +public struct COBStatus { + let cob: Double + let timestamp: NSDate + + public init(cob: Double, timestamp: NSDate) { + self.cob = cob // grams + self.timestamp = timestamp + } + + public var dictionaryRepresentation: [String: AnyObject] { + + var rval = [String: AnyObject]() + + rval["timestamp"] = TimeFormat.timestampStrFromDate(timestamp) + rval["cob"] = cob + + return rval + } + +} \ No newline at end of file diff --git a/NightscoutUploadKit/DeviceStatus/DeviceStatus.swift b/NightscoutUploadKit/DeviceStatus/DeviceStatus.swift new file mode 100644 index 000000000..a0c8fc66b --- /dev/null +++ b/NightscoutUploadKit/DeviceStatus/DeviceStatus.swift @@ -0,0 +1,48 @@ +// +// DeviceStatus.swift +// RileyLink +// +// Created by Pete Schwamb on 7/26/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import Foundation + +public struct DeviceStatus { + let device: String + let timestamp: NSDate + let pumpStatus: PumpStatus? + let uploaderStatus: UploaderStatus? + let loopStatus: LoopStatus? + + public init(device: String, timestamp: NSDate, pumpStatus: PumpStatus? = nil, uploaderStatus: UploaderStatus? = nil, loopStatus: LoopStatus? = nil) { + self.device = device + self.timestamp = timestamp + self.pumpStatus = pumpStatus + self.uploaderStatus = uploaderStatus + self.loopStatus = loopStatus + } + + public var dictionaryRepresentation: [String: AnyObject] { + var rval = [String: AnyObject]() + + rval["device"] = device + rval["created_at"] = TimeFormat.timestampStrFromDate(timestamp) + + if let pump = pumpStatus { + rval["pump"] = pump.dictionaryRepresentation + } + + if let uploader = uploaderStatus { + rval["uploader"] = uploader.dictionaryRepresentation + } + + if let loop = loopStatus { + // Would like to change this to avoid confusion about whether or not this was uploaded from Loop or openaps + rval["openaps"] = loop.dictionaryRepresentation + } + + return rval + } +} + diff --git a/NightscoutUploadKit/DeviceStatus/IOBStatus.swift b/NightscoutUploadKit/DeviceStatus/IOBStatus.swift new file mode 100644 index 000000000..8e42dee32 --- /dev/null +++ b/NightscoutUploadKit/DeviceStatus/IOBStatus.swift @@ -0,0 +1,38 @@ +// +// IOBStatus.swift +// RileyLink +// +// Created by Pete Schwamb on 7/28/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import Foundation + +public struct IOBStatus { + let timestamp: NSDate + let iob: Double? // basal iob + bolus iob: can be negative + let basalIOB: Double? // does not include bolus iob + + public init(timestamp: NSDate, iob: Double? = nil, basalIOB: Double? = nil) { + self.timestamp = timestamp + self.iob = iob + self.basalIOB = basalIOB + } + + public var dictionaryRepresentation: [String: AnyObject] { + + var rval = [String: AnyObject]() + + rval["timestamp"] = TimeFormat.timestampStrFromDate(timestamp) + + if let iob = iob { + rval["iob"] = iob + } + + if let basalIOB = basalIOB { + rval["basaliob"] = basalIOB + } + + return rval + } +} \ No newline at end of file diff --git a/NightscoutUploadKit/DeviceStatus/LoopEnacted.swift b/NightscoutUploadKit/DeviceStatus/LoopEnacted.swift new file mode 100644 index 000000000..606c902fc --- /dev/null +++ b/NightscoutUploadKit/DeviceStatus/LoopEnacted.swift @@ -0,0 +1,32 @@ +// +// LoopEnacted.swift +// RileyLink +// +// Created by Pete Schwamb on 7/28/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import Foundation + +public struct LoopEnacted { + let rate: Double + let duration: NSTimeInterval + let timestamp: NSDate + let received: Bool + + public init(rate: Double, duration: NSTimeInterval, timestamp: NSDate, received: Bool) { + self.rate = rate + self.duration = duration + self.timestamp = timestamp + self.received = received + } + + public var dictionaryRepresentation: [String: AnyObject] { + return [ + "rate": rate, + "duration": duration / 60.0, + "timestamp": TimeFormat.timestampStrFromDate(timestamp), + "recieved": received // [sic] + ] + } +} \ No newline at end of file diff --git a/NightscoutUploadKit/DeviceStatus/LoopStatus.swift b/NightscoutUploadKit/DeviceStatus/LoopStatus.swift new file mode 100644 index 000000000..f97e20de0 --- /dev/null +++ b/NightscoutUploadKit/DeviceStatus/LoopStatus.swift @@ -0,0 +1,57 @@ +// +// LoopStatus.swift +// RileyLink +// +// Created by Pete Schwamb on 7/26/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import Foundation + +public struct LoopStatus { + let name: String + let timestamp: NSDate + + let iob: IOBStatus? + let cob: COBStatus? + let suggested: LoopSuggested? + let enacted: LoopEnacted? + + let failureReason: String? + + public init(name: String, timestamp: NSDate, glucose: Int? = nil, iob: IOBStatus? = nil, cob: COBStatus? = nil, suggested: LoopSuggested? = nil, enacted: LoopEnacted?, failureReason: String? = nil) { + self.name = name + self.timestamp = timestamp + self.suggested = suggested + self.enacted = enacted + self.iob = iob + self.cob = cob + self.failureReason = failureReason + } + + public var dictionaryRepresentation: [String: AnyObject] { + var rval = [String: AnyObject]() + + rval["name"] = name + rval["timestamp"] = TimeFormat.timestampStrFromDate(timestamp) + + if let suggested = suggested { + rval["suggested"] = suggested.dictionaryRepresentation + } + + if let enacted = enacted { + rval["enacted"] = enacted.dictionaryRepresentation + } + + if let iob = iob { + rval["iob"] = iob.dictionaryRepresentation + } + + if let cob = cob { + rval["cob"] = cob.dictionaryRepresentation + } + + return rval + } +} + diff --git a/NightscoutUploadKit/DeviceStatus/LoopSuggested.swift b/NightscoutUploadKit/DeviceStatus/LoopSuggested.swift new file mode 100644 index 000000000..45ab81313 --- /dev/null +++ b/NightscoutUploadKit/DeviceStatus/LoopSuggested.swift @@ -0,0 +1,66 @@ +// +// LoopSuggested.swift +// RileyLink +// +// Created by Pete Schwamb on 7/28/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import Foundation + +public struct LoopSuggested { + let timestamp: NSDate + let rate: Double + let duration: NSTimeInterval + let eventualBG: Int + let bg: Int + let reason: String? + let tick: Int? + let correction: Double? + + public init(timestamp: NSDate, rate: Double, duration: NSTimeInterval, eventualBG: Int, bg: Int, reason: String? = nil, tick: Int? = nil, correction: Double? = nil) { + self.timestamp = timestamp + self.rate = rate + self.duration = duration + self.eventualBG = eventualBG + self.bg = bg + self.reason = reason + self.tick = tick + self.correction = correction + } + + public var dictionaryRepresentation: [String: AnyObject] { + + var rval = [String: AnyObject]() + + rval["timestamp"] = TimeFormat.timestampStrFromDate(timestamp) + + if let tick = tick { + let tickStr: String + if tick > 0 { + tickStr = "+\(tick)" + } else if tick < 0 { + tickStr = "-\(tick)" + } else { + tickStr = "0" + } + rval["tick"] = tickStr + } + + rval["rate"] = rate + rval["duration"] = duration / 60.0 + rval["bg"] = bg + rval["eventualBG"] = eventualBG + + if let reason = reason { + rval["reason"] = reason + } + + if let correction = correction { + rval["correction"] = correction + } + + + return rval + } +} diff --git a/NightscoutUploadKit/DeviceStatus/PumpStatus.swift b/NightscoutUploadKit/DeviceStatus/PumpStatus.swift new file mode 100644 index 000000000..3c6c27a2c --- /dev/null +++ b/NightscoutUploadKit/DeviceStatus/PumpStatus.swift @@ -0,0 +1,50 @@ +// +// PumpStatus.swift +// RileyLink +// +// Created by Pete Schwamb on 7/26/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import Foundation + +public struct PumpStatus { + let clock: NSDate + let pumpID: String + let iob: IOBStatus? + let battery: BatteryStatus? + let suspended: Bool? + let bolusing: Bool? + let reservoir: Double? + + public init(clock: NSDate, pumpID: String, iob: IOBStatus? = nil, battery: BatteryStatus? = nil, suspended: Bool? = nil, bolusing: Bool? = nil, reservoir: Double? = nil) { + self.clock = clock + self.pumpID = pumpID + self.iob = iob + self.battery = battery + self.suspended = suspended + self.bolusing = bolusing + self.reservoir = reservoir + } + + public var dictionaryRepresentation: [String: AnyObject] { + var rval = [String: AnyObject]() + + rval["clock"] = TimeFormat.timestampStrFromDate(clock) + rval["pumpID"] = pumpID + + if let battery = battery { + rval["battery"] = battery.dictionaryRepresentation + } + + if let reservoir = reservoir { + rval["reservoir"] = reservoir + } + + if let iob = iob { + rval["iob"] = iob.dictionaryRepresentation + } + + return rval + } +} \ No newline at end of file diff --git a/NightscoutUploadKit/DeviceStatus/UploaderStatus.swift b/NightscoutUploadKit/DeviceStatus/UploaderStatus.swift new file mode 100644 index 000000000..a591f566d --- /dev/null +++ b/NightscoutUploadKit/DeviceStatus/UploaderStatus.swift @@ -0,0 +1,35 @@ +// +// UploaderStatus.swift +// RileyLink +// +// Created by Pete Schwamb on 7/26/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import Foundation + +public struct UploaderStatus { + + public let battery: Int? + public let name: String + public let timestamp: NSDate + + public init(name: String, timestamp: NSDate, battery: Int? = nil) { + self.name = name + self.timestamp = timestamp + self.battery = battery + } + + public var dictionaryRepresentation: [String: AnyObject] { + var rval = [String: AnyObject]() + + rval["name"] = name + rval["timestamp"] = TimeFormat.timestampStrFromDate(timestamp) + + if let battery = battery { + rval["battery"] = battery + } + + return rval + } +} \ No newline at end of file diff --git a/NightscoutUploadKit/Info.plist b/NightscoutUploadKit/Info.plist index 425ef70fb..5d7827c27 100644 --- a/NightscoutUploadKit/Info.plist +++ b/NightscoutUploadKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.7.1 + 0.8.0 CFBundleSignature ???? CFBundleVersion diff --git a/NightscoutUploadKit/NightscoutUploader.swift b/NightscoutUploadKit/NightscoutUploader.swift index 1d3daf196..0c5471f12 100644 --- a/NightscoutUploadKit/NightscoutUploader.swift +++ b/NightscoutUploadKit/NightscoutUploader.swift @@ -64,7 +64,14 @@ public class NightscoutUploader { } // MARK: - Processing data from pump - + + /** + Enqueues pump history events for upload, with automatic retry management. + + - parameter events: An array of timestamped history events. Only types with known Nightscout mappings will be uploaded. + - parameter source: The device identifier to display in Nightscout + - parameter pumpModel: The pump model info associated with the events + */ public func processPumpEvents(events: [TimestampedHistoryEvent], source: String, pumpModel: PumpModel) { // Find valid event times @@ -98,6 +105,27 @@ public class NightscoutUploader { } self.flushAll() } + + /** + Attempts to upload pump history events. + + This method will not retry if the network task failed. + + - parameter pumpEvents: An array of timestamped history events. Only types with known Nightscout mappings will be uploaded. + - parameter source: The device identifier to display in Nightscout + - parameter pumpModel: The pump model info associated with the events + - parameter completionHandler: A closure to execute when the task completes. It has a single argument for any error that might have occurred during the upload. + */ + public func upload(pumpEvents: [TimestampedHistoryEvent], forSource source: String, from pumpModel: PumpModel, completionHandler: (ErrorType?) -> Void) { + let treatments = NightscoutPumpEvents.translate(pumpEvents, eventSource: source).map { $0.dictionaryRepresentation } + + uploadToNS(treatments, endpoint: defaultNightscoutTreatmentPath, completion: completionHandler) + } + + public func uploadDeviceStatus(status: DeviceStatus) { + deviceStatuses.append(status.dictionaryRepresentation) + flushAll() + } // Entries [ { sgv: 375, // date: 1432421525000, @@ -107,10 +135,9 @@ public class NightscoutUploader { // device: 'share2', // type: 'sgv' } ] - public func handlePumpStatus(status: MySentryPumpStatusMessageBody, device: String) { + public func uploadSGVFromMySentryPumpStatus(status: MySentryPumpStatusMessageBody, device: String) { var recordSGV = true - let glucose: Int = { switch status.glucose { case .Active(glucose: let glucose): @@ -127,52 +154,7 @@ public class NightscoutUploader { } }() - // Create deviceStatus record from this mysentry packet - - var nsStatus = [String: AnyObject]() - - nsStatus["device"] = device - nsStatus["created_at"] = TimeFormat.timestampStrFromDate(NSDate()) - - // TODO: use battery monitoring to post updates if we're not hearing from pump? - - let uploaderDevice = UIDevice.currentDevice() - - if uploaderDevice.batteryMonitoringEnabled { - nsStatus["uploader"] = ["battery":uploaderDevice.batteryLevel * 100] - } - - guard let pumpDate = status.pumpDateComponents.date else { - self.errorHandler?(error: UploadError.MissingTimezone, context: "Unable to get status.pumpDateComponents.date") - return - } - - let pumpDateStr = TimeFormat.timestampStrFromDate(pumpDate) - - nsStatus["pump"] = [ - "clock": pumpDateStr, - "iob": [ - "timestamp": pumpDateStr, - "bolusiob": status.iob, - ], - "reservoir": status.reservoirRemainingUnits, - "battery": [ - "percent": status.batteryRemainingPercent - ] - ] - - switch status.glucose { - case .Active(glucose: _): - nsStatus["sensor"] = [ - "sensorAge": status.sensorAgeHours, - "sensorRemaining": status.sensorRemainingHours, - ] - default: - nsStatus["sensorNotActive"] = true - } - deviceStatuses.append(nsStatus) - - + // Create SGV entry from this mysentry packet if (recordSGV) { var entry: [String: AnyObject] = [ @@ -252,18 +234,15 @@ public class NightscoutUploader { let uploadURL = siteURL.URLByAppendingPathComponent(endpoint) let request = NSMutableURLRequest(URL: uploadURL) + request.HTTPMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.setValue("application/json", forHTTPHeaderField: "Accept") + request.setValue(APISecret.SHA1, forHTTPHeaderField: "api-secret") + do { - - let sendData = try NSJSONSerialization.dataWithJSONObject(json, options: NSJSONWritingOptions.PrettyPrinted) - request.HTTPMethod = "POST" - - request.setValue("application/json", forHTTPHeaderField:"Content-Type") - request.setValue("application/json", forHTTPHeaderField:"Accept") - request.setValue(APISecret.SHA1, forHTTPHeaderField:"api-secret") - request.HTTPBody = sendData - - let task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: { (data, response, error) in - + let sendData = try NSJSONSerialization.dataWithJSONObject(json, options: []) + + let task = NSURLSession.sharedSession().uploadTaskWithRequest(request, fromData: sendData) { (data, response, error) in if let error = error { completion(error) return @@ -275,7 +254,7 @@ public class NightscoutUploader { } else { completion(nil) } - }) + } task.resume() } catch let error as NSError { completion(error) diff --git a/NightscoutUploadKit/BGCheckNightscoutTreatment.swift b/NightscoutUploadKit/Treatments/BGCheckNightscoutTreatment.swift similarity index 100% rename from NightscoutUploadKit/BGCheckNightscoutTreatment.swift rename to NightscoutUploadKit/Treatments/BGCheckNightscoutTreatment.swift diff --git a/NightscoutUploadKit/BolusNightscoutTreatment.swift b/NightscoutUploadKit/Treatments/BolusNightscoutTreatment.swift similarity index 100% rename from NightscoutUploadKit/BolusNightscoutTreatment.swift rename to NightscoutUploadKit/Treatments/BolusNightscoutTreatment.swift diff --git a/NightscoutUploadKit/MealBolusNightscoutTreatment.swift b/NightscoutUploadKit/Treatments/MealBolusNightscoutTreatment.swift similarity index 100% rename from NightscoutUploadKit/MealBolusNightscoutTreatment.swift rename to NightscoutUploadKit/Treatments/MealBolusNightscoutTreatment.swift diff --git a/NightscoutUploadKit/NightscoutTreatment.swift b/NightscoutUploadKit/Treatments/NightscoutTreatment.swift similarity index 100% rename from NightscoutUploadKit/NightscoutTreatment.swift rename to NightscoutUploadKit/Treatments/NightscoutTreatment.swift diff --git a/NightscoutUploadKit/TempBasalNightscoutTreatment.swift b/NightscoutUploadKit/Treatments/TempBasalNightscoutTreatment.swift similarity index 100% rename from NightscoutUploadKit/TempBasalNightscoutTreatment.swift rename to NightscoutUploadKit/Treatments/TempBasalNightscoutTreatment.swift diff --git a/NightscoutUploadKitTests/Info.plist b/NightscoutUploadKitTests/Info.plist index 8d8ed6042..dc2ece127 100644 --- a/NightscoutUploadKitTests/Info.plist +++ b/NightscoutUploadKitTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 0.7.1 + 0.8.0 CFBundleSignature ???? CFBundleVersion diff --git a/RileyLink.xcodeproj/project.pbxproj b/RileyLink.xcodeproj/project.pbxproj index 7ce5e3968..75f5b6963 100644 --- a/RileyLink.xcodeproj/project.pbxproj +++ b/RileyLink.xcodeproj/project.pbxproj @@ -151,6 +151,9 @@ C1711A5C1C953F3000CB25BD /* GetBatteryCarelinkMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1711A5B1C953F3000CB25BD /* GetBatteryCarelinkMessageBody.swift */; }; C1711A5E1C977BD000CB25BD /* GetHistoryPageCarelinkMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1711A5D1C977BD000CB25BD /* GetHistoryPageCarelinkMessageBody.swift */; }; C174F26B19EB824D00398C72 /* ISO8601DateFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = C174F26A19EB824D00398C72 /* ISO8601DateFormatter.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + C178845D1D4EF3D800405663 /* ReadPumpStatusMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C178845C1D4EF3D800405663 /* ReadPumpStatusMessageBody.swift */; }; + C178845F1D5166BE00405663 /* COBStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C178845E1D5166BE00405663 /* COBStatus.swift */; }; + C17884611D519F1E00405663 /* BatteryIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17884601D519F1E00405663 /* BatteryIndicator.swift */; }; C1842BBB1C8E184300DB42AC /* PumpModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1842BBA1C8E184300DB42AC /* PumpModel.swift */; }; C1842BBD1C8E7C6E00DB42AC /* PumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1842BBC1C8E7C6E00DB42AC /* PumpEvent.swift */; }; C1842BBF1C8E855A00DB42AC /* PumpEventType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1842BBE1C8E855A00DB42AC /* PumpEventType.swift */; }; @@ -205,8 +208,21 @@ C1842C231C8FA45100DB42AC /* ChangeAlarmClockEnablePumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1842BF91C8FA45100DB42AC /* ChangeAlarmClockEnablePumpEvent.swift */; }; C1842C241C8FA45100DB42AC /* BatteryPumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1842BFA1C8FA45100DB42AC /* BatteryPumpEvent.swift */; }; C1842C251C8FA45100DB42AC /* AlarmSensorPumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1842BFB1C8FA45100DB42AC /* AlarmSensorPumpEvent.swift */; }; + C1A492631D4A5A19008964FF /* IOBStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A492621D4A5A19008964FF /* IOBStatus.swift */; }; + C1A492651D4A5DEB008964FF /* BatteryStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A492641D4A5DEB008964FF /* BatteryStatus.swift */; }; + C1A492671D4A65D9008964FF /* LoopSuggested.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A492661D4A65D9008964FF /* LoopSuggested.swift */; }; + C1A492691D4A66C0008964FF /* LoopEnacted.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A492681D4A66C0008964FF /* LoopEnacted.swift */; }; C1AA39941AB6804000BC9E33 /* UIAlertView+Blocks.m in Sources */ = {isa = PBXBuildFile; fileRef = C1AA39931AB6804000BC9E33 /* UIAlertView+Blocks.m */; }; C1AACC6B1CECD5F500C07049 /* RileyLinkListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1AACC6A1CECD5F500C07049 /* RileyLinkListTableViewController.swift */; }; + C1AF21E21D4838C90088C41D /* DeviceStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1AF21E11D4838C90088C41D /* DeviceStatus.swift */; }; + C1AF21E41D4865320088C41D /* LoopStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1AF21E31D4865320088C41D /* LoopStatus.swift */; }; + C1AF21E61D48667F0088C41D /* UploaderStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1AF21E51D48667F0088C41D /* UploaderStatus.swift */; }; + C1AF21E81D4866960088C41D /* PumpStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1AF21E71D4866960088C41D /* PumpStatus.swift */; }; + C1AF21F01D4901220088C41D /* BolusNightscoutTreatment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1AF21EB1D4901220088C41D /* BolusNightscoutTreatment.swift */; }; + C1AF21F11D4901220088C41D /* NightscoutTreatment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1AF21EC1D4901220088C41D /* NightscoutTreatment.swift */; }; + C1AF21F21D4901220088C41D /* BGCheckNightscoutTreatment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1AF21ED1D4901220088C41D /* BGCheckNightscoutTreatment.swift */; }; + C1AF21F31D4901220088C41D /* MealBolusNightscoutTreatment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1AF21EE1D4901220088C41D /* MealBolusNightscoutTreatment.swift */; }; + C1AF21F41D4901220088C41D /* TempBasalNightscoutTreatment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1AF21EF1D4901220088C41D /* TempBasalNightscoutTreatment.swift */; }; C1B3830E1CD0665D00CE7782 /* NightscoutUploadKit.h in Headers */ = {isa = PBXBuildFile; fileRef = C1B3830D1CD0665D00CE7782 /* NightscoutUploadKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; C1B383151CD0665D00CE7782 /* NightscoutUploadKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1B3830B1CD0665D00CE7782 /* NightscoutUploadKit.framework */; }; C1B3831C1CD0665D00CE7782 /* NightscoutUploadKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1B3831B1CD0665D00CE7782 /* NightscoutUploadKitTests.swift */; }; @@ -214,11 +230,6 @@ C1B383211CD0665D00CE7782 /* NightscoutUploadKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C1B3830B1CD0665D00CE7782 /* NightscoutUploadKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C1B383281CD0668600CE7782 /* NightscoutUploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1842C281C908A3C00DB42AC /* NightscoutUploader.swift */; }; C1B383291CD0668600CE7782 /* NightscoutPumpEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1842C2A1C90DFB600DB42AC /* NightscoutPumpEvents.swift */; }; - C1B3832A1CD0668600CE7782 /* NightscoutTreatment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1842C2E1C90F6D900DB42AC /* NightscoutTreatment.swift */; }; - C1B3832B1CD0668600CE7782 /* BGCheckNightscoutTreatment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1842C301C91D56400DB42AC /* BGCheckNightscoutTreatment.swift */; }; - C1B3832C1CD0668600CE7782 /* MealBolusNightscoutTreatment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1842C321C91DAFA00DB42AC /* MealBolusNightscoutTreatment.swift */; }; - C1B3832D1CD0668600CE7782 /* BolusNightscoutTreatment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C19F94AA1C91DF6E00018F7D /* BolusNightscoutTreatment.swift */; }; - C1B3832E1CD0668600CE7782 /* TempBasalNightscoutTreatment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12FB2751CC5893C00879B80 /* TempBasalNightscoutTreatment.swift */; }; C1B383301CD0680800CE7782 /* MinimedKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C10D9BC11C8269D500378342 /* MinimedKit.framework */; }; C1B383311CD068C300CE7782 /* RileyLinkBLEKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 430D64CB1CB855AB00FCA750 /* RileyLinkBLEKit.framework */; }; C1B383361CD1BA8100CE7782 /* DeviceDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1B383351CD1BA8100CE7782 /* DeviceDataManager.swift */; }; @@ -473,7 +484,6 @@ C12EA25D198B436900309FA4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; C12EA25F198B436900309FA4 /* RileyLinkTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RileyLinkTests.m; sourceTree = ""; }; C12EA269198B442100309FA4 /* Storyboard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Storyboard.storyboard; sourceTree = ""; }; - C12FB2751CC5893C00879B80 /* TempBasalNightscoutTreatment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TempBasalNightscoutTreatment.swift; sourceTree = ""; }; C139AC221BFD84B500B0518F /* RuntimeUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RuntimeUtils.h; sourceTree = ""; }; C139AC231BFD84B500B0518F /* RuntimeUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RuntimeUtils.m; sourceTree = ""; }; C14303151C97C98000A40450 /* PumpAckMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PumpAckMessageBody.swift; sourceTree = ""; }; @@ -513,6 +523,9 @@ C1711A5D1C977BD000CB25BD /* GetHistoryPageCarelinkMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetHistoryPageCarelinkMessageBody.swift; sourceTree = ""; }; C174F26919EB824D00398C72 /* ISO8601DateFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ISO8601DateFormatter.h; sourceTree = ""; }; C174F26A19EB824D00398C72 /* ISO8601DateFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ISO8601DateFormatter.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + C178845C1D4EF3D800405663 /* ReadPumpStatusMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadPumpStatusMessageBody.swift; sourceTree = ""; }; + C178845E1D5166BE00405663 /* COBStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = COBStatus.swift; sourceTree = ""; }; + C17884601D519F1E00405663 /* BatteryIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BatteryIndicator.swift; sourceTree = ""; }; C1842BBA1C8E184300DB42AC /* PumpModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PumpModel.swift; sourceTree = ""; }; C1842BBC1C8E7C6E00DB42AC /* PumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PumpEvent.swift; sourceTree = ""; }; C1842BBE1C8E855A00DB42AC /* PumpEventType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = PumpEventType.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; @@ -569,13 +582,22 @@ C1842BFB1C8FA45100DB42AC /* AlarmSensorPumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AlarmSensorPumpEvent.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; C1842C281C908A3C00DB42AC /* NightscoutUploader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NightscoutUploader.swift; sourceTree = ""; }; C1842C2A1C90DFB600DB42AC /* NightscoutPumpEvents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NightscoutPumpEvents.swift; sourceTree = ""; }; - C1842C2E1C90F6D900DB42AC /* NightscoutTreatment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NightscoutTreatment.swift; sourceTree = ""; }; - C1842C301C91D56400DB42AC /* BGCheckNightscoutTreatment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BGCheckNightscoutTreatment.swift; sourceTree = ""; }; - C1842C321C91DAFA00DB42AC /* MealBolusNightscoutTreatment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = MealBolusNightscoutTreatment.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - C19F94AA1C91DF6E00018F7D /* BolusNightscoutTreatment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BolusNightscoutTreatment.swift; sourceTree = ""; }; + C1A492621D4A5A19008964FF /* IOBStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IOBStatus.swift; sourceTree = ""; }; + C1A492641D4A5DEB008964FF /* BatteryStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BatteryStatus.swift; sourceTree = ""; }; + C1A492661D4A65D9008964FF /* LoopSuggested.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoopSuggested.swift; sourceTree = ""; }; + C1A492681D4A66C0008964FF /* LoopEnacted.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoopEnacted.swift; sourceTree = ""; }; C1AA39921AB6804000BC9E33 /* UIAlertView+Blocks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIAlertView+Blocks.h"; sourceTree = ""; }; C1AA39931AB6804000BC9E33 /* UIAlertView+Blocks.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIAlertView+Blocks.m"; sourceTree = ""; }; C1AACC6A1CECD5F500C07049 /* RileyLinkListTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RileyLinkListTableViewController.swift; sourceTree = ""; }; + C1AF21E11D4838C90088C41D /* DeviceStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceStatus.swift; sourceTree = ""; }; + C1AF21E31D4865320088C41D /* LoopStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoopStatus.swift; sourceTree = ""; }; + C1AF21E51D48667F0088C41D /* UploaderStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UploaderStatus.swift; sourceTree = ""; }; + C1AF21E71D4866960088C41D /* PumpStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PumpStatus.swift; sourceTree = ""; }; + C1AF21EB1D4901220088C41D /* BolusNightscoutTreatment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BolusNightscoutTreatment.swift; sourceTree = ""; }; + C1AF21EC1D4901220088C41D /* NightscoutTreatment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NightscoutTreatment.swift; sourceTree = ""; }; + C1AF21ED1D4901220088C41D /* BGCheckNightscoutTreatment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BGCheckNightscoutTreatment.swift; sourceTree = ""; }; + C1AF21EE1D4901220088C41D /* MealBolusNightscoutTreatment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MealBolusNightscoutTreatment.swift; sourceTree = ""; }; + C1AF21EF1D4901220088C41D /* TempBasalNightscoutTreatment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TempBasalNightscoutTreatment.swift; sourceTree = ""; }; C1B3830B1CD0665D00CE7782 /* NightscoutUploadKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = NightscoutUploadKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C1B3830D1CD0665D00CE7782 /* NightscoutUploadKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NightscoutUploadKit.h; sourceTree = ""; }; C1B3830F1CD0665D00CE7782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -1090,22 +1112,48 @@ C14FFC5A1D3D74F90049CF85 /* NibLoadable.swift */, C14FFC601D3D75470049CF85 /* UIColor.swift */, C14FFC6C1D3D85A40049CF85 /* NSTimeInterval.swift */, + C17884601D519F1E00405663 /* BatteryIndicator.swift */, ); name = Extensions; sourceTree = ""; }; + C1AF21E91D4900300088C41D /* DeviceStatus */ = { + isa = PBXGroup; + children = ( + C1AF21E11D4838C90088C41D /* DeviceStatus.swift */, + C1AF21E51D48667F0088C41D /* UploaderStatus.swift */, + C1AF21E71D4866960088C41D /* PumpStatus.swift */, + C1A492621D4A5A19008964FF /* IOBStatus.swift */, + C178845E1D5166BE00405663 /* COBStatus.swift */, + C1A492641D4A5DEB008964FF /* BatteryStatus.swift */, + C1AF21E31D4865320088C41D /* LoopStatus.swift */, + C1A492661D4A65D9008964FF /* LoopSuggested.swift */, + C1A492681D4A66C0008964FF /* LoopEnacted.swift */, + ); + path = DeviceStatus; + sourceTree = ""; + }; + C1AF21EA1D4900880088C41D /* Treatments */ = { + isa = PBXGroup; + children = ( + C1AF21EB1D4901220088C41D /* BolusNightscoutTreatment.swift */, + C1AF21EC1D4901220088C41D /* NightscoutTreatment.swift */, + C1AF21ED1D4901220088C41D /* BGCheckNightscoutTreatment.swift */, + C1AF21EE1D4901220088C41D /* MealBolusNightscoutTreatment.swift */, + C1AF21EF1D4901220088C41D /* TempBasalNightscoutTreatment.swift */, + ); + path = Treatments; + sourceTree = ""; + }; C1B3830C1CD0665D00CE7782 /* NightscoutUploadKit */ = { isa = PBXGroup; children = ( + C1AF21EA1D4900880088C41D /* Treatments */, + C1AF21E91D4900300088C41D /* DeviceStatus */, C1B3830D1CD0665D00CE7782 /* NightscoutUploadKit.h */, C1B3830F1CD0665D00CE7782 /* Info.plist */, - C1842C301C91D56400DB42AC /* BGCheckNightscoutTreatment.swift */, - C19F94AA1C91DF6E00018F7D /* BolusNightscoutTreatment.swift */, - C1842C321C91DAFA00DB42AC /* MealBolusNightscoutTreatment.swift */, - C1842C2A1C90DFB600DB42AC /* NightscoutPumpEvents.swift */, - C1842C2E1C90F6D900DB42AC /* NightscoutTreatment.swift */, C1842C281C908A3C00DB42AC /* NightscoutUploader.swift */, - C12FB2751CC5893C00879B80 /* TempBasalNightscoutTreatment.swift */, + C1842C2A1C90DFB600DB42AC /* NightscoutPumpEvents.swift */, 43B0ADC81D1268B300AAD278 /* TimeFormat.swift */, C1B4A94D1D1C423D003B8985 /* NSUserDefaults.swift */, ); @@ -1182,6 +1230,7 @@ 43CA932C1CB8CFA1000026B5 /* ReadTempBasalCarelinkMessageBody.swift */, 43CA932D1CB8CFA1000026B5 /* ReadTimeCarelinkMessageBody.swift */, C1EAD6C41C826B92006DBA60 /* UnknownMessageBody.swift */, + C178845C1D4EF3D800405663 /* ReadPumpStatusMessageBody.swift */, ); path = Messages; sourceTree = ""; @@ -1365,6 +1414,7 @@ C12EA234198B436800309FA4 /* Frameworks */, C12EA235198B436800309FA4 /* Resources */, C10D9BB81C82614F00378342 /* Embed Frameworks */, + C1AF21F51D4BC0C30088C41D /* ShellScript */, ); buildRules = ( ); @@ -1589,6 +1639,23 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + C1AF21F51D4BC0C30088C41D /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "$(SRCROOT)/Carthage/Build/iOS/Crypto.framework", + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/usr/local/bin/carthage copy-frameworks"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 430D64C61CB855AB00FCA750 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -1672,6 +1739,7 @@ C1842C041C8FA45100DB42AC /* DeleteOtherDeviceIDPumpEvent.swift in Sources */, C1842C0B1C8FA45100DB42AC /* ChangeTimePumpEvent.swift in Sources */, C1842C0D1C8FA45100DB42AC /* TempBasalPumpEvent.swift in Sources */, + C178845D1D4EF3D800405663 /* ReadPumpStatusMessageBody.swift in Sources */, 43B0ADCB1D126B1100AAD278 /* SelectBasalProfilePumpEvent.swift in Sources */, C1842C0C1C8FA45100DB42AC /* ChangeTimeFormatPumpEvent.swift in Sources */, C10AB08D1C855613000F102E /* FindDeviceMessageBody.swift in Sources */, @@ -1809,6 +1877,7 @@ C14FFC551D3D72A50049CF85 /* UIViewController.swift in Sources */, 434FF1DE1CF268F3000DB779 /* RileyLinkDeviceTableViewCell.swift in Sources */, C14FFC651D3D7E250049CF85 /* RemoteDataManager.swift in Sources */, + C17884611D519F1E00405663 /* BatteryIndicator.swift in Sources */, C1AA39941AB6804000BC9E33 /* UIAlertView+Blocks.m in Sources */, C1EF58881B3F93FE001C8C80 /* Config.m in Sources */, C126164B1B685F93001FAD87 /* RileyLink.xcdatamodeld in Sources */, @@ -1838,17 +1907,26 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - C1B3832E1CD0668600CE7782 /* TempBasalNightscoutTreatment.swift in Sources */, - C1B3832B1CD0668600CE7782 /* BGCheckNightscoutTreatment.swift in Sources */, C1B383291CD0668600CE7782 /* NightscoutPumpEvents.swift in Sources */, 43B0ADCC1D126E3000AAD278 /* NSDateFormatter.swift in Sources */, + C1AF21E81D4866960088C41D /* PumpStatus.swift in Sources */, C1B4A94E1D1C423D003B8985 /* NSUserDefaults.swift in Sources */, - C1B3832D1CD0668600CE7782 /* BolusNightscoutTreatment.swift in Sources */, + C1AF21F01D4901220088C41D /* BolusNightscoutTreatment.swift in Sources */, + C1AF21F21D4901220088C41D /* BGCheckNightscoutTreatment.swift in Sources */, + C1AF21E61D48667F0088C41D /* UploaderStatus.swift in Sources */, + C1AF21F31D4901220088C41D /* MealBolusNightscoutTreatment.swift in Sources */, + C1A492691D4A66C0008964FF /* LoopEnacted.swift in Sources */, + C1A492651D4A5DEB008964FF /* BatteryStatus.swift in Sources */, + C1A492631D4A5A19008964FF /* IOBStatus.swift in Sources */, 43D657461D0CF38F00216E20 /* NSTimeInterval.swift in Sources */, - C1B3832C1CD0668600CE7782 /* MealBolusNightscoutTreatment.swift in Sources */, + C1AF21E41D4865320088C41D /* LoopStatus.swift in Sources */, + C1AF21F11D4901220088C41D /* NightscoutTreatment.swift in Sources */, + C1A492671D4A65D9008964FF /* LoopSuggested.swift in Sources */, + C178845F1D5166BE00405663 /* COBStatus.swift in Sources */, 43B0ADC91D1268B300AAD278 /* TimeFormat.swift in Sources */, + C1AF21E21D4838C90088C41D /* DeviceStatus.swift in Sources */, C1B383281CD0668600CE7782 /* NightscoutUploader.swift in Sources */, - C1B3832A1CD0668600CE7782 /* NightscoutTreatment.swift in Sources */, + C1AF21F41D4901220088C41D /* TempBasalNightscoutTreatment.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1943,11 +2021,11 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 10; + CURRENT_PROJECT_VERSION = 11; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 10; + DYLIB_CURRENT_VERSION = 11; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; @@ -1971,11 +2049,11 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 10; + CURRENT_PROJECT_VERSION = 11; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 10; + DYLIB_CURRENT_VERSION = 11; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; @@ -2034,11 +2112,11 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 10; + CURRENT_PROJECT_VERSION = 11; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 10; + DYLIB_CURRENT_VERSION = 11; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; @@ -2064,11 +2142,11 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 10; + CURRENT_PROJECT_VERSION = 11; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 10; + DYLIB_CURRENT_VERSION = 11; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; @@ -2127,11 +2205,11 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 10; + CURRENT_PROJECT_VERSION = 11; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 10; + DYLIB_CURRENT_VERSION = 11; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; @@ -2156,11 +2234,11 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 10; + CURRENT_PROJECT_VERSION = 11; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 10; + DYLIB_CURRENT_VERSION = 11; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; @@ -2229,7 +2307,7 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 10; + CURRENT_PROJECT_VERSION = 11; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; @@ -2271,7 +2349,7 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 10; + CURRENT_PROJECT_VERSION = 11; ENABLE_NS_ASSERTIONS = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -2376,11 +2454,11 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 10; + CURRENT_PROJECT_VERSION = 11; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 10; + DYLIB_CURRENT_VERSION = 11; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -2409,11 +2487,11 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 10; + CURRENT_PROJECT_VERSION = 11; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 10; + DYLIB_CURRENT_VERSION = 11; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = ( diff --git a/RileyLink/BatteryIndicator.swift b/RileyLink/BatteryIndicator.swift new file mode 100644 index 000000000..9c14117e6 --- /dev/null +++ b/RileyLink/BatteryIndicator.swift @@ -0,0 +1,23 @@ +// +// BatteryIndicator.swift +// RileyLink +// +// Created by Pete Schwamb on 8/2/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import NightscoutUploadKit +import MinimedKit + +extension BatteryIndicator { + init?(batteryStatus: MinimedKit.BatteryStatus) { + switch batteryStatus { + case .Low: + self = .Low + case .Normal: + self = .Normal + default: + return nil + } + } +} \ No newline at end of file diff --git a/RileyLink/Config.h b/RileyLink/Config.h index a56d92aeb..dd3b89aa9 100644 --- a/RileyLink/Config.h +++ b/RileyLink/Config.h @@ -17,6 +17,7 @@ @property (nonatomic, nullable, strong) NSURL *nightscoutURL; @property (nonatomic, nullable, strong) NSString *nightscoutAPISecret; @property (nonatomic, nullable, strong) NSString *pumpID; +@property (nonatomic, nullable, strong) NSString *pumpModelNumber; @property (nonatomic, nullable, strong) NSTimeZone *pumpTimeZone; @property (nonatomic, nullable, strong) NSSet *autoConnectIds; @property (nonatomic, assign) BOOL uploadEnabled; diff --git a/RileyLink/Config.m b/RileyLink/Config.m index 4f7922d5c..6f7dd85f0 100644 --- a/RileyLink/Config.m +++ b/RileyLink/Config.m @@ -64,6 +64,15 @@ - (NSString*) pumpID { return [_defaults stringForKey:@"pumpID"]; } +- (void) setPumpModelNumber:(NSString *)pumpModelNumber { + [_defaults setValue:pumpModelNumber forKey:@"pumpModelNumber"]; +} + +- (NSString*) pumpModelNumber { + return [_defaults stringForKey:@"pumpModelNumber"]; +} + + - (void) setPumpTimeZone:(NSTimeZone *)pumpTimeZone { if (pumpTimeZone) { diff --git a/RileyLink/DeviceDataManager.swift b/RileyLink/DeviceDataManager.swift index c501daf00..54e927037 100644 --- a/RileyLink/DeviceDataManager.swift +++ b/RileyLink/DeviceDataManager.swift @@ -29,45 +29,87 @@ class DeviceDataManager { } } - var latestPumpStatus: MySentryPumpStatusMessageBody? + var latestPumpStatusDate: NSDate? - var pumpTimeZone: NSTimeZone? = Config.sharedInstance().pumpTimeZone { + var latestPumpStatusFromMySentry: MySentryPumpStatusMessageBody? { didSet { - Config.sharedInstance().pumpTimeZone = pumpTimeZone - - if let pumpTimeZone = pumpTimeZone { - - if let pumpState = rileyLinkManager.pumpState { - pumpState.timeZone = pumpTimeZone - } + if let update = latestPumpStatusFromMySentry, let timeZone = pumpState?.timeZone { + let pumpClock = update.pumpDateComponents + pumpClock.timeZone = timeZone + latestPumpStatusDate = pumpClock.date } } } - - var pumpID: String? = Config.sharedInstance().pumpID { + + + var latestPolledPumpStatus: RileyLinkKit.PumpStatus? { didSet { - if pumpID?.characters.count != 6 { - pumpID = nil + if let update = latestPolledPumpStatus, let timeZone = pumpState?.timeZone { + let pumpClock = update.clock + pumpClock.timeZone = timeZone + latestPumpStatusDate = pumpClock.date + } + } + } + + var pumpID: String? { + get { + return pumpState?.pumpID + } + set { + guard newValue?.characters.count == 6 && newValue != pumpState?.pumpID else { + return } - if let pumpID = pumpID { + if let pumpID = newValue { let pumpState = PumpState(pumpID: pumpID) - if let timeZone = pumpTimeZone { + if let timeZone = self.pumpState?.timeZone { pumpState.timeZone = timeZone } - rileyLinkManager.pumpState = pumpState + self.pumpState = pumpState } else { - rileyLinkManager.pumpState = nil + self.pumpState = nil } - + remoteDataManager.nightscoutUploader?.reset() Config.sharedInstance().pumpID = pumpID } } + var pumpState: PumpState? { + didSet { + rileyLinkManager.pumpState = pumpState + + if let oldValue = oldValue { + NSNotificationCenter.defaultCenter().removeObserver(self, name: PumpState.ValuesDidChangeNotification, object: oldValue) + } + + if let pumpState = pumpState { + NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(pumpStateValuesDidChange(_:)), name: PumpState.ValuesDidChangeNotification, object: pumpState) + } + } + } + + @objc private func pumpStateValuesDidChange(note: NSNotification) { + switch note.userInfo?[PumpState.PropertyKey] as? String { + case "timeZone"?: + Config.sharedInstance().pumpTimeZone = pumpState?.timeZone + case "pumpModel"?: + if let sentrySupported = pumpState?.pumpModel?.larger { + rileyLinkManager.idleListeningEnabled = sentrySupported + rileyLinkManager.timerTickEnabled = !sentrySupported + } + Config.sharedInstance().pumpModelNumber = pumpState?.pumpModel?.rawValue + case "lastHistoryDump"?, "awakeUntil"?: + break + default: + break + } + } + var lastHistoryAttempt: NSDate? = nil var lastRileyLinkHeardFrom: RileyLinkDevice? = nil @@ -89,7 +131,7 @@ class DeviceDataManager { } } - func receivedRileyLinkManagerNotification(note: NSNotification) { + @objc private func receivedRileyLinkManagerNotification(note: NSNotification) { NSNotificationCenter.defaultCenter().postNotificationName(note.name, object: self, userInfo: note.userInfo) } @@ -100,29 +142,24 @@ class DeviceDataManager { return self.rileyLinkManager.firstConnectedDevice } - func receivedRileyLinkPacketNotification(note: NSNotification) { + /** + Called when a new idle message is received by the RileyLink. + + Only MySentryPumpStatus messages are handled. + + - parameter note: The notification object + */ + @objc private func receivedRileyLinkPacketNotification(note: NSNotification) { if let device = note.object as? RileyLinkDevice, data = note.userInfo?[RileyLinkDevice.IdleMessageDataKey] as? NSData, message = PumpMessage(rxData: data) { - lastRileyLinkHeardFrom = device switch message.packetType { case .MySentry: switch message.messageBody { case let body as MySentryPumpStatusMessageBody: - updatePumpStatus(body, fromDevice: device) - case is MySentryAlertMessageBody: - break - // TODO: de-dupe - // logger?.addMessage(body.dictionaryRepresentation, toCollection: "sentryAlert") - case is MySentryAlertClearedMessageBody: - break - // TODO: de-dupe - // logger?.addMessage(body.dictionaryRepresentation, toCollection: "sentryAlert") - case is UnknownMessageBody: - break - //logger?.addMessage(body.dictionaryRepresentation, toCollection: "sentryOther") + pumpStatusUpdateReceived(body, fromDevice: device) default: break } @@ -132,6 +169,11 @@ class DeviceDataManager { } } + @objc private func receivedRileyLinkTimerTickNotification(note: NSNotification) { + self.assertCurrentPumpData() + } + + func connectToRileyLink(device: RileyLinkDevice) { connectedPeripheralIDs.insert(device.peripheral.identifier.UUIDString) @@ -144,24 +186,141 @@ class DeviceDataManager { rileyLinkManager.disconnectDevice(device) } - private func updatePumpStatus(status: MySentryPumpStatusMessageBody, fromDevice device: RileyLinkDevice) { - status.pumpDateComponents.timeZone = pumpTimeZone - status.glucoseDateComponents?.timeZone = pumpTimeZone - - if status != latestPumpStatus { - latestPumpStatus = status + private func pumpStatusUpdateReceived(status: MySentryPumpStatusMessageBody, fromDevice device: RileyLinkDevice) { + status.pumpDateComponents.timeZone = pumpState?.timeZone + status.glucoseDateComponents?.timeZone = pumpState?.timeZone + + // Avoid duplicates + if status != latestPumpStatusFromMySentry { + latestPumpStatusFromMySentry = status + + // Sentry packets are sent in groups of 3, 5s apart. Wait 11s to avoid conflicting comms. + let delay = dispatch_time(DISPATCH_TIME_NOW, Int64(11 * NSEC_PER_SEC)) + dispatch_after(delay, dispatch_get_main_queue()) { + self.getPumpHistory(device) + } if status.batteryRemainingPercent == 0 { //NotificationManager.sendPumpBatteryLowNotification() } - if Config.sharedInstance().uploadEnabled { - remoteDataManager.nightscoutUploader?.handlePumpStatus(status, device: device.deviceURI) + + guard Config.sharedInstance().uploadEnabled, let pumpID = pumpID else { + return } - // Sentry packets are sent in groups of 3, 5s apart. Wait 11s to avoid conflicting comms. - let delay = dispatch_time(DISPATCH_TIME_NOW, Int64(11 * NSEC_PER_SEC)) - dispatch_after(delay, dispatch_get_main_queue()) { - self.getPumpHistory(device) + // Gather PumpStatus from MySentry packet + let pumpStatus: NightscoutUploadKit.PumpStatus? + if let pumpDate = status.pumpDateComponents.date { + + let batteryStatus = BatteryStatus(percent: status.batteryRemainingPercent) + let iobStatus = IOBStatus(timestamp: pumpDate, iob: status.iob) + + pumpStatus = NightscoutUploadKit.PumpStatus(clock: pumpDate, pumpID: pumpID, iob: iobStatus, battery: batteryStatus, reservoir: status.reservoirRemainingUnits) + } else { + pumpStatus = nil + print("Could not interpret pump clock: \(status.pumpDateComponents)") + } + + // Trigger device status upload, even if something is wrong with pumpStatus + self.uploadDeviceStatus(pumpStatus) + + // Send SGVs + remoteDataManager.nightscoutUploader?.uploadSGVFromMySentryPumpStatus(status, device: device.deviceURI) + } + } + + private func uploadDeviceStatus(pumpStatus: NightscoutUploadKit.PumpStatus? /*, loopStatus: LoopStatus */) { + + guard let uploader = remoteDataManager.nightscoutUploader else { + return + } + + // Gather UploaderStatus + let uploaderDevice = UIDevice.currentDevice() + + let battery: Int? + if uploaderDevice.batteryMonitoringEnabled { + battery = Int(uploaderDevice.batteryLevel * 100) + } else { + battery = nil + } + let uploaderStatus = UploaderStatus(name: uploaderDevice.name, timestamp: NSDate(), battery: battery) + + + // Mock out some loop data for testing + // let loopTime = NSDate() + // let iob = IOBStatus(iob: 3.0, basaliob: 1.2, timestamp: NSDate()) + // let loopSuggested = LoopSuggested(timestamp: loopTime, rate: 1.2, duration: NSTimeInterval(30*60), correction: 0, eventualBG: 200, reason: "Test Reason", bg: 205, tick: 5) + // let loopEnacted = LoopEnacted(rate: 1.2, duration: NSTimeInterval(30*60), timestamp: loopTime, received: true) + // let loopStatus = LoopStatus(name: "TestLoopName", timestamp: NSDate(), iob: iob, suggested: loopSuggested, enacted: loopEnacted, failureReason: nil) + + // Build DeviceStatus + let deviceStatus = DeviceStatus(device: uploaderDevice.name, timestamp: NSDate(), pumpStatus: pumpStatus, uploaderStatus: uploaderStatus) + + uploader.uploadDeviceStatus(deviceStatus) + } + + /** + Ensures pump data is current by either waking and polling, or ensuring we're listening to sentry packets. + */ + private func assertCurrentPumpData() { + guard let device = rileyLinkManager.firstConnectedDevice else { + return + } + + device.assertIdleListening() + + // How long should we wait before we poll for new pump data? + let pumpStatusAgeTolerance = rileyLinkManager.idleListeningEnabled ? NSTimeInterval(minutes: 11) : NSTimeInterval(minutes: 4) + + // If we don't yet have pump status, or it's old, poll for it. + if latestPumpStatusDate == nil || latestPumpStatusDate!.timeIntervalSinceNow <= -pumpStatusAgeTolerance { + guard let device = rileyLinkManager.firstConnectedDevice else { + return + } + + guard let ops = device.ops else { + self.troubleshootPumpCommsWithDevice(device) + return + } + + ops.readPumpStatus({ (result) in + switch result { + case .Success(let status): + self.latestPolledPumpStatus = status + let battery = BatteryStatus(voltage: status.batteryVolts, status: BatteryIndicator(batteryStatus: status.batteryStatus)) + status.clock.timeZone = ops.pumpState.timeZone + guard let date = status.clock.date else { + print("Could not interpret clock") + return + } + let nsPumpStatus = NightscoutUploadKit.PumpStatus(clock: date, pumpID: ops.pumpState.pumpID, iob: nil, battery: battery, suspended: status.suspended, bolusing: status.bolusing, reservoir: status.reservoir) + self.uploadDeviceStatus(nsPumpStatus) + case .Failure: + self.troubleshootPumpCommsWithDevice(device) + } + }) + } + } + + /** + Attempts to fix an extended communication failure between a RileyLink device and the pump + + - parameter device: The RileyLink device + */ + private func troubleshootPumpCommsWithDevice(device: RileyLinkDevice) { + + // How long we should wait before we re-tune the RileyLink + let tuneTolerance = NSTimeInterval(minutes: 14) + + if device.lastTuned?.timeIntervalSinceNow <= -tuneTolerance { + device.tunePumpWithResultHandler { (result) in + switch result { + case .Success(let scanResult): + print("Device auto-tuned to \(scanResult.bestFrequency) MHz") + case .Failure(let error): + print("Device auto-tune failed with error: \(error)") + } } } } @@ -194,7 +353,6 @@ class DeviceDataManager { private func handleNewHistoryEvents(events: [TimestampedHistoryEvent], pumpModel: PumpModel, device: RileyLinkDevice) { // TODO: get insulin doses from history - // TODO: upload events to Nightscout if Config.sharedInstance().uploadEnabled { remoteDataManager.nightscoutUploader?.processPumpEvents(events, source: device.deviceURI, pumpModel: pumpModel) } @@ -206,46 +364,45 @@ class DeviceDataManager { init() { - let pumpState: PumpState? + let pumpID = Config.sharedInstance().pumpID + + var idleListeningEnabled = true if let pumpID = pumpID { - pumpState = PumpState(pumpID: pumpID) + let pumpState = PumpState(pumpID: pumpID) - if let timeZone = pumpTimeZone { - pumpState?.timeZone = timeZone + if let timeZone = Config.sharedInstance().pumpTimeZone { + pumpState.timeZone = timeZone } - } else { - pumpState = nil + + if let pumpModelNumber = Config.sharedInstance().pumpModelNumber { + if let model = PumpModel(rawValue: pumpModelNumber) { + pumpState.pumpModel = model + + idleListeningEnabled = model.larger + } + } + + self.pumpState = pumpState } rileyLinkManager = RileyLinkDeviceManager( - pumpState: pumpState, + pumpState: self.pumpState, autoConnectIDs: connectedPeripheralIDs ) + rileyLinkManager.idleListeningEnabled = idleListeningEnabled + rileyLinkManager.timerTickEnabled = !idleListeningEnabled - getHistoryTimer = NSTimer.scheduledTimerWithTimeInterval(5.0 * 60, target:self, selector:#selector(DeviceDataManager.timerTriggered), userInfo:nil, repeats:true) - - // This triggers one history fetch right away (in 10s) -// let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(10 * Double(NSEC_PER_SEC))) -// dispatch_after(delayTime, dispatch_get_main_queue()) { -// if let rl = DeviceDataManager.sharedManager.preferredRileyLink() { -// DeviceDataManager.sharedManager.getPumpHistory(rl) -// } -// } - UIDevice.currentDevice().batteryMonitoringEnabled = true - rileyLinkManager.timerTickEnabled = false - rileyLinkManagerObserver = NSNotificationCenter.defaultCenter().addObserverForName(nil, object: rileyLinkManager, queue: nil) { [weak self] (note) -> Void in - self?.receivedRileyLinkManagerNotification(note) - } + NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(receivedRileyLinkManagerNotification(_:)), name: nil, object: rileyLinkManager) + NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(receivedRileyLinkPacketNotification(_:)), name: RileyLinkDevice.DidReceiveIdleMessageNotification, object: nil) + NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(receivedRileyLinkTimerTickNotification(_:)), name: RileyLinkDevice.DidUpdateTimerTickNotification, object: nil) - // TODO: Use delegation instead. - rileyLinkDevicePacketObserver = NSNotificationCenter.defaultCenter().addObserverForName(RileyLinkDevice.DidReceiveIdleMessageNotification, object: nil, queue: nil) { [weak self] (note) -> Void in - self?.receivedRileyLinkPacketNotification(note) + if let pumpState = pumpState { + NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(pumpStateValuesDidChange(_:)), name: PumpState.ValuesDidChangeNotification, object: pumpState) } - } deinit { diff --git a/RileyLink/RemoteDataManager.swift b/RileyLink/RemoteDataManager.swift index cec2c6c61..de7a48d66 100644 --- a/RileyLink/RemoteDataManager.swift +++ b/RileyLink/RemoteDataManager.swift @@ -18,7 +18,7 @@ class RemoteDataManager { var nightscoutService: NightscoutService { didSet { - try! keychain.setNightscoutURL(nightscoutService.siteURL, secret: nightscoutService.APISecret) + keychain.setNightscoutURL(nightscoutService.siteURL, secret: nightscoutService.APISecret) } } @@ -32,7 +32,7 @@ class RemoteDataManager { } else if let siteURL = Config.sharedInstance().nightscoutURL, APISecret = Config.sharedInstance().nightscoutAPISecret { - try! keychain.setNightscoutURL(siteURL, secret: APISecret) + keychain.setNightscoutURL(siteURL, secret: APISecret) nightscoutService = NightscoutService(siteURL: siteURL, APISecret: APISecret) } else { nightscoutService = NightscoutService(siteURL: nil, APISecret: nil) diff --git a/RileyLink/RileyLink-Info.plist b/RileyLink/RileyLink-Info.plist index 1deb996b6..7a682dd5f 100644 --- a/RileyLink/RileyLink-Info.plist +++ b/RileyLink/RileyLink-Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.7.1 + 0.8.0 CFBundleSignature ???? CFBundleVersion diff --git a/RileyLinkBLEKit/Info.plist b/RileyLinkBLEKit/Info.plist index 425ef70fb..5d7827c27 100644 --- a/RileyLinkBLEKit/Info.plist +++ b/RileyLinkBLEKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.7.1 + 0.8.0 CFBundleSignature ???? CFBundleVersion diff --git a/RileyLinkBLEKitTests/Info.plist b/RileyLinkBLEKitTests/Info.plist index 621016d74..ab895996c 100644 --- a/RileyLinkBLEKitTests/Info.plist +++ b/RileyLinkBLEKitTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 0.7.1 + 0.8.0 CFBundleSignature ???? CFBundleVersion diff --git a/RileyLinkKit/Info.plist b/RileyLinkKit/Info.plist index 425ef70fb..5d7827c27 100644 --- a/RileyLinkKit/Info.plist +++ b/RileyLinkKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.7.1 + 0.8.0 CFBundleSignature ???? CFBundleVersion diff --git a/RileyLinkKit/PumpOps.swift b/RileyLinkKit/PumpOps.swift index 4902eabf2..1cee3002e 100644 --- a/RileyLinkKit/PumpOps.swift +++ b/RileyLinkKit/PumpOps.swift @@ -105,7 +105,7 @@ public class PumpOps { - parameter startDate: The earliest date of events to retrieve - parameter completion: A closure called after the command is complete. This closure takes a single Result argument: - - Success(events): An array of fetched history entries + - Success(events): An array of fetched history entries, in ascending order of insertion - Failure(error): An error describing why the command failed */ @@ -149,6 +149,30 @@ public class PumpOps { } } + + /** + Reads clock, reservoir, battery, bolusing, and suspended state from pump + + This operation is performed asynchronously and the completion will be executed on an arbitrary background queue. + + - parameter completion: A closure called after the command is complete. This closure takes a single Result argument: + - Success(status): A structure describing the current status of the pump + - Failure(error): An error describing why the command failed + */ + public func readPumpStatus(completion: (Either) -> Void) { + device.runSession { (session) in + let ops = PumpOpsSynchronous(pumpState: self.pumpState, session: session) + + do { + let response: PumpStatus = try ops.readPumpStatus() + completion(.Success(response)) + } catch let error { + completion(.Failure(error)) + } + } + } + + /** Sets a bolus diff --git a/RileyLinkKit/PumpOpsSynchronous.swift b/RileyLinkKit/PumpOpsSynchronous.swift index ead51960d..131cdb5d1 100644 --- a/RileyLinkKit/PumpOpsSynchronous.swift +++ b/RileyLinkKit/PumpOpsSynchronous.swift @@ -366,6 +366,16 @@ class PumpOpsSynchronous { events.insert(TimestampedHistoryEvent(pumpEvent: event, date: date), atIndex: 0) } } + + if let alarm = event as? PumpAlarmPumpEvent { + switch alarm.alarmType { + case .BatteryDepleted, .BatteryOutLimitExceeded, .DeviceReset: + print("Found clock loss in pump history. Ending history fetch.") + break pages + default: + break + } + } } if let event = event as? ChangeTimePumpEvent { @@ -411,6 +421,34 @@ class PumpOpsSynchronous { } return frameData } + + internal func readPumpStatus() throws -> PumpStatus { + let clockResp: ReadTimeCarelinkMessageBody = try getMessageBodyWithType(.ReadTime) + + let pumpModel = try getPumpModel() + + let resResp: ReadRemainingInsulinMessageBody = try getMessageBodyWithType(.ReadRemainingInsulin) + + let reservoir = resResp.getUnitsRemainingForStrokes(pumpModel.strokesPerUnit) + + let battResp: GetBatteryCarelinkMessageBody = try getMessageBodyWithType(.GetBattery) + + let statusResp: ReadPumpStatusMessageBody = try getMessageBodyWithType(.ReadPumpStatus) + + return PumpStatus(clock: clockResp.dateComponents, batteryVolts: battResp.volts, batteryStatus: battResp.status, suspended: statusResp.suspended, bolusing: statusResp.bolusing, reservoir: reservoir, model: pumpModel, pumpID: pump.pumpID) + + } +} + +public struct PumpStatus { + public let clock: NSDateComponents + public let batteryVolts: Double + public let batteryStatus: BatteryStatus + public let suspended: Bool + public let bolusing: Bool + public let reservoir: Double + public let model: PumpModel + public let pumpID: String } public struct FrequencyTrial { diff --git a/RileyLinkKit/UI/RileyLinkDeviceTableViewController.swift b/RileyLinkKit/UI/RileyLinkDeviceTableViewController.swift index ab24ec932..e418a9211 100644 --- a/RileyLinkKit/UI/RileyLinkDeviceTableViewController.swift +++ b/RileyLinkKit/UI/RileyLinkDeviceTableViewController.swift @@ -153,7 +153,7 @@ public class RileyLinkDeviceTableViewController: UITableViewController, TextFiel case DumpHistory case GetPumpModel case PressDownButton - case ReadRemainingInsulin + case ReadPumpStatus static let count = 7 } @@ -288,8 +288,8 @@ public class RileyLinkDeviceTableViewController: UITableViewController, TextFiel case .PressDownButton: cell.textLabel?.text = NSLocalizedString("Send Button Press", comment: "The title of the command to send a button press") - case .ReadRemainingInsulin: - cell.textLabel?.text = NSLocalizedString("Read Remaining Insulin", comment: "The title of the command to read remaining insulin") + case .ReadPumpStatus: + cell.textLabel?.text = NSLocalizedString("Read Pump Status", comment: "The title of the command to read pump status") } } @@ -477,21 +477,25 @@ public class RileyLinkDeviceTableViewController: UITableViewController, TextFiel }) return NSLocalizedString("Sending button press…", comment: "Progress message for sending button press to pump.") } - case .ReadRemainingInsulin: + case .ReadPumpStatus: vc = CommandResponseViewController { [unowned self] (completionHandler) -> String in - self.device.ops?.readRemainingInsulin { (result) in + self.device.ops?.readPumpStatus { (result) in dispatch_async(dispatch_get_main_queue()) { switch result { - case .Success(let units): - completionHandler(responseText: String(format: NSLocalizedString("%1$@ Units remaining", comment: "The format string describing units of insulin remaining: (1: number of units)"), self.decimalFormatter.stringFromNumber(units)!)) + case .Success(let status): + var str = String(format: NSLocalizedString("%1$@ Units of insulin remaining\n", comment: "The format string describing units of insulin remaining: (1: number of units)"), self.decimalFormatter.stringFromNumber(status.reservoir)!) + str += String(format: NSLocalizedString("Battery: %1$@ volts\n", comment: "The format string describing pump battery voltage: (1: battery voltage)"), self.decimalFormatter.stringFromNumber(status.batteryVolts)!) + str += String(format: NSLocalizedString("Suspended: %1$@\n", comment: "The format string describing pump suspended state: (1: suspended)"), String(status.suspended)) + str += String(format: NSLocalizedString("Bolusing: %1$@\n", comment: "The format string describing pump bolusing state: (1: bolusing)"), String(status.bolusing)) + completionHandler(responseText: str) case .Failure(let error): completionHandler(responseText: String(error)) } } } - return NSLocalizedString("Reading remaining insulin…", comment: "Progress message for reading pump insulin reservoir volume") + return NSLocalizedString("Reading pump status…", comment: "Progress message for reading pump status") } } diff --git a/RileyLinkKitTests/Info.plist b/RileyLinkKitTests/Info.plist index 621016d74..ab895996c 100644 --- a/RileyLinkKitTests/Info.plist +++ b/RileyLinkKitTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 0.7.1 + 0.8.0 CFBundleSignature ???? CFBundleVersion diff --git a/RileyLinkTests/RileyLinkTests-Info.plist b/RileyLinkTests/RileyLinkTests-Info.plist index c3315ea15..81ef2657b 100644 --- a/RileyLinkTests/RileyLinkTests-Info.plist +++ b/RileyLinkTests/RileyLinkTests-Info.plist @@ -13,7 +13,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 0.7.1 + 0.8.0 CFBundleSignature ???? CFBundleVersion