PodState.swift 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  1. //
  2. // PodState.swift
  3. // OmniKit
  4. //
  5. // Created by Pete Schwamb on 10/13/17.
  6. // Copyright © 2017 Pete Schwamb. All rights reserved.
  7. //
  8. import Foundation
  9. import LoopKit
  10. public enum SetupProgress: Int {
  11. case addressAssigned = 0
  12. case podPaired
  13. case startingPrime
  14. case priming
  15. case settingInitialBasalSchedule
  16. case initialBasalScheduleSet
  17. case startingInsertCannula
  18. case cannulaInserting
  19. case completed
  20. case activationTimeout
  21. case podIncompatible
  22. public var isPaired: Bool {
  23. return self.rawValue >= SetupProgress.podPaired.rawValue
  24. }
  25. public var primingNeverAttempted: Bool {
  26. return self.rawValue < SetupProgress.startingPrime.rawValue
  27. }
  28. public var primingNeeded: Bool {
  29. return self.rawValue < SetupProgress.priming.rawValue
  30. }
  31. public var needsInitialBasalSchedule: Bool {
  32. return self.rawValue < SetupProgress.initialBasalScheduleSet.rawValue
  33. }
  34. public var needsCannulaInsertion: Bool {
  35. return self.rawValue < SetupProgress.completed.rawValue
  36. }
  37. }
  38. // TODO: Mutating functions aren't guaranteed to synchronize read/write calls.
  39. // mutating funcs should be moved to something like this:
  40. // extension Locked where T == PodState {
  41. // }
  42. public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertible {
  43. public typealias RawValue = [String: Any]
  44. public let address: UInt32
  45. fileprivate var nonceState: NonceState
  46. public var activatedAt: Date?
  47. public var expiresAt: Date? // set based on StatusResponse timeActive and can change with Pod clock drift and/or system time change
  48. public var setupUnitsDelivered: Double?
  49. public let piVersion: String
  50. public let pmVersion: String
  51. public let lot: UInt32
  52. public let tid: UInt32
  53. var activeAlertSlots: AlertSet
  54. public var lastInsulinMeasurements: PodInsulinMeasurements?
  55. public var unfinalizedBolus: UnfinalizedDose?
  56. public var unfinalizedTempBasal: UnfinalizedDose?
  57. public var unfinalizedSuspend: UnfinalizedDose?
  58. public var unfinalizedResume: UnfinalizedDose?
  59. var finalizedDoses: [UnfinalizedDose]
  60. public var dosesToStore: [UnfinalizedDose] {
  61. return finalizedDoses + [unfinalizedTempBasal, unfinalizedSuspend, unfinalizedBolus].compactMap {$0}
  62. }
  63. public var suspendState: SuspendState
  64. public var isSuspended: Bool {
  65. if case .suspended = suspendState {
  66. return true
  67. }
  68. return false
  69. }
  70. public var fault: DetailedStatus?
  71. public var messageTransportState: MessageTransportState
  72. public var primeFinishTime: Date?
  73. public var setupProgress: SetupProgress
  74. public var configuredAlerts: [AlertSlot: PodAlert]
  75. public var activeAlerts: [AlertSlot: PodAlert] {
  76. var active = [AlertSlot: PodAlert]()
  77. for slot in activeAlertSlots {
  78. if let alert = configuredAlerts[slot] {
  79. active[slot] = alert
  80. }
  81. }
  82. return active
  83. }
  84. public var insulinType: InsulinType
  85. // the following two vars are not persistent across app restarts
  86. public var deliveryStatusVerified: Bool
  87. public var lastCommsOK: Bool
  88. public init(address: UInt32, piVersion: String, pmVersion: String, lot: UInt32, tid: UInt32, packetNumber: Int = 0, messageNumber: Int = 0, insulinType: InsulinType) {
  89. self.address = address
  90. self.nonceState = NonceState(lot: lot, tid: tid)
  91. self.piVersion = piVersion
  92. self.pmVersion = pmVersion
  93. self.lot = lot
  94. self.tid = tid
  95. self.lastInsulinMeasurements = nil
  96. self.finalizedDoses = []
  97. self.suspendState = .resumed(Date())
  98. self.fault = nil
  99. self.activeAlertSlots = .none
  100. self.messageTransportState = MessageTransportState(packetNumber: packetNumber, messageNumber: messageNumber)
  101. self.primeFinishTime = nil
  102. self.setupProgress = .addressAssigned
  103. self.configuredAlerts = [.slot7: .waitingForPairingReminder]
  104. self.insulinType = insulinType
  105. self.deliveryStatusVerified = false
  106. self.lastCommsOK = false
  107. }
  108. public var unfinishedSetup: Bool {
  109. return setupProgress != .completed
  110. }
  111. public var readyForCannulaInsertion: Bool {
  112. guard let primeFinishTime = self.primeFinishTime else {
  113. return false
  114. }
  115. return !setupProgress.primingNeeded && primeFinishTime.timeIntervalSinceNow < 0
  116. }
  117. public var isActive: Bool {
  118. return setupProgress == .completed && fault == nil
  119. }
  120. // variation on isActive that doesn't care if Pod is faulted
  121. public var isSetupComplete: Bool {
  122. return setupProgress == .completed
  123. }
  124. public var isFaulted: Bool {
  125. return fault != nil || setupProgress == .activationTimeout || setupProgress == .podIncompatible
  126. }
  127. public mutating func advanceToNextNonce() {
  128. nonceState.advanceToNextNonce()
  129. }
  130. public var currentNonce: UInt32 {
  131. return nonceState.currentNonce
  132. }
  133. public mutating func resyncNonce(syncWord: UInt16, sentNonce: UInt32, messageSequenceNum: Int) {
  134. let sum = (sentNonce & 0xffff) + UInt32(crc16Table[messageSequenceNum]) + (lot & 0xffff) + (tid & 0xffff)
  135. let seed = UInt16(sum & 0xffff) ^ syncWord
  136. nonceState = NonceState(lot: lot, tid: tid, seed: seed)
  137. }
  138. private mutating func updatePodTimes(timeActive: TimeInterval) -> Date {
  139. let now = Date()
  140. let activatedAtComputed = now - timeActive
  141. if activatedAt == nil {
  142. self.activatedAt = activatedAtComputed
  143. }
  144. let expiresAtComputed = activatedAtComputed + Pod.nominalPodLife
  145. if expiresAt == nil {
  146. self.expiresAt = expiresAtComputed
  147. } else if expiresAtComputed < self.expiresAt! || expiresAtComputed > (self.expiresAt! + TimeInterval(minutes: 1)) {
  148. // The computed expiresAt time is earlier than or more than a minute later than the current expiresAt time,
  149. // so use the computed expiresAt time instead to handle Pod clock drift and/or system time changes issues.
  150. // The more than a minute later test prevents oscillation of expiresAt based on the timing of the responses.
  151. self.expiresAt = expiresAtComputed
  152. }
  153. return now
  154. }
  155. public mutating func updateFromStatusResponse(_ response: StatusResponse) {
  156. let now = updatePodTimes(timeActive: response.timeActive)
  157. updateDeliveryStatus(deliveryStatus: response.deliveryStatus, podProgressStatus: response.podProgressStatus, bolusNotDelivered: response.bolusNotDelivered)
  158. lastInsulinMeasurements = PodInsulinMeasurements(insulinDelivered: response.insulin, reservoirLevel: response.reservoirLevel, setupUnitsDelivered: setupUnitsDelivered, validTime: now)
  159. activeAlertSlots = response.alerts
  160. }
  161. public mutating func updateFromDetailedStatusResponse(_ response: DetailedStatus) {
  162. let now = updatePodTimes(timeActive: response.timeActive)
  163. updateDeliveryStatus(deliveryStatus: response.deliveryStatus, podProgressStatus: response.podProgressStatus, bolusNotDelivered: response.bolusNotDelivered)
  164. lastInsulinMeasurements = PodInsulinMeasurements(insulinDelivered: response.totalInsulinDelivered, reservoirLevel: response.reservoirLevel, setupUnitsDelivered: setupUnitsDelivered, validTime: now)
  165. activeAlertSlots = response.unacknowledgedAlerts
  166. }
  167. public mutating func registerConfiguredAlert(slot: AlertSlot, alert: PodAlert) {
  168. configuredAlerts[slot] = alert
  169. }
  170. public mutating func finalizeFinishedDoses() {
  171. if let bolus = unfinalizedBolus, bolus.isFinished {
  172. finalizedDoses.append(bolus)
  173. unfinalizedBolus = nil
  174. }
  175. if let tempBasal = unfinalizedTempBasal, tempBasal.isFinished {
  176. finalizedDoses.append(tempBasal)
  177. unfinalizedTempBasal = nil
  178. }
  179. }
  180. private mutating func updateDeliveryStatus(deliveryStatus: DeliveryStatus, podProgressStatus: PodProgressStatus, bolusNotDelivered: Double) {
  181. deliveryStatusVerified = true
  182. // See if the pod deliveryStatus indicates an active bolus or temp basal that the PodState isn't tracking (possible Loop restart)
  183. if deliveryStatus.bolusing && unfinalizedBolus == nil { // active bolus that Loop doesn't know about?
  184. deliveryStatusVerified = false // remember that we had inconsistent (bolus) delivery status
  185. if podProgressStatus.readyForDelivery {
  186. // Create an unfinalizedBolus with the remaining bolus amount to capture what we can.
  187. unfinalizedBolus = UnfinalizedDose(bolusAmount: bolusNotDelivered, startTime: Date(), scheduledCertainty: .certain, insulinType: insulinType, automatic: nil)
  188. }
  189. }
  190. if deliveryStatus.tempBasalRunning && unfinalizedTempBasal == nil { // active temp basal that Loop doesn't know about?
  191. deliveryStatusVerified = false // remember that we had inconsistent (temp basal) delivery status
  192. }
  193. finalizeFinishedDoses()
  194. if let bolus = unfinalizedBolus, bolus.scheduledCertainty == .uncertain {
  195. if deliveryStatus.bolusing {
  196. // Bolus did schedule
  197. unfinalizedBolus?.scheduledCertainty = .certain
  198. } else {
  199. // Bolus didn't happen
  200. unfinalizedBolus = nil
  201. }
  202. }
  203. if let tempBasal = unfinalizedTempBasal, tempBasal.scheduledCertainty == .uncertain {
  204. if deliveryStatus.tempBasalRunning {
  205. // Temp basal did schedule
  206. unfinalizedTempBasal?.scheduledCertainty = .certain
  207. } else {
  208. // Temp basal didn't happen
  209. unfinalizedTempBasal = nil
  210. }
  211. }
  212. if let resume = unfinalizedResume, resume.scheduledCertainty == .uncertain {
  213. if deliveryStatus != .suspended {
  214. // Resume was enacted
  215. unfinalizedResume?.scheduledCertainty = .certain
  216. } else {
  217. // Resume wasn't enacted
  218. unfinalizedResume = nil
  219. }
  220. }
  221. if let suspend = unfinalizedSuspend {
  222. if suspend.scheduledCertainty == .uncertain {
  223. if deliveryStatus == .suspended {
  224. // Suspend was enacted
  225. unfinalizedSuspend?.scheduledCertainty = .certain
  226. } else {
  227. // Suspend wasn't enacted
  228. unfinalizedSuspend = nil
  229. }
  230. }
  231. if let resume = unfinalizedResume, suspend.startTime < resume.startTime {
  232. finalizedDoses.append(suspend)
  233. finalizedDoses.append(resume)
  234. unfinalizedSuspend = nil
  235. unfinalizedResume = nil
  236. }
  237. }
  238. }
  239. // MARK: - RawRepresentable
  240. public init?(rawValue: RawValue) {
  241. guard
  242. let address = rawValue["address"] as? UInt32,
  243. let nonceStateRaw = rawValue["nonceState"] as? NonceState.RawValue,
  244. let nonceState = NonceState(rawValue: nonceStateRaw),
  245. let piVersion = rawValue["piVersion"] as? String,
  246. let pmVersion = rawValue["pmVersion"] as? String,
  247. let lot = rawValue["lot"] as? UInt32,
  248. let tid = rawValue["tid"] as? UInt32
  249. else {
  250. return nil
  251. }
  252. self.address = address
  253. self.nonceState = nonceState
  254. self.piVersion = piVersion
  255. self.pmVersion = pmVersion
  256. self.lot = lot
  257. self.tid = tid
  258. if let activatedAt = rawValue["activatedAt"] as? Date {
  259. self.activatedAt = activatedAt
  260. if let expiresAt = rawValue["expiresAt"] as? Date {
  261. self.expiresAt = expiresAt
  262. } else {
  263. self.expiresAt = activatedAt + Pod.nominalPodLife
  264. }
  265. }
  266. if let setupUnitsDelivered = rawValue["setupUnitsDelivered"] as? Double {
  267. self.setupUnitsDelivered = setupUnitsDelivered
  268. }
  269. if let suspended = rawValue["suspended"] as? Bool {
  270. // Migrate old value
  271. if suspended {
  272. suspendState = .suspended(Date())
  273. } else {
  274. suspendState = .resumed(Date())
  275. }
  276. } else if let rawSuspendState = rawValue["suspendState"] as? SuspendState.RawValue, let suspendState = SuspendState(rawValue: rawSuspendState) {
  277. self.suspendState = suspendState
  278. } else {
  279. return nil
  280. }
  281. if let rawUnfinalizedBolus = rawValue["unfinalizedBolus"] as? UnfinalizedDose.RawValue
  282. {
  283. self.unfinalizedBolus = UnfinalizedDose(rawValue: rawUnfinalizedBolus)
  284. }
  285. if let rawUnfinalizedTempBasal = rawValue["unfinalizedTempBasal"] as? UnfinalizedDose.RawValue
  286. {
  287. self.unfinalizedTempBasal = UnfinalizedDose(rawValue: rawUnfinalizedTempBasal)
  288. }
  289. if let rawUnfinalizedSuspend = rawValue["unfinalizedSuspend"] as? UnfinalizedDose.RawValue
  290. {
  291. self.unfinalizedSuspend = UnfinalizedDose(rawValue: rawUnfinalizedSuspend)
  292. }
  293. if let rawUnfinalizedResume = rawValue["unfinalizedResume"] as? UnfinalizedDose.RawValue
  294. {
  295. self.unfinalizedResume = UnfinalizedDose(rawValue: rawUnfinalizedResume)
  296. }
  297. if let rawLastInsulinMeasurements = rawValue["lastInsulinMeasurements"] as? PodInsulinMeasurements.RawValue {
  298. self.lastInsulinMeasurements = PodInsulinMeasurements(rawValue: rawLastInsulinMeasurements)
  299. } else {
  300. self.lastInsulinMeasurements = nil
  301. }
  302. if let rawFinalizedDoses = rawValue["finalizedDoses"] as? [UnfinalizedDose.RawValue] {
  303. self.finalizedDoses = rawFinalizedDoses.compactMap( { UnfinalizedDose(rawValue: $0) } )
  304. } else {
  305. self.finalizedDoses = []
  306. }
  307. if let rawFault = rawValue["fault"] as? DetailedStatus.RawValue,
  308. let fault = DetailedStatus(rawValue: rawFault),
  309. fault.faultEventCode.faultType != .noFaults
  310. {
  311. self.fault = fault
  312. } else {
  313. self.fault = nil
  314. }
  315. if let alarmsRawValue = rawValue["alerts"] as? UInt8 {
  316. self.activeAlertSlots = AlertSet(rawValue: alarmsRawValue)
  317. } else {
  318. self.activeAlertSlots = .none
  319. }
  320. if let setupProgressRaw = rawValue["setupProgress"] as? Int,
  321. let setupProgress = SetupProgress(rawValue: setupProgressRaw)
  322. {
  323. self.setupProgress = setupProgress
  324. } else {
  325. // Migrate
  326. self.setupProgress = .completed
  327. }
  328. if let messageTransportStateRaw = rawValue["messageTransportState"] as? MessageTransportState.RawValue,
  329. let messageTransportState = MessageTransportState(rawValue: messageTransportStateRaw)
  330. {
  331. self.messageTransportState = messageTransportState
  332. } else {
  333. self.messageTransportState = MessageTransportState(packetNumber: 0, messageNumber: 0)
  334. }
  335. if let rawConfiguredAlerts = rawValue["configuredAlerts"] as? [String: PodAlert.RawValue] {
  336. var configuredAlerts = [AlertSlot: PodAlert]()
  337. for (rawSlot, rawAlert) in rawConfiguredAlerts {
  338. if let slotNum = UInt8(rawSlot), let slot = AlertSlot(rawValue: slotNum), let alert = PodAlert(rawValue: rawAlert) {
  339. configuredAlerts[slot] = alert
  340. }
  341. }
  342. self.configuredAlerts = configuredAlerts
  343. } else {
  344. // Assume migration, and set up with alerts that are normally configured
  345. self.configuredAlerts = [
  346. .slot2: .shutdownImminentAlarm(0),
  347. .slot3: .expirationAlert(0),
  348. .slot4: .lowReservoirAlarm(0),
  349. .slot5: .podSuspendedReminder(active: false, suspendTime: 0),
  350. .slot6: .suspendTimeExpired(suspendTime: 0),
  351. .slot7: .expirationAdvisoryAlarm(alarmTime: 0, duration: 0)
  352. ]
  353. }
  354. self.primeFinishTime = rawValue["primeFinishTime"] as? Date
  355. if let rawInsulinType = rawValue["insulinType"] as? InsulinType.RawValue, let insulinType = InsulinType(rawValue: rawInsulinType) {
  356. self.insulinType = insulinType
  357. } else {
  358. insulinType = .novolog
  359. }
  360. self.deliveryStatusVerified = false
  361. self.lastCommsOK = false
  362. }
  363. public var rawValue: RawValue {
  364. var rawValue: RawValue = [
  365. "address": address,
  366. "nonceState": nonceState.rawValue,
  367. "piVersion": piVersion,
  368. "pmVersion": pmVersion,
  369. "lot": lot,
  370. "tid": tid,
  371. "suspendState": suspendState.rawValue,
  372. "finalizedDoses": finalizedDoses.map( { $0.rawValue }),
  373. "alerts": activeAlertSlots.rawValue,
  374. "messageTransportState": messageTransportState.rawValue,
  375. "setupProgress": setupProgress.rawValue,
  376. "insulinType": insulinType.rawValue
  377. ]
  378. if let unfinalizedBolus = self.unfinalizedBolus {
  379. rawValue["unfinalizedBolus"] = unfinalizedBolus.rawValue
  380. }
  381. if let unfinalizedTempBasal = self.unfinalizedTempBasal {
  382. rawValue["unfinalizedTempBasal"] = unfinalizedTempBasal.rawValue
  383. }
  384. if let unfinalizedSuspend = self.unfinalizedSuspend {
  385. rawValue["unfinalizedSuspend"] = unfinalizedSuspend.rawValue
  386. }
  387. if let unfinalizedResume = self.unfinalizedResume {
  388. rawValue["unfinalizedResume"] = unfinalizedResume.rawValue
  389. }
  390. if let lastInsulinMeasurements = self.lastInsulinMeasurements {
  391. rawValue["lastInsulinMeasurements"] = lastInsulinMeasurements.rawValue
  392. }
  393. if let fault = self.fault {
  394. rawValue["fault"] = fault.rawValue
  395. }
  396. if let primeFinishTime = primeFinishTime {
  397. rawValue["primeFinishTime"] = primeFinishTime
  398. }
  399. if let activatedAt = activatedAt {
  400. rawValue["activatedAt"] = activatedAt
  401. }
  402. if let expiresAt = expiresAt {
  403. rawValue["expiresAt"] = expiresAt
  404. }
  405. if let setupUnitsDelivered = setupUnitsDelivered {
  406. rawValue["setupUnitsDelivered"] = setupUnitsDelivered
  407. }
  408. if configuredAlerts.count > 0 {
  409. let rawConfiguredAlerts = Dictionary(uniqueKeysWithValues:
  410. configuredAlerts.map { slot, alarm in (String(describing: slot.rawValue), alarm.rawValue) })
  411. rawValue["configuredAlerts"] = rawConfiguredAlerts
  412. }
  413. return rawValue
  414. }
  415. // MARK: - CustomDebugStringConvertible
  416. public var debugDescription: String {
  417. return [
  418. "### PodState",
  419. "* address: \(String(format: "%04X", address))",
  420. "* activatedAt: \(String(reflecting: activatedAt))",
  421. "* expiresAt: \(String(reflecting: expiresAt))",
  422. "* setupUnitsDelivered: \(String(reflecting: setupUnitsDelivered))",
  423. "* piVersion: \(piVersion)",
  424. "* pmVersion: \(pmVersion)",
  425. "* lot: \(lot)",
  426. "* tid: \(tid)",
  427. "* suspendState: \(suspendState)",
  428. "* unfinalizedBolus: \(String(describing: unfinalizedBolus))",
  429. "* unfinalizedTempBasal: \(String(describing: unfinalizedTempBasal))",
  430. "* unfinalizedSuspend: \(String(describing: unfinalizedSuspend))",
  431. "* unfinalizedResume: \(String(describing: unfinalizedResume))",
  432. "* finalizedDoses: \(String(describing: finalizedDoses))",
  433. "* activeAlerts: \(String(describing: activeAlerts))",
  434. "* messageTransportState: \(String(describing: messageTransportState))",
  435. "* setupProgress: \(setupProgress)",
  436. "* primeFinishTime: \(String(describing: primeFinishTime))",
  437. "* configuredAlerts: \(String(describing: configuredAlerts))",
  438. "* insulinType: \(String(describing: insulinType))",
  439. "",
  440. fault != nil ? String(reflecting: fault!) : "fault: nil",
  441. "",
  442. ].joined(separator: "\n")
  443. }
  444. }
  445. fileprivate struct NonceState: RawRepresentable, Equatable {
  446. public typealias RawValue = [String: Any]
  447. var table: [UInt32]
  448. var idx: UInt8
  449. public init(lot: UInt32 = 0, tid: UInt32 = 0, seed: UInt16 = 0) {
  450. table = Array(repeating: UInt32(0), count: 2 + 16)
  451. table[0] = (lot & 0xFFFF) &+ (lot >> 16) &+ 0x55543DC3
  452. table[1] = (tid & 0xFFFF) &+ (tid >> 16) &+ 0xAAAAE44E
  453. idx = 0
  454. table[0] += UInt32((seed & 0x00ff))
  455. table[1] += UInt32((seed & 0xff00) >> 8)
  456. for i in 0..<16 {
  457. table[2 + i] = generateEntry()
  458. }
  459. idx = UInt8((table[0] + table[1]) & 0x0F)
  460. }
  461. private mutating func generateEntry() -> UInt32 {
  462. table[0] = (table[0] >> 16) &+ ((table[0] & 0xFFFF) &* 0x5D7F)
  463. table[1] = (table[1] >> 16) &+ ((table[1] & 0xFFFF) &* 0x8CA0)
  464. return table[1] &+ ((table[0] & 0xFFFF) << 16)
  465. }
  466. public mutating func advanceToNextNonce() {
  467. let nonce = currentNonce
  468. table[Int(2 + idx)] = generateEntry()
  469. idx = UInt8(nonce & 0x0F)
  470. }
  471. public var currentNonce: UInt32 {
  472. return table[Int(2 + idx)]
  473. }
  474. // RawRepresentable
  475. public init?(rawValue: RawValue) {
  476. guard
  477. let table = rawValue["table"] as? [UInt32],
  478. let idx = rawValue["idx"] as? UInt8
  479. else {
  480. return nil
  481. }
  482. self.table = table
  483. self.idx = idx
  484. }
  485. public var rawValue: RawValue {
  486. let rawValue: RawValue = [
  487. "table": table,
  488. "idx": idx,
  489. ]
  490. return rawValue
  491. }
  492. }
  493. public enum SuspendState: Equatable, RawRepresentable {
  494. public typealias RawValue = [String: Any]
  495. private enum SuspendStateType: Int {
  496. case suspend, resume
  497. }
  498. case suspended(Date)
  499. case resumed(Date)
  500. private var identifier: Int {
  501. switch self {
  502. case .suspended:
  503. return 1
  504. case .resumed:
  505. return 2
  506. }
  507. }
  508. public init?(rawValue: RawValue) {
  509. guard let suspendStateType = rawValue["case"] as? SuspendStateType.RawValue,
  510. let date = rawValue["date"] as? Date else {
  511. return nil
  512. }
  513. switch SuspendStateType(rawValue: suspendStateType) {
  514. case .suspend?:
  515. self = .suspended(date)
  516. case .resume?:
  517. self = .resumed(date)
  518. default:
  519. return nil
  520. }
  521. }
  522. public var rawValue: RawValue {
  523. switch self {
  524. case .suspended(let date):
  525. return [
  526. "case": SuspendStateType.suspend.rawValue,
  527. "date": date
  528. ]
  529. case .resumed(let date):
  530. return [
  531. "case": SuspendStateType.resume.rawValue,
  532. "date": date
  533. ]
  534. }
  535. }
  536. }