DetailedStatus.swift 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. //
  2. // DetailedStatus.swift
  3. // OmniKit
  4. //
  5. // Created by Pete Schwamb on 2/23/18.
  6. // Copyright © 2018 Pete Schwamb. All rights reserved.
  7. //
  8. import Foundation
  9. // DetailedStatus is the PodInfo subtype 2 returned for a type 2 GetStatus command and
  10. // is also returned on a pod fault for any command normally returning a StatusResponse
  11. public struct DetailedStatus : PodInfo, Equatable {
  12. // CMD 1 2 3 4 5 6 7 8 9 10 1112 1314 1516 17 18 19 20 21 2223
  13. // DATA 0 1 2 3 4 5 6 7 8 910 1112 1314 15 16 17 18 19 2021
  14. // 02 16 02 0J 0K LLLL MM NNNN PP QQQQ RRRR SSSS TT UU VV WW 0X YYYY
  15. public let podInfoType: PodInfoResponseSubType = .detailedStatus
  16. public let podProgressStatus: PodProgressStatus
  17. public let deliveryStatus: DeliveryStatus
  18. public let bolusNotDelivered: Double
  19. public let lastProgrammingMessageSeqNum: UInt8 // updated by pod for 03, 08, $11, $19, $1A, $1C, $1E & $1F command messages
  20. public let totalInsulinDelivered: Double
  21. public let faultEventCode: FaultEventCode
  22. public let faultEventTimeSinceActivation: TimeInterval?
  23. public let reservoirLevel: Double?
  24. public let timeActive: TimeInterval
  25. public let unacknowledgedAlerts: AlertSet
  26. public let faultAccessingTables: Bool
  27. public let errorEventInfo: ErrorEventInfo?
  28. public let receiverLowGain: UInt8
  29. public let radioRSSI: UInt8
  30. public let previousPodProgressStatus: PodProgressStatus?
  31. // YYYY is uninitialized data for Eros
  32. public let data: Data
  33. public init(encodedData: Data) throws {
  34. guard encodedData.count >= 21 else {
  35. throw MessageBlockError.notEnoughData
  36. }
  37. guard PodProgressStatus(rawValue: encodedData[1]) != nil else {
  38. throw MessageError.unknownValue(value: encodedData[1], typeDescription: "PodProgressStatus")
  39. }
  40. self.podProgressStatus = PodProgressStatus(rawValue: encodedData[1])!
  41. self.deliveryStatus = DeliveryStatus(rawValue: encodedData[2] & 0xf)!
  42. self.bolusNotDelivered = Pod.pulseSize * Double((Int(encodedData[3] & 0x3) << 8) | Int(encodedData[4]))
  43. self.lastProgrammingMessageSeqNum = encodedData[5]
  44. self.totalInsulinDelivered = Pod.pulseSize * Double(encodedData[6...7].toBigEndian(UInt16.self))
  45. self.faultEventCode = FaultEventCode(rawValue: encodedData[8])
  46. let minutesSinceActivation = encodedData[9...10].toBigEndian(UInt16.self)
  47. if minutesSinceActivation != 0xffff {
  48. self.faultEventTimeSinceActivation = TimeInterval(minutes: Double(minutesSinceActivation))
  49. } else {
  50. self.faultEventTimeSinceActivation = nil
  51. }
  52. let reservoirValue = Double((Int(encodedData[11] & 0x3) << 8) + Int(encodedData[12])) * Pod.pulseSize
  53. if reservoirValue <= Pod.maximumReservoirReading {
  54. self.reservoirLevel = reservoirValue
  55. } else {
  56. self.reservoirLevel = nil
  57. }
  58. self.timeActive = TimeInterval(minutes: Double(encodedData[13...14].toBigEndian(UInt16.self)))
  59. self.unacknowledgedAlerts = AlertSet(rawValue: encodedData[15])
  60. self.faultAccessingTables = (encodedData[16] & 2) != 0
  61. if encodedData[17] == 0x00 {
  62. self.errorEventInfo = nil // this byte is not valid (no fault has occurred)
  63. } else {
  64. self.errorEventInfo = ErrorEventInfo(rawValue: encodedData[17])
  65. }
  66. self.receiverLowGain = UInt8(encodedData[18] >> 6)
  67. self.radioRSSI = UInt8(encodedData[18] & 0x3F)
  68. if encodedData[19] == 0xFF {
  69. self.previousPodProgressStatus = nil // this byte is not valid (no fault has occurred)
  70. } else {
  71. self.previousPodProgressStatus = PodProgressStatus(rawValue: encodedData[19] & 0xF)!
  72. }
  73. self.data = Data(encodedData)
  74. }
  75. public var isFaulted: Bool {
  76. return faultEventCode.faultType != .noFaults || podProgressStatus == .activationTimeExceeded
  77. }
  78. // Returns an appropropriate PDM style Ref string for the Detailed Status.
  79. // For most types, Ref: TT-VVVHH-IIIRR-FFF computed as {19|17}-{VV}{SSSS/60}-{NNNN/20}{RRRR/20}-PP
  80. public var pdmRef: String? {
  81. let TT, VVV, HH, III, RR, FFF: UInt8
  82. let refStr = LocalizedString("Ref", comment: "PDM style 'Ref' string")
  83. switch faultEventCode.faultType {
  84. case .noFaults, .reservoirEmpty, .exceededMaximumPodLife80Hrs:
  85. return nil // no PDM Ref # generated for these cases
  86. case .insulinDeliveryCommandError:
  87. // This fault is treated as a PDM fault which uses an alternate Ref format
  88. return String(format: "%@:\u{00a0}11-144-0018-00049", refStr) // all fixed values for this fault
  89. case .occluded:
  90. // Ref: 17-000HH-IIIRR-000
  91. TT = 17 // Occlusion detected Ref type
  92. VVV = 0 // no VVV value for an occlusion fault
  93. FFF = 0 // no FFF value for an occlusion fault
  94. default:
  95. // Ref: 19-VVVHH-IIIRR-FFF
  96. TT = 19 // pod fault Ref type
  97. VVV = data[17] // use the raw VV byte value
  98. FFF = faultEventCode.rawValue
  99. }
  100. HH = UInt8(timeActive.hours)
  101. III = UInt8(totalInsulinDelivered)
  102. if let reservoirLevel = self.reservoirLevel {
  103. RR = UInt8(reservoirLevel)
  104. } else {
  105. RR = 51 // value used for 50+ U
  106. }
  107. return String(format: "%@:\u{00a0}%02d-%03d%02d-%03d%02d-%03d", refStr, TT, VVV, HH, III, RR, FFF)
  108. }
  109. }
  110. extension DetailedStatus: CustomDebugStringConvertible {
  111. public typealias RawValue = Data
  112. public var debugDescription: String {
  113. return [
  114. "## DetailedStatus",
  115. "* rawHex: \(data.hexadecimalString)",
  116. "* podProgressStatus: \(podProgressStatus)",
  117. "* deliveryStatus: \(deliveryStatus.description)",
  118. "* bolusNotDelivered: \(bolusNotDelivered.twoDecimals) U",
  119. "* lastProgrammingMessageSeqNum: \(lastProgrammingMessageSeqNum)",
  120. "* totalInsulinDelivered: \(totalInsulinDelivered.twoDecimals) U",
  121. "* faultEventCode: \(faultEventCode.description)",
  122. "* faultEventTimeSinceActivation: \(faultEventTimeSinceActivation?.stringValue ?? "none")",
  123. "* reservoirLevel: \(reservoirLevel?.twoDecimals ?? "50+") U",
  124. "* timeActive: \(timeActive.stringValue)",
  125. "* unacknowledgedAlerts: \(unacknowledgedAlerts)",
  126. "* faultAccessingTables: \(faultAccessingTables)",
  127. "* errorEventInfo: \(errorEventInfo?.description ?? "NA")",
  128. "* receiverLowGain: \(receiverLowGain)",
  129. "* radioRSSI: \(radioRSSI)",
  130. "* previousPodProgressStatus: \(previousPodProgressStatus?.description ?? "NA")",
  131. "",
  132. ].joined(separator: "\n")
  133. }
  134. }
  135. extension DetailedStatus: RawRepresentable {
  136. public init?(rawValue: Data) {
  137. do {
  138. try self.init(encodedData: rawValue)
  139. } catch {
  140. return nil
  141. }
  142. }
  143. public var rawValue: Data {
  144. return data
  145. }
  146. }
  147. extension TimeInterval {
  148. var stringValue: String {
  149. let totalSeconds = self
  150. let minutes = Int(totalSeconds / 60) % 60
  151. let hours = Int(totalSeconds / 3600) - (Int(self / 3600)/24 * 24)
  152. let days = Int((totalSeconds / 3600) / 24)
  153. var pluralFormOfDays = "days"
  154. if days == 1 {
  155. pluralFormOfDays = "day"
  156. }
  157. let timeComponent = String(format: "%02d:%02d", hours, minutes)
  158. if days > 0 {
  159. return String(format: "%d \(pluralFormOfDays) plus %@", days, timeComponent)
  160. } else {
  161. return timeComponent
  162. }
  163. }
  164. }
  165. extension Double {
  166. var twoDecimals: String {
  167. let reservoirLevel = self
  168. return String(format: "%.2f", reservoirLevel)
  169. }
  170. }
  171. // Type for the ErrorEventInfo VV byte if valid
  172. // a: insulin state table corruption found during error logging
  173. // bb: internal 2-bit occlusion type
  174. // c: immediate bolus in progress during error
  175. // dddd: Pod Progress at time of first logged fault event
  176. //
  177. public struct ErrorEventInfo: CustomStringConvertible, Equatable {
  178. let rawValue: UInt8
  179. let insulinStateTableCorruption: Bool // 'a' bit
  180. let occlusionType: Int // 'bb' 2-bit occlusion type
  181. let immediateBolusInProgress: Bool // 'c' bit
  182. let podProgressStatus: PodProgressStatus // 'dddd' bits
  183. public var errorEventInfo: ErrorEventInfo? {
  184. return ErrorEventInfo(rawValue: rawValue)
  185. }
  186. public var description: String {
  187. let hexString = String(format: "%02X", rawValue)
  188. return [
  189. "rawValue: 0x\(hexString)",
  190. "insulinStateTableCorruption: \(insulinStateTableCorruption)",
  191. "occlusionType: \(occlusionType)",
  192. "immediateBolusInProgress: \(immediateBolusInProgress)",
  193. "podProgressStatus: \(podProgressStatus)",
  194. ].joined(separator: ", ")
  195. }
  196. init(rawValue: UInt8) {
  197. self.rawValue = rawValue
  198. self.insulinStateTableCorruption = (rawValue & 0x80) != 0
  199. self.occlusionType = Int((rawValue & 0x60) >> 5)
  200. self.immediateBolusInProgress = (rawValue & 0x10) != 0
  201. self.podProgressStatus = PodProgressStatus(rawValue: rawValue & 0xF)!
  202. }
  203. }
  204. extension DetailedStatus {
  205. var highlightText: String {
  206. switch faultEventCode.faultType {
  207. case .exceededMaximumPodLife80Hrs:
  208. return LocalizedString("Pod Expired", comment: "Highlight string for pod expired (80hrs).")
  209. default:
  210. return LocalizedString("Pod Fault", comment: "Highlight string for pod faults.")
  211. }
  212. }
  213. }