BasalDeliveryTable.swift 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. //
  2. // BasalDeliveryTable.swift
  3. // OmniKit
  4. //
  5. // Created by Pete Schwamb on 4/4/18.
  6. // Copyright © 2018 Pete Schwamb. All rights reserved.
  7. //
  8. import Foundation
  9. public struct BasalTableEntry {
  10. let segments: Int
  11. let pulses: Int
  12. let alternateSegmentPulse: Bool
  13. public init(encodedData: Data) {
  14. segments = Int(encodedData[0] >> 4) + 1
  15. pulses = (Int(encodedData[0] & 0b11) << 8) + Int(encodedData[1])
  16. alternateSegmentPulse = (encodedData[0] >> 3) & 0x1 == 1
  17. }
  18. public init(segments: Int, pulses: Int, alternateSegmentPulse: Bool) {
  19. self.segments = segments
  20. self.pulses = pulses
  21. self.alternateSegmentPulse = alternateSegmentPulse
  22. }
  23. public var data: Data {
  24. let pulsesHighBits = UInt8((pulses >> 8) & 0b11)
  25. let pulsesLowBits = UInt8(pulses & 0xff)
  26. return Data([
  27. UInt8((segments - 1) << 4) + UInt8((alternateSegmentPulse ? 1 : 0) << 3) + pulsesHighBits,
  28. UInt8(pulsesLowBits)
  29. ])
  30. }
  31. public func checksum() -> UInt16 {
  32. let checksumPerSegment = (pulses & 0xff) + (pulses >> 8)
  33. return UInt16(checksumPerSegment * segments + (alternateSegmentPulse ? segments / 2 : 0))
  34. }
  35. }
  36. extension BasalTableEntry: CustomDebugStringConvertible {
  37. public var debugDescription: String {
  38. return "BasalTableEntry(segments:\(segments), pulses:\(pulses), alternateSegmentPulse:\(alternateSegmentPulse))"
  39. }
  40. }
  41. public struct BasalDeliveryTable {
  42. static let segmentDuration: TimeInterval = .minutes(30)
  43. let entries: [BasalTableEntry]
  44. public init(entries: [BasalTableEntry]) {
  45. self.entries = entries
  46. }
  47. public init(schedule: BasalSchedule) {
  48. struct TempSegment {
  49. let pulses: Int
  50. }
  51. let numSegments = 48
  52. let maxSegmentsPerEntry = 16
  53. var halfPulseRemainder = false
  54. let expandedSegments = stride(from: 0, to: numSegments, by: 1).map { (index) -> TempSegment in
  55. let rate = schedule.rateAt(offset: Double(index) * .minutes(30))
  56. let pulsesPerHour = Int(round(rate / Pod.pulseSize))
  57. let pulsesPerSegment = pulsesPerHour >> 1
  58. let halfPulse = pulsesPerHour & 0b1 != 0
  59. let segment = TempSegment(pulses: pulsesPerSegment + ((halfPulseRemainder && halfPulse) ? 1 : 0))
  60. halfPulseRemainder = halfPulseRemainder != halfPulse
  61. return segment
  62. }
  63. var tableEntries = [BasalTableEntry]()
  64. let addEntry = { (segments: [TempSegment], alternateSegmentPulse: Bool) in
  65. tableEntries.append(BasalTableEntry(
  66. segments: segments.count,
  67. pulses: segments.first!.pulses,
  68. alternateSegmentPulse: alternateSegmentPulse
  69. ))
  70. }
  71. var altSegmentPulse = false
  72. var segmentsToMerge = [TempSegment]()
  73. for segment in expandedSegments {
  74. guard let firstSegment = segmentsToMerge.first else {
  75. segmentsToMerge.append(segment)
  76. continue
  77. }
  78. let delta = segment.pulses - firstSegment.pulses
  79. if segmentsToMerge.count == 1 {
  80. altSegmentPulse = delta == 1
  81. }
  82. let expectedDelta: Int
  83. if !altSegmentPulse {
  84. expectedDelta = 0
  85. } else {
  86. expectedDelta = segmentsToMerge.count % 2
  87. }
  88. if expectedDelta != delta || segmentsToMerge.count == maxSegmentsPerEntry {
  89. addEntry(segmentsToMerge, altSegmentPulse)
  90. segmentsToMerge.removeAll()
  91. }
  92. segmentsToMerge.append(segment)
  93. }
  94. addEntry(segmentsToMerge, altSegmentPulse)
  95. self.entries = tableEntries
  96. }
  97. public init(tempBasalRate: Double, duration: TimeInterval) {
  98. self.entries = BasalDeliveryTable.rateToTableEntries(rate: tempBasalRate, duration: duration)
  99. }
  100. private static func rateToTableEntries(rate: Double, duration: TimeInterval) -> [BasalTableEntry] {
  101. var tableEntries = [BasalTableEntry]()
  102. let pulsesPerHour = Int(round(rate / Pod.pulseSize))
  103. let pulsesPerSegment = pulsesPerHour >> 1
  104. let alternateSegmentPulse = pulsesPerHour & 0b1 != 0
  105. var remaining = Int(round(duration / BasalDeliveryTable.segmentDuration))
  106. while remaining > 0 {
  107. let segments = min(remaining, 16)
  108. let tableEntry = BasalTableEntry(segments: segments, pulses: Int(pulsesPerSegment), alternateSegmentPulse: segments > 1 ? alternateSegmentPulse : false)
  109. tableEntries.append(tableEntry)
  110. remaining -= segments
  111. }
  112. return tableEntries
  113. }
  114. public func numSegments() -> Int {
  115. return entries.reduce(0) { $0 + $1.segments }
  116. }
  117. }
  118. extension BasalDeliveryTable: CustomDebugStringConvertible {
  119. public var debugDescription: String {
  120. return "BasalDeliveryTable(\(entries))"
  121. }
  122. }
  123. public struct RateEntry {
  124. let totalPulses: Double
  125. let delayBetweenPulses: TimeInterval
  126. public init(totalPulses: Double, delayBetweenPulses: TimeInterval) {
  127. self.totalPulses = totalPulses
  128. self.delayBetweenPulses = delayBetweenPulses
  129. }
  130. public var rate: Double {
  131. if totalPulses == 0 {
  132. return 0
  133. } else {
  134. return round(TimeInterval(hours: 1) / delayBetweenPulses) / Pod.pulsesPerUnit
  135. }
  136. }
  137. public var duration: TimeInterval {
  138. if totalPulses == 0 {
  139. return delayBetweenPulses
  140. } else {
  141. return round(delayBetweenPulses * Double(totalPulses))
  142. }
  143. }
  144. public var data: Data {
  145. var data = Data()
  146. data.appendBigEndian(UInt16(round(totalPulses * 10)))
  147. if totalPulses == 0 {
  148. data.appendBigEndian(UInt32(delayBetweenPulses.hundredthsOfMilliseconds) * 10)
  149. } else {
  150. data.appendBigEndian(UInt32(delayBetweenPulses.hundredthsOfMilliseconds))
  151. }
  152. return data
  153. }
  154. public static func makeEntries(rate: Double, duration: TimeInterval) -> [RateEntry] {
  155. let maxPulsesPerEntry: Double = 6400
  156. var entries = [RateEntry]()
  157. var remainingSegments = Int(round(duration.minutes / 30))
  158. let pulsesPerSegment = round(rate / Pod.pulseSize) / 2
  159. let maxSegmentsPerEntry = pulsesPerSegment > 0 ? Int(maxPulsesPerEntry / pulsesPerSegment) : 1
  160. var remainingPulses = rate * duration.hours / Pod.pulseSize
  161. let delayBetweenPulses = TimeInterval(hours: 1) / rate * Pod.pulseSize
  162. while (remainingSegments > 0) {
  163. if rate == 0 {
  164. entries.append(RateEntry(totalPulses: 0, delayBetweenPulses: .minutes(30)))
  165. remainingSegments -= 1
  166. } else {
  167. let numSegments = min(maxSegmentsPerEntry, Int(round(remainingPulses / pulsesPerSegment)))
  168. if numSegments == 0 {
  169. break // prevent infinite loop and subsequent malloc crash with certain bad rate values
  170. }
  171. remainingSegments -= numSegments
  172. let pulseCount = pulsesPerSegment * Double(numSegments)
  173. let entry = RateEntry(totalPulses: pulseCount, delayBetweenPulses: delayBetweenPulses)
  174. entries.append(entry)
  175. remainingPulses -= pulseCount
  176. }
  177. }
  178. return entries
  179. }
  180. }
  181. extension RateEntry: CustomDebugStringConvertible {
  182. public var debugDescription: String {
  183. return "RateEntry(rate:\(rate) duration:\(duration.stringValue))"
  184. }
  185. }