MockPumpManager.swift 25 KB

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