Skip to content

Commit

Permalink
Use detailed status for read status command (#625)
Browse files Browse the repository at this point in the history
* Use detailed status for read status command
  • Loading branch information
ps2 authored Sep 24, 2020
1 parent de09211 commit 1486ffe
Show file tree
Hide file tree
Showing 16 changed files with 340 additions and 244 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ cache:
- Carthage

before_script:
- carthage bootstrap
- carthage bootstrap --cache-builds

script:
- set -o pipefail && xcodebuild -project RileyLink.xcodeproj -scheme Shared build -destination 'name=iPhone 8' test | xcpretty
9 changes: 5 additions & 4 deletions OmniKit/MessageTransport/Message.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,14 @@ struct Message {
return data
}

var fault: PodInfoFaultEvent? {
var fault: DetailedStatus? {
if messageBlocks.count > 0 && messageBlocks[0].blockType == .podInfoResponse,
let infoResponse = messageBlocks[0] as? PodInfoResponse,
infoResponse.podInfoResponseSubType == .faultEvents,
let fault = infoResponse.podInfo as? PodInfoFaultEvent
infoResponse.podInfoResponseSubType == .detailedStatus,
let detailedStatus = infoResponse.podInfo as? DetailedStatus,
detailedStatus.faultEventCode.faultType != .noFaults || detailedStatus.podProgressStatus == .activationTimeExceeded
{
return fault
return detailedStatus
} else {
return nil
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// PodInfoFaultEvent.swift
// DetailedStatus.swift
// OmniKit
//
// Created by Pete Schwamb on 2/23/18.
Expand All @@ -8,18 +8,18 @@

import Foundation

public struct PodInfoFaultEvent : PodInfo, Equatable {
public struct DetailedStatus : PodInfo, Equatable {
// CMD 1 2 3 4 5 6 7 8 9 10 1112 1314 1516 17 18 19 20 21 2223
// DATA 0 1 2 3 4 5 6 7 8 910 1112 1314 15 16 17 18 19 2021
// 02 16 02 0J 0K LLLL MM NNNN PP QQQQ RRRR SSSS TT UU VV WW 0X YYYY

public var podInfoType: PodInfoResponseSubType = .faultEvents
public var podInfoType: PodInfoResponseSubType = .detailedStatus
public let podProgressStatus: PodProgressStatus
public let deliveryStatus: DeliveryStatus
public let insulinNotDelivered: Double
public let bolusNotDelivered: Double
public let podMessageCounter: UInt8
public let totalInsulinDelivered: Double
public let currentStatus: FaultEventCode
public let faultEventCode: FaultEventCode
public let faultEventTimeSinceActivation: TimeInterval?
public let reservoirLevel: Double?
public let timeActive: TimeInterval
Expand All @@ -46,13 +46,13 @@ public struct PodInfoFaultEvent : PodInfo, Equatable {

self.deliveryStatus = DeliveryStatus(rawValue: encodedData[2] & 0xf)!

self.insulinNotDelivered = Pod.pulseSize * Double((Int(encodedData[3] & 0x3) << 8) | Int(encodedData[4]))
self.bolusNotDelivered = Pod.pulseSize * Double((Int(encodedData[3] & 0x3) << 8) | Int(encodedData[4]))

self.podMessageCounter = encodedData[5]

self.totalInsulinDelivered = Pod.pulseSize * Double((Int(encodedData[6]) << 8) | Int(encodedData[7]))

self.currentStatus = FaultEventCode(rawValue: encodedData[8])
self.faultEventCode = FaultEventCode(rawValue: encodedData[8])

let minutesSinceActivation = encodedData[9...10].toBigEndian(UInt16.self)
if minutesSinceActivation != 0xffff {
Expand Down Expand Up @@ -97,18 +97,18 @@ public struct PodInfoFaultEvent : PodInfo, Equatable {
}
}

extension PodInfoFaultEvent: CustomDebugStringConvertible {
extension DetailedStatus: CustomDebugStringConvertible {
public typealias RawValue = Data
public var debugDescription: String {
return [
"## PodInfoFaultEvent",
"## DetailedStatus",
"* rawHex: \(data.hexadecimalString)",
"* podProgressStatus: \(podProgressStatus)",
"* deliveryStatus: \(deliveryStatus.description)",
"* insulinNotDelivered: \(insulinNotDelivered.twoDecimals) U",
"* bolusNotDelivered: \(bolusNotDelivered.twoDecimals) U",
"* podMessageCounter: \(podMessageCounter)",
"* totalInsulinDelivered: \(totalInsulinDelivered.twoDecimals) U",
"* currentStatus: \(currentStatus.description)",
"* faultEventCode: \(faultEventCode.description)",
"* faultEventTimeSinceActivation: \(faultEventTimeSinceActivation?.stringValue ?? "none")",
"* reservoirLevel: \(reservoirLevel?.twoDecimals ?? "50+") U",
"* timeActive: \(timeActive.stringValue)",
Expand All @@ -125,7 +125,7 @@ extension PodInfoFaultEvent: CustomDebugStringConvertible {
}
}

extension PodInfoFaultEvent: RawRepresentable {
extension DetailedStatus: RawRepresentable {
public init?(rawValue: Data) {
do {
try self.init(encodedData: rawValue)
Expand Down
6 changes: 3 additions & 3 deletions OmniKit/MessageTransport/MessageBlocks/PodInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public protocol PodInfo {
public enum PodInfoResponseSubType: UInt8, Equatable {
case normal = 0x00
case configuredAlerts = 0x01
case faultEvents = 0x02
case detailedStatus = 0x02 // Returned on any pod fault
case dataLog = 0x03
case fault = 0x05
case pulseLogRecent = 0x50 // dumps up to 50 entries data from the pulse log
Expand All @@ -30,8 +30,8 @@ public enum PodInfoResponseSubType: UInt8, Equatable {
return StatusResponse.self as! PodInfo.Type
case .configuredAlerts:
return PodInfoConfiguredAlerts.self
case .faultEvents:
return PodInfoFaultEvent.self
case .detailedStatus:
return DetailedStatus.self
case .dataLog:
return PodInfoDataLog.self
case .fault:
Expand Down
6 changes: 3 additions & 3 deletions OmniKit/MessageTransport/MessageBlocks/StatusResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public struct StatusResponse : MessageBlock {
public let timeActive: TimeInterval
public let reservoirLevel: Double?
public let insulin: Double
public let insulinNotDelivered: Double
public let bolusNotDelivered: Double
public let podMessageCounter: UInt8
public let alerts: AlertSet

Expand Down Expand Up @@ -86,7 +86,7 @@ public struct StatusResponse : MessageBlock {

self.podMessageCounter = (encodedData[4] >> 3) & 0xf

self.insulinNotDelivered = Double((Int(encodedData[4] & 0x3) << 8) | Int(encodedData[5])) / Pod.pulsesPerUnit
self.bolusNotDelivered = Double((Int(encodedData[4] & 0x3) << 8) | Int(encodedData[5])) / Pod.pulsesPerUnit

self.alerts = AlertSet(rawValue: ((encodedData[6] & 0x7f) << 1) | (encodedData[7] >> 7))

Expand All @@ -101,7 +101,7 @@ public struct StatusResponse : MessageBlock {

extension StatusResponse: CustomDebugStringConvertible {
public var debugDescription: String {
return "StatusResponse(deliveryStatus:\(deliveryStatus), progressStatus:\(podProgressStatus), timeActive:\(timeActive.stringValue), reservoirLevel:\(String(describing: reservoirLevel)), delivered:\(insulin), undelivered:\(insulinNotDelivered), seq:\(podMessageCounter), alerts:\(alerts))"
return "StatusResponse(deliveryStatus:\(deliveryStatus), progressStatus:\(podProgressStatus), timeActive:\(timeActive.stringValue), reservoirLevel:\(String(describing: reservoirLevel)), delivered:\(insulin), bolusNotDelivered:\(bolusNotDelivered), seq:\(podMessageCounter), alerts:\(alerts))"
}
}

2 changes: 2 additions & 0 deletions OmniKit/Model/FaultEventCode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,8 @@ public struct FaultEventCode: CustomStringConvertible, Equatable {
public var localizedDescription: String {
if let faultType = faultType {
switch faultType {
case .noFaults:
return LocalizedString("No faults", comment: "Description for Fault Event Code .noFaults")
case .reservoirEmpty:
return LocalizedString("Empty reservoir", comment: "Description for Empty reservoir pod fault")
case .exceededMaximumPodLife80Hrs:
Expand Down
69 changes: 10 additions & 59 deletions OmniKit/PumpManager/OmnipodPumpManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -473,14 +473,14 @@ extension OmnipodPumpManager {

// MARK: Testing
#if targetEnvironment(simulator)
private func jumpStartPod(address: UInt32, lot: UInt32, tid: UInt32, fault: PodInfoFaultEvent? = nil, startDate: Date? = nil, mockFault: Bool) {
private func jumpStartPod(address: UInt32, lot: UInt32, tid: UInt32, fault: DetailedStatus? = nil, startDate: Date? = nil, mockFault: Bool) {
let start = startDate ?? Date()
var podState = PodState(address: address, piVersion: "jumpstarted", pmVersion: "jumpstarted", lot: lot, tid: tid)
podState.setupProgress = .podConfigured
podState.activatedAt = start
podState.expiresAt = start + .hours(72)

let fault = mockFault ? try? PodInfoFaultEvent(encodedData: Data(hexadecimalString: "020d0000000e00c36a020703ff020900002899080082")!) : nil
let fault = mockFault ? try? DetailedStatus(encodedData: Data(hexadecimalString: "020d0000000e00c36a020703ff020900002899080082")!) : nil
podState.fault = fault

self.podComms = PodComms(podState: podState)
Expand All @@ -502,7 +502,7 @@ extension OmnipodPumpManager {
let mockCommsErrorDuringPairing = true
DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + .seconds(2)) {
self.jumpStartPod(address: 0x1f0b3557, lot: 40505, tid: 6439, mockFault: mockFaultDuringPairing)
let fault: PodInfoFaultEvent? = self.setStateWithResult({ (state) in
let fault: DetailedStatus? = self.setStateWithResult({ (state) in
state.podState?.setupProgress = .priming
return state.podState?.fault
})
Expand Down Expand Up @@ -599,7 +599,7 @@ extension OmnipodPumpManager {
DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + mockDelay) {
let result = self.setStateWithResult({ (state) -> PumpManagerResult<TimeInterval> in
// Mock fault
// let fault = try! PodInfoFaultEvent(encodedData: Data(hexadecimalString: "020d0000000e00c36a020703ff020900002899080082")!)
// let fault = try! DetailedStatus(encodedData: Data(hexadecimalString: "020d0000000e00c36a020703ff020900002899080082")!)
// self.state.podState?.fault = fault
// return .failure(PodCommsError.podFault(fault: fault))

Expand Down Expand Up @@ -907,74 +907,25 @@ extension OmnipodPumpManager {
#endif
}

private func podStatusString(status: StatusResponse) -> String {
var result, str: String
var delivered: Double

let formatter = DateComponentsFormatter()
formatter.unitsStyle = .full
formatter.allowedUnits = [.day, .hour, .minute]
if let timeStr = formatter.string(from: status.timeActive) {
str = timeStr
} else {
str = String(format: LocalizedString("%1$@ minutes", comment: "The format string for minutes (1: number of minutes string)"), String(describing: Int(status.timeActive / 60)))
}
result = String(format: LocalizedString("Pod Active: %1$@\n", comment: "The format string for Pod Active: (1: Pod active time string)"), str)

result += String(format: LocalizedString("Delivery Status: %1$@\n", comment: "The format string for Delivery Status: (1: delivery status string)"), String(describing: status.deliveryStatus))

if let lastInsulinMeasurements = self.state.podState?.lastInsulinMeasurements {
delivered = lastInsulinMeasurements.delivered
} else {
delivered = status.insulin
}
result += String(format: LocalizedString("Total Insulin Delivered: %1$@ U\n", comment: "The format string for Total Insulin Delivered: (1: total insulin delivered string)"), delivered.twoDecimals)

result += String(format: LocalizedString("Reservoir Level: %1$@ U\n", comment: "The format string for Reservoir Level: (1: reservoir level string)"), status.reservoirLevel?.twoDecimals ?? "50+")

result += String(format: LocalizedString("Last Bolus Not Delivered: %1$@ U\n", comment: "The format string for Last Bolus Not Delivered: (1: bolus not delivered string)"), status.insulinNotDelivered.twoDecimals)

if let podState = self.state.podState,
podState.activeAlerts.startIndex != podState.activeAlerts.endIndex
{
// generate a more helpful string with the actual alert names
str = String(describing: podState.activeAlerts)
} else {
str = String(describing: status.alerts)
}
result += String(format: LocalizedString("Alerts: %1$@\n", comment: "The format string for Alerts: (1: the alerts string)"), str)

return result
}

public func readPodStatus(completion: @escaping (String) -> Void) {
public func readPodStatus(completion: @escaping (Result<DetailedStatus, Error>) -> Void) {
// use hasSetupPod to be able to read pod info from a faulted Pod
guard self.hasSetupPod else {
completion(PodCommsError.noPodPaired.errorDescription!)
completion(.failure(PodCommsError.noPodPaired))
return
}

let rileyLinkSelector = self.rileyLinkDeviceProvider.firstConnectedDevice
podComms.runSession(withName: "Read pod status", using: rileyLinkSelector) { (result) in

let reportError = { (errorStr: String) in
self.log.error("Failed to read pod status: %{public}@", errorStr)
completion(errorStr)
}

do {
switch result {
case .success(let session):
let status = try session.getStatus()
session.dosesForStorage({ (doses) -> Bool in
self.store(doses: doses, in: session)
})
completion(self.podStatusString(status: status))
let faultStatus = try session.getDetailedStatus()
completion(.success(faultStatus))
case .failure(let error):
reportError(String(describing: error))
completion(.failure(error))
}
} catch let error {
reportError(error.localizedDescription)
completion(.failure(error))
}
}
}
Expand Down
22 changes: 16 additions & 6 deletions OmniKit/PumpManager/PodCommsSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public enum PodCommsError: Error {
case unfinalizedTempBasal
case nonceResyncFailed
case podSuspended
case podFault(fault: PodInfoFaultEvent)
case podFault(fault: DetailedStatus)
case commsError(error: Error)
case podChange
case activationTimeExceeded
Expand Down Expand Up @@ -66,7 +66,7 @@ extension PodCommsError: LocalizedError {
case .podSuspended:
return LocalizedString("Pod is suspended", comment: "Error message action could not be performed because pod is suspended")
case .podFault(let fault):
let faultDescription = String(describing: fault.currentStatus)
let faultDescription = String(describing: fault.faultEventCode)
return String(format: LocalizedString("Pod Fault: %1$@", comment: "Format string for pod fault code"), faultDescription)
case .commsError(let error):
return error.localizedDescription
Expand Down Expand Up @@ -158,15 +158,15 @@ public class PodCommsSession {
}

// Will throw either PodCommsError.podFault or PodCommsError.activationTimeExceeded
private func throwPodFault(fault: PodInfoFaultEvent) throws {
private func throwPodFault(fault: DetailedStatus) throws {
if self.podState.fault == nil {
self.podState.fault = fault // save the first fault returned
}
log.error("Pod Fault: %@", String(describing: fault))
if fault.deliveryStatus == .suspended {
let now = Date()
podState.unfinalizedTempBasal?.cancel(at: now)
podState.unfinalizedBolus?.cancel(at: now, withRemaining: fault.insulinNotDelivered)
podState.unfinalizedBolus?.cancel(at: now, withRemaining: fault.bolusNotDelivered)
}
if fault.podProgressStatus == .activationTimeExceeded {
// avoids a confusing "No fault" error when activation time is exceeded
Expand Down Expand Up @@ -531,7 +531,7 @@ public class PodCommsSession {
do {
let status: StatusResponse = try send(message)

let canceledDose = handleCancelDosing(deliveryType: deliveryType, bolusNotDelivered: status.insulinNotDelivered)
let canceledDose = handleCancelDosing(deliveryType: deliveryType, bolusNotDelivered: status.bolusNotDelivered)

podState.updateFromStatusResponse(status)

Expand Down Expand Up @@ -619,6 +619,16 @@ public class PodCommsSession {
podState.updateFromStatusResponse(statusResponse)
return statusResponse
}

@discardableResult
public func getDetailedStatus() throws -> DetailedStatus {
let infoResponse: PodInfoResponse = try send([GetStatusCommand(podInfoType: .detailedStatus)])

guard let faultEvent = infoResponse.podInfo as? DetailedStatus else {
throw PodCommsError.unexpectedResponse(response: .podInfoResponse)
}
return faultEvent
}

@discardableResult
public func readPulseLogsRequest(podInfoResponseSubType: PodInfoResponseSubType) throws -> PodInfoResponse {
Expand Down Expand Up @@ -655,7 +665,7 @@ public class PodCommsSession {
if let fault = podState.fault {
// Be sure to clean up the dosing info in case cancelDelivery() wasn't called
// (or if it was called and it had a fault return) & then read the pulse log.
handleCancelDosing(deliveryType: .all, bolusNotDelivered: fault.insulinNotDelivered)
handleCancelDosing(deliveryType: .all, bolusNotDelivered: fault.bolusNotDelivered)
do {
// read the most recent pulse log entries for later analysis, but don't throw on error
let podInfoCommand = GetStatusCommand(podInfoType: .pulseLogRecent)
Expand Down
12 changes: 7 additions & 5 deletions OmniKit/PumpManager/PodState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,11 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl
return false
}

public var fault: PodInfoFaultEvent?
public var fault: DetailedStatus?
public var messageTransportState: MessageTransportState
public var primeFinishTime: Date?
public var setupProgress: SetupProgress
var configuredAlerts: [AlertSlot: PodAlert]
public var configuredAlerts: [AlertSlot: PodAlert]

public var activeAlerts: [AlertSlot: PodAlert] {
var active = [AlertSlot: PodAlert]()
Expand Down Expand Up @@ -318,8 +318,11 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl
self.finalizedDoses = []
}

if let rawFault = rawValue["fault"] as? PodInfoFaultEvent.RawValue {
self.fault = PodInfoFaultEvent(rawValue: rawFault)
if let rawFault = rawValue["fault"] as? DetailedStatus.RawValue,
let fault = DetailedStatus(rawValue: rawFault),
fault.faultEventCode.faultType != .noFaults
{
self.fault = fault
} else {
self.fault = nil
}
Expand Down Expand Up @@ -423,7 +426,6 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl
rawValue["setupUnitsDelivered"] = setupUnitsDelivered
}


if configuredAlerts.count > 0 {
let rawConfiguredAlerts = Dictionary(uniqueKeysWithValues:
configuredAlerts.map { slot, alarm in (String(describing: slot.rawValue), alarm.rawValue) })
Expand Down
2 changes: 1 addition & 1 deletion OmniKitTests/MessageTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class MessageTests: XCTestCase {
XCTAssertEqual(.normal, statusResponse.deliveryStatus)
XCTAssertEqual(.aboveFiftyUnits, statusResponse.podProgressStatus)
XCTAssertEqual(6.3, statusResponse.insulin, accuracy: 0.01)
XCTAssertEqual(0, statusResponse.insulinNotDelivered)
XCTAssertEqual(0, statusResponse.bolusNotDelivered)
XCTAssertEqual(3, statusResponse.podMessageCounter)
XCTAssert(statusResponse.alerts.isEmpty)

Expand Down
Loading

0 comments on commit 1486ffe

Please sign in to comment.