MockPumpManager.swift 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  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: String(LoopKitVersionNumber),
  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: NSLocalizedString("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: NSLocalizedString("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: NSLocalizedString("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: NSLocalizedString("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: NSLocalizedString("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: NSLocalizedString("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 var state: MockPumpManagerState {
  227. didSet {
  228. let newValue = state
  229. guard newValue != oldValue else {
  230. return
  231. }
  232. let oldStatus = status(for: oldValue)
  233. let newStatus = status(for: newValue)
  234. if oldStatus != newStatus {
  235. notifyStatusObservers(oldStatus: oldStatus)
  236. }
  237. // stop insulin delivery as pump state requires
  238. if (newValue.occlusionDetected != oldValue.occlusionDetected && newValue.occlusionDetected) ||
  239. (newValue.pumpErrorDetected != oldValue.pumpErrorDetected && newValue.pumpErrorDetected) ||
  240. (newValue.pumpBatteryChargeRemaining != oldValue.pumpBatteryChargeRemaining && newValue.pumpBatteryChargeRemaining == 0) ||
  241. (newValue.reservoirUnitsRemaining != oldValue.reservoirUnitsRemaining && newValue.reservoirUnitsRemaining == 0)
  242. {
  243. stopInsulinDelivery()
  244. }
  245. stateObservers.forEach { $0.mockPumpManager(self, didUpdate: self.state) }
  246. delegate.notify { (delegate) in
  247. if newValue.reservoirUnitsRemaining != oldValue.reservoirUnitsRemaining {
  248. delegate?.pumpManager(self, didReadReservoirValue: self.state.reservoirUnitsRemaining, at: Date()) { result in
  249. // nothing to do here
  250. }
  251. }
  252. delegate?.pumpManagerDidUpdateState(self)
  253. delegate?.pumpManager(self, didUpdate: newStatus, oldStatus: oldStatus)
  254. }
  255. }
  256. }
  257. public var pumpManagerDelegate: PumpManagerDelegate? {
  258. get {
  259. return delegate.delegate
  260. }
  261. set {
  262. delegate.delegate = newValue
  263. }
  264. }
  265. public var delegateQueue: DispatchQueue! {
  266. get {
  267. return delegate.queue
  268. }
  269. set {
  270. delegate.queue = newValue
  271. }
  272. }
  273. private let delegate = WeakSynchronizedDelegate<PumpManagerDelegate>()
  274. private var statusObservers = WeakSynchronizedSet<PumpManagerStatusObserver>()
  275. private var stateObservers = WeakSynchronizedSet<MockPumpManagerStateObserver>()
  276. public init() {
  277. state = MockPumpManagerState(reservoirUnitsRemaining: MockPumpManager.pumpReservoirCapacity)
  278. }
  279. public init?(rawState: RawStateValue) {
  280. if let state = (rawState["state"] as? MockPumpManagerState.RawValue).flatMap(MockPumpManagerState.init(rawValue:)) {
  281. self.state = state
  282. } else {
  283. self.state = MockPumpManagerState(reservoirUnitsRemaining: MockPumpManager.pumpReservoirCapacity)
  284. }
  285. }
  286. public var rawState: RawStateValue {
  287. return ["state": state.rawValue]
  288. }
  289. public let isOnboarded = true // No distinction between created and onboarded
  290. private func logDeviceCommunication(_ message: String, type: DeviceLogEntryType = .send) {
  291. self.delegate.delegate?.deviceManager(self, logEventForDeviceIdentifier: "MockId", type: type, message: message, completion: nil)
  292. }
  293. public func createBolusProgressReporter(reportingOn dispatchQueue: DispatchQueue) -> DoseProgressReporter? {
  294. if case .inProgress(let dose) = status.bolusState {
  295. return MockDoseProgressEstimator(reportingQueue: dispatchQueue, dose: dose)
  296. }
  297. return nil
  298. }
  299. public var pumpRecordsBasalProfileStartEvents: Bool {
  300. return false
  301. }
  302. public func addStatusObserver(_ observer: PumpManagerStatusObserver, queue: DispatchQueue) {
  303. statusObservers.insert(observer, queue: queue)
  304. }
  305. public func addStateObserver(_ observer: MockPumpManagerStateObserver, queue: DispatchQueue) {
  306. stateObservers.insert(observer, queue: queue)
  307. }
  308. public func removeStatusObserver(_ observer: PumpManagerStatusObserver) {
  309. statusObservers.removeElement(observer)
  310. }
  311. public func ensureCurrentPumpData(completion: ((Date?) -> Void)?) {
  312. // Change this to artificially increase the delay fetching the current pump data
  313. let fetchDelay = 0
  314. DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(fetchDelay)) {
  315. self.state.finalizeFinishedDoses()
  316. self.storePumpEvents { (error) in
  317. guard error == nil else {
  318. completion?(self.lastSync)
  319. return
  320. }
  321. let totalInsulinUsage = self.state.finalizedDoses.reduce(into: 0 as Double) { total, dose in
  322. total += dose.units
  323. }
  324. self.state.finalizedDoses = []
  325. self.state.reservoirUnitsRemaining = max(self.state.reservoirUnitsRemaining - totalInsulinUsage, 0)
  326. completion?(self.lastSync)
  327. }
  328. }
  329. }
  330. private func storePumpEvents(completion: @escaping (_ error: Error?) -> Void) {
  331. state.finalizeFinishedDoses()
  332. let pendingPumpEvents = state.pumpEventsToStore
  333. delegate.notify { (delegate) in
  334. delegate?.pumpManager(self, hasNewPumpEvents: pendingPumpEvents, lastReconciliation: self.lastSync) { error in
  335. if error == nil {
  336. self.state.additionalPumpEvents = []
  337. }
  338. completion(error)
  339. }
  340. }
  341. }
  342. public func enactTempBasal(unitsPerHour: Double, for duration: TimeInterval, completion: @escaping (PumpManagerError?) -> Void) {
  343. logDeviceComms(.send, message: "Temp Basal \(unitsPerHour) U/hr Duration:\(duration.hours)")
  344. if state.tempBasalEnactmentShouldError || state.pumpBatteryChargeRemaining == 0 {
  345. let error = PumpManagerError.communication(MockPumpManagerError.communicationFailure)
  346. logDeviceComms(.error, message: "Temp Basal failed with error \(error)")
  347. completion(error)
  348. } else if state.deliveryCommandsShouldTriggerUncertainDelivery {
  349. state.deliveryIsUncertain = true
  350. logDeviceComms(.error, message: "Uncertain delivery for temp basal")
  351. completion(.uncertainDelivery)
  352. } else if state.occlusionDetected || state.pumpErrorDetected {
  353. let error = PumpManagerError.deviceState(MockPumpManagerError.pumpError)
  354. logDeviceComms(.error, message: "Temp Basal failed because the pump is in an error state")
  355. completion(error)
  356. } else if case .suspended = state.suspendState {
  357. let error = PumpManagerError.deviceState(MockPumpManagerError.pumpSuspended)
  358. logDeviceComms(.error, message: "Temp Basal failed because inulin delivery is suspended")
  359. completion(error)
  360. } else if state.reservoirUnitsRemaining == 0 {
  361. let error = PumpManagerError.deviceState(MockPumpManagerError.pumpSuspended)
  362. logDeviceComms(.error, message: "Temp Basal failed because there is no insulin in the reservoir")
  363. completion(error)
  364. } else {
  365. let now = Date()
  366. if let temp = state.unfinalizedTempBasal, temp.finishTime.compare(now) == .orderedDescending {
  367. state.unfinalizedTempBasal?.cancel(at: now)
  368. }
  369. state.finalizeFinishedDoses()
  370. logDeviceComms(.receive, message: "Temp Basal succeeded")
  371. if duration < .ulpOfOne {
  372. // Cancel temp basal
  373. storePumpEvents { (error) in
  374. completion(nil)
  375. }
  376. } else {
  377. let temp = UnfinalizedDose(tempBasalRate: unitsPerHour, startTime: now, duration: duration, insulinType: state.insulinType)
  378. state.unfinalizedTempBasal = temp
  379. storePumpEvents { (error) in
  380. completion(nil)
  381. }
  382. }
  383. logDeviceCommunication("enactTempBasal succeeded", type: .receive)
  384. }
  385. }
  386. private func logDeviceComms(_ type: DeviceLogEntryType, message: String) {
  387. self.delegate.delegate?.deviceManager(self, logEventForDeviceIdentifier: "mockpump", type: type, message: message, completion: nil)
  388. }
  389. public func enactBolus(units: Double, activationType: BolusActivationType, completion: @escaping (PumpManagerError?) -> Void) {
  390. logDeviceCommunication("enactBolus(\(units), \(activationType))")
  391. if state.bolusEnactmentShouldError || state.pumpBatteryChargeRemaining == 0 {
  392. let error = PumpManagerError.communication(MockPumpManagerError.communicationFailure)
  393. logDeviceComms(.error, message: "Bolus failed with error \(error)")
  394. completion(error)
  395. } else if state.deliveryCommandsShouldTriggerUncertainDelivery {
  396. state.deliveryIsUncertain = true
  397. logDeviceComms(.error, message: "Uncertain delivery for bolus")
  398. completion(PumpManagerError.uncertainDelivery)
  399. } else if state.occlusionDetected || state.pumpErrorDetected {
  400. let error = PumpManagerError.deviceState(MockPumpManagerError.pumpError)
  401. logDeviceComms(.error, message: "Bolus failed because the pump is in an error state")
  402. completion(error)
  403. } else if state.reservoirUnitsRemaining == 0 {
  404. let error = PumpManagerError.deviceState(MockPumpManagerError.pumpSuspended)
  405. logDeviceComms(.error, message: "Bolus failed because there is no insulin in the reservoir")
  406. completion(error)
  407. } else {
  408. state.finalizeFinishedDoses()
  409. if let _ = state.unfinalizedBolus {
  410. logDeviceCommunication("enactBolus failed: bolusInProgress", type: .error)
  411. completion(PumpManagerError.deviceState(MockPumpManagerError.bolusInProgress))
  412. return
  413. }
  414. if case .suspended = status.basalDeliveryState {
  415. logDeviceCommunication("enactBolus failed: pumpSuspended", type: .error)
  416. completion(PumpManagerError.deviceState(MockPumpManagerError.pumpSuspended))
  417. return
  418. }
  419. let bolus = UnfinalizedDose(bolusAmount: units, startTime: Date(), duration: .minutes(units / type(of: self).deliveryUnitsPerMinute), insulinType: state.insulinType, automatic: activationType.isAutomatic)
  420. state.unfinalizedBolus = bolus
  421. logDeviceComms(.receive, message: "Bolus accepted")
  422. storePumpEvents { (error) in
  423. completion(nil)
  424. self.logDeviceCommunication("enactBolus succeeded", type: .receive)
  425. }
  426. }
  427. }
  428. public func cancelBolus(completion: @escaping (PumpManagerResult<DoseEntry?>) -> Void) {
  429. logDeviceComms(.send, message: "Cancel")
  430. if self.state.bolusCancelShouldError {
  431. let error = PumpManagerError.communication(MockPumpManagerError.communicationFailure)
  432. logDeviceComms(.error, message: "Cancel failed with error: \(error)")
  433. completion(.failure(error))
  434. } else {
  435. state.unfinalizedBolus?.cancel(at: Date())
  436. storePumpEvents { (_) in
  437. DispatchQueue.main.async {
  438. self.state.finalizeFinishedDoses()
  439. completion(.success(nil))
  440. }
  441. }
  442. }
  443. }
  444. public func setMustProvideBLEHeartbeat(_ mustProvideBLEHeartbeat: Bool) {
  445. // nothing to do here
  446. }
  447. private func stopInsulinDelivery() {
  448. let now = Date()
  449. state.unfinalizedTempBasal?.cancel(at: now)
  450. state.unfinalizedBolus?.cancel(at: now)
  451. storePumpEvents { _ in }
  452. }
  453. public func suspendDelivery(completion: @escaping (Error?) -> Void) {
  454. logDeviceComms(.send, message: "Suspend")
  455. if self.state.deliverySuspensionShouldError {
  456. let error = PumpManagerError.communication(MockPumpManagerError.communicationFailure)
  457. logDeviceComms(.error, message: "Suspend failed with error: \(error)")
  458. completion(error)
  459. } else {
  460. let now = Date()
  461. state.unfinalizedTempBasal?.cancel(at: now)
  462. state.unfinalizedBolus?.cancel(at: now)
  463. let suspendDate = Date()
  464. let suspend = UnfinalizedDose(suspendStartTime: suspendDate)
  465. self.state.finalizedDoses.append(suspend)
  466. self.state.suspendState = .suspended(suspendDate)
  467. logDeviceComms(.receive, message: "Suspend accepted")
  468. storePumpEvents { (error) in
  469. completion(error)
  470. }
  471. logDeviceCommunication("suspendDelivery succeeded", type: .receive)
  472. }
  473. }
  474. public func resumeDelivery(completion: @escaping (Error?) -> Void) {
  475. logDeviceComms(.send, message: "Resume")
  476. if self.state.deliveryResumptionShouldError {
  477. let error = PumpManagerError.communication(MockPumpManagerError.communicationFailure)
  478. logDeviceComms(.error, message: "Resume failed with error: \(error)")
  479. completion(error)
  480. } else {
  481. let resumeDate = Date()
  482. let resume = UnfinalizedDose(resumeStartTime: resumeDate, insulinType: state.insulinType)
  483. self.state.finalizedDoses.append(resume)
  484. self.state.suspendState = .resumed(resumeDate)
  485. storePumpEvents { (error) in
  486. completion(error)
  487. }
  488. logDeviceCommunication("resumeDelivery succeeded", type: .receive)
  489. }
  490. }
  491. public func injectPumpEvents(_ pumpEvents: [NewPumpEvent]) {
  492. state.finalizedDoses += pumpEvents.compactMap { $0.unfinalizedDose }
  493. state.additionalPumpEvents += pumpEvents.filter { $0.dose == nil }
  494. }
  495. public func setMaximumTempBasalRate(_ rate: Double) { }
  496. public func syncBasalRateSchedule(items scheduleItems: [RepeatingScheduleValue<Double>], completion: @escaping (Result<BasalRateSchedule, Error>) -> Void) {
  497. state.basalRateSchedule = BasalRateSchedule(dailyItems: scheduleItems, timeZone: self.status.timeZone)
  498. DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
  499. completion(.success(BasalRateSchedule(dailyItems: scheduleItems, timeZone: self.status.timeZone)!))
  500. }
  501. }
  502. public func syncDeliveryLimits(limits deliveryLimits: DeliveryLimits, completion: @escaping (Result<DeliveryLimits, Error>) -> Void) {
  503. DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
  504. completion(.success(deliveryLimits))
  505. }
  506. }
  507. }
  508. // MARK: - AlertResponder implementation
  509. extension MockPumpManager {
  510. public func acknowledgeAlert(alertIdentifier: Alert.AlertIdentifier, completion: @escaping (Error?) -> Void) {
  511. completion(nil)
  512. }
  513. }
  514. // MARK: - AlertSoundVendor implementation
  515. extension MockPumpManager {
  516. public func getSoundBaseURL() -> URL? { return nil }
  517. public func getSounds() -> [Alert.Sound] { return [] }
  518. }
  519. extension MockPumpManager {
  520. public var debugDescription: String {
  521. return """
  522. ## MockPumpManager
  523. status: \(status)
  524. state: \(state)
  525. stateObservers.count: \(stateObservers.cleanupDeallocatedElements().count)
  526. statusObservers.count: \(statusObservers.cleanupDeallocatedElements().count)
  527. """
  528. }
  529. }