MockPumpManager.swift 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  1. //
  2. // MockPumpManager.swift
  3. // LoopKit
  4. //
  5. // Created by Michael Pangburn on 11/20/18.
  6. // Copyright © 2018 LoopKit Authors. All rights reserved.
  7. //
  8. import HealthKit
  9. import LoopKit
  10. import LoopTestingKit
  11. public protocol MockPumpManagerStateObserver {
  12. func mockPumpManager(_ manager: MockPumpManager, didUpdate state: MockPumpManagerState)
  13. func mockPumpManager(_ manager: MockPumpManager, didUpdate status: PumpManagerStatus, oldStatus: PumpManagerStatus)
  14. }
  15. public enum MockPumpManagerError: LocalizedError {
  16. case pumpSuspended
  17. case communicationFailure
  18. case bolusInProgress
  19. case missingSettings
  20. case pumpError
  21. public var failureReason: String? {
  22. switch self {
  23. case .pumpSuspended:
  24. return "Pump is suspended"
  25. case .communicationFailure:
  26. return "Unable to communicate with pump"
  27. case .bolusInProgress:
  28. return "Bolus in progress"
  29. case .missingSettings:
  30. return "Missing Settings"
  31. case .pumpError:
  32. return "Pump is in an error state"
  33. }
  34. }
  35. }
  36. public final class MockPumpManager: TestingPumpManager {
  37. public static let managerIdentifier = "MockPumpManager"
  38. public var managerIdentifier: String {
  39. return MockPumpManager.managerIdentifier
  40. }
  41. public static let localizedTitle = "Insulin Pump Simulator"
  42. public var localizedTitle: String {
  43. return MockPumpManager.localizedTitle
  44. }
  45. public static var onboardingMaximumBasalScheduleEntryCount: Int {
  46. return 48
  47. }
  48. public static var onboardingSupportedBasalRates: [Double] {
  49. MockPumpManagerState.DeliverableIncrements.medtronicX22.supportedBasalRates!
  50. }
  51. public static var onboardingSupportedBolusVolumes: [Double] {
  52. MockPumpManagerState.DeliverableIncrements.medtronicX22.supportedBolusVolumes!
  53. }
  54. public static var onboardingSupportedMaximumBolusVolumes: [Double] {
  55. self.onboardingSupportedBolusVolumes
  56. }
  57. private static let device = HKDevice(
  58. name: MockPumpManager.managerIdentifier,
  59. manufacturer: nil,
  60. model: nil,
  61. hardwareVersion: nil,
  62. firmwareVersion: nil,
  63. softwareVersion: "1.0",
  64. localIdentifier: nil,
  65. udiDeviceIdentifier: nil
  66. )
  67. private static let deliveryUnitsPerMinute = 1.5
  68. private static let pumpReservoirCapacity: Double = 200
  69. public var pumpReservoirCapacity: Double {
  70. return MockPumpManager.pumpReservoirCapacity
  71. }
  72. public var reservoirFillFraction: Double {
  73. get {
  74. return state.reservoirUnitsRemaining / pumpReservoirCapacity
  75. }
  76. set {
  77. state.reservoirUnitsRemaining = max(newValue * pumpReservoirCapacity, 0)
  78. }
  79. }
  80. public var currentBasalRate: HKQuantity? {
  81. switch status.basalDeliveryState {
  82. case .suspending, .suspended(_):
  83. return HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 0)
  84. case .tempBasal(let dose):
  85. return HKQuantity(unit: .internationalUnitsPerHour, doubleValue: dose.unitsPerHour)
  86. case .none:
  87. return nil
  88. default:
  89. guard let scheduledBasalRate = state.basalRateSchedule?.value(at: Date()) else { return nil }
  90. return HKQuantity(unit: .internationalUnitsPerHour, doubleValue: scheduledBasalRate)
  91. }
  92. }
  93. public var supportedBolusVolumes: [Double] {
  94. return state.supportedBolusVolumes
  95. }
  96. public var supportedMaximumBolusVolumes: [Double] {
  97. state.supportedBolusVolumes
  98. }
  99. public var supportedBasalRates: [Double] {
  100. return state.supportedBasalRates
  101. }
  102. public var maximumBasalScheduleEntryCount: Int {
  103. return 48
  104. }
  105. public var minimumBasalScheduleEntryDuration: TimeInterval {
  106. return .minutes(30)
  107. }
  108. public var testingDevice: HKDevice {
  109. return type(of: self).device
  110. }
  111. public var testLastReconciliation: Date? = nil
  112. public var lastSync: Date? {
  113. return testLastReconciliation ?? Date()
  114. }
  115. public var insulinType: InsulinType? {
  116. return state.insulinType
  117. }
  118. private func basalDeliveryState(for state: MockPumpManagerState) -> PumpManagerStatus.BasalDeliveryState? {
  119. if case .suspended(let date) = state.suspendState {
  120. return .suspended(date)
  121. }
  122. if state.occlusionDetected || state.pumpErrorDetected || state.pumpBatteryChargeRemaining == 0 || state.reservoirUnitsRemaining == 0 {
  123. return nil
  124. }
  125. if let temp = state.unfinalizedTempBasal, !temp.finished {
  126. return .tempBasal(DoseEntry(temp))
  127. }
  128. if case .resumed(let date) = state.suspendState {
  129. return .active(date)
  130. } else {
  131. return .active(Date())
  132. }
  133. }
  134. private func bolusState(for state: MockPumpManagerState) -> PumpManagerStatus.BolusState {
  135. if let bolus = state.unfinalizedBolus, !bolus.finished {
  136. return .inProgress(DoseEntry(bolus))
  137. } else {
  138. return .noBolus
  139. }
  140. }
  141. public func buildPumpStatusHighlight(for state: MockPumpManagerState) -> PumpStatusHighlight? {
  142. if state.deliveryIsUncertain {
  143. return PumpStatusHighlight(localizedMessage: LocalizedString("Comms Issue", comment: "Status highlight that delivery is uncertain."),
  144. imageName: "exclamationmark.circle.fill",
  145. state: .critical)
  146. }
  147. else if state.reservoirUnitsRemaining == 0 {
  148. return PumpStatusHighlight(localizedMessage: LocalizedString("No Insulin", comment: "Status highlight that a pump is out of insulin."),
  149. imageName: "exclamationmark.circle.fill",
  150. state: .critical)
  151. } else if state.occlusionDetected {
  152. return PumpStatusHighlight(localizedMessage: LocalizedString("Pump Occlusion", comment: "Status highlight that an occlusion was detected."),
  153. imageName: "exclamationmark.circle.fill",
  154. state: .critical)
  155. } else if state.pumpErrorDetected {
  156. return PumpStatusHighlight(localizedMessage: LocalizedString("Pump Error", comment: "Status highlight that a pump error occurred."),
  157. imageName: "exclamationmark.circle.fill",
  158. state: .critical)
  159. } else if pumpBatteryChargeRemaining == 0 {
  160. return PumpStatusHighlight(localizedMessage: LocalizedString("Pump Battery Dead", comment: "Status highlight that pump has a dead battery."),
  161. imageName: "exclamationmark.circle.fill",
  162. state: .critical)
  163. } else if case .suspended = state.suspendState {
  164. return PumpStatusHighlight(localizedMessage: LocalizedString("Insulin Suspended", comment: "Status highlight that insulin delivery was suspended."),
  165. imageName: "pause.circle.fill",
  166. state: .warning)
  167. }
  168. return nil
  169. }
  170. public func buildPumpLifecycleProgress(for state: MockPumpManagerState) -> PumpLifecycleProgress? {
  171. guard let progressPercentComplete = state.progressPercentComplete else {
  172. return nil
  173. }
  174. let progressState: DeviceLifecycleProgressState
  175. if let progressCriticalThresholdPercentValue = state.progressCriticalThresholdPercentValue,
  176. progressPercentComplete >= progressCriticalThresholdPercentValue
  177. {
  178. progressState = .critical
  179. } else if let progressWarningThresholdPercentValue = state.progressWarningThresholdPercentValue,
  180. progressPercentComplete >= progressWarningThresholdPercentValue
  181. {
  182. progressState = .warning
  183. } else {
  184. progressState = .normalPump
  185. }
  186. return PumpLifecycleProgress(percentComplete: progressPercentComplete,
  187. progressState: progressState)
  188. }
  189. public var isClockOffset: Bool {
  190. let now = Date()
  191. return TimeZone.current.secondsFromGMT(for: now) != state.timeZone.secondsFromGMT(for: now)
  192. }
  193. private func status(for state: MockPumpManagerState) -> PumpManagerStatus {
  194. return PumpManagerStatus(
  195. timeZone: state.timeZone,
  196. device: MockPumpManager.device,
  197. pumpBatteryChargeRemaining: state.pumpBatteryChargeRemaining,
  198. basalDeliveryState: basalDeliveryState(for: state),
  199. bolusState: bolusState(for: state),
  200. insulinType: state.insulinType,
  201. deliveryIsUncertain: state.deliveryIsUncertain
  202. )
  203. }
  204. public var pumpBatteryChargeRemaining: Double? {
  205. get {
  206. return state.pumpBatteryChargeRemaining
  207. }
  208. set {
  209. state.pumpBatteryChargeRemaining = newValue
  210. }
  211. }
  212. public var status: PumpManagerStatus {
  213. get {
  214. return status(for: self.state)
  215. }
  216. }
  217. private func notifyStatusObservers(oldStatus: PumpManagerStatus) {
  218. let status = self.status
  219. delegate.notify { (delegate) in
  220. delegate?.pumpManager(self, didUpdate: status, oldStatus: oldStatus)
  221. }
  222. statusObservers.forEach { (observer) in
  223. observer.pumpManager(self, didUpdate: status, oldStatus: oldStatus)
  224. }
  225. }
  226. public func estimatedDuration(toBolus units: Double) -> TimeInterval {
  227. .minutes(units / type(of: self).deliveryUnitsPerMinute)
  228. }
  229. public var state: MockPumpManagerState {
  230. didSet {
  231. let newValue = state
  232. guard newValue != oldValue else {
  233. return
  234. }
  235. let oldStatus = status(for: oldValue)
  236. let newStatus = status(for: newValue)
  237. if oldStatus != newStatus {
  238. notifyStatusObservers(oldStatus: oldStatus)
  239. }
  240. // stop insulin delivery as pump state requires
  241. if (newValue.occlusionDetected != oldValue.occlusionDetected && newValue.occlusionDetected) ||
  242. (newValue.pumpErrorDetected != oldValue.pumpErrorDetected && newValue.pumpErrorDetected) ||
  243. (newValue.pumpBatteryChargeRemaining != oldValue.pumpBatteryChargeRemaining && newValue.pumpBatteryChargeRemaining == 0) ||
  244. (newValue.reservoirUnitsRemaining != oldValue.reservoirUnitsRemaining && newValue.reservoirUnitsRemaining == 0)
  245. {
  246. stopInsulinDelivery()
  247. }
  248. stateObservers.forEach { $0.mockPumpManager(self, didUpdate: self.state) }
  249. delegate.notify { (delegate) in
  250. if newValue.reservoirUnitsRemaining != oldValue.reservoirUnitsRemaining {
  251. delegate?.pumpManager(self, didReadReservoirValue: self.state.reservoirUnitsRemaining, at: Date()) { result in
  252. // nothing to do here
  253. }
  254. }
  255. delegate?.pumpManagerDidUpdateState(self)
  256. delegate?.pumpManager(self, didUpdate: newStatus, oldStatus: oldStatus)
  257. }
  258. }
  259. }
  260. public var pumpManagerDelegate: PumpManagerDelegate? {
  261. get {
  262. return delegate.delegate
  263. }
  264. set {
  265. delegate.delegate = newValue
  266. }
  267. }
  268. public var delegateQueue: DispatchQueue! {
  269. get {
  270. return delegate.queue
  271. }
  272. set {
  273. delegate.queue = newValue
  274. }
  275. }
  276. private let delegate = WeakSynchronizedDelegate<PumpManagerDelegate>()
  277. private var statusObservers = WeakSynchronizedSet<PumpManagerStatusObserver>()
  278. private var stateObservers = WeakSynchronizedSet<MockPumpManagerStateObserver>()
  279. public init() {
  280. state = MockPumpManagerState(reservoirUnitsRemaining: MockPumpManager.pumpReservoirCapacity)
  281. }
  282. public init?(rawState: RawStateValue) {
  283. if let state = (rawState["state"] as? MockPumpManagerState.RawValue).flatMap(MockPumpManagerState.init(rawValue:)) {
  284. self.state = state
  285. } else {
  286. self.state = MockPumpManagerState(reservoirUnitsRemaining: MockPumpManager.pumpReservoirCapacity)
  287. }
  288. }
  289. public var rawState: RawStateValue {
  290. return ["state": state.rawValue]
  291. }
  292. public let isOnboarded = true // No distinction between created and onboarded
  293. private func logDeviceCommunication(_ message: String, type: DeviceLogEntryType = .send) {
  294. self.delegate.delegate?.deviceManager(self, logEventForDeviceIdentifier: "MockId", type: type, message: message, completion: nil)
  295. }
  296. public func createBolusProgressReporter(reportingOn dispatchQueue: DispatchQueue) -> DoseProgressReporter? {
  297. if case .inProgress(let dose) = status.bolusState {
  298. return MockDoseProgressEstimator(reportingQueue: dispatchQueue, dose: dose)
  299. }
  300. return nil
  301. }
  302. public var pumpRecordsBasalProfileStartEvents: Bool {
  303. return false
  304. }
  305. public func addStatusObserver(_ observer: PumpManagerStatusObserver, queue: DispatchQueue) {
  306. statusObservers.insert(observer, queue: queue)
  307. }
  308. public func addStateObserver(_ observer: MockPumpManagerStateObserver, queue: DispatchQueue) {
  309. stateObservers.insert(observer, queue: queue)
  310. }
  311. public func removeStatusObserver(_ observer: PumpManagerStatusObserver) {
  312. statusObservers.removeElement(observer)
  313. }
  314. public func ensureCurrentPumpData(completion: ((Date?) -> Void)?) {
  315. // Change this to artificially increase the delay fetching the current pump data
  316. let fetchDelay = 0
  317. DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(fetchDelay)) {
  318. self.state.finalizeFinishedDoses()
  319. self.storePumpEvents { (error) in
  320. guard error == nil else {
  321. completion?(self.lastSync)
  322. return
  323. }
  324. let totalInsulinUsage = self.state.finalizedDoses.reduce(into: 0 as Double) { total, dose in
  325. total += dose.units
  326. }
  327. self.state.finalizedDoses = []
  328. self.state.reservoirUnitsRemaining = max(self.state.reservoirUnitsRemaining - totalInsulinUsage, 0)
  329. completion?(self.lastSync)
  330. }
  331. }
  332. }
  333. private func storePumpEvents(completion: @escaping (_ error: Error?) -> Void) {
  334. state.finalizeFinishedDoses()
  335. let pendingPumpEvents = state.pumpEventsToStore
  336. delegate.notify { (delegate) in
  337. delegate?.pumpManager(self, hasNewPumpEvents: pendingPumpEvents, lastReconciliation: self.lastSync) { error in
  338. if error == nil {
  339. self.state.additionalPumpEvents = []
  340. }
  341. completion(error)
  342. }
  343. }
  344. }
  345. public func enactTempBasal(unitsPerHour: Double, for duration: TimeInterval, completion: @escaping (PumpManagerError?) -> Void) {
  346. logDeviceComms(.send, message: "Temp Basal \(unitsPerHour) U/hr Duration:\(duration.hours)")
  347. if state.tempBasalEnactmentShouldError || state.pumpBatteryChargeRemaining == 0 {
  348. let error = PumpManagerError.communication(MockPumpManagerError.communicationFailure)
  349. logDeviceComms(.error, message: "Temp Basal failed with error \(error)")
  350. completion(error)
  351. } else if state.deliveryCommandsShouldTriggerUncertainDelivery {
  352. state.deliveryIsUncertain = true
  353. logDeviceComms(.error, message: "Uncertain delivery for temp basal")
  354. completion(.uncertainDelivery)
  355. } else if state.occlusionDetected || state.pumpErrorDetected {
  356. let error = PumpManagerError.deviceState(MockPumpManagerError.pumpError)
  357. logDeviceComms(.error, message: "Temp Basal failed because the pump is in an error state")
  358. completion(error)
  359. } else if case .suspended = state.suspendState {
  360. let error = PumpManagerError.deviceState(MockPumpManagerError.pumpSuspended)
  361. logDeviceComms(.error, message: "Temp Basal failed because inulin delivery is suspended")
  362. completion(error)
  363. } else if state.reservoirUnitsRemaining == 0 {
  364. let error = PumpManagerError.deviceState(MockPumpManagerError.pumpSuspended)
  365. logDeviceComms(.error, message: "Temp Basal failed because there is no insulin in the reservoir")
  366. completion(error)
  367. } else {
  368. let now = Date()
  369. if let temp = state.unfinalizedTempBasal, temp.finishTime.compare(now) == .orderedDescending {
  370. state.unfinalizedTempBasal?.cancel(at: now)
  371. }
  372. state.finalizeFinishedDoses()
  373. logDeviceComms(.receive, message: "Temp Basal succeeded")
  374. if duration < .ulpOfOne {
  375. // Cancel temp basal
  376. storePumpEvents { (error) in
  377. completion(nil)
  378. }
  379. } else {
  380. let temp = UnfinalizedDose(tempBasalRate: unitsPerHour, startTime: now, duration: duration, insulinType: state.insulinType)
  381. state.unfinalizedTempBasal = temp
  382. storePumpEvents { (error) in
  383. completion(nil)
  384. }
  385. }
  386. logDeviceCommunication("enactTempBasal succeeded", type: .receive)
  387. }
  388. }
  389. private func logDeviceComms(_ type: DeviceLogEntryType, message: String) {
  390. self.delegate.delegate?.deviceManager(self, logEventForDeviceIdentifier: "mockpump", type: type, message: message, completion: nil)
  391. }
  392. public func enactBolus(units: Double, activationType: BolusActivationType, completion: @escaping (PumpManagerError?) -> Void) {
  393. logDeviceCommunication("enactBolus(\(units), \(activationType))")
  394. if state.bolusEnactmentShouldError || state.pumpBatteryChargeRemaining == 0 {
  395. let error = PumpManagerError.communication(MockPumpManagerError.communicationFailure)
  396. logDeviceComms(.error, message: "Bolus failed with error \(error)")
  397. completion(error)
  398. } else if state.deliveryCommandsShouldTriggerUncertainDelivery {
  399. state.deliveryIsUncertain = true
  400. logDeviceComms(.error, message: "Uncertain delivery for bolus")
  401. completion(PumpManagerError.uncertainDelivery)
  402. } else if state.occlusionDetected || state.pumpErrorDetected {
  403. let error = PumpManagerError.deviceState(MockPumpManagerError.pumpError)
  404. logDeviceComms(.error, message: "Bolus failed because the pump is in an error state")
  405. completion(error)
  406. } else if state.reservoirUnitsRemaining == 0 {
  407. let error = PumpManagerError.deviceState(MockPumpManagerError.pumpSuspended)
  408. logDeviceComms(.error, message: "Bolus failed because there is no insulin in the reservoir")
  409. completion(error)
  410. } else {
  411. state.finalizeFinishedDoses()
  412. if let _ = state.unfinalizedBolus {
  413. logDeviceCommunication("enactBolus failed: bolusInProgress", type: .error)
  414. completion(PumpManagerError.deviceState(MockPumpManagerError.bolusInProgress))
  415. return
  416. }
  417. if case .suspended = status.basalDeliveryState {
  418. logDeviceCommunication("enactBolus failed: pumpSuspended", type: .error)
  419. completion(PumpManagerError.deviceState(MockPumpManagerError.pumpSuspended))
  420. return
  421. }
  422. let bolus = UnfinalizedDose(bolusAmount: units, startTime: Date(), duration: .minutes(units / type(of: self).deliveryUnitsPerMinute), insulinType: state.insulinType, automatic: activationType.isAutomatic)
  423. state.unfinalizedBolus = bolus
  424. logDeviceComms(.receive, message: "Bolus accepted")
  425. storePumpEvents { (error) in
  426. completion(nil)
  427. self.logDeviceCommunication("enactBolus succeeded", type: .receive)
  428. }
  429. }
  430. }
  431. public func cancelBolus(completion: @escaping (PumpManagerResult<DoseEntry?>) -> Void) {
  432. logDeviceComms(.send, message: "Cancel")
  433. if self.state.bolusCancelShouldError {
  434. let error = PumpManagerError.communication(MockPumpManagerError.communicationFailure)
  435. logDeviceComms(.error, message: "Cancel failed with error: \(error)")
  436. completion(.failure(error))
  437. } else {
  438. state.unfinalizedBolus?.cancel(at: Date())
  439. storePumpEvents { (_) in
  440. DispatchQueue.main.async {
  441. self.state.finalizeFinishedDoses()
  442. completion(.success(nil))
  443. }
  444. }
  445. }
  446. }
  447. public func setMustProvideBLEHeartbeat(_ mustProvideBLEHeartbeat: Bool) {
  448. // nothing to do here
  449. }
  450. private func stopInsulinDelivery() {
  451. let now = Date()
  452. state.unfinalizedTempBasal?.cancel(at: now)
  453. state.unfinalizedBolus?.cancel(at: now)
  454. storePumpEvents { _ in }
  455. }
  456. public func suspendDelivery(completion: @escaping (Error?) -> Void) {
  457. logDeviceComms(.send, message: "Suspend")
  458. if self.state.deliverySuspensionShouldError {
  459. let error = PumpManagerError.communication(MockPumpManagerError.communicationFailure)
  460. logDeviceComms(.error, message: "Suspend failed with error: \(error)")
  461. completion(error)
  462. } else {
  463. let now = Date()
  464. state.unfinalizedTempBasal?.cancel(at: now)
  465. state.unfinalizedBolus?.cancel(at: now)
  466. let suspendDate = Date()
  467. let suspend = UnfinalizedDose(suspendStartTime: suspendDate)
  468. self.state.finalizedDoses.append(suspend)
  469. self.state.suspendState = .suspended(suspendDate)
  470. logDeviceComms(.receive, message: "Suspend accepted")
  471. storePumpEvents { (error) in
  472. completion(error)
  473. }
  474. logDeviceCommunication("suspendDelivery succeeded", type: .receive)
  475. }
  476. }
  477. public func resumeDelivery(completion: @escaping (Error?) -> Void) {
  478. logDeviceComms(.send, message: "Resume")
  479. if self.state.deliveryResumptionShouldError {
  480. let error = PumpManagerError.communication(MockPumpManagerError.communicationFailure)
  481. logDeviceComms(.error, message: "Resume failed with error: \(error)")
  482. completion(error)
  483. } else {
  484. let resumeDate = Date()
  485. let resume = UnfinalizedDose(resumeStartTime: resumeDate, insulinType: state.insulinType)
  486. self.state.finalizedDoses.append(resume)
  487. self.state.suspendState = .resumed(resumeDate)
  488. storePumpEvents { (error) in
  489. completion(error)
  490. }
  491. logDeviceCommunication("resumeDelivery succeeded", type: .receive)
  492. }
  493. }
  494. public func injectPumpEvents(_ pumpEvents: [NewPumpEvent]) {
  495. state.finalizedDoses += pumpEvents.compactMap { $0.unfinalizedDose }
  496. state.additionalPumpEvents += pumpEvents.filter { $0.dose == nil }
  497. }
  498. public func setMaximumTempBasalRate(_ rate: Double) { }
  499. public func syncBasalRateSchedule(items scheduleItems: [RepeatingScheduleValue<Double>], completion: @escaping (Result<BasalRateSchedule, Error>) -> Void) {
  500. state.basalRateSchedule = BasalRateSchedule(dailyItems: scheduleItems, timeZone: self.status.timeZone)
  501. DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
  502. completion(.success(BasalRateSchedule(dailyItems: scheduleItems, timeZone: self.status.timeZone)!))
  503. }
  504. }
  505. public func syncDeliveryLimits(limits deliveryLimits: DeliveryLimits, completion: @escaping (Result<DeliveryLimits, Error>) -> Void) {
  506. DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
  507. completion(.success(deliveryLimits))
  508. }
  509. }
  510. }
  511. // MARK: - AlertResponder implementation
  512. extension MockPumpManager {
  513. public func acknowledgeAlert(alertIdentifier: Alert.AlertIdentifier, completion: @escaping (Error?) -> Void) {
  514. completion(nil)
  515. }
  516. }
  517. // MARK: - AlertSoundVendor implementation
  518. extension MockPumpManager {
  519. public func getSoundBaseURL() -> URL? { return nil }
  520. public func getSounds() -> [Alert.Sound] { return [] }
  521. }
  522. extension MockPumpManager {
  523. public var debugDescription: String {
  524. return """
  525. ## MockPumpManager
  526. status: \(status)
  527. state: \(state)
  528. stateObservers.count: \(stateObservers.cleanupDeallocatedElements().count)
  529. statusObservers.count: \(statusObservers.cleanupDeallocatedElements().count)
  530. """
  531. }
  532. }