diff --git a/Crypto/Info.plist b/Crypto/Info.plist index a86af35aa..1903193db 100644 --- a/Crypto/Info.plist +++ b/Crypto/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.12.5 + 0.12.6 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/MinimedKit/Extensions/NSData.swift b/MinimedKit/Extensions/NSData.swift index 284a8a816..3699b4909 100644 --- a/MinimedKit/Extensions/NSData.swift +++ b/MinimedKit/Extensions/NSData.swift @@ -71,7 +71,6 @@ extension Data { */ } - extension Data { init?(hexadecimalString: String) { guard let chars = hexadecimalString.cString(using: String.Encoding.utf8) else { diff --git a/MinimedKit/Extensions/NSDateComponents.swift b/MinimedKit/Extensions/NSDateComponents.swift index 8f758190e..6fd023610 100644 --- a/MinimedKit/Extensions/NSDateComponents.swift +++ b/MinimedKit/Extensions/NSDateComponents.swift @@ -47,4 +47,17 @@ extension DateComponents { calendar = Calendar(identifier: Calendar.Identifier.gregorian) } + + init(glucoseEventBytes: Data) { + self.init() + + year = Int(glucoseEventBytes[3] & 0b01111111) + 2000 + month = Int((glucoseEventBytes[0] & 0b11000000) >> 4 + + (glucoseEventBytes[1] & 0b11000000) >> 6) + day = Int(glucoseEventBytes[2] & 0b00011111) + hour = Int(glucoseEventBytes[0] & 0b00011111) + minute = Int(glucoseEventBytes[1] & 0b00111111) + + calendar = Calendar(identifier: Calendar.Identifier.gregorian) + } } diff --git a/MinimedKit/GlucoseEventType.swift b/MinimedKit/GlucoseEventType.swift new file mode 100644 index 000000000..e89d74335 --- /dev/null +++ b/MinimedKit/GlucoseEventType.swift @@ -0,0 +1,56 @@ +// +// GlucoseEventType.swift +// RileyLink +// +// Created by Timothy Mecklem on 10/16/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import Foundation + +public enum GlucoseEventType: UInt8 { + case dataEnd = 0x01 + case sensorWeakSignal = 0x02 + case sensorCal = 0x03 + case fokko7 = 0x07 + case sensorTimestamp = 0x08 + case batteryChange = 0x0a + case sensorStatus = 0x0b + case dateTimeChange = 0x0c + case sensorSync = 0x0d + case calBGForGH = 0x0e + case sensorCalFactor = 0x0f + case tenSomething = 0x10 + case nineteenSomething = 0x13 + + public var eventType: GlucoseEvent.Type { + switch self { + case .dataEnd: + return DataEndGlucoseEvent.self + case .sensorWeakSignal: + return SensorWeakSignalGlucoseEvent.self + case .sensorCal: + return SensorCalGlucoseEvent.self + case .fokko7: + return Fokko7GlucoseEvent.self + case .sensorTimestamp: + return SensorTimestampGlucoseEvent.self + case .batteryChange: + return BatteryChangeGlucoseEvent.self + case .sensorStatus: + return SensorStatusGlucoseEvent.self + case .dateTimeChange: + return DateTimeChangeGlucoseEvent.self + case .sensorSync: + return SensorSyncGlucoseEvent.self + case .calBGForGH: + return CalBGForGHGlucoseEvent.self + case .sensorCalFactor: + return SensorCalFactorGlucoseEvent.self + case .tenSomething: + return TenSomethingGlucoseEvent.self + case .nineteenSomething: + return NineteenSomethingGlucoseEvent.self + } + } +} diff --git a/MinimedKit/GlucoseEvents/BatteryChangeGlucoseEvent.swift b/MinimedKit/GlucoseEvents/BatteryChangeGlucoseEvent.swift new file mode 100644 index 000000000..22af2c62f --- /dev/null +++ b/MinimedKit/GlucoseEvents/BatteryChangeGlucoseEvent.swift @@ -0,0 +1,32 @@ +// +// BatteryChangeGlucoseEvent.swift +// RileyLink +// +// Created by Timothy Mecklem on 10/16/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import Foundation + +public struct BatteryChangeGlucoseEvent : GlucoseEvent { + public let length: Int + public let rawData: Data + public let timestamp: DateComponents + + public init?(availableData: Data) { + length = 5 + + guard length <= availableData.count else { + return nil + } + + rawData = availableData.subdata(in: 0.. Int { + return Int(availableData[idx] as UInt8) + } + + rawData = availableData.subdata(in: 0.. Int { + return Int(availableData[idx] as UInt8) + } + + rawData = availableData.subdata(in: 0.. Int { + return Int(availableData[idx] as UInt8) + } + + rawData = availableData.subdata(in: 0.. GlucoseEvent { + let remainingData = pageData.subdata(in: offset..= 20 { + return GlucoseSensorDataGlucoseEvent(availableData: remainingData)! + } + + return UnknownGlucoseEvent(availableData: remainingData)! + } + + func addTimestampsToEvents(startTimestamp: DateComponents, eventsNeedingTimestamp: [RelativeTimestampedGlucoseEvent]) -> [GlucoseEvent] { + var eventsWithTimestamps = [GlucoseEvent]() + let calendar = Calendar.current + var date : Date = calendar.date(from: startTimestamp)! + for var event in eventsNeedingTimestamp { + if !(event is NineteenSomethingGlucoseEvent) { + date = calendar.date(byAdding: Calendar.Component.minute, value: 5, to: date)! + } + event.timestamp = calendar.dateComponents([.year, .month, .day, .hour, .minute], from: date) + event.timestamp.calendar = calendar + eventsWithTimestamps.append(event) + } + return eventsWithTimestamps + } + + while offset < length { + // Slurp up 0's + if pageData[offset] as UInt8 == 0 { + offset += 1 + continue + } + + let event = matchEvent(offset) + + if let event = event as? RelativeTimestampedGlucoseEvent { + eventsNeedingTimestamp.insert(event, at: 0) + } else if let event = event as? ReferenceTimestampedGlucoseEvent { + let eventsWithTimestamp = addTimestampsToEvents(startTimestamp: event.timestamp, eventsNeedingTimestamp: eventsNeedingTimestamp).reversed() + tempEvents.append(contentsOf: eventsWithTimestamp) + eventsNeedingTimestamp.removeAll() + tempEvents.append(event) + } else { + tempEvents.append(event) + } + + offset += event.length + } + events = tempEvents.reversed() + } +} diff --git a/MinimedKit/HistoryPage.swift b/MinimedKit/HistoryPage.swift index b7bb505c7..6442cdd38 100644 --- a/MinimedKit/HistoryPage.swift +++ b/MinimedKit/HistoryPage.swift @@ -6,6 +6,8 @@ // Copyright © 2016 Pete Schwamb. All rights reserved. // +import Foundation + public class HistoryPage { public enum HistoryPageError: Error { diff --git a/MinimedKit/Info.plist b/MinimedKit/Info.plist index 2ed8188bd..c66882b70 100644 --- a/MinimedKit/Info.plist +++ b/MinimedKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.12.5 + 0.12.6 CFBundleSignature ???? CFBundleVersion diff --git a/MinimedKit/MessageType.swift b/MinimedKit/MessageType.swift index e1b75bd32..45481924a 100644 --- a/MinimedKit/MessageType.swift +++ b/MinimedKit/MessageType.swift @@ -27,7 +27,9 @@ public enum MessageType: UInt8 { case getHistoryPage = 0x80 case getPumpModel = 0x8d case readTempBasal = 0x98 + case getGlucosePage = 0x9A case readSettings = 0xc0 + case readCurrentGlucosePage = 0xcd case readPumpStatus = 0xce var bodyType: MessageBody.Type { @@ -62,6 +64,10 @@ public enum MessageType: UInt8 { return ReadRemainingInsulinMessageBody.self case .readPumpStatus: return ReadPumpStatusMessageBody.self + case .readCurrentGlucosePage: + return ReadCurrentGlucosePageMessageBody.self + case .getGlucosePage: + return GetGlucosePageMessageBody.self default: return UnknownMessageBody.self } diff --git a/MinimedKit/Messages/GetGlucosePageMessageBody.swift b/MinimedKit/Messages/GetGlucosePageMessageBody.swift new file mode 100644 index 000000000..0f15b04ed --- /dev/null +++ b/MinimedKit/Messages/GetGlucosePageMessageBody.swift @@ -0,0 +1,35 @@ +// +// GetGlucosePageMessageBody.swift +// RileyLink +// +// Created by Timothy Mecklem on 10/19/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import Foundation + +public class GetGlucosePageMessageBody: CarelinkLongMessageBody { + public let lastFrame: Bool + public let frameNumber: Int + public let frame: Data + + public required init?(rxData: Data) { + guard rxData.count == type(of: self).length else { + return nil + } + frameNumber = Int(rxData[0] as UInt8) & 0b1111111 + lastFrame = (rxData[0] as UInt8) & 0b10000000 > 0 + frame = rxData.subdata(in: 1..<65) + super.init(rxData: rxData) + } + + public required init(pageNum: UInt32) { + let numArgs = 4 + lastFrame = false + frame = Data() + frameNumber = 0 + let data = Data(hexadecimalString: String(format: "%02x%08x", numArgs, pageNum))! + super.init(rxData: data)! + } + +} diff --git a/MinimedKit/Messages/ReadCurrentGlucosePageMessageBody.swift b/MinimedKit/Messages/ReadCurrentGlucosePageMessageBody.swift new file mode 100644 index 000000000..0d2f6274f --- /dev/null +++ b/MinimedKit/Messages/ReadCurrentGlucosePageMessageBody.swift @@ -0,0 +1,30 @@ +// +// ReadCurrentGlucosePageMessageBody.swift +// RileyLink +// +// Created by Timothy Mecklem on 10/19/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import Foundation + +public class ReadCurrentGlucosePageMessageBody: CarelinkLongMessageBody { + + public let pageNum: UInt32 + public let glucose: Int + public let isig: Int + + public required init?(rxData: Data) { + guard rxData.count == type(of: self).length else { + return nil + } + + self.pageNum = rxData.subdata(in: 1..<5).withUnsafeBytes({ (bytes: UnsafePointer) -> UInt32 in + return UInt32(bigEndian: bytes.pointee) + }) + self.glucose = Int(rxData[6] as UInt8) + self.isig = Int(rxData[8] as UInt8) + + super.init(rxData: rxData) + } +} diff --git a/MinimedKit/PumpModel.swift b/MinimedKit/PumpModel.swift index 865bfc682..951073b27 100644 --- a/MinimedKit/PumpModel.swift +++ b/MinimedKit/PumpModel.swift @@ -12,8 +12,11 @@ public enum PumpModel: String { case Model508 = "508" case Model511 = "511" + case Model711 = "711" case Model512 = "512" + case Model712 = "712" case Model515 = "515" + case Model715 = "715" case Model522 = "522" case Model722 = "722" case Model523 = "523" diff --git a/MinimedKit/TimestampedGlucoseEvent.swift b/MinimedKit/TimestampedGlucoseEvent.swift new file mode 100644 index 000000000..ca7cfd69e --- /dev/null +++ b/MinimedKit/TimestampedGlucoseEvent.swift @@ -0,0 +1,34 @@ +// +// TimestampedGlucoseEvent.swift +// RileyLink +// +// Created by Timothy Mecklem on 10/19/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import Foundation + +public struct TimestampedGlucoseEvent { + public let glucoseEvent: GlucoseEvent + public let date: Date + + public func isMutable(atDate date: Date = Date()) -> Bool { + return false + } + + public init(glucoseEvent: GlucoseEvent, date: Date) { + self.glucoseEvent = glucoseEvent + self.date = date + } +} + + +extension TimestampedGlucoseEvent: DictionaryRepresentable { + public var dictionaryRepresentation: [String : Any] { + var dict = glucoseEvent.dictionaryRepresentation + + dict["timestamp"] = DateFormatter.ISO8601DateFormatter().string(from: date) + + return dict + } +} diff --git a/MinimedKitTests/GlucoseEvents/BatteryChangeGlucoseEventTests.swift b/MinimedKitTests/GlucoseEvents/BatteryChangeGlucoseEventTests.swift new file mode 100644 index 000000000..e7af514b8 --- /dev/null +++ b/MinimedKitTests/GlucoseEvents/BatteryChangeGlucoseEventTests.swift @@ -0,0 +1,31 @@ +// +// BatteryChangeGlucoseEventTests.swift +// RileyLink +// +// Created by Timothy Mecklem on 10/18/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import XCTest +@testable import MinimedKit + +class BatteryChangeGlucoseEventTests: XCTestCase { + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testDecoding() { + let rawData = Data(hexadecimalString: "0a0bae0a0e")! + let subject = BatteryChangeGlucoseEvent(availableData: rawData)! + + let expectedTimestamp = DateComponents(calendar: Calendar.current, + year: 2014, month: 2, day: 10, hour: 11, minute: 46) + XCTAssertEqual(subject.timestamp, expectedTimestamp) + } +} diff --git a/MinimedKitTests/GlucoseEvents/CalBGForGHGlucoseEventTests.swift b/MinimedKitTests/GlucoseEvents/CalBGForGHGlucoseEventTests.swift new file mode 100644 index 000000000..3301e3f75 --- /dev/null +++ b/MinimedKitTests/GlucoseEvents/CalBGForGHGlucoseEventTests.swift @@ -0,0 +1,34 @@ +// +// CalBGForGHGlucoseEventTests.swift +// RileyLink +// +// Created by Timothy Mecklem on 10/18/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import XCTest +@testable import MinimedKit + +class CalBGForGHGlucoseEventTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testDecoding() { + let rawData = Data(hexadecimalString: "0e4f5b138fa0")! + let subject = CalBGForGHGlucoseEvent(availableData: rawData)! + + let expectedTimestamp = DateComponents(calendar: Calendar.current, + year: 2015, month: 5, day: 19, hour: 15, minute: 27) + XCTAssertEqual(subject.timestamp, expectedTimestamp) + XCTAssertEqual(subject.amount, 160) + } + +} diff --git a/MinimedKitTests/GlucoseEvents/DateTimeChangeGlucoseEventTests.swift b/MinimedKitTests/GlucoseEvents/DateTimeChangeGlucoseEventTests.swift new file mode 100644 index 000000000..ae3b72ea3 --- /dev/null +++ b/MinimedKitTests/GlucoseEvents/DateTimeChangeGlucoseEventTests.swift @@ -0,0 +1,33 @@ +// +// DateTimeChangeGlucoseEventTests.swift +// RileyLink +// +// Created by Timothy Mecklem on 10/18/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import XCTest +@testable import MinimedKit + +class DateTimeChangeGlucoseEventTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testDecoding() { + let rawData = Data(hexadecimalString: "0c0ad23e0e")! + let subject = DateTimeChangeGlucoseEvent(availableData: rawData)! + + let expectedTimestamp = DateComponents(calendar: Calendar.current, + year: 2014, month: 3, day: 30, hour: 10, minute: 18) + XCTAssertEqual(subject.timestamp, expectedTimestamp) + } + +} diff --git a/MinimedKitTests/GlucoseEvents/GlucoseSensorDataGlucoseEventTests.swift b/MinimedKitTests/GlucoseEvents/GlucoseSensorDataGlucoseEventTests.swift new file mode 100644 index 000000000..89654aa08 --- /dev/null +++ b/MinimedKitTests/GlucoseEvents/GlucoseSensorDataGlucoseEventTests.swift @@ -0,0 +1,31 @@ +// +// GlucoseSensorDataGlucoseEventTests.swift +// RileyLink +// +// Created by Timothy Mecklem on 10/18/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import XCTest +@testable import MinimedKit + +class GlucoseSensorDataGlucoseEventTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testDecoding() { + let rawData = Data(hexadecimalString: "35")! + let subject = GlucoseSensorDataGlucoseEvent(availableData: rawData)! + + XCTAssertEqual(subject.sgv, 106) + } + +} diff --git a/MinimedKitTests/GlucoseEvents/SensorCalFactorGlucoseEventTests.swift b/MinimedKitTests/GlucoseEvents/SensorCalFactorGlucoseEventTests.swift new file mode 100644 index 000000000..e5c45f70a --- /dev/null +++ b/MinimedKitTests/GlucoseEvents/SensorCalFactorGlucoseEventTests.swift @@ -0,0 +1,34 @@ +// +// SensorCalFactorGlucoseEventTests.swift +// RileyLink +// +// Created by Timothy Mecklem on 10/18/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import XCTest +@testable import MinimedKit + +class SensorCalFactorGlucoseEventTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testDecoding() { + let rawData = Data(hexadecimalString: "0f4f67130f128c")! + let subject = SensorCalFactorGlucoseEvent(availableData: rawData)! + + let expectedTimestamp = DateComponents(calendar: Calendar.current, + year: 2015, month: 5, day: 19, hour: 15, minute: 39) + XCTAssertEqual(subject.timestamp, expectedTimestamp) + XCTAssertEqual(subject.factor, 4.748) + } + +} diff --git a/MinimedKitTests/GlucoseEvents/SensorCalGlucoseEventTests.swift b/MinimedKitTests/GlucoseEvents/SensorCalGlucoseEventTests.swift new file mode 100644 index 000000000..2b14f66d1 --- /dev/null +++ b/MinimedKitTests/GlucoseEvents/SensorCalGlucoseEventTests.swift @@ -0,0 +1,38 @@ +// +// SensorCalGlucoseEventTests.swift +// RileyLink +// +// Created by Timothy Mecklem on 10/18/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import XCTest +@testable import MinimedKit + +class SensorCalGlucoseEventTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testDecodingMeterBgNow() { + let rawData = Data(hexadecimalString: "0300")! + let subject = SensorCalGlucoseEvent(availableData: rawData)! + + XCTAssertEqual(subject.waiting, "meter_bg_now") + } + + func testDecodingWaiting() { + let rawData = Data(hexadecimalString: "0301")! + let subject = SensorCalGlucoseEvent(availableData: rawData)! + + XCTAssertEqual(subject.waiting, "waiting") + } + +} diff --git a/MinimedKitTests/GlucoseEvents/SensorStatusGlucoseEventTests.swift b/MinimedKitTests/GlucoseEvents/SensorStatusGlucoseEventTests.swift new file mode 100644 index 000000000..482863555 --- /dev/null +++ b/MinimedKitTests/GlucoseEvents/SensorStatusGlucoseEventTests.swift @@ -0,0 +1,32 @@ +// +// SensorStatusGlucoseEventTests.swift +// RileyLink +// +// Created by Timothy Mecklem on 10/18/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import XCTest +@testable import MinimedKit + +class SensorStatusGlucoseEventTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testDecoding() { + let rawData = Data(hexadecimalString: "0b0baf0a0e")! + let subject = SensorStatusGlucoseEvent(availableData: rawData)! + + let expectedTimestamp = DateComponents(calendar: Calendar.current, + year: 2014, month: 2, day: 10, hour: 11, minute: 47) + XCTAssertEqual(subject.timestamp, expectedTimestamp) + } +} diff --git a/MinimedKitTests/GlucoseEvents/SensorSyncGlucoseEventTests.swift b/MinimedKitTests/GlucoseEvents/SensorSyncGlucoseEventTests.swift new file mode 100644 index 000000000..8d2a49f56 --- /dev/null +++ b/MinimedKitTests/GlucoseEvents/SensorSyncGlucoseEventTests.swift @@ -0,0 +1,38 @@ +// +// SensorSyncGlucoseEventTests.swift +// RileyLink +// +// Created by Timothy Mecklem on 10/18/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import XCTest +@testable import MinimedKit + +class SensorSyncGlucoseEventTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + let rawData = Data(hexadecimalString: "0d4d44330f")! + let subject = SensorSyncGlucoseEvent(availableData: rawData)! + + let expectedTimestamp = DateComponents(calendar: Calendar.current, + year: 2015, month: 5, day: 19, hour: 13, minute: 04) + XCTAssertEqual(subject.timestamp, expectedTimestamp) + } + +} diff --git a/MinimedKitTests/GlucoseEvents/SensorTimestampGlucoseEventTests.swift b/MinimedKitTests/GlucoseEvents/SensorTimestampGlucoseEventTests.swift new file mode 100644 index 000000000..e899bfb04 --- /dev/null +++ b/MinimedKitTests/GlucoseEvents/SensorTimestampGlucoseEventTests.swift @@ -0,0 +1,33 @@ +// +// SensorTimestampGlucoseEventTests.swift +// RileyLink +// +// Created by Timothy Mecklem on 10/18/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import XCTest +@testable import MinimedKit + +class SensorTimestampGlucoseEventTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testDecoding() { + let rawData = Data(hexadecimalString: "088d9b5d0c")! + let subject = SensorTimestampGlucoseEvent(availableData: rawData)! + + let expectedTimestamp = DateComponents(calendar: Calendar.current, + year: 2012, month: 10, day: 29, hour: 13, minute: 27) + XCTAssertEqual(subject.timestamp, expectedTimestamp) + } + +} diff --git a/MinimedKitTests/GlucoseEvents/TenSomethingGlucoseEventTests.swift b/MinimedKitTests/GlucoseEvents/TenSomethingGlucoseEventTests.swift new file mode 100644 index 000000000..6a98b2dec --- /dev/null +++ b/MinimedKitTests/GlucoseEvents/TenSomethingGlucoseEventTests.swift @@ -0,0 +1,33 @@ +// +// TenSomethingGlucoseEventTests.swift +// RileyLink +// +// Created by Timothy Mecklem on 10/18/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import XCTest +@testable import MinimedKit + +class TenSomethingGlucoseEventTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testDecoding() { + let rawData = Data(hexadecimalString: "100bb40a0e")! + let subject = TenSomethingGlucoseEvent(availableData: rawData)! + + let expectedTimestamp = DateComponents(calendar: Calendar.current, + year: 2014, month: 2, day: 10, hour: 11, minute: 52) + XCTAssertEqual(subject.timestamp, expectedTimestamp) + } + +} diff --git a/MinimedKitTests/GlucosePageTests.swift b/MinimedKitTests/GlucosePageTests.swift new file mode 100644 index 000000000..5329e4f49 --- /dev/null +++ b/MinimedKitTests/GlucosePageTests.swift @@ -0,0 +1,122 @@ +// +// GlucosePageTests.swift +// RileyLink +// +// Created by Timothy Mecklem on 10/16/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import XCTest +@testable import MinimedKit + +class GlucosePageTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testGlucosePageCRC() { + do { + let _ = try GlucosePage(pageData: Data(hexadecimalString: "201E1D1C1A1817161718191B1D1B1C1F212527282A2E3235383C3FCD9086850E0E0000011006850E1043484857EB181006940E0F595E00000110069F0E106670757374767A7D7F8181817D7813726D6663605E5B554F4C4E4F4B484946423E3B38353234332D2B2B2B2C2E302F2F312F2D2D2E2D2C2D2D2A2B292700000110069F1310242323272B2C3235383C00000110069514103E3E3F404141444A4945413E3D3D3A35302C2823201D1B1C1D1A1A1B1C1E2123272B2F3231323235383B3F43484949474542779007A0000E41423D6C171007AD000F3C3D3D3B3C3E3F41474F545A626A6E700000011007860210717271727476797A7A7B7C7D7C7B777472706E6D6C6B6A6763605C5A6064636160605F5D5C5C5B5956545250555A585656555554565654504D4C4A484E515455555452504F4D4D504E4D4E505050504F4E4D4C4C4F50510000011007980910539290079C090E54554BC4141007A8090F4A4A49494C4C4D50515253524F53524F4D4C7590078E0B0E7590878E0B0E4A4947433BD0121007A30B0F373333312F2B26242423252526292A2A2A0000011007810D102A2A2828282B3337383A3F44474746484B4F52504B4846434343413D393500000110079F0F10312F2D2B27211E1D3F90078E100E1C1A191214100798100F181817181F20242A2E33383E41464B4B4F56595E000001100788121065686667660000011007A3121066686B6D6F727375777774707273706C6764615F5A575553453D39322C251F1A1C191614171D2124282C2E31353A3F4144484A4B4C4D50555B000001100790171061656A6665656768C99007B9170E0000011007B917106869659413100889000F625F5D5D5F5D5A56524B4E4E4C4B4847494B494A4D5051514F4E4C4E4E4D4B4A494A4B4B4B4B47474D4E4E4E4E4E4C4B4A46434B4D4C4B49490000011008B804104B4B4B4D515456565655545455595A5800000110088F0610595A5A595A595755555858575655545453525152534E4A474445484B4E4F13505251135113515051525154AF9088A6090E0000011008A6091052505594141008B3090F55565B5A58534F4B49134037132F28132320131A13181514151615171919191A1A1B1A1B1C1D202325242221225890088B0D0E00000110088C0D1023252AEC151008990D0F2B2B2E31393F3A3C41484E555D00000110089F0E1063696E73767A7C7D7F7E7F8085878689E59008B50F0E0000011008B50F10878979CD13100885100F76737272706D0000011008A71010696461605D5A57555352520000011008A11110504C4947484743434278900893120E0000011008941210423F3A3837D6131008A9120F33322F2E2F31322F2C2C2B2828292A2827243A90088E140E211C19181809141008A4140F191A19191028B6140813131313133C95")!) + } catch GlucosePage.GlucosePageError.invalidCRC { + XCTFail("page decoding threw invalid crc") + } catch GlucosePage.GlucosePageError.unknownEventType(let eventType) { + XCTFail("unknown event type" + String(eventType)) + } catch { + XCTFail("Unexpected exception...") + } + } + + func testGlucosePageInvalidCRC() { + do { + let _ = try GlucosePage(pageData: Data(hexadecimalString: "201E1D1C1A1817161718191B1D1B1C1F212527282A2E3235383C3FCD9086850E0E0000011006850E1043484857EB181006940E0F595E00000110069F0E106670757374767A7D7F8181817D7813726D6663605E5B554F4C4E4F4B484946423E3B38353234332D2B2B2B2C2E302F2F312F2D2D2E2D2C2D2D2A2B292700000110069F1310242323272B2C3235383C00000110069514103E3E3F404141444A4945413E3D3D3A35302C2823201D1B1C1D1A1A1B1C1E2123272B2F3231323235383B3F43484949474542779007A0000E41423D6C171007AD000F3C3D3D3B3C3E3F41474F545A626A6E700000011007860210717271727476797A7A7B7C7D7C7B777472706E6D6C6B6A6763605C5A6064636160605F5D5C5C5B5956545250555A585656555554565654504D4C4A484E515455555452504F4D4D504E4D4E505050504F4E4D4C4C4F50510000011007980910539290079C090E54554BC4141007A8090F4A4A49494C4C4D50515253524F53524F4D4C7590078E0B0E7590878E0B0E4A4947433BD0121007A30B0F373333312F2B26242423252526292A2A2A0000011007810D102A2A2828282B3337383A3F44474746484B4F52504B4846434343413D393500000110079F0F10312F2D2B27211E1D3F90078E100E1C1A191214100798100F181817181F20242A2E33383E41464B4B4F56595E000001100788121065686667660000011007A3121066686B6D6F727375777774707273706C6764615F5A575553453D39322C251F1A1C191614171D2124282C2E31353A3F4144484A4B4C4D50555B000001100790171061656A6665656768C99007B9170E0000011007B917106869659413100889000F625F5D5D5F5D5A56524B4E4E4C4B4847494B494A4D5051514F4E4C4E4E4D4B4A494A4B4B4B4B47474D4E4E4E4E4E4C4B4A46434B4D4C4B49490000011008B804104B4B4B4D515456565655545455595A5800000110088F0610595A5A595A595755555858575655545453525152534E4A474445484B4E4F13505251135113515051525154AF9088A6090E0000011008A6091052505594141008B3090F55565B5A58534F4B49134037132F28132320131A13181514151615171919191A1A1B1A1B1C1D202325242221225890088B0D0E00000110088C0D1023252AEC151008990D0F2B2B2E31393F3A3C41484E555D00000110089F0E1063696E73767A7C7D7F7E7F8085878689E59008B50F0E0000011008B50F10878979CD13100885100F76737272706D0000011008A71010696461605D5A57555352520000011008A11110504C4947484743434278900893120E0000011008941210423F3A3837D6131008A9120F33322F2E2F31322F2C2C2B2828292A2827243A90088E140E211C19181809141008A4140F191A19191028B6140813131313133C94")!) + XCTFail("Should have thrown InvalidCRC") + } catch GlucosePage.GlucosePageError.invalidCRC { + // Happy path + } catch GlucosePage.GlucosePageError.unknownEventType(let eventType) { + XCTFail("unknown event type" + String(eventType)) + } catch { + XCTFail("Unexpected exception...") + } + } + + func testUnknownRecords() { + do { + //05 is a currently unknown opcode + let pageData = Data(hexadecimalString: "051053b394081053b3940b014a60".leftPadding(toLength: 2048, withPad: "0"))! + let page = try GlucosePage(pageData: pageData) + let events = page.events + + XCTAssertEqual(events.count, 4) + XCTAssertEqual((events.first as! UnknownGlucoseEvent).op, "05") + + } catch GlucosePage.GlucosePageError.invalidCRC { + XCTFail("page decoding threw invalid crc") + } catch GlucosePage.GlucosePageError.unknownEventType(let eventType) { + XCTFail("unknown event type" + String(eventType)) + } catch { + XCTFail("Unexpected exception...") + } + } + + func testRelativeTimestamping() { + do { + let calendar = Calendar.current + // a sensor timestamp, then "19-Something" and glucose relative timestamp records + let pageData = Data(hexadecimalString: "1028B6140813306BFB".leftPadding(toLength: 2048, withPad: "0"))! + let page = try GlucosePage(pageData: pageData) + let events = page.events + + XCTAssertEqual(events.count, 3) + //the initial timestamp comes from the sensor timestamp reference record + let expectedTimestamp = DateComponents(calendar: calendar, + year: 2016, month: 2, day: 8, + hour: 20, minute: 54) + XCTAssertEqual(events[0].timestamp, expectedTimestamp) + + //The 19-Something needs a timestamp, but doesn't increment the relative counter + XCTAssertEqual(events[1].timestamp, expectedTimestamp) + + //The GlucoseSensorData event needs a timestamp and does increment the relative counter by 5 minutes + let expectedGlucoseTimestamp = DateComponents(calendar: calendar, + year: 2016, month: 2, day: 8, + hour: 20, minute: 59) + XCTAssertEqual(events[2].timestamp, expectedGlucoseTimestamp) + + } catch GlucosePage.GlucosePageError.invalidCRC { + XCTFail("page decoding threw invalid crc") + } catch GlucosePage.GlucosePageError.unknownEventType(let eventType) { + XCTFail("unknown event type" + String(eventType)) + } catch { + XCTFail("Unexpected exception...") + } + } + + func testGlucosePageEventDecoding() { + do { + let page = try GlucosePage(pageData: Data(hexadecimalString: "201E1D1C1A1817161718191B1D1B1C1F212527282A2E3235383C3FCD9086850E0E0000011006850E1043484857EB181006940E0F595E00000110069F0E106670757374767A7D7F8181817D7813726D6663605E5B554F4C4E4F4B484946423E3B38353234332D2B2B2B2C2E302F2F312F2D2D2E2D2C2D2D2A2B292700000110069F1310242323272B2C3235383C00000110069514103E3E3F404141444A4945413E3D3D3A35302C2823201D1B1C1D1A1A1B1C1E2123272B2F3231323235383B3F43484949474542779007A0000E41423D6C171007AD000F3C3D3D3B3C3E3F41474F545A626A6E700000011007860210717271727476797A7A7B7C7D7C7B777472706E6D6C6B6A6763605C5A6064636160605F5D5C5C5B5956545250555A585656555554565654504D4C4A484E515455555452504F4D4D504E4D4E505050504F4E4D4C4C4F50510000011007980910539290079C090E54554BC4141007A8090F4A4A49494C4C4D50515253524F53524F4D4C7590078E0B0E7590878E0B0E4A4947433BD0121007A30B0F373333312F2B26242423252526292A2A2A0000011007810D102A2A2828282B3337383A3F44474746484B4F52504B4846434343413D393500000110079F0F10312F2D2B27211E1D3F90078E100E1C1A191214100798100F181817181F20242A2E33383E41464B4B4F56595E000001100788121065686667660000011007A3121066686B6D6F727375777774707273706C6764615F5A575553453D39322C251F1A1C191614171D2124282C2E31353A3F4144484A4B4C4D50555B000001100790171061656A6665656768C99007B9170E0000011007B917106869659413100889000F625F5D5D5F5D5A56524B4E4E4C4B4847494B494A4D5051514F4E4C4E4E4D4B4A494A4B4B4B4B47474D4E4E4E4E4E4C4B4A46434B4D4C4B49490000011008B804104B4B4B4D515456565655545455595A5800000110088F0610595A5A595A595755555858575655545453525152534E4A474445484B4E4F13505251135113515051525154AF9088A6090E0000011008A6091052505594141008B3090F55565B5A58534F4B49134037132F28132320131A13181514151615171919191A1A1B1A1B1C1D202325242221225890088B0D0E00000110088C0D1023252AEC151008990D0F2B2B2E31393F3A3C41484E555D00000110089F0E1063696E73767A7C7D7F7E7F8085878689E59008B50F0E0000011008B50F10878979CD13100885100F76737272706D0000011008A71010696461605D5A57555352520000011008A11110504C4947484743434278900893120E0000011008941210423F3A3837D6131008A9120F33322F2E2F31322F2C2C2B2828292A2827243A90088E140E211C19181809141008A4140F191A19191028B6140813131313133C95")!) + + let events = page.events + + let expectedEventNames: [String] = ["CalBGForGH", "10-Something", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "SensorCalFactor", "GlucoseSensorData", "GlucoseSensorData", "DataEnd", "10-Something", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "19-Something", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "DataEnd", "10-Something", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "DataEnd", "10-Something", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "CalBGForGH", "SensorCalFactor", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "DataEnd", "10-Something", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "DataEnd", "10-Something", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "CalBGForGH", "SensorCalFactor", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "CalBGForGH", "CalBGForGH", "SensorCalFactor", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "DataEnd", "10-Something", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "DataEnd", "10-Something", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "CalBGForGH", "SensorCalFactor", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "DataEnd", "10-Something", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "DataEnd", "10-Something", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "DataEnd", "10-Something", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "DataEnd", "CalBGForGH", "10-Something", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "SensorCalFactor", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "DataEnd", "10-Something", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "DataEnd", "10-Something", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "19-Something", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "19-Something", "GlucoseSensorData", "19-Something", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "DataEnd", "CalBGForGH", "10-Something", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "SensorCalFactor", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "19-Something", "GlucoseSensorData", "GlucoseSensorData", "19-Something", "GlucoseSensorData", "GlucoseSensorData", "19-Something", "GlucoseSensorData", "GlucoseSensorData", "19-Something", "GlucoseSensorData", "19-Something", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "DataEnd", "CalBGForGH", "10-Something", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "SensorCalFactor", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "DataEnd", "10-Something", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "DataEnd", "CalBGForGH", "10-Something", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "SensorCalFactor", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "DataEnd", "10-Something", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "DataEnd", "10-Something", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "DataEnd", "CalBGForGH", "10-Something", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "SensorCalFactor", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "CalBGForGH", "SensorCalFactor", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "GlucoseSensorData", "SensorTimestamp", "19-Something", "19-Something", "19-Something", "19-Something", "19-Something"] + + for (index, values) in zip(expectedEventNames, events).enumerated() { + let eventName = values.1.dictionaryRepresentation["name"] as! String + XCTAssertEqual(values.0, eventName, "Decoded events don't match at index: \(index). Event: \(values.1.dictionaryRepresentation)") + } + + } catch GlucosePage.GlucosePageError.invalidCRC { + XCTFail("page decoding threw invalid crc") + } catch GlucosePage.GlucosePageError.unknownEventType(let eventType) { + XCTFail("unknown event type" + String(eventType)) + } catch { + XCTFail("Unexpected exception...") + } + } +} diff --git a/MinimedKitTests/Info.plist b/MinimedKitTests/Info.plist index c0a7b8da5..8e0afffd2 100644 --- a/MinimedKitTests/Info.plist +++ b/MinimedKitTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 0.12.5 + 0.12.6 CFBundleSignature ???? CFBundleVersion diff --git a/MinimedKitTests/Messages/GetGlucosePageMessageBodyTests.swift b/MinimedKitTests/Messages/GetGlucosePageMessageBodyTests.swift new file mode 100644 index 000000000..b516004c3 --- /dev/null +++ b/MinimedKitTests/Messages/GetGlucosePageMessageBodyTests.swift @@ -0,0 +1,30 @@ +// +// GetGlucosePageMessageBodyTests.swift +// RileyLink +// +// Created by Timothy Mecklem on 10/19/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import XCTest +@testable import MinimedKit + +class GetGlucosePageMessageBodyTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testTxDataEncoding() { + let messageBody = GetGlucosePageMessageBody(pageNum: 13) + + XCTAssertEqual(messageBody.txData.subdata(in: 0..<5).hexadecimalString, "040000000d") + } + +} diff --git a/MinimedKitTests/Messages/ReadCurrentGlucosePageMessageBodyTests.swift b/MinimedKitTests/Messages/ReadCurrentGlucosePageMessageBodyTests.swift new file mode 100644 index 000000000..99a655c7c --- /dev/null +++ b/MinimedKitTests/Messages/ReadCurrentGlucosePageMessageBodyTests.swift @@ -0,0 +1,35 @@ +// +// ReadCurrentGlucosePageMessageBodyTests.swift +// RileyLink +// +// Created by Timothy Mecklem on 10/19/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import XCTest +@testable import MinimedKit + +class ReadCurrentGlucosePageMessageBodyTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testResponseInitializer() { + var responseData = Data(hexadecimalString: "0000000D6100100020")! + responseData.append(contentsOf: [UInt8](repeating: 0, count: 65 - responseData.count)) + + let messageBody = ReadCurrentGlucosePageMessageBody(rxData: responseData)! + + XCTAssertEqual(messageBody.pageNum, 3425) + XCTAssertEqual(messageBody.glucose, 16) + XCTAssertEqual(messageBody.isig, 32) + } + +} diff --git a/MinimedKitTests/NSDateComponentsTests.swift b/MinimedKitTests/NSDateComponentsTests.swift index c4a141cf5..b61ee07b1 100644 --- a/MinimedKitTests/NSDateComponentsTests.swift +++ b/MinimedKitTests/NSDateComponentsTests.swift @@ -30,4 +30,13 @@ class NSDateComponentsTests: XCTestCase { XCTAssertEqual(2, comps.month) } + func testInitWithGlucoseData() { + let input = Data(hexadecimalString: "0bae0a0e")! + let comps = DateComponents(glucoseEventBytes: input) + XCTAssertEqual(2014, comps.year) + XCTAssertEqual(2, comps.month) + XCTAssertEqual(10, comps.day) + XCTAssertEqual(11, comps.hour) + XCTAssertEqual(46, comps.minute) + } } diff --git a/MinimedKitTests/NSStringExtensions.swift b/MinimedKitTests/NSStringExtensions.swift new file mode 100644 index 000000000..ad445a477 --- /dev/null +++ b/MinimedKitTests/NSStringExtensions.swift @@ -0,0 +1,20 @@ +// +// NSStringExtensions.swift +// RileyLink +// +// Created by Timothy Mecklem on 10/18/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import Foundation + +extension String { + func leftPadding(toLength: Int, withPad character: Character) -> String { + let newLength = self.characters.count + if newLength < toLength { + return String(repeatElement(character, count: toLength - newLength)) + self + } else { + return self.substring(from: index(self.startIndex, offsetBy: newLength - toLength)) + } + } +} diff --git a/NightscoutUploadKit/Either.swift b/NightscoutUploadKit/Either.swift new file mode 100644 index 000000000..16eb92c43 --- /dev/null +++ b/NightscoutUploadKit/Either.swift @@ -0,0 +1,14 @@ +// +// Either.swift +// RileyLink +// +// Created by Pete Schwamb on 10/9/16. +// Copyright © 2016 Pete Schwamb. All rights reserved. +// + +import Foundation + +public enum Either { + case success(T1) + case failure(T2) +} diff --git a/NightscoutUploadKit/Info.plist b/NightscoutUploadKit/Info.plist index 2ed8188bd..c66882b70 100644 --- a/NightscoutUploadKit/Info.plist +++ b/NightscoutUploadKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.12.5 + 0.12.6 CFBundleSignature ???? CFBundleVersion diff --git a/NightscoutUploadKit/NightscoutUploader.swift b/NightscoutUploadKit/NightscoutUploader.swift index a2bb6caa6..fda11ba07 100644 --- a/NightscoutUploadKit/NightscoutUploader.swift +++ b/NightscoutUploadKit/NightscoutUploader.swift @@ -13,12 +13,13 @@ import Crypto public enum UploadError: Error { case httpError(status: Int, body: String) case missingTimezone + case invalidResponse(reason: String) case unauthorized } -private let defaultNightscoutEntriesPath = "/api/v1/entries.json" -private let defaultNightscoutTreatmentPath = "/api/v1/treatments.json" -private let defaultNightscoutDeviceStatusPath = "/api/v1/devicestatus.json" +private let defaultNightscoutEntriesPath = "/api/v1/entries" +private let defaultNightscoutTreatmentPath = "/api/v1/treatments" +private let defaultNightscoutDeviceStatusPath = "/api/v1/devicestatus" private let defaultNightscoutAuthTestPath = "/api/v1/experiments/test" public class NightscoutUploader { @@ -51,6 +52,9 @@ public class NightscoutUploader { public var errorHandler: ((_ error: Error, _ context: String) -> Void)? + private var dataAccessQueue: DispatchQueue = DispatchQueue(label: "com.rileylink.NightscoutUploadKit.dataAccessQueue", attributes: []) + + public func reset() { observingPumpEventsSince = Date(timeIntervalSinceNow: TimeInterval(hours: -24)) lastStoredTreatmentTimestamp = nil @@ -119,7 +123,72 @@ public class NightscoutUploader { public func upload(_ pumpEvents: [TimestampedHistoryEvent], forSource source: String, from pumpModel: PumpModel, completionHandler: @escaping (Error?) -> Void) { let treatments = NightscoutPumpEvents.translate(pumpEvents, eventSource: source).map { $0.dictionaryRepresentation } - uploadToNS(treatments, endpoint: defaultNightscoutTreatmentPath, completion: completionHandler) + postToNS(treatments, endpoint: defaultNightscoutTreatmentPath) { (result) in + switch result { + case .success( _): + completionHandler(nil) + case .failure(let error): + completionHandler(error) + } + } + } + + /// Attempts to upload nightscout treatment objects. + /// This method will not retry if the network task failed. + /// + /// - parameter treatments: An array of nightscout treatments. + /// - 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(_ treatments: [NightscoutTreatment], completionHandler: @escaping (Either<[String],Error>) -> Void) { + postToNS(treatments.map { $0.dictionaryRepresentation }, endpoint: defaultNightscoutTreatmentPath, completion: completionHandler) + } + + /// Attempts to modify nightscout treatments. This method will not retry if the network task failed. + /// + /// - parameter treatments: An array of nightscout treatments. The id attribute must be set, identifying the treatment to update. + /// - parameter completionHandler: A closure to execute when the task completes. It has a single argument for any error that might have occurred during the modify. + public func modifyTreatments(_ treatments:[NightscoutTreatment], completionHandler: @escaping (Error?) -> Void) { + dataAccessQueue.async { + let modifyGroup = DispatchGroup() + var errors = [Error]() + + for treatment in treatments { + modifyGroup.enter() + self.putToNS( treatment.dictionaryRepresentation, endpoint: defaultNightscoutTreatmentPath ) { (error) in + if let error = error { + errors.append(error) + } + modifyGroup.leave() + } + } + + _ = modifyGroup.wait(timeout: DispatchTime.distantFuture) + completionHandler(errors.first) + } + + } + + /// Attempts to delete treatments from nightscout. This method will not retry if the network task failed. + /// + /// - parameter id: An array of nightscout treatment ids + /// - parameter completionHandler: A closure to execute when the task completes. It has a single argument for any error that might have occurred during the deletion. + public func deleteTreatmentsById(_ ids:[String], completionHandler: @escaping (Error?) -> Void) { + dataAccessQueue.async { + let deleteGroup = DispatchGroup() + var errors = [Error]() + + for id in ids { + deleteGroup.enter() + self.deleteFromNS(id, endpoint: defaultNightscoutTreatmentPath) { (error) in + if let error = error { + errors.append(error) + } + deleteGroup.leave() + } + } + + _ = deleteGroup.wait(timeout: DispatchTime.distantFuture) + completionHandler(errors.first) + } } public func uploadDeviceStatus(_ status: DeviceStatus) { @@ -225,50 +294,158 @@ public class NightscoutUploader { flushEntries() flushTreatments() } - - func uploadToNS(_ json: [Any], endpoint:String, completion: @escaping (Error?) -> Void) { + + func deleteFromNS(_ id: String, endpoint:String, completion: @escaping (Error?) -> Void) { + let resource = "\(endpoint)/\(id)" + callNS(nil, endpoint: resource, method: "DELETE") { (result) in + switch result { + case .success( _): + completion(nil) + case .failure(let error): + completion(error) + } + } + + } + + func putToNS(_ json: Any, endpoint:String, completion: @escaping (Error?) -> Void) { + callNS(json, endpoint: endpoint, method: "PUT") { (result) in + switch result { + case .success( _): + completion(nil) + case .failure(let error): + completion(error) + } + } + } + + func postToNS(_ json: [Any], endpoint:String, completion: @escaping (Either<[String],Error>) -> Void) { if json.count == 0 { - completion(nil) + completion(.success([])) return } - + + callNS(json, endpoint: endpoint, method: "POST") { (result) in + switch result { + case .success(let json): + guard let insertedEntries = json as? [[String: Any]] else { + completion(.failure(UploadError.invalidResponse(reason: "Expected array of objects in JSON response"))) + return + } + + let ids = insertedEntries.map({ (entry: [String: Any]) -> String in + if let id = entry["_id"] as? String { + return id + } else { + // Upload still succeeded; likely that this is an old version of NS + // Instead of failing (which would cause retries later, we just mark + // This entry has having an id of 'NA', which will let us consider it + // uploaded. + //throw UploadError.invalidResponse(reason: "Invalid/missing id in response.") + return "NA" + } + }) + completion(.success(ids)) + case .failure(let error): + completion(.failure(error)) + } + + } + } + + func callNS(_ json: Any?, endpoint:String, method:String, completion: @escaping (Either) -> Void) { let uploadURL = siteURL.appendingPathComponent(endpoint) var request = URLRequest(url: uploadURL) - request.httpMethod = "POST" + request.httpMethod = method request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue("application/json", forHTTPHeaderField: "Accept") request.setValue(apiSecret.sha1, forHTTPHeaderField: "api-secret") do { - let sendData = try JSONSerialization.data(withJSONObject: json, options: []) - let task = URLSession.shared.uploadTask(with: request, from: sendData, completionHandler: { (data, response, error) in - if let error = error { - completion(error) - return - } - - if let httpResponse = response as? HTTPURLResponse , - httpResponse.statusCode != 200 { - completion(UploadError.httpError(status: httpResponse.statusCode, body:String(data: data!, encoding: String.Encoding.utf8)!)) - } else { - completion(nil) - } - }) - task.resume() + if let json = json { + let sendData = try JSONSerialization.data(withJSONObject: json, options: []) + let task = URLSession.shared.uploadTask(with: request, from: sendData, completionHandler: { (data, response, error) in + if let error = error { + completion(.failure(error)) + return + } + + guard let httpResponse = response as? HTTPURLResponse else { + completion(.failure(UploadError.invalidResponse(reason: "Response is not HTTPURLResponse"))) + return + } + + if httpResponse.statusCode != 200 { + let error = UploadError.httpError(status: httpResponse.statusCode, body:String(data: data!, encoding: String.Encoding.utf8)!) + completion(.failure(error)) + return + } + + guard let data = data else { + completion(.failure(UploadError.invalidResponse(reason: "No data in response"))) + return + } + + do { + let json = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions()) + completion(.success(json)) + } catch { + completion(.failure(error)) + return + } + }) + task.resume() + } else { + let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in + if let error = error { + completion(.failure(error)) + return + } + + guard let httpResponse = response as? HTTPURLResponse else { + completion(.failure(UploadError.invalidResponse(reason: "Response is not HTTPURLResponse"))) + return + } + + if httpResponse.statusCode != 200 { + let error = UploadError.httpError(status: httpResponse.statusCode, body:String(data: data!, encoding: String.Encoding.utf8)!) + completion(.failure(error)) + return + } + + guard let data = data else { + completion(.failure(UploadError.invalidResponse(reason: "No data in response"))) + return + } + + do { + let json = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions()) + completion(.success(json)) + } catch { + completion(.failure(error)) + return + } + }) + task.resume() + } + } catch let error { - completion(error) + completion(.failure(error)) } } func flushDeviceStatuses() { let inFlight = deviceStatuses deviceStatuses = [] - uploadToNS(inFlight as [Any], endpoint: defaultNightscoutDeviceStatusPath) { (error) in - if let error = error { + postToNS(inFlight as [Any], endpoint: defaultNightscoutDeviceStatusPath) { (result) in + switch result { + case .failure(let error): self.errorHandler?(error, "Uploading device status") // Requeue self.deviceStatuses.append(contentsOf: inFlight) + case .success(_): + break } } } @@ -276,11 +453,14 @@ public class NightscoutUploader { func flushEntries() { let inFlight = entries entries = [] - uploadToNS(inFlight as [Any], endpoint: defaultNightscoutEntriesPath) { (error) in - if let error = error { + postToNS(inFlight as [Any], endpoint: defaultNightscoutEntriesPath) { (result) in + switch result { + case .failure(let error): self.errorHandler?(error, "Uploading nightscout entries") // Requeue self.entries.append(contentsOf: inFlight) + case .success(_): + break } } } @@ -288,12 +468,13 @@ public class NightscoutUploader { func flushTreatments() { let inFlight = treatmentsQueue treatmentsQueue = [] - uploadToNS(inFlight.map({$0.dictionaryRepresentation}), endpoint: defaultNightscoutTreatmentPath) { (error) in - if let error = error { + postToNS(inFlight.map({$0.dictionaryRepresentation}), endpoint: defaultNightscoutTreatmentPath) { (result) in + switch result { + case .failure(let error): self.errorHandler?(error, "Uploading nightscout treatment records") // Requeue self.treatmentsQueue.append(contentsOf: inFlight) - } else { + case .success(_): if let last = inFlight.last { self.lastStoredTreatmentTimestamp = last.timestamp } diff --git a/NightscoutUploadKit/Treatments/MealBolusNightscoutTreatment.swift b/NightscoutUploadKit/Treatments/MealBolusNightscoutTreatment.swift index 3c88077be..3112adce4 100644 --- a/NightscoutUploadKit/Treatments/MealBolusNightscoutTreatment.swift +++ b/NightscoutUploadKit/Treatments/MealBolusNightscoutTreatment.swift @@ -10,28 +10,36 @@ import Foundation public class MealBolusNightscoutTreatment: NightscoutTreatment { - let glucose: Int - let glucoseType: GlucoseType - let units: Units let carbs: Int - let insulin: Double - - init(timestamp: Date, enteredBy: String, glucose: Int, glucoseType: GlucoseType, units: Units, carbs: Int, insulin: Double) { + let absorptionTime: TimeInterval? + let insulin: Double? + let glucose: Int? + let units: Units? // of glucose entry + let glucoseType: GlucoseType? + + public init(timestamp: Date, enteredBy: String, id: String?, carbs: Int, absorptionTime: TimeInterval? = nil, insulin: Double? = nil, glucose: Int? = nil, glucoseType: GlucoseType? = nil, units: Units? = nil) { + self.carbs = carbs + self.absorptionTime = absorptionTime self.glucose = glucose self.glucoseType = glucoseType self.units = units self.insulin = insulin - self.carbs = carbs - super.init(timestamp: timestamp, enteredBy: enteredBy) + super.init(timestamp: timestamp, enteredBy: enteredBy, id: id) } override public var dictionaryRepresentation: [String: Any] { var rval = super.dictionaryRepresentation rval["eventType"] = "Meal Bolus" - rval["glucose"] = glucose - rval["glucoseType"] = glucoseType.rawValue - rval["units"] = units.rawValue + rval["carbs"] = carbs + if let absorptionTime = absorptionTime { + rval["absorptionTime"] = absorptionTime.minutes + } + rval["insulin"] = insulin + if let glucose = glucose { + rval["glucose"] = glucose + rval["glucoseType"] = glucoseType?.rawValue + rval["units"] = units?.rawValue + } return rval } - } diff --git a/NightscoutUploadKit/Treatments/NightscoutTreatment.swift b/NightscoutUploadKit/Treatments/NightscoutTreatment.swift index 61f20866c..96afdad4b 100644 --- a/NightscoutUploadKit/Treatments/NightscoutTreatment.swift +++ b/NightscoutUploadKit/Treatments/NightscoutTreatment.swift @@ -10,7 +10,7 @@ import MinimedKit public class NightscoutTreatment : DictionaryRepresentable { - enum GlucoseType: String { + public enum GlucoseType: String { case Meter case Sensor } @@ -22,17 +22,23 @@ public class NightscoutTreatment : DictionaryRepresentable { let timestamp: Date let enteredBy: String - - init(timestamp: Date, enteredBy: String) { + let id: String? + + init(timestamp: Date, enteredBy: String, id: String? = nil) { self.timestamp = timestamp self.enteredBy = enteredBy + self.id = id } public var dictionaryRepresentation: [String: Any] { - return [ + var rval = [ "created_at": TimeFormat.timestampStrFromDate(timestamp), "timestamp": TimeFormat.timestampStrFromDate(timestamp), "enteredBy": enteredBy, ] + if let id = id { + rval["_id"] = id + } + return rval } } diff --git a/NightscoutUploadKitTests/Info.plist b/NightscoutUploadKitTests/Info.plist index 8c56e9a00..d6de4eb78 100644 --- a/NightscoutUploadKitTests/Info.plist +++ b/NightscoutUploadKitTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 0.12.5 + 0.12.6 CFBundleSignature ???? CFBundleVersion diff --git a/RileyLink.xcodeproj/project.pbxproj b/RileyLink.xcodeproj/project.pbxproj index 9d4a33765..2d02d956f 100644 --- a/RileyLink.xcodeproj/project.pbxproj +++ b/RileyLink.xcodeproj/project.pbxproj @@ -86,6 +86,43 @@ 43EC9DCB1B786C6200DB0D18 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 43EC9DCA1B786C6200DB0D18 /* LaunchScreen.xib */; }; 43F348061D596270009933DC /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43F348051D596270009933DC /* HKUnit.swift */; }; 43FF221C1CB9B9DE00024F30 /* NSDateComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43FF221B1CB9B9DE00024F30 /* NSDateComponents.swift */; }; + 541688DB1DB820BF005B1891 /* ReadCurrentGlucosePageMessageBodyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541688DA1DB820BF005B1891 /* ReadCurrentGlucosePageMessageBodyTests.swift */; }; + 541688DD1DB82213005B1891 /* ReadCurrentGlucosePageMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541688DC1DB82213005B1891 /* ReadCurrentGlucosePageMessageBody.swift */; }; + 541688DF1DB82E72005B1891 /* TimestampedGlucoseEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541688DE1DB82E72005B1891 /* TimestampedGlucoseEvent.swift */; }; + 54A840D11DB85D0600B1F202 /* UnknownGlucoseEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A840D01DB85D0600B1F202 /* UnknownGlucoseEvent.swift */; }; + 54BC44731DB46A5200340EED /* GlucosePageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC44721DB46A5200340EED /* GlucosePageTests.swift */; }; + 54BC44751DB46B0A00340EED /* GlucosePage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC44741DB46B0A00340EED /* GlucosePage.swift */; }; + 54BC44781DB46C7D00340EED /* GlucoseEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC44771DB46C7D00340EED /* GlucoseEvent.swift */; }; + 54BC447C1DB4742F00340EED /* GlucoseEventType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC447B1DB4742F00340EED /* GlucoseEventType.swift */; }; + 54BC447E1DB4753A00340EED /* GlucoseSensorDataGlucoseEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC447D1DB4753A00340EED /* GlucoseSensorDataGlucoseEvent.swift */; }; + 54BC44801DB4762200340EED /* TenSomethingGlucoseEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC447F1DB4762200340EED /* TenSomethingGlucoseEvent.swift */; }; + 54BC44821DB476BB00340EED /* CalBGForGHGlucoseEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC44811DB476BB00340EED /* CalBGForGHGlucoseEvent.swift */; }; + 54BC44841DB476F600340EED /* SensorCalFactorGlucoseEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC44831DB476F600340EED /* SensorCalFactorGlucoseEvent.swift */; }; + 54BC44881DB47B5F00340EED /* DataEndGlucoseEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC44871DB47B5F00340EED /* DataEndGlucoseEvent.swift */; }; + 54BC448A1DB47BA500340EED /* SensorWeakSignalGlucoseEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC44891DB47BA500340EED /* SensorWeakSignalGlucoseEvent.swift */; }; + 54BC448C1DB47BEA00340EED /* SensorCalGlucoseEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC448B1DB47BEA00340EED /* SensorCalGlucoseEvent.swift */; }; + 54BC448E1DB47C1E00340EED /* Fokko7GlucoseEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC448D1DB47C1E00340EED /* Fokko7GlucoseEvent.swift */; }; + 54BC44901DB47C7400340EED /* SensorTimestampGlucoseEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC448F1DB47C7400340EED /* SensorTimestampGlucoseEvent.swift */; }; + 54BC44921DB47CBA00340EED /* BatteryChangeGlucoseEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC44911DB47CBA00340EED /* BatteryChangeGlucoseEvent.swift */; }; + 54BC44941DB47CFB00340EED /* SensorStatusGlucoseEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC44931DB47CFB00340EED /* SensorStatusGlucoseEvent.swift */; }; + 54BC44961DB47D2A00340EED /* DateTimeChangeGlucoseEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC44951DB47D2A00340EED /* DateTimeChangeGlucoseEvent.swift */; }; + 54BC44981DB47D5300340EED /* SensorSyncGlucoseEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC44971DB47D5300340EED /* SensorSyncGlucoseEvent.swift */; }; + 54BC449A1DB47DFE00340EED /* NineteenSomethingGlucoseEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC44991DB47DFE00340EED /* NineteenSomethingGlucoseEvent.swift */; }; + 54BC449C1DB483F700340EED /* RelativeTimestampedGlucoseEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC449B1DB483F700340EED /* RelativeTimestampedGlucoseEvent.swift */; }; + 54BC449E1DB484BD00340EED /* ReferenceTimestampedGlucoseEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC449D1DB484BD00340EED /* ReferenceTimestampedGlucoseEvent.swift */; }; + 54BC44A11DB6F74300340EED /* BatteryChangeGlucoseEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC44A01DB6F74300340EED /* BatteryChangeGlucoseEventTests.swift */; }; + 54BC44A31DB7021B00340EED /* SensorStatusGlucoseEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC44A21DB7021B00340EED /* SensorStatusGlucoseEventTests.swift */; }; + 54BC44A51DB702C800340EED /* DateTimeChangeGlucoseEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC44A41DB702C800340EED /* DateTimeChangeGlucoseEventTests.swift */; }; + 54BC44A71DB703E900340EED /* SensorSyncGlucoseEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC44A61DB703E900340EED /* SensorSyncGlucoseEventTests.swift */; }; + 54BC44A91DB704A600340EED /* CalBGForGHGlucoseEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC44A81DB704A600340EED /* CalBGForGHGlucoseEventTests.swift */; }; + 54BC44AB1DB7093700340EED /* SensorTimestampGlucoseEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC44AA1DB7093700340EED /* SensorTimestampGlucoseEventTests.swift */; }; + 54BC44AD1DB70A5E00340EED /* SensorCalFactorGlucoseEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC44AC1DB70A5E00340EED /* SensorCalFactorGlucoseEventTests.swift */; }; + 54BC44AF1DB70C3E00340EED /* TenSomethingGlucoseEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC44AE1DB70C3E00340EED /* TenSomethingGlucoseEventTests.swift */; }; + 54BC44B11DB70F4A00340EED /* GlucoseSensorDataGlucoseEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC44B01DB70F4A00340EED /* GlucoseSensorDataGlucoseEventTests.swift */; }; + 54BC44B31DB711BE00340EED /* SensorCalGlucoseEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC44B21DB711BE00340EED /* SensorCalGlucoseEventTests.swift */; }; + 54BC44B51DB7184D00340EED /* NSStringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC44B41DB7184D00340EED /* NSStringExtensions.swift */; }; + 54BC44B71DB81B5100340EED /* GetGlucosePageMessageBodyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC44B61DB81B5100340EED /* GetGlucosePageMessageBodyTests.swift */; }; + 54BC44B91DB81D6100340EED /* GetGlucosePageMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BC44B81DB81D6100340EED /* GetGlucosePageMessageBody.swift */; }; C10AB08D1C855613000F102E /* FindDeviceMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10AB08C1C855613000F102E /* FindDeviceMessageBody.swift */; }; C10AB08F1C855F34000F102E /* DeviceLinkMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10AB08E1C855F34000F102E /* DeviceLinkMessageBody.swift */; }; C10D9BC41C8269D500378342 /* MinimedKit.h in Headers */ = {isa = PBXBuildFile; fileRef = C10D9BC31C8269D500378342 /* MinimedKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -130,6 +167,7 @@ C12EA26A198B442100309FA4 /* Storyboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C12EA269198B442100309FA4 /* Storyboard.storyboard */; }; C133CF931D5943780034B82D /* PredictedBG.swift in Sources */ = {isa = PBXBuildFile; fileRef = C133CF921D5943780034B82D /* PredictedBG.swift */; }; C139AC241BFD84B500B0518F /* RuntimeUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = C139AC231BFD84B500B0518F /* RuntimeUtils.m */; }; + C13D155A1DAACE8400ADC044 /* Either.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13D15591DAACE8400ADC044 /* Either.swift */; }; C14303161C97C98000A40450 /* PumpAckMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14303151C97C98000A40450 /* PumpAckMessageBody.swift */; }; C14303181C97CC6B00A40450 /* GetPumpModelCarelinkMessageBodyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14303171C97CC6B00A40450 /* GetPumpModelCarelinkMessageBodyTests.swift */; }; C143031A1C9A610B00A40450 /* GetBatteryCarelinkMessageBodyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14303191C9A610B00A40450 /* GetBatteryCarelinkMessageBodyTests.swift */; }; @@ -469,6 +507,43 @@ 43EC9DCA1B786C6200DB0D18 /* LaunchScreen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LaunchScreen.xib; sourceTree = ""; }; 43F348051D596270009933DC /* HKUnit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HKUnit.swift; sourceTree = ""; }; 43FF221B1CB9B9DE00024F30 /* NSDateComponents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSDateComponents.swift; sourceTree = ""; }; + 541688DA1DB820BF005B1891 /* ReadCurrentGlucosePageMessageBodyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadCurrentGlucosePageMessageBodyTests.swift; sourceTree = ""; }; + 541688DC1DB82213005B1891 /* ReadCurrentGlucosePageMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadCurrentGlucosePageMessageBody.swift; sourceTree = ""; }; + 541688DE1DB82E72005B1891 /* TimestampedGlucoseEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimestampedGlucoseEvent.swift; sourceTree = ""; }; + 54A840D01DB85D0600B1F202 /* UnknownGlucoseEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnknownGlucoseEvent.swift; sourceTree = ""; }; + 54BC44721DB46A5200340EED /* GlucosePageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucosePageTests.swift; sourceTree = ""; }; + 54BC44741DB46B0A00340EED /* GlucosePage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucosePage.swift; sourceTree = ""; }; + 54BC44771DB46C7D00340EED /* GlucoseEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseEvent.swift; sourceTree = ""; }; + 54BC447B1DB4742F00340EED /* GlucoseEventType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseEventType.swift; sourceTree = ""; }; + 54BC447D1DB4753A00340EED /* GlucoseSensorDataGlucoseEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseSensorDataGlucoseEvent.swift; sourceTree = ""; }; + 54BC447F1DB4762200340EED /* TenSomethingGlucoseEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TenSomethingGlucoseEvent.swift; sourceTree = ""; }; + 54BC44811DB476BB00340EED /* CalBGForGHGlucoseEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CalBGForGHGlucoseEvent.swift; sourceTree = ""; }; + 54BC44831DB476F600340EED /* SensorCalFactorGlucoseEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SensorCalFactorGlucoseEvent.swift; sourceTree = ""; }; + 54BC44871DB47B5F00340EED /* DataEndGlucoseEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataEndGlucoseEvent.swift; sourceTree = ""; }; + 54BC44891DB47BA500340EED /* SensorWeakSignalGlucoseEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SensorWeakSignalGlucoseEvent.swift; sourceTree = ""; }; + 54BC448B1DB47BEA00340EED /* SensorCalGlucoseEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SensorCalGlucoseEvent.swift; sourceTree = ""; }; + 54BC448D1DB47C1E00340EED /* Fokko7GlucoseEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fokko7GlucoseEvent.swift; sourceTree = ""; }; + 54BC448F1DB47C7400340EED /* SensorTimestampGlucoseEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SensorTimestampGlucoseEvent.swift; sourceTree = ""; }; + 54BC44911DB47CBA00340EED /* BatteryChangeGlucoseEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BatteryChangeGlucoseEvent.swift; sourceTree = ""; }; + 54BC44931DB47CFB00340EED /* SensorStatusGlucoseEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SensorStatusGlucoseEvent.swift; sourceTree = ""; }; + 54BC44951DB47D2A00340EED /* DateTimeChangeGlucoseEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateTimeChangeGlucoseEvent.swift; sourceTree = ""; }; + 54BC44971DB47D5300340EED /* SensorSyncGlucoseEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SensorSyncGlucoseEvent.swift; sourceTree = ""; }; + 54BC44991DB47DFE00340EED /* NineteenSomethingGlucoseEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NineteenSomethingGlucoseEvent.swift; sourceTree = ""; }; + 54BC449B1DB483F700340EED /* RelativeTimestampedGlucoseEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RelativeTimestampedGlucoseEvent.swift; sourceTree = ""; }; + 54BC449D1DB484BD00340EED /* ReferenceTimestampedGlucoseEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReferenceTimestampedGlucoseEvent.swift; sourceTree = ""; }; + 54BC44A01DB6F74300340EED /* BatteryChangeGlucoseEventTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BatteryChangeGlucoseEventTests.swift; sourceTree = ""; }; + 54BC44A21DB7021B00340EED /* SensorStatusGlucoseEventTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SensorStatusGlucoseEventTests.swift; sourceTree = ""; }; + 54BC44A41DB702C800340EED /* DateTimeChangeGlucoseEventTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateTimeChangeGlucoseEventTests.swift; sourceTree = ""; }; + 54BC44A61DB703E900340EED /* SensorSyncGlucoseEventTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SensorSyncGlucoseEventTests.swift; sourceTree = ""; }; + 54BC44A81DB704A600340EED /* CalBGForGHGlucoseEventTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CalBGForGHGlucoseEventTests.swift; sourceTree = ""; }; + 54BC44AA1DB7093700340EED /* SensorTimestampGlucoseEventTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SensorTimestampGlucoseEventTests.swift; sourceTree = ""; }; + 54BC44AC1DB70A5E00340EED /* SensorCalFactorGlucoseEventTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SensorCalFactorGlucoseEventTests.swift; sourceTree = ""; }; + 54BC44AE1DB70C3E00340EED /* TenSomethingGlucoseEventTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TenSomethingGlucoseEventTests.swift; sourceTree = ""; }; + 54BC44B01DB70F4A00340EED /* GlucoseSensorDataGlucoseEventTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseSensorDataGlucoseEventTests.swift; sourceTree = ""; }; + 54BC44B21DB711BE00340EED /* SensorCalGlucoseEventTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SensorCalGlucoseEventTests.swift; sourceTree = ""; }; + 54BC44B41DB7184D00340EED /* NSStringExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSStringExtensions.swift; sourceTree = ""; }; + 54BC44B61DB81B5100340EED /* GetGlucosePageMessageBodyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetGlucosePageMessageBodyTests.swift; sourceTree = ""; }; + 54BC44B81DB81D6100340EED /* GetGlucosePageMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetGlucosePageMessageBody.swift; sourceTree = ""; }; C10AB08C1C855613000F102E /* FindDeviceMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FindDeviceMessageBody.swift; sourceTree = ""; }; C10AB08E1C855F34000F102E /* DeviceLinkMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceLinkMessageBody.swift; sourceTree = ""; }; C10D9BC11C8269D500378342 /* MinimedKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MinimedKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -524,6 +599,7 @@ C133CF921D5943780034B82D /* PredictedBG.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PredictedBG.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 = ""; }; + C13D15591DAACE8400ADC044 /* Either.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Either.swift; sourceTree = ""; }; C14303151C97C98000A40450 /* PumpAckMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PumpAckMessageBody.swift; sourceTree = ""; }; C14303171C97CC6B00A40450 /* GetPumpModelCarelinkMessageBodyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetPumpModelCarelinkMessageBodyTests.swift; sourceTree = ""; }; C14303191C9A610B00A40450 /* GetBatteryCarelinkMessageBodyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetBatteryCarelinkMessageBodyTests.swift; sourceTree = ""; }; @@ -862,15 +938,60 @@ path = Crypto; sourceTree = ""; }; + 54BC44761DB46C3100340EED /* GlucoseEvents */ = { + isa = PBXGroup; + children = ( + 54BC44911DB47CBA00340EED /* BatteryChangeGlucoseEvent.swift */, + 54BC44811DB476BB00340EED /* CalBGForGHGlucoseEvent.swift */, + 54BC44871DB47B5F00340EED /* DataEndGlucoseEvent.swift */, + 54BC44951DB47D2A00340EED /* DateTimeChangeGlucoseEvent.swift */, + 54BC448D1DB47C1E00340EED /* Fokko7GlucoseEvent.swift */, + 54BC44771DB46C7D00340EED /* GlucoseEvent.swift */, + 54BC447D1DB4753A00340EED /* GlucoseSensorDataGlucoseEvent.swift */, + 54BC44991DB47DFE00340EED /* NineteenSomethingGlucoseEvent.swift */, + 54BC449D1DB484BD00340EED /* ReferenceTimestampedGlucoseEvent.swift */, + 54BC449B1DB483F700340EED /* RelativeTimestampedGlucoseEvent.swift */, + 54BC44831DB476F600340EED /* SensorCalFactorGlucoseEvent.swift */, + 54BC448B1DB47BEA00340EED /* SensorCalGlucoseEvent.swift */, + 54BC44931DB47CFB00340EED /* SensorStatusGlucoseEvent.swift */, + 54BC44971DB47D5300340EED /* SensorSyncGlucoseEvent.swift */, + 54BC448F1DB47C7400340EED /* SensorTimestampGlucoseEvent.swift */, + 54BC44891DB47BA500340EED /* SensorWeakSignalGlucoseEvent.swift */, + 54BC447F1DB4762200340EED /* TenSomethingGlucoseEvent.swift */, + 54A840D01DB85D0600B1F202 /* UnknownGlucoseEvent.swift */, + ); + path = GlucoseEvents; + sourceTree = ""; + }; + 54BC449F1DB6F6C100340EED /* GlucoseEvents */ = { + isa = PBXGroup; + children = ( + 54BC44A01DB6F74300340EED /* BatteryChangeGlucoseEventTests.swift */, + 54BC44A81DB704A600340EED /* CalBGForGHGlucoseEventTests.swift */, + 54BC44A41DB702C800340EED /* DateTimeChangeGlucoseEventTests.swift */, + 54BC44B01DB70F4A00340EED /* GlucoseSensorDataGlucoseEventTests.swift */, + 54BC44AC1DB70A5E00340EED /* SensorCalFactorGlucoseEventTests.swift */, + 54BC44B21DB711BE00340EED /* SensorCalGlucoseEventTests.swift */, + 54BC44A21DB7021B00340EED /* SensorStatusGlucoseEventTests.swift */, + 54BC44A61DB703E900340EED /* SensorSyncGlucoseEventTests.swift */, + 54BC44AA1DB7093700340EED /* SensorTimestampGlucoseEventTests.swift */, + 54BC44AE1DB70C3E00340EED /* TenSomethingGlucoseEventTests.swift */, + ); + path = GlucoseEvents; + sourceTree = ""; + }; C10D9BC21C8269D500378342 /* MinimedKit */ = { isa = PBXGroup; children = ( C1EAD6B81C826B92006DBA60 /* Extensions */, C1EAD6BC1C826B92006DBA60 /* Messages */, + 54BC44761DB46C3100340EED /* GlucoseEvents */, C1842BB91C8E15C600DB42AC /* PumpEvents */, C1EAD6AE1C826B6D006DBA60 /* AlertType.swift */, C1EAD6DD1C82B78C006DBA60 /* CRC8.swift */, C1EAD6E11C82BA7A006DBA60 /* CRC16.swift */, + 54BC447B1DB4742F00340EED /* GlucoseEventType.swift */, + 54BC44741DB46B0A00340EED /* GlucosePage.swift */, C1EB955C1C887FE5002517DF /* HistoryPage.swift */, C10D9BC51C8269D500378342 /* Info.plist */, C1EAD6AF1C826B6D006DBA60 /* MessageBody.swift */, @@ -884,6 +1005,7 @@ C1274F851D8242BE0002912B /* PumpRegion.swift */, C1EAD6D91C829104006DBA60 /* RFTools.swift */, 43B0ADC11D12454700AAD278 /* TimestampedHistoryEvent.swift */, + 541688DE1DB82E72005B1891 /* TimestampedGlucoseEvent.swift */, ); path = MinimedKit; sourceTree = ""; @@ -891,17 +1013,20 @@ C10D9BD01C8269D500378342 /* MinimedKitTests */ = { isa = PBXGroup; children = ( - C10D9BD31C8269D500378342 /* Info.plist */, + 54BC449F1DB6F6C100340EED /* GlucoseEvents */, + C121985D1C8DE72500BC374C /* Messages */, C1EAD6DF1C82B910006DBA60 /* CRC8Tests.swift */, C1EAD6E31C82BA87006DBA60 /* CRC16Tests.swift */, + 54BC44721DB46A5200340EED /* GlucosePageTests.swift */, C12198621C8DF4C800BC374C /* HistoryPageTests.swift */, + C10D9BD31C8269D500378342 /* Info.plist */, C1C357901C92733A009BDD4F /* MeterMessageTests.swift */, C1EAD6D11C826C43006DBA60 /* MinimedKitTests.swift */, C1EAD6D31C826C43006DBA60 /* NSDataTests.swift */, 43FF221B1CB9B9DE00024F30 /* NSDateComponents.swift */, 43B0ADBF1D0FC03200AAD278 /* NSDateComponentsTests.swift */, C1EAD6DB1C82A4AB006DBA60 /* RFToolsTests.swift */, - C121985D1C8DE72500BC374C /* Messages */, + 54BC44B41DB7184D00340EED /* NSStringExtensions.swift */, ); path = MinimedKitTests; sourceTree = ""; @@ -915,11 +1040,13 @@ C12198601C8DEB7B00BC374C /* DeviceLinkMessageBodyTests.swift */, C121985E1C8DE77D00BC374C /* FindDeviceMessageBodyTests.swift */, C14303191C9A610B00A40450 /* GetBatteryCarelinkMessageBodyTests.swift */, + 54BC44B61DB81B5100340EED /* GetGlucosePageMessageBodyTests.swift */, C14303171C97CC6B00A40450 /* GetPumpModelCarelinkMessageBodyTests.swift */, C1EAD6D21C826C43006DBA60 /* MySentryPumpStatusMessageBodyTests.swift */, + 43A068EB1CF6BA6900F9EFE4 /* ReadRemainingInsulinMessageBodyTests.swift */, C1EAD6D41C826C43006DBA60 /* ReadSettingsCarelinkMessageBodyTests.swift */, 43CA93301CB97191000026B5 /* ReadTempBasalCarelinkMessageBodyTests.swift */, - 43A068EB1CF6BA6900F9EFE4 /* ReadRemainingInsulinMessageBodyTests.swift */, + 541688DA1DB820BF005B1891 /* ReadCurrentGlucosePageMessageBodyTests.swift */, ); path = Messages; sourceTree = ""; @@ -1227,6 +1354,7 @@ children = ( C1B3830D1CD0665D00CE7782 /* NightscoutUploadKit.h */, C1B3830F1CD0665D00CE7782 /* Info.plist */, + C13D15591DAACE8400ADC044 /* Either.swift */, 43F348051D596270009933DC /* HKUnit.swift */, C1842C2A1C90DFB600DB42AC /* NightscoutPumpEvents.swift */, C1842C281C908A3C00DB42AC /* NightscoutUploader.swift */, @@ -1295,6 +1423,7 @@ C10AB08E1C855F34000F102E /* DeviceLinkMessageBody.swift */, C10AB08C1C855613000F102E /* FindDeviceMessageBody.swift */, C1711A5B1C953F3000CB25BD /* GetBatteryCarelinkMessageBody.swift */, + 54BC44B81DB81D6100340EED /* GetGlucosePageMessageBody.swift */, C1711A5D1C977BD000CB25BD /* GetHistoryPageCarelinkMessageBody.swift */, C1711A591C952D2900CB25BD /* GetPumpModelCarelinkMessageBody.swift */, C1EAD6BE1C826B92006DBA60 /* MySentryAckMessageBody.swift */, @@ -1303,12 +1432,13 @@ C1EAD6C11C826B92006DBA60 /* MySentryPumpStatusMessageBody.swift */, C1EAD6C21C826B92006DBA60 /* PowerOnCarelinkMessageBody.swift */, C14303151C97C98000A40450 /* PumpAckMessageBody.swift */, + C178845C1D4EF3D800405663 /* ReadPumpStatusMessageBody.swift */, 433568751CF67FA800FD9D54 /* ReadRemainingInsulinMessageBody.swift */, C1EAD6C31C826B92006DBA60 /* ReadSettingsCarelinkMessageBody.swift */, 43CA932C1CB8CFA1000026B5 /* ReadTempBasalCarelinkMessageBody.swift */, 43CA932D1CB8CFA1000026B5 /* ReadTimeCarelinkMessageBody.swift */, C1EAD6C41C826B92006DBA60 /* UnknownMessageBody.swift */, - C178845C1D4EF3D800405663 /* ReadPumpStatusMessageBody.swift */, + 541688DC1DB82213005B1891 /* ReadCurrentGlucosePageMessageBody.swift */, ); path = Messages; sourceTree = ""; @@ -1819,6 +1949,7 @@ C1842BD11C8FA3D200DB42AC /* AlarmClockReminderPumpEvent.swift in Sources */, C1842C1B1C8FA45100DB42AC /* ChangeBolusReminderEnablePumpEvent.swift in Sources */, C1842BC51C8F897E00DB42AC /* BasalProfileStartPumpEvent.swift in Sources */, + 54BC448C1DB47BEA00340EED /* SensorCalGlucoseEvent.swift in Sources */, C14FFC4A1D3AF1AC0049CF85 /* JournalEntryInsulinMarkerPumpEvent.swift in Sources */, C1842C081C8FA45100DB42AC /* ChangeWatchdogMarriageProfilePumpEvent.swift in Sources */, C1842BFF1C8FA45100DB42AC /* DailyTotal522PumpEvent.swift in Sources */, @@ -1828,9 +1959,12 @@ C1842BC11C8E8B2500DB42AC /* UnabsorbedInsulinPumpEvent.swift in Sources */, C1EAD6B51C826B6D006DBA60 /* MessageType.swift in Sources */, C1842C111C8FA45100DB42AC /* ChangeParadigmLinkIDPumpEvent.swift in Sources */, + 54BC44981DB47D5300340EED /* SensorSyncGlucoseEvent.swift in Sources */, + 54BC448A1DB47BA500340EED /* SensorWeakSignalGlucoseEvent.swift in Sources */, C1C3578F1C927303009BDD4F /* MeterMessage.swift in Sources */, C1842BFC1C8FA45100DB42AC /* SuspendPumpEvent.swift in Sources */, C1842C131C8FA45100DB42AC /* ChangeMaxBolusPumpEvent.swift in Sources */, + 54BC44901DB47C7400340EED /* SensorTimestampGlucoseEvent.swift in Sources */, C18C8C531D64123400E043FB /* EnableBolusWizardPumpEvent.swift in Sources */, C1EAD6B61C826B6D006DBA60 /* PacketType.swift in Sources */, C1842C041C8FA45100DB42AC /* DeleteOtherDeviceIDPumpEvent.swift in Sources */, @@ -1838,24 +1972,31 @@ C1842C0B1C8FA45100DB42AC /* ChangeTimePumpEvent.swift in Sources */, C1842C0D1C8FA45100DB42AC /* TempBasalPumpEvent.swift in Sources */, C178845D1D4EF3D800405663 /* ReadPumpStatusMessageBody.swift in Sources */, + 54BC447C1DB4742F00340EED /* GlucoseEventType.swift in Sources */, 43B0ADCB1D126B1100AAD278 /* SelectBasalProfilePumpEvent.swift in Sources */, C1842C0C1C8FA45100DB42AC /* ChangeTimeFormatPumpEvent.swift in Sources */, C1274F861D8242BE0002912B /* PumpRegion.swift in Sources */, C15AF2AF1D7498930031FC9D /* RestoreMystery54PumpEvent.swift in Sources */, C10AB08D1C855613000F102E /* FindDeviceMessageBody.swift in Sources */, + 54BC449E1DB484BD00340EED /* ReferenceTimestampedGlucoseEvent.swift in Sources */, C1711A5A1C952D2900CB25BD /* GetPumpModelCarelinkMessageBody.swift in Sources */, C1EB955D1C887FE5002517DF /* HistoryPage.swift in Sources */, + 54BC44921DB47CBA00340EED /* BatteryChangeGlucoseEvent.swift in Sources */, C1EAD6CA1C826B92006DBA60 /* MySentryAlertClearedMessageBody.swift in Sources */, C1842C181C8FA45100DB42AC /* BolusWizardSetupPumpEvent.swift in Sources */, C1274F791D823A550002912B /* ChangeMeterIDPumpEvent.swift in Sources */, C1842C201C8FA45100DB42AC /* ChangeAudioBolusPumpEvent.swift in Sources */, + 54BC44881DB47B5F00340EED /* DataEndGlucoseEvent.swift in Sources */, C1EAD6B31C826B6D006DBA60 /* AlertType.swift in Sources */, C1842C051C8FA45100DB42AC /* DeleteBolusReminderTimePumpEvent.swift in Sources */, C1711A5C1C953F3000CB25BD /* GetBatteryCarelinkMessageBody.swift in Sources */, C1842C1D1C8FA45100DB42AC /* ChangeBGReminderEnablePumpEvent.swift in Sources */, + 54BC448E1DB47C1E00340EED /* Fokko7GlucoseEvent.swift in Sources */, + 54BC44961DB47D2A00340EED /* DateTimeChangeGlucoseEvent.swift in Sources */, C1842C061C8FA45100DB42AC /* DeleteAlarmClockTimePumpEvent.swift in Sources */, C10AB08F1C855F34000F102E /* DeviceLinkMessageBody.swift in Sources */, C15AF2B11D7498DD0031FC9D /* RestoreMystery55PumpEvent.swift in Sources */, + 54BC449C1DB483F700340EED /* RelativeTimestampedGlucoseEvent.swift in Sources */, C1842C091C8FA45100DB42AC /* ChangeWatchdogEnablePumpEvent.swift in Sources */, C1842BC91C8F968B00DB42AC /* BGReceivedPumpEvent.swift in Sources */, C1842C0F1C8FA45100DB42AC /* ChangeSensorRateOfChangeAlertSetupPumpEvent.swift in Sources */, @@ -1866,7 +2007,9 @@ C1EAD6C81C826B92006DBA60 /* CarelinkMessageBody.swift in Sources */, C1842C121C8FA45100DB42AC /* ChangeOtherDeviceIDPumpEvent.swift in Sources */, 433568761CF67FA800FD9D54 /* ReadRemainingInsulinMessageBody.swift in Sources */, + 54BC44941DB47CFB00340EED /* SensorStatusGlucoseEvent.swift in Sources */, C1842C231C8FA45100DB42AC /* ChangeAlarmClockEnablePumpEvent.swift in Sources */, + 54BC44821DB476BB00340EED /* CalBGForGHGlucoseEvent.swift in Sources */, C1EAD6CF1C826B92006DBA60 /* UnknownMessageBody.swift in Sources */, C1842C161C8FA45100DB42AC /* ChangeCarbUnitsPumpEvent.swift in Sources */, C1842C241C8FA45100DB42AC /* BatteryPumpEvent.swift in Sources */, @@ -1874,6 +2017,7 @@ C1711A561C94F13400CB25BD /* ButtonPressCarelinkMessageBody.swift in Sources */, 43D657451D0CF1D500216E20 /* NSTimeInterval.swift in Sources */, C1842C011C8FA45100DB42AC /* JournalEntryPumpLowBatteryPumpEvent.swift in Sources */, + 54BC447E1DB4753A00340EED /* GlucoseSensorDataGlucoseEvent.swift in Sources */, C1842BBD1C8E7C6E00DB42AC /* PumpEvent.swift in Sources */, C1842BCB1C8F9A7200DB42AC /* RewindPumpEvent.swift in Sources */, C1842BCD1C8F9BBD00DB42AC /* PrimePumpEvent.swift in Sources */, @@ -1882,11 +2026,15 @@ C1EAD6C91C826B92006DBA60 /* MySentryAckMessageBody.swift in Sources */, C1EAD6CE1C826B92006DBA60 /* ReadSettingsCarelinkMessageBody.swift in Sources */, C12198AD1C8F332500BC374C /* TimestampedPumpEvent.swift in Sources */, + 54BC44751DB46B0A00340EED /* GlucosePage.swift in Sources */, C1EAD6DE1C82B78C006DBA60 /* CRC8.swift in Sources */, C1EAD6C51C826B92006DBA60 /* Int.swift in Sources */, C1842C021C8FA45100DB42AC /* JournalEntryExerciseMarkerPumpEvent.swift in Sources */, + 541688DF1DB82E72005B1891 /* TimestampedGlucoseEvent.swift in Sources */, C1EAD6CB1C826B92006DBA60 /* MySentryAlertMessageBody.swift in Sources */, + 54BC449A1DB47DFE00340EED /* NineteenSomethingGlucoseEvent.swift in Sources */, C15AF2AD1D74929D0031FC9D /* ChangeBolusWizardSetupPumpEvent.swift in Sources */, + 54BC44801DB4762200340EED /* TenSomethingGlucoseEvent.swift in Sources */, C1842BC71C8F8DC200DB42AC /* CalBGForPHPumpEvent.swift in Sources */, C1842C251C8FA45100DB42AC /* AlarmSensorPumpEvent.swift in Sources */, C1842BBB1C8E184300DB42AC /* PumpModel.swift in Sources */, @@ -1897,11 +2045,14 @@ 438D39221D19011700D40CA4 /* PlaceholderPumpEvent.swift in Sources */, C1EAD6B41C826B6D006DBA60 /* MessageBody.swift in Sources */, C1842C001C8FA45100DB42AC /* JournalEntryPumpLowReservoirPumpEvent.swift in Sources */, + 54A840D11DB85D0600B1F202 /* UnknownGlucoseEvent.swift in Sources */, C1842BCF1C8F9E5100DB42AC /* PumpAlarmPumpEvent.swift in Sources */, C1EAD6C61C826B92006DBA60 /* NSData.swift in Sources */, C1842C211C8FA45100DB42AC /* ChangeAlarmNotifyModePumpEvent.swift in Sources */, C1842BC31C8E931E00DB42AC /* BolusNormalPumpEvent.swift in Sources */, + 541688DD1DB82213005B1891 /* ReadCurrentGlucosePageMessageBody.swift in Sources */, 43CA932F1CB8CFA1000026B5 /* ReadTimeCarelinkMessageBody.swift in Sources */, + 54BC44781DB46C7D00340EED /* GlucoseEvent.swift in Sources */, C1842C221C8FA45100DB42AC /* ChangeAlarmClockTimePumpEvent.swift in Sources */, C1842BFD1C8FA45100DB42AC /* ResumePumpEvent.swift in Sources */, C1EAD6CC1C826B92006DBA60 /* MySentryPumpStatusMessageBody.swift in Sources */, @@ -1917,6 +2068,7 @@ C1842C0A1C8FA45100DB42AC /* ChangeVariableBolusPumpEvent.swift in Sources */, C1842C191C8FA45100DB42AC /* ChangeBolusScrollStepSizePumpEvent.swift in Sources */, C1842C171C8FA45100DB42AC /* ChangeCaptureEventEnablePumpEvent.swift in Sources */, + 54BC44B91DB81D6100340EED /* GetGlucosePageMessageBody.swift in Sources */, 43B0ADC41D12506A00AAD278 /* NSDateFormatter.swift in Sources */, C1EAD6DA1C829104006DBA60 /* RFTools.swift in Sources */, 43CA932B1CB8CF76000026B5 /* ChangeTimeCarelinkMessageBody.swift in Sources */, @@ -1924,6 +2076,7 @@ C1842C031C8FA45100DB42AC /* EnableDisableRemotePumpEvent.swift in Sources */, C1EAD6B71C826B6D006DBA60 /* PumpMessage.swift in Sources */, C12198A91C8F2AF200BC374C /* DailyTotal523PumpEvent.swift in Sources */, + 54BC44841DB476F600340EED /* SensorCalFactorGlucoseEvent.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1932,22 +2085,36 @@ buildActionMask = 2147483647; files = ( C12198631C8DF4C800BC374C /* HistoryPageTests.swift in Sources */, + 54BC44A71DB703E900340EED /* SensorSyncGlucoseEventTests.swift in Sources */, + 54BC44A11DB6F74300340EED /* BatteryChangeGlucoseEventTests.swift in Sources */, + 54BC44A51DB702C800340EED /* DateTimeChangeGlucoseEventTests.swift in Sources */, + 54BC44B31DB711BE00340EED /* SensorCalGlucoseEventTests.swift in Sources */, + 54BC44B11DB70F4A00340EED /* GlucoseSensorDataGlucoseEventTests.swift in Sources */, 43A068EC1CF6BA6900F9EFE4 /* ReadRemainingInsulinMessageBodyTests.swift in Sources */, C1EAD6D61C826C43006DBA60 /* MySentryPumpStatusMessageBodyTests.swift in Sources */, C1EAD6D81C826C43006DBA60 /* ReadSettingsCarelinkMessageBodyTests.swift in Sources */, 43B0ADC01D0FC03200AAD278 /* NSDateComponentsTests.swift in Sources */, C1C357911C92733A009BDD4F /* MeterMessageTests.swift in Sources */, + 54BC44AD1DB70A5E00340EED /* SensorCalFactorGlucoseEventTests.swift in Sources */, C12198611C8DEB7B00BC374C /* DeviceLinkMessageBodyTests.swift in Sources */, C14303181C97CC6B00A40450 /* GetPumpModelCarelinkMessageBodyTests.swift in Sources */, + 54BC44A31DB7021B00340EED /* SensorStatusGlucoseEventTests.swift in Sources */, C1EAD6E41C82BA87006DBA60 /* CRC16Tests.swift in Sources */, 43CA93311CB97191000026B5 /* ReadTempBasalCarelinkMessageBodyTests.swift in Sources */, + 54BC44A91DB704A600340EED /* CalBGForGHGlucoseEventTests.swift in Sources */, 43FF221C1CB9B9DE00024F30 /* NSDateComponents.swift in Sources */, C12198A31C8DFC3600BC374C /* BolusCarelinkMessageBodyTests.swift in Sources */, C121985F1C8DE77D00BC374C /* FindDeviceMessageBodyTests.swift in Sources */, + 541688DB1DB820BF005B1891 /* ReadCurrentGlucosePageMessageBodyTests.swift in Sources */, C1EAD6D71C826C43006DBA60 /* NSDataTests.swift in Sources */, + 54BC44731DB46A5200340EED /* GlucosePageTests.swift in Sources */, + 54BC44AF1DB70C3E00340EED /* TenSomethingGlucoseEventTests.swift in Sources */, C143031A1C9A610B00A40450 /* GetBatteryCarelinkMessageBodyTests.swift in Sources */, 43CA93351CB9727F000026B5 /* ChangeTempBasalCarelinkMessageBodyTests.swift in Sources */, + 54BC44B71DB81B5100340EED /* GetGlucosePageMessageBodyTests.swift in Sources */, C1EAD6E01C82B910006DBA60 /* CRC8Tests.swift in Sources */, + 54BC44B51DB7184D00340EED /* NSStringExtensions.swift in Sources */, + 54BC44AB1DB7093700340EED /* SensorTimestampGlucoseEventTests.swift in Sources */, 43CA93331CB9726A000026B5 /* ChangeTimeCarelinMessageBodyTests.swift in Sources */, C1EAD6DC1C82A4AB006DBA60 /* RFToolsTests.swift in Sources */, C1EAD6D51C826C43006DBA60 /* MinimedKitTests.swift in Sources */, @@ -2025,6 +2192,7 @@ 43D657461D0CF38F00216E20 /* NSTimeInterval.swift in Sources */, C133CF931D5943780034B82D /* PredictedBG.swift in Sources */, C1AF21E41D4865320088C41D /* LoopStatus.swift in Sources */, + C13D155A1DAACE8400ADC044 /* Either.swift in Sources */, C1AF21F11D4901220088C41D /* NightscoutTreatment.swift in Sources */, C1A492671D4A65D9008964FF /* RecommendedTempBasal.swift in Sources */, C178845F1D5166BE00405663 /* COBStatus.swift in Sources */, @@ -2136,11 +2304,11 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - CURRENT_PROJECT_VERSION = 22; + CURRENT_PROJECT_VERSION = 23; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 22; + DYLIB_CURRENT_VERSION = 23; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; @@ -2164,11 +2332,11 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 22; + CURRENT_PROJECT_VERSION = 23; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 22; + DYLIB_CURRENT_VERSION = 23; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; @@ -2227,11 +2395,11 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - CURRENT_PROJECT_VERSION = 22; + CURRENT_PROJECT_VERSION = 23; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 22; + DYLIB_CURRENT_VERSION = 23; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; @@ -2258,11 +2426,11 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 22; + CURRENT_PROJECT_VERSION = 23; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 22; + DYLIB_CURRENT_VERSION = 23; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; @@ -2325,12 +2493,12 @@ CLANG_WARN_SUSPICIOUS_MOVES = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 22; + CURRENT_PROJECT_VERSION = 23; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 22; + DYLIB_CURRENT_VERSION = 23; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Crypto/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -2353,12 +2521,12 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 22; + CURRENT_PROJECT_VERSION = 23; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 22; + DYLIB_CURRENT_VERSION = 23; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Crypto/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -2379,11 +2547,11 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - CURRENT_PROJECT_VERSION = 22; + CURRENT_PROJECT_VERSION = 23; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 22; + DYLIB_CURRENT_VERSION = 23; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; @@ -2409,11 +2577,11 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 22; + CURRENT_PROJECT_VERSION = 23; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 22; + DYLIB_CURRENT_VERSION = 23; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; @@ -2488,7 +2656,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 22; + CURRENT_PROJECT_VERSION = 23; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; @@ -2535,7 +2703,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 22; + CURRENT_PROJECT_VERSION = 23; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; @@ -2639,11 +2807,11 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - CURRENT_PROJECT_VERSION = 22; + CURRENT_PROJECT_VERSION = 23; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 22; + DYLIB_CURRENT_VERSION = 23; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; @@ -2669,11 +2837,11 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 22; + CURRENT_PROJECT_VERSION = 23; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 22; + DYLIB_CURRENT_VERSION = 23; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; diff --git a/RileyLink/RileyLink-Info.plist b/RileyLink/RileyLink-Info.plist index 0312a807b..63e3011c7 100644 --- a/RileyLink/RileyLink-Info.plist +++ b/RileyLink/RileyLink-Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.12.5 + 0.12.6 CFBundleSignature ???? CFBundleVersion diff --git a/RileyLinkBLEKit/Info.plist b/RileyLinkBLEKit/Info.plist index 2ed8188bd..c66882b70 100644 --- a/RileyLinkBLEKit/Info.plist +++ b/RileyLinkBLEKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.12.5 + 0.12.6 CFBundleSignature ???? CFBundleVersion diff --git a/RileyLinkBLEKitTests/Info.plist b/RileyLinkBLEKitTests/Info.plist index c0a7b8da5..8e0afffd2 100644 --- a/RileyLinkBLEKitTests/Info.plist +++ b/RileyLinkBLEKitTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 0.12.5 + 0.12.6 CFBundleSignature ???? CFBundleVersion diff --git a/RileyLinkKit/Info.plist b/RileyLinkKit/Info.plist index 2ed8188bd..c66882b70 100644 --- a/RileyLinkKit/Info.plist +++ b/RileyLinkKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.12.5 + 0.12.6 CFBundleSignature ???? CFBundleVersion diff --git a/RileyLinkKit/PumpOps.swift b/RileyLinkKit/PumpOps.swift index 929f71e09..9510dac8c 100644 --- a/RileyLinkKit/PumpOps.swift +++ b/RileyLinkKit/PumpOps.swift @@ -143,6 +143,34 @@ public class PumpOps { } } + /** + Fetches glucose history entries which occurred on or after the specified date. + + History timestamps are reconciled with UTC based on the `timeZone` property of PumpState, as well as recorded clock change events. + + - 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, in ascending order of insertion + - failure(error): An error describing why the command failed + + */ + public func getGlucoseHistoryEvents(since startDate: Date, completion: @escaping (Either<[TimestampedGlucoseEvent], Error>) -> Void) { + device.runSession(withName: "Get glucose history events") { (session) -> Void in + NSLog("Glucose history fetching task started.") + let ops = PumpOpsSynchronous(pumpState: self.pumpState, session: session) + do { + let events = try ops.getGlucoseHistoryEvents(since: startDate) + DispatchQueue.main.async { () -> Void in + completion(.success(events)) + } + } catch let error { + DispatchQueue.main.async { () -> Void in + completion(.failure(error)) + } + } + } + } + /** Reads the pump's clock diff --git a/RileyLinkKit/PumpOpsSynchronous.swift b/RileyLinkKit/PumpOpsSynchronous.swift index 588add72c..370ba537f 100644 --- a/RileyLinkKit/PumpOpsSynchronous.swift +++ b/RileyLinkKit/PumpOpsSynchronous.swift @@ -432,7 +432,7 @@ class PumpOpsSynchronous { try wakeup() let pumpModel = try getPumpModel() - + var events = [TimestampedHistoryEvent]() var timeAdjustmentInterval: TimeInterval = 0 @@ -544,6 +544,104 @@ class PumpOpsSynchronous { } return frameData as Data } + + internal func getGlucoseHistoryEvents(since startDate: Date) throws -> [TimestampedGlucoseEvent] { + try wakeup() + + var events = [TimestampedGlucoseEvent]() + + let currentGlucosePage = try readCurrentGlucosePage() + let startPage = Int(currentGlucosePage.pageNum) + //max lookback of 15 pages or when page is 0 + let endPage = max(startPage - 15, 0) + + pages: for pageNum in stride(from: startPage, to: endPage - 1, by: -1) { + NSLog("Fetching page %d", pageNum) + let pageData: Data + + do { + pageData = try getGlucosePage(UInt32(pageNum)) + } catch let error as PumpCommsError { + if case .unexpectedResponse(let response, from: _) = error, response.messageType == .emptyHistoryPage { + break pages + } else { + throw error + } + } + + var idx = 0 + let chunkSize = 256; + while idx < pageData.count { + let top = min(idx + chunkSize, pageData.count) + let range = Range(uncheckedBounds: (lower: idx, upper: top)) + NSLog(String(format: "GlucosePage %02d - (bytes %03d-%03d): ", pageNum, idx, top-1) + pageData.subdata(in: range).hexadecimalString) + idx = top + } + + let page = try GlucosePage(pageData: pageData) + + for event in page.events.reversed() { + var timestamp = event.timestamp + timestamp.timeZone = pump.timeZone + + if event is UnknownGlucoseEvent { + continue pages + } + + if let date = timestamp.date { + if date < startDate && event is ReferenceTimestampedGlucoseEvent { + NSLog("Found reference event at (%@) to be before startDate(%@)", date as NSDate, startDate as NSDate); + break pages + } else { + events.insert(TimestampedGlucoseEvent(glucoseEvent: event, date: date), at: 0) + } + } + } + } + return events + } + + private func readCurrentGlucosePage() throws -> ReadCurrentGlucosePageMessageBody { + let readCurrentGlucosePageResponse: ReadCurrentGlucosePageMessageBody = try messageBody(to: .readCurrentGlucosePage) + + return readCurrentGlucosePageResponse + } + + private func getGlucosePage(_ pageNum: UInt32) throws -> Data { + var frameData = Data() + + let msg = makePumpMessage(to: .getGlucosePage, using: GetGlucosePageMessageBody(pageNum: pageNum)) + + let firstResponse = try runCommandWithArguments(msg, responseMessageType: .getGlucosePage) + + var expectedFrameNum = 1 + var curResp = firstResponse.messageBody as! GetGlucosePageMessageBody + + while(expectedFrameNum == curResp.frameNumber) { + frameData.append(curResp.frame) + expectedFrameNum += 1 + let msg = makePumpMessage(to: .pumpAck) + if !curResp.lastFrame { + guard let resp = try? sendAndListen(msg) else { + throw PumpCommsError.rfCommsFailure("Did not receive frame data from pump") + } + guard resp.packetType == .carelink && resp.messageType == .getGlucosePage else { + throw PumpCommsError.rfCommsFailure("Bad packet type or message type. Possible interference.") + } + curResp = resp.messageBody as! GetGlucosePageMessageBody + } else { + let cmd = SendPacketCmd() + cmd.packet = RFPacket(data: msg.txData) + session.doCmd(cmd, withTimeoutMs: expectedMaxBLELatencyMS) + break + } + } + + guard frameData.count == 1024 else { + throw PumpCommsError.rfCommsFailure("Short glucose history page: \(frameData.count) bytes. Expected 1024") + } + return frameData as Data + } internal func readPumpStatus() throws -> PumpStatus { let clockResp: ReadTimeCarelinkMessageBody = try messageBody(to: .readTime) diff --git a/RileyLinkKit/UI/RileyLinkDeviceTableViewController.swift b/RileyLinkKit/UI/RileyLinkDeviceTableViewController.swift index f9780be78..f410d36d5 100644 --- a/RileyLinkKit/UI/RileyLinkDeviceTableViewController.swift +++ b/RileyLinkKit/UI/RileyLinkDeviceTableViewController.swift @@ -151,6 +151,7 @@ public class RileyLinkDeviceTableViewController: UITableViewController, TextFiel case changeTime case mySentryPair case dumpHistory + case fetchGlucose case getPumpModel case pressDownButton case readPumpStatus @@ -282,6 +283,9 @@ public class RileyLinkDeviceTableViewController: UITableViewController, TextFiel case .dumpHistory: cell.textLabel?.text = NSLocalizedString("Fetch Recent History", comment: "The title of the command to fetch recent history") + case .fetchGlucose: + cell.textLabel?.text = NSLocalizedString("Fetch Recent Glucose", comment: "The title of the command to fetch recent glucose") + case .getPumpModel: cell.textLabel?.text = NSLocalizedString("Get Pump Model", comment: "The title of the command to get pump model") @@ -452,6 +456,24 @@ public class RileyLinkDeviceTableViewController: UITableViewController, TextFiel } return NSLocalizedString("Fetching history…", comment: "Progress message for fetching pump history.") } + case .fetchGlucose: + vc = CommandResponseViewController { [unowned self] (completionHandler) -> String in + let calendar = Calendar(identifier: Calendar.Identifier.gregorian) + let oneDayAgo = calendar.date(byAdding: DateComponents(day: -1), to: Date()) + self.device.ops?.getGlucoseHistoryEvents(since: oneDayAgo!) { (response) -> Void in + switch response { + case .success(let events): + var responseText = String(format:"Found %d events since %@", events.count, oneDayAgo! as NSDate) + for event in events { + responseText += String(format:"\nEvent: %@", event.dictionaryRepresentation) + } + completionHandler(responseText) + case .failure(let error): + completionHandler(String(describing: error)) + } + } + return NSLocalizedString("Fetching glucose…", comment: "Progress message for fetching pump glucose.") + } case .getPumpModel: vc = CommandResponseViewController { [unowned self] (completionHandler) -> String in self.device.ops?.getPumpModel({ (response) in diff --git a/RileyLinkKitTests/Info.plist b/RileyLinkKitTests/Info.plist index c0a7b8da5..8e0afffd2 100644 --- a/RileyLinkKitTests/Info.plist +++ b/RileyLinkKitTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 0.12.5 + 0.12.6 CFBundleSignature ???? CFBundleVersion diff --git a/RileyLinkTests/RileyLinkTests-Info.plist b/RileyLinkTests/RileyLinkTests-Info.plist index d80a918aa..4845fc698 100644 --- a/RileyLinkTests/RileyLinkTests-Info.plist +++ b/RileyLinkTests/RileyLinkTests-Info.plist @@ -13,7 +13,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 0.12.5 + 0.12.6 CFBundleSignature ???? CFBundleVersion