Skip to content

Commit

Permalink
Add pump ops for fetching recent glucose
Browse files Browse the repository at this point in the history
  • Loading branch information
tmecklem committed Oct 20, 2016
1 parent 9209656 commit 06f2917
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 1 deletion.
34 changes: 34 additions & 0 deletions MinimedKit/TimestampedGlucoseEvent.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
4 changes: 4 additions & 0 deletions RileyLink.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
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 */; };
Expand Down Expand Up @@ -508,6 +509,7 @@
43FF221B1CB9B9DE00024F30 /* NSDateComponents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSDateComponents.swift; sourceTree = "<group>"; };
541688DA1DB820BF005B1891 /* ReadCurrentGlucosePageMessageBodyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadCurrentGlucosePageMessageBodyTests.swift; sourceTree = "<group>"; };
541688DC1DB82213005B1891 /* ReadCurrentGlucosePageMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadCurrentGlucosePageMessageBody.swift; sourceTree = "<group>"; };
541688DE1DB82E72005B1891 /* TimestampedGlucoseEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimestampedGlucoseEvent.swift; sourceTree = "<group>"; };
54A840D01DB85D0600B1F202 /* UnknownGlucoseEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnknownGlucoseEvent.swift; sourceTree = "<group>"; };
54BC44721DB46A5200340EED /* GlucosePageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucosePageTests.swift; sourceTree = "<group>"; };
54BC44741DB46B0A00340EED /* GlucosePage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucosePage.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1003,6 +1005,7 @@
C1274F851D8242BE0002912B /* PumpRegion.swift */,
C1EAD6D91C829104006DBA60 /* RFTools.swift */,
43B0ADC11D12454700AAD278 /* TimestampedHistoryEvent.swift */,
541688DE1DB82E72005B1891 /* TimestampedGlucoseEvent.swift */,
);
path = MinimedKit;
sourceTree = "<group>";
Expand Down Expand Up @@ -2027,6 +2030,7 @@
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 */,
Expand Down
28 changes: 28 additions & 0 deletions RileyLinkKit/PumpOps.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<(events: [TimestampedGlucoseEvent], pumpModel: PumpModel), 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, pumpModel) = try ops.getGlucoseHistoryEvents(since: startDate)
DispatchQueue.main.async { () -> Void in
completion(.success(events: events, pumpModel: pumpModel))
}
} catch let error {
DispatchQueue.main.async { () -> Void in
completion(.failure(error))
}
}
}
}

/**
Reads the pump's clock
Expand Down
102 changes: 101 additions & 1 deletion RileyLinkKit/PumpOpsSynchronous.swift
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ class PumpOpsSynchronous {
try wakeup()

let pumpModel = try getPumpModel()

var events = [TimestampedHistoryEvent]()
var timeAdjustmentInterval: TimeInterval = 0

Expand Down Expand Up @@ -544,6 +544,106 @@ class PumpOpsSynchronous {
}
return frameData as Data
}

internal func getGlucoseHistoryEvents(since startDate: Date) throws -> ([TimestampedGlucoseEvent], PumpModel) {
try wakeup()

let pumpModel = try getPumpModel()

var events = [TimestampedGlucoseEvent]()

// Going to scan backwards in time through events, so event time should be monotonically decreasing.
let eventTimestampDeltaAllowance = TimeInterval(hours: 9)

let currentGlucosePage = try readCurrentGlucosePage()
let startPage = Int(currentGlucosePage.pageNum)
let endPage = max(startPage - 1, 0)

pages: for pageNum in stride(from: startPage, to: endPage, 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, pumpModel: pumpModel)

for event in page.events.reversed() {
var timestamp = event.timestamp
timestamp.timeZone = pump.timeZone

if let date = timestamp.date {
if date.timeIntervalSince(startDate) < -eventTimestampDeltaAllowance {
NSLog("Found event at (%@) to be more than %@s before startDate(%@)", date as NSDate, String(describing: eventTimestampDeltaAllowance), startDate as NSDate);
break pages
} else {
events.insert(TimestampedGlucoseEvent(glucoseEvent: event, date: date), at: 0)
}
} else {
events.insert(TimestampedGlucoseEvent(glucoseEvent: event, date: Date()), at: 0)
}
}
}
return (events, pumpModel)
}

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)
Expand Down
22 changes: 22 additions & 0 deletions RileyLinkKit/UI/RileyLinkDeviceTableViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ public class RileyLinkDeviceTableViewController: UITableViewController, TextFiel
case changeTime
case mySentryPair
case dumpHistory
case fetchGlucose
case getPumpModel
case pressDownButton
case readPumpStatus
Expand Down Expand Up @@ -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")

Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 06f2917

Please sign in to comment.