| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 |
- //
- // DetailedStatus.swift
- // OmniKit
- //
- // Created by Pete Schwamb on 2/23/18.
- // Copyright © 2018 Pete Schwamb. All rights reserved.
- //
- import Foundation
- // DetailedStatus is the PodInfo subtype 2 returned for a type 2 GetStatus command and
- // is also returned on a pod fault for any command normally returning a StatusResponse
- 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 let podInfoType: PodInfoResponseSubType = .detailedStatus
- public let podProgressStatus: PodProgressStatus
- public let deliveryStatus: DeliveryStatus
- public let bolusNotDelivered: Double
- public let lastProgrammingMessageSeqNum: UInt8 // updated by pod for 03, 08, $11, $19, $1A, $1C, $1E & $1F command messages
- public let totalInsulinDelivered: Double
- public let faultEventCode: FaultEventCode
- public let faultEventTimeSinceActivation: TimeInterval?
- public let reservoirLevel: Double?
- public let timeActive: TimeInterval
- public let unacknowledgedAlerts: AlertSet
- public let faultAccessingTables: Bool
- public let errorEventInfo: ErrorEventInfo?
- public let receiverLowGain: UInt8
- public let radioRSSI: UInt8
- public let previousPodProgressStatus: PodProgressStatus?
- // YYYY is uninitialized data for Eros
- public let data: Data
-
- public init(encodedData: Data) throws {
- guard encodedData.count >= 21 else {
- throw MessageBlockError.notEnoughData
- }
-
- guard PodProgressStatus(rawValue: encodedData[1]) != nil else {
- throw MessageError.unknownValue(value: encodedData[1], typeDescription: "PodProgressStatus")
- }
- self.podProgressStatus = PodProgressStatus(rawValue: encodedData[1])!
-
- self.deliveryStatus = DeliveryStatus(rawValue: encodedData[2] & 0xf)!
-
- self.bolusNotDelivered = Pod.pulseSize * Double((Int(encodedData[3] & 0x3) << 8) | Int(encodedData[4]))
-
- self.lastProgrammingMessageSeqNum = encodedData[5]
-
- self.totalInsulinDelivered = Pod.pulseSize * Double(encodedData[6...7].toBigEndian(UInt16.self))
-
- self.faultEventCode = FaultEventCode(rawValue: encodedData[8])
-
- let minutesSinceActivation = encodedData[9...10].toBigEndian(UInt16.self)
- if minutesSinceActivation != 0xffff {
- self.faultEventTimeSinceActivation = TimeInterval(minutes: Double(minutesSinceActivation))
- } else {
- self.faultEventTimeSinceActivation = nil
- }
-
- let reservoirValue = Double((Int(encodedData[11] & 0x3) << 8) + Int(encodedData[12])) * Pod.pulseSize
-
- if reservoirValue <= Pod.maximumReservoirReading {
- self.reservoirLevel = reservoirValue
- } else {
- self.reservoirLevel = nil
- }
-
- self.timeActive = TimeInterval(minutes: Double(encodedData[13...14].toBigEndian(UInt16.self)))
-
- self.unacknowledgedAlerts = AlertSet(rawValue: encodedData[15])
-
- self.faultAccessingTables = (encodedData[16] & 2) != 0
-
- if encodedData[17] == 0x00 {
- self.errorEventInfo = nil // this byte is not valid (no fault has occurred)
- } else {
- self.errorEventInfo = ErrorEventInfo(rawValue: encodedData[17])
- }
-
- self.receiverLowGain = UInt8(encodedData[18] >> 6)
- self.radioRSSI = UInt8(encodedData[18] & 0x3F)
-
- if encodedData[19] == 0xFF {
- self.previousPodProgressStatus = nil // this byte is not valid (no fault has occurred)
- } else {
- self.previousPodProgressStatus = PodProgressStatus(rawValue: encodedData[19] & 0xF)!
- }
-
- self.data = Data(encodedData)
- }
- public var isFaulted: Bool {
- return faultEventCode.faultType != .noFaults || podProgressStatus == .activationTimeExceeded
- }
- // Returns an appropropriate PDM style Ref string for the Detailed Status.
- // For most types, Ref: TT-VVVHH-IIIRR-FFF computed as {19|17}-{VV}{SSSS/60}-{NNNN/20}{RRRR/20}-PP
- public var pdmRef: String? {
- let TT, VVV, HH, III, RR, FFF: UInt8
- let refStr = LocalizedString("Ref", comment: "PDM style 'Ref' string")
- switch faultEventCode.faultType {
- case .noFaults, .reservoirEmpty, .exceededMaximumPodLife80Hrs:
- return nil // no PDM Ref # generated for these cases
- case .insulinDeliveryCommandError:
- // This fault is treated as a PDM fault which uses an alternate Ref format
- return String(format: "%@:\u{00a0}11-144-0018-00049", refStr) // all fixed values for this fault
- case .occluded:
- // Ref: 17-000HH-IIIRR-000
- TT = 17 // Occlusion detected Ref type
- VVV = 0 // no VVV value for an occlusion fault
- FFF = 0 // no FFF value for an occlusion fault
- default:
- // Ref: 19-VVVHH-IIIRR-FFF
- TT = 19 // pod fault Ref type
- VVV = data[17] // use the raw VV byte value
- FFF = faultEventCode.rawValue
- }
- HH = UInt8(timeActive.hours)
- III = UInt8(totalInsulinDelivered)
- if let reservoirLevel = self.reservoirLevel {
- RR = UInt8(reservoirLevel)
- } else {
- RR = 51 // value used for 50+ U
- }
- return String(format: "%@:\u{00a0}%02d-%03d%02d-%03d%02d-%03d", refStr, TT, VVV, HH, III, RR, FFF)
- }
- }
- extension DetailedStatus: CustomDebugStringConvertible {
- public typealias RawValue = Data
- public var debugDescription: String {
- return [
- "## DetailedStatus",
- "* rawHex: \(data.hexadecimalString)",
- "* podProgressStatus: \(podProgressStatus)",
- "* deliveryStatus: \(deliveryStatus.description)",
- "* bolusNotDelivered: \(bolusNotDelivered.twoDecimals) U",
- "* lastProgrammingMessageSeqNum: \(lastProgrammingMessageSeqNum)",
- "* totalInsulinDelivered: \(totalInsulinDelivered.twoDecimals) U",
- "* faultEventCode: \(faultEventCode.description)",
- "* faultEventTimeSinceActivation: \(faultEventTimeSinceActivation?.stringValue ?? "none")",
- "* reservoirLevel: \(reservoirLevel?.twoDecimals ?? "50+") U",
- "* timeActive: \(timeActive.stringValue)",
- "* unacknowledgedAlerts: \(unacknowledgedAlerts)",
- "* faultAccessingTables: \(faultAccessingTables)",
- "* errorEventInfo: \(errorEventInfo?.description ?? "NA")",
- "* receiverLowGain: \(receiverLowGain)",
- "* radioRSSI: \(radioRSSI)",
- "* previousPodProgressStatus: \(previousPodProgressStatus?.description ?? "NA")",
- "",
- ].joined(separator: "\n")
- }
- }
- extension DetailedStatus: RawRepresentable {
- public init?(rawValue: Data) {
- do {
- try self.init(encodedData: rawValue)
- } catch {
- return nil
- }
- }
-
- public var rawValue: Data {
- return data
- }
- }
- extension TimeInterval {
- var stringValue: String {
- let totalSeconds = self
- let minutes = Int(totalSeconds / 60) % 60
- let hours = Int(totalSeconds / 3600) - (Int(self / 3600)/24 * 24)
- let days = Int((totalSeconds / 3600) / 24)
- var pluralFormOfDays = "days"
- if days == 1 {
- pluralFormOfDays = "day"
- }
- let timeComponent = String(format: "%02d:%02d", hours, minutes)
- if days > 0 {
- return String(format: "%d \(pluralFormOfDays) plus %@", days, timeComponent)
- } else {
- return timeComponent
- }
- }
- }
- extension Double {
- var twoDecimals: String {
- let reservoirLevel = self
- return String(format: "%.2f", reservoirLevel)
- }
- }
- // Type for the ErrorEventInfo VV byte if valid
- // a: insulin state table corruption found during error logging
- // bb: internal 2-bit occlusion type
- // c: immediate bolus in progress during error
- // dddd: Pod Progress at time of first logged fault event
- //
- public struct ErrorEventInfo: CustomStringConvertible, Equatable {
- let rawValue: UInt8
- let insulinStateTableCorruption: Bool // 'a' bit
- let occlusionType: Int // 'bb' 2-bit occlusion type
- let immediateBolusInProgress: Bool // 'c' bit
- let podProgressStatus: PodProgressStatus // 'dddd' bits
- public var errorEventInfo: ErrorEventInfo? {
- return ErrorEventInfo(rawValue: rawValue)
- }
- public var description: String {
- let hexString = String(format: "%02X", rawValue)
- return [
- "rawValue: 0x\(hexString)",
- "insulinStateTableCorruption: \(insulinStateTableCorruption)",
- "occlusionType: \(occlusionType)",
- "immediateBolusInProgress: \(immediateBolusInProgress)",
- "podProgressStatus: \(podProgressStatus)",
- ].joined(separator: ", ")
- }
- init(rawValue: UInt8) {
- self.rawValue = rawValue
- self.insulinStateTableCorruption = (rawValue & 0x80) != 0
- self.occlusionType = Int((rawValue & 0x60) >> 5)
- self.immediateBolusInProgress = (rawValue & 0x10) != 0
- self.podProgressStatus = PodProgressStatus(rawValue: rawValue & 0xF)!
- }
- }
- extension DetailedStatus {
- var highlightText: String {
- switch faultEventCode.faultType {
- case .exceededMaximumPodLife80Hrs:
- return LocalizedString("Pod Expired", comment: "Highlight string for pod expired (80hrs).")
- default:
- return LocalizedString("Pod Fault", comment: "Highlight string for pod faults.")
- }
- }
- }
|