SetInsulinScheduleCommand.swift 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. //
  2. // SetInsulinScheduleCommand.swift
  3. // OmniKit
  4. //
  5. // Created by Pete Schwamb on 2/24/18.
  6. // Copyright © 2018 Pete Schwamb. All rights reserved.
  7. //
  8. import Foundation
  9. public struct SetInsulinScheduleCommand : NonceResyncableMessageBlock {
  10. fileprivate enum ScheduleTypeCode: UInt8 {
  11. case basalSchedule = 0
  12. case tempBasal = 1
  13. case bolus = 2
  14. }
  15. public enum DeliverySchedule {
  16. case basalSchedule(currentSegment: UInt8, secondsRemaining: UInt16, pulsesRemaining: UInt16, table: BasalDeliveryTable)
  17. case tempBasal(secondsRemaining: UInt16, firstSegmentPulses: UInt16, table: BasalDeliveryTable)
  18. case bolus(units: Double, timeBetweenPulses: TimeInterval)
  19. fileprivate func typeCode() -> ScheduleTypeCode {
  20. switch self {
  21. case .basalSchedule:
  22. return .basalSchedule
  23. case .tempBasal:
  24. return .tempBasal
  25. case .bolus:
  26. return .bolus
  27. }
  28. }
  29. fileprivate var data: Data {
  30. switch self {
  31. case .basalSchedule(let currentSegment, let secondsRemaining, let pulsesRemaining, let table):
  32. var data = Data([currentSegment])
  33. data.appendBigEndian(secondsRemaining << 3)
  34. data.appendBigEndian(pulsesRemaining)
  35. for entry in table.entries {
  36. data.append(entry.data)
  37. }
  38. return data
  39. case .bolus(let units, let timeBetweenPulses):
  40. let pulseCount = UInt16(round(units / Pod.pulseSize))
  41. let multiplier = UInt16(round(timeBetweenPulses * 8))
  42. let fieldA = pulseCount * multiplier
  43. let numHalfHourSegments: UInt8 = 1
  44. var data = Data([numHalfHourSegments])
  45. data.appendBigEndian(fieldA)
  46. data.appendBigEndian(pulseCount)
  47. data.appendBigEndian(pulseCount)
  48. return data
  49. case .tempBasal(let secondsRemaining, let firstSegmentPulses, let table):
  50. var data = Data([UInt8(table.numSegments())])
  51. data.appendBigEndian(secondsRemaining << 3)
  52. data.appendBigEndian(firstSegmentPulses)
  53. for entry in table.entries {
  54. data.append(entry.data)
  55. }
  56. return data
  57. }
  58. }
  59. fileprivate func checksum() -> UInt16 {
  60. switch self {
  61. case .basalSchedule( _, _, _, let table):
  62. return data[0..<5].reduce(0) { $0 + UInt16($1) } +
  63. table.entries.reduce(0) { $0 + $1.checksum() }
  64. case .bolus:
  65. return data[0..<7].reduce(0) { $0 + UInt16($1) }
  66. case .tempBasal(_, _, let table):
  67. return data[0..<5].reduce(0) { $0 + UInt16($1) } +
  68. table.entries.reduce(0) { $0 + $1.checksum() }
  69. }
  70. }
  71. }
  72. public let blockType: MessageBlockType = .setInsulinSchedule
  73. public var nonce: UInt32
  74. public let deliverySchedule: DeliverySchedule
  75. public var data: Data {
  76. var data = Data([
  77. blockType.rawValue,
  78. UInt8(7 + deliverySchedule.data.count),
  79. ])
  80. data.appendBigEndian(nonce)
  81. data.append(deliverySchedule.typeCode().rawValue)
  82. data.appendBigEndian(deliverySchedule.checksum())
  83. data.append(deliverySchedule.data)
  84. return data
  85. }
  86. public init(encodedData: Data) throws {
  87. if encodedData.count < 6 {
  88. throw MessageBlockError.notEnoughData
  89. }
  90. let length = encodedData[1]
  91. nonce = encodedData[2...].toBigEndian(UInt32.self)
  92. let checksum = encodedData[7...].toBigEndian(UInt16.self)
  93. guard let scheduleTypeCode = ScheduleTypeCode(rawValue: encodedData[6]) else {
  94. throw MessageError.unknownValue(value: encodedData[6], typeDescription: "ScheduleTypeCode")
  95. }
  96. switch scheduleTypeCode {
  97. case .basalSchedule:
  98. var entries = [BasalTableEntry]()
  99. let numEntries = (length - 12) / 2
  100. for i in 0..<numEntries {
  101. let dataStart = Int(i*2 + 14)
  102. let entryData = encodedData.subdata(in: dataStart..<(dataStart+2))
  103. entries.append(BasalTableEntry(encodedData: entryData))
  104. }
  105. let currentTableIndex = encodedData[9]
  106. let secondsRemaining = encodedData[10...].toBigEndian(UInt16.self) >> 3
  107. let pulsesRemaining = encodedData[12...].toBigEndian(UInt16.self)
  108. let table = BasalDeliveryTable(entries: entries)
  109. deliverySchedule = .basalSchedule(currentSegment: currentTableIndex, secondsRemaining: secondsRemaining, pulsesRemaining: pulsesRemaining, table: table)
  110. case .tempBasal:
  111. let secondsRemaining = encodedData[10...].toBigEndian(UInt16.self) >> 3
  112. let firstSegmentPulses = encodedData[12...].toBigEndian(UInt16.self)
  113. var entries = [BasalTableEntry]()
  114. let numEntries = (length - 12) / 2
  115. for i in 0..<numEntries {
  116. let dataStart = Int(i*2 + 14)
  117. let entryData = encodedData.subdata(in: dataStart..<(dataStart+2))
  118. entries.append(BasalTableEntry(encodedData: entryData))
  119. }
  120. let table = BasalDeliveryTable(entries: entries)
  121. deliverySchedule = .tempBasal(secondsRemaining: secondsRemaining, firstSegmentPulses: firstSegmentPulses, table: table)
  122. case .bolus:
  123. let duration = TimeInterval(minutes: Double(encodedData[9] * 30))
  124. let fieldA = encodedData[10...].toBigEndian(UInt16.self)
  125. let unitRate = encodedData[12...].toBigEndian(UInt16.self)
  126. let units = Double(unitRate) * 0.1 * duration.hours
  127. let multiplier = fieldA / unitRate
  128. deliverySchedule = .bolus(units: units, timeBetweenPulses: Double(multiplier) / 8)
  129. }
  130. guard checksum == deliverySchedule.checksum() else {
  131. throw MessageError.validationFailed(description: "InsulinDeliverySchedule checksum failed")
  132. }
  133. }
  134. public init(nonce: UInt32, deliverySchedule: DeliverySchedule) {
  135. self.nonce = nonce
  136. self.deliverySchedule = deliverySchedule
  137. }
  138. public init(nonce: UInt32, tempBasalRate: Double, duration: TimeInterval) {
  139. self.nonce = nonce
  140. let pulsesPerHour = Int(round(tempBasalRate / Pod.pulseSize))
  141. let table = BasalDeliveryTable(tempBasalRate: tempBasalRate, duration: duration)
  142. self.deliverySchedule = SetInsulinScheduleCommand.DeliverySchedule.tempBasal(secondsRemaining: 30*60, firstSegmentPulses: UInt16(pulsesPerHour / 2), table: table)
  143. }
  144. public init(nonce: UInt32, basalSchedule: BasalSchedule, scheduleOffset: TimeInterval) {
  145. let scheduleOffsetNearestSecond = round(scheduleOffset)
  146. let table = BasalDeliveryTable(schedule: basalSchedule)
  147. let rate = basalSchedule.rateAt(offset: scheduleOffsetNearestSecond)
  148. let segment = Int(scheduleOffsetNearestSecond / BasalDeliveryTable.segmentDuration)
  149. let segmentOffset = round(scheduleOffsetNearestSecond.truncatingRemainder(dividingBy: BasalDeliveryTable.segmentDuration))
  150. let timeRemainingInSegment = BasalDeliveryTable.segmentDuration - segmentOffset
  151. let timeBetweenPulses: TimeInterval = .hours(1) / (rate / Pod.pulseSize)
  152. let offsetToNextTenth = timeRemainingInSegment.truncatingRemainder(dividingBy: timeBetweenPulses / 10.0)
  153. let pulsesRemainingInSegment = (timeRemainingInSegment + timeBetweenPulses / 10.0 - offsetToNextTenth) / timeBetweenPulses
  154. self.deliverySchedule = SetInsulinScheduleCommand.DeliverySchedule.basalSchedule(currentSegment: UInt8(segment), secondsRemaining: UInt16(timeRemainingInSegment), pulsesRemaining: UInt16(pulsesRemainingInSegment), table: table)
  155. self.nonce = nonce
  156. }
  157. }
  158. fileprivate func calculateChecksum(_ data: Data) -> UInt16 {
  159. return data.reduce(0) { $0 + UInt16($1) }
  160. }
  161. extension SetInsulinScheduleCommand: CustomDebugStringConvertible {
  162. public var debugDescription: String {
  163. return "SetInsulinScheduleCommand(nonce:\(nonce), \(deliverySchedule))"
  164. }
  165. }