PumpManager.swift 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. //
  2. // PumpManager.swift
  3. // Loop
  4. //
  5. // Copyright © 2018 LoopKit Authors. All rights reserved.
  6. //
  7. import Foundation
  8. import HealthKit
  9. public enum PumpManagerResult<T> {
  10. case success(T)
  11. case failure(PumpManagerError)
  12. }
  13. public protocol PumpManagerStatusObserver: AnyObject {
  14. func pumpManager(_ pumpManager: PumpManager, didUpdate status: PumpManagerStatus, oldStatus: PumpManagerStatus)
  15. }
  16. public protocol PumpManagerDelegate: DeviceManagerDelegate, PumpManagerStatusObserver {
  17. func pumpManagerBLEHeartbeatDidFire(_ pumpManager: PumpManager)
  18. /// Queries the delegate as to whether Loop requires the pump to provide its own periodic scheduling
  19. /// via BLE.
  20. /// This is the companion to `PumpManager.setMustProvideBLEHeartbeat(_:)`
  21. func pumpManagerMustProvideBLEHeartbeat(_ pumpManager: PumpManager) -> Bool
  22. /// Informs the delegate that the manager is deactivating and should be deleted
  23. func pumpManagerWillDeactivate(_ pumpManager: PumpManager)
  24. /// Triggered when pump model changes. With a more formalized setup flow (which requires a successful model fetch),
  25. /// this delegate method could go away.
  26. func pumpManager(_ pumpManager: PumpManager, didUpdatePumpRecordsBasalProfileStartEvents pumpRecordsBasalProfileStartEvents: Bool)
  27. /// Reports an error that should be surfaced to the user
  28. func pumpManager(_ pumpManager: PumpManager, didError error: PumpManagerError)
  29. func pumpManager(_ pumpManager: PumpManager, hasNewPumpEvents events: [NewPumpEvent], lastReconciliation: Date?, completion: @escaping (_ error: Error?) -> Void)
  30. func pumpManager(_ pumpManager: PumpManager, didReadReservoirValue units: Double, at date: Date, completion: @escaping (_ result: Result<(newValue: ReservoirValue, lastValue: ReservoirValue?, areStoredValuesContinuous: Bool), Error>) -> Void)
  31. func pumpManager(_ pumpManager: PumpManager, didAdjustPumpClockBy adjustment: TimeInterval)
  32. func pumpManagerDidUpdateState(_ pumpManager: PumpManager)
  33. func pumpManagerRecommendsLoop(_ pumpManager: PumpManager)
  34. func startDateToFilterNewPumpEvents(for manager: PumpManager) -> Date
  35. }
  36. public protocol PumpManager: DeviceManager {
  37. /// Rounds a basal rate in U/hr to a rate supported by this pump.
  38. ///
  39. /// - Parameters:
  40. /// - unitsPerHour: A desired rate of delivery in Units per Hour
  41. /// - Returns: The rounded rate of delivery in Units per Hour. The rate returned should not be larger than the passed in rate.
  42. func roundToSupportedBasalRate(unitsPerHour: Double) -> Double
  43. /// Rounds a bolus volume in Units to a volume supported by this pump.
  44. ///
  45. /// - Parameters:
  46. /// - units: A desired volume of delivery in Units
  47. /// - Returns: The rounded bolus volume in Units. The volume returned should not be larger than the passed in rate.
  48. func roundToSupportedBolusVolume(units: Double) -> Double
  49. /// All user-selectable basal rates, in Units per Hour. Must be non-empty.
  50. var supportedBasalRates: [Double] { get }
  51. /// All user-selectable bolus volumes, in Units. Must be non-empty.
  52. var supportedBolusVolumes: [Double] { get }
  53. /// The maximum number of scheduled basal rates in a single day supported by the pump
  54. var maximumBasalScheduleEntryCount: Int { get }
  55. /// The basal schedule duration increment, beginning at midnight, supported by the pump
  56. var minimumBasalScheduleEntryDuration: TimeInterval { get }
  57. /// The primary client receiving notifications about the pump lifecycle
  58. /// All delegate methods are called on `delegateQueue`
  59. var pumpManagerDelegate: PumpManagerDelegate? { get set }
  60. /// Whether the PumpManager provides DoseEntry values for scheduled basal delivery. If false, Loop will use the basal schedule to infer normal basal delivery during times not overridden by:
  61. /// - Temporary basal delivery
  62. /// - Suspend/Resume pairs
  63. /// - Rewind/Prime pairs
  64. var pumpRecordsBasalProfileStartEvents: Bool { get }
  65. /// The maximum reservoir volume of the pump
  66. var pumpReservoirCapacity: Double { get }
  67. /// The time of the last reconciliation with the pump's event history
  68. var lastReconciliation: Date? { get }
  69. /// The most-recent status
  70. var status: PumpManagerStatus { get }
  71. /// Adds an observer of changes in PumpManagerStatus
  72. ///
  73. /// Observers are held by weak reference.
  74. ///
  75. /// - Parameters:
  76. /// - observer: The observing object
  77. /// - queue: The queue on which the observer methods should be called
  78. func addStatusObserver(_ observer: PumpManagerStatusObserver, queue: DispatchQueue)
  79. /// Removes an observer of changes in PumpManagerStatus
  80. ///
  81. /// Since observers are held weakly, calling this method is not required when the observer is deallocated
  82. ///
  83. /// - Parameter observer: The observing object
  84. func removeStatusObserver(_ observer: PumpManagerStatusObserver)
  85. /// Ensure that the pump's data (reservoir/events) is up to date. If not, fetch it.
  86. /// After a successful fetch, the PumpManager should call the completion block.
  87. /// Then, it must call the delegate method `pumpManagerRecommendsLoop(_:)` if it has an accurate and up to date understanding of insulin delivery
  88. /// and has reported it via the appropriate status observer and delegate calls.
  89. func ensureCurrentPumpData(completion: (() -> Void)?)
  90. /// Loop calls this method when the current environment requires the pump to provide its own periodic
  91. /// scheduling via BLE.
  92. /// The manager may choose to still enable its own heartbeat even if `mustProvideBLEHeartbeat` is false
  93. func setMustProvideBLEHeartbeat(_ mustProvideBLEHeartbeat: Bool)
  94. /// Returns a dose estimator for the current bolus, if one is in progress
  95. func createBolusProgressReporter(reportingOn dispatchQueue: DispatchQueue) -> DoseProgressReporter?
  96. /// Send a bolus command and handle the result
  97. ///
  98. /// - Parameters:
  99. /// - units: The number of units to deliver
  100. /// - automatic: Whether the dose was triggered automatically as opposed to commanded by user
  101. /// - completion: A closure called after the command is complete
  102. /// - result: A DoseEntry or an error describing why the command failed
  103. func enactBolus(units: Double, automatic: Bool, completion: @escaping (_ result: PumpManagerResult<DoseEntry>) -> Void)
  104. /// Cancels the current, in progress, bolus.
  105. ///
  106. /// - Parameters:
  107. /// - completion: A closure called after the command is complete
  108. /// - result: A DoseEntry containing the actual delivery amount of the canceled bolus, nil if canceled bolus information is not available, or an error describing why the command failed.
  109. func cancelBolus(completion: @escaping (_ result: PumpManagerResult<DoseEntry?>) -> Void)
  110. /// Send a temporary basal rate command and handle the result
  111. ///
  112. /// - Parameters:
  113. /// - unitsPerHour: The temporary basal rate to set
  114. /// - duration: The duration of the temporary basal rate.
  115. /// - completion: A closure called after the command is complete
  116. /// - result: A DoseEntry or an error describing why the command failed
  117. func enactTempBasal(unitsPerHour: Double, for duration: TimeInterval, completion: @escaping (_ result: PumpManagerResult<DoseEntry>) -> Void)
  118. /// Send a command to the pump to suspend delivery
  119. ///
  120. /// - Parameters:
  121. /// - completion: A closure called after the command is complete
  122. /// - error: An error describing why the command failed
  123. func suspendDelivery(completion: @escaping (_ error: Error?) -> Void)
  124. /// Send a command to the pump to resume delivery
  125. ///
  126. /// - Parameters:
  127. /// - completion: A closure called after the command is complete
  128. /// - error: An error describing why the command failed
  129. func resumeDelivery(completion: @escaping (_ error: Error?) -> Void)
  130. /// Notifies the PumpManager of a change in the user's preference for maximum basal rate.
  131. ///
  132. /// - Parameters:
  133. /// - rate: The maximum rate the pumpmanager should expect to receive in an enactTempBasal command.
  134. func setMaximumTempBasalRate(_ rate: Double)
  135. typealias SyncSchedule = (_ items: [RepeatingScheduleValue<Double>], _ completion: @escaping (Result<BasalRateSchedule, Error>) -> Void) -> Void
  136. /// Sync the schedule of basal rates to the pump, annotating the result with the proper time zone.
  137. ///
  138. /// - Precondition:
  139. /// - `scheduleItems` must not be empty.
  140. ///
  141. /// - Parameters:
  142. /// - scheduleItems: The items comprising the basal rate schedule
  143. /// - completion: A closure called after the command is complete
  144. /// - result: A BasalRateSchedule or an error describing why the command failed
  145. func syncBasalRateSchedule(items scheduleItems: [RepeatingScheduleValue<Double>], completion: @escaping (_ result: Result<BasalRateSchedule, Error>) -> Void)
  146. }
  147. public extension PumpManager {
  148. func roundToSupportedBasalRate(unitsPerHour: Double) -> Double {
  149. return supportedBasalRates.filter({$0 <= unitsPerHour}).max() ?? 0
  150. }
  151. func roundToSupportedBolusVolume(units: Double) -> Double {
  152. return supportedBolusVolumes.filter({$0 <= units}).max() ?? 0
  153. }
  154. /// Convenience wrapper for notifying the delegate of deactivation on the delegate queue
  155. ///
  156. /// - Parameters:
  157. /// - completion: A closure called from the delegate queue after the delegate is called
  158. func notifyDelegateOfDeactivation(completion: @escaping () -> Void) {
  159. delegateQueue.async {
  160. self.pumpManagerDelegate?.pumpManagerWillDeactivate(self)
  161. completion()
  162. }
  163. }
  164. }