DeviceDataManager.swift 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  1. import Algorithms
  2. import Combine
  3. import Foundation
  4. import LoopKit
  5. import LoopKitUI
  6. import MinimedKit
  7. import MockKit
  8. import OmniBLE
  9. import OmniKit
  10. import ShareClient
  11. import SwiftDate
  12. import Swinject
  13. import UserNotifications
  14. protocol DeviceDataManager: GlucoseSource {
  15. var pumpManager: PumpManagerUI? { get set }
  16. var bluetoothManager: BluetoothStateManager { get }
  17. var loopInProgress: Bool { get set }
  18. var pumpDisplayState: CurrentValueSubject<PumpDisplayState?, Never> { get }
  19. var recommendsLoop: PassthroughSubject<Void, Never> { get }
  20. var bolusTrigger: PassthroughSubject<Bool, Never> { get }
  21. var manualTempBasal: PassthroughSubject<Bool, Never> { get }
  22. var errorSubject: PassthroughSubject<Error, Never> { get }
  23. var pumpName: CurrentValueSubject<String, Never> { get }
  24. var pumpExpiresAtDate: CurrentValueSubject<Date?, Never> { get }
  25. func heartbeat(date: Date)
  26. func createBolusProgressReporter() -> DoseProgressReporter?
  27. var alertHistoryStorage: AlertHistoryStorage! { get }
  28. }
  29. private let staticPumpManagers: [PumpManagerUI.Type] = [
  30. MinimedPumpManager.self,
  31. OmnipodPumpManager.self,
  32. OmniBLEPumpManager.self,
  33. MockPumpManager.self
  34. ]
  35. private let staticPumpManagersByIdentifier: [String: PumpManagerUI.Type] = [
  36. MinimedPumpManager.pluginIdentifier: MinimedPumpManager.self,
  37. OmnipodPumpManager.pluginIdentifier: OmnipodPumpManager.self,
  38. OmniBLEPumpManager.pluginIdentifier: OmniBLEPumpManager.self,
  39. MockPumpManager.pluginIdentifier: MockPumpManager.self
  40. ]
  41. private let accessLock = NSRecursiveLock(label: "BaseDeviceDataManager.accessLock")
  42. final class BaseDeviceDataManager: DeviceDataManager, Injectable {
  43. private let processQueue = DispatchQueue.markedQueue(label: "BaseDeviceDataManager.processQueue")
  44. @Injected() private var pumpHistoryStorage: PumpHistoryStorage!
  45. @Injected() var alertHistoryStorage: AlertHistoryStorage!
  46. @Injected() private var storage: FileStorage!
  47. @Injected() private var broadcaster: Broadcaster!
  48. @Injected() private var glucoseStorage: GlucoseStorage!
  49. @Injected() private var settingsManager: SettingsManager!
  50. @Injected() private var bluetoothProvider: BluetoothStateManager!
  51. @Persisted(key: "BaseDeviceDataManager.lastEventDate") var lastEventDate: Date? = nil
  52. @SyncAccess(lock: accessLock) @Persisted(key: "BaseDeviceDataManager.lastHeartBeatTime") var lastHeartBeatTime: Date =
  53. .distantPast
  54. let recommendsLoop = PassthroughSubject<Void, Never>()
  55. let bolusTrigger = PassthroughSubject<Bool, Never>()
  56. let errorSubject = PassthroughSubject<Error, Never>()
  57. let pumpNewStatus = PassthroughSubject<Void, Never>()
  58. let manualTempBasal = PassthroughSubject<Bool, Never>()
  59. private let router = FreeAPSApp.resolver.resolve(Router.self)!
  60. @SyncAccess private var pumpUpdateCancellable: AnyCancellable?
  61. private var pumpUpdatePromise: Future<Bool, Never>.Promise?
  62. @SyncAccess var loopInProgress: Bool = false
  63. var pumpManager: PumpManagerUI? {
  64. didSet {
  65. pumpManager?.pumpManagerDelegate = self
  66. pumpManager?.delegateQueue = processQueue
  67. rawPumpManager = pumpManager?.rawValue
  68. UserDefaults.standard.clearLegacyPumpManagerRawValue()
  69. if let pumpManager = pumpManager {
  70. pumpDisplayState.value = PumpDisplayState(name: pumpManager.localizedTitle, image: pumpManager.smallImage)
  71. pumpName.send(pumpManager.localizedTitle)
  72. if let omnipod = pumpManager as? OmnipodPumpManager {
  73. guard let endTime = omnipod.state.podState?.expiresAt else {
  74. pumpExpiresAtDate.send(nil)
  75. return
  76. }
  77. pumpExpiresAtDate.send(endTime)
  78. }
  79. if let omnipodBLE = pumpManager as? OmniBLEPumpManager {
  80. guard let endTime = omnipodBLE.state.podState?.expiresAt else {
  81. pumpExpiresAtDate.send(nil)
  82. return
  83. }
  84. pumpExpiresAtDate.send(endTime)
  85. }
  86. } else {
  87. pumpDisplayState.value = nil
  88. pumpExpiresAtDate.send(nil)
  89. pumpName.send("")
  90. }
  91. }
  92. }
  93. @PersistedProperty(key: "PumpManagerState") var rawPumpManager: PumpManager.RawValue?
  94. var bluetoothManager: BluetoothStateManager { bluetoothProvider }
  95. var hasBLEHeartbeat: Bool {
  96. (pumpManager as? MockPumpManager) == nil
  97. }
  98. let pumpDisplayState = CurrentValueSubject<PumpDisplayState?, Never>(nil)
  99. let pumpExpiresAtDate = CurrentValueSubject<Date?, Never>(nil)
  100. let pumpName = CurrentValueSubject<String, Never>("Pump")
  101. init(resolver: Resolver) {
  102. injectServices(resolver)
  103. setupPumpManager()
  104. UIDevice.current.isBatteryMonitoringEnabled = true
  105. broadcaster.register(AlertObserver.self, observer: self)
  106. }
  107. func setupPumpManager() {
  108. if let pumpManagerRawValue = rawPumpManager ?? UserDefaults.standard.legacyPumpManagerRawValue {
  109. pumpManager = pumpManagerFromRawValue(pumpManagerRawValue)
  110. } else {
  111. pumpManager = nil
  112. }
  113. }
  114. func createBolusProgressReporter() -> DoseProgressReporter? {
  115. pumpManager?.createBolusProgressReporter(reportingOn: processQueue)
  116. }
  117. func heartbeat(date: Date) {
  118. guard pumpUpdateCancellable == nil else {
  119. warning(.deviceManager, "Pump updating already in progress. Skip updating.")
  120. return
  121. }
  122. guard !loopInProgress else {
  123. warning(.deviceManager, "Loop in progress. Skip updating.")
  124. return
  125. }
  126. func update(_: Future<Bool, Never>.Promise?) {}
  127. processQueue.safeSync {
  128. lastHeartBeatTime = date
  129. updatePumpData()
  130. }
  131. }
  132. private func updatePumpData() {
  133. guard let pumpManager = pumpManager else {
  134. debug(.deviceManager, "Pump is not set, skip updating")
  135. updateUpdateFinished(false)
  136. return
  137. }
  138. debug(.deviceManager, "Start updating the pump data")
  139. processQueue.safeSync {
  140. pumpManager.ensureCurrentPumpData { _ in
  141. debug(.deviceManager, "Pump data updated.")
  142. self.updateUpdateFinished(true)
  143. }
  144. }
  145. }
  146. private func updateUpdateFinished(_ recommendsLoop: Bool) {
  147. pumpUpdateCancellable = nil
  148. pumpUpdatePromise = nil
  149. if !recommendsLoop {
  150. warning(.deviceManager, "Loop recommendation time out or got error. Trying to loop right now.")
  151. }
  152. self.recommendsLoop.send()
  153. }
  154. private func pumpManagerFromRawValue(_ rawValue: [String: Any]) -> PumpManagerUI? {
  155. guard let rawState = rawValue["state"] as? PumpManager.RawStateValue,
  156. let Manager = pumpManagerTypeFromRawValue(rawValue)
  157. else {
  158. return nil
  159. }
  160. return Manager.init(rawState: rawState) as? PumpManagerUI
  161. }
  162. private func pumpManagerTypeFromRawValue(_ rawValue: [String: Any]) -> PumpManager.Type? {
  163. guard let managerIdentifier = rawValue["managerIdentifier"] as? String else {
  164. return nil
  165. }
  166. return staticPumpManagersByIdentifier[managerIdentifier]
  167. }
  168. // MARK: - GlucoseSource
  169. @Persisted(key: "BaseDeviceDataManager.lastFetchGlucoseDate") private var lastFetchGlucoseDate: Date = .distantPast
  170. var glucoseManager: FetchGlucoseManager?
  171. var cgmManager: CGMManagerUI?
  172. var cgmType: CGMType = .enlite
  173. func fetchIfNeeded() -> AnyPublisher<[BloodGlucose], Never> {
  174. fetch(nil)
  175. }
  176. func fetch(_: DispatchTimer?) -> AnyPublisher<[BloodGlucose], Never> {
  177. guard let medtronic = pumpManager as? MinimedPumpManager else {
  178. warning(.deviceManager, "Fetch minilink glucose failed: Pump is not Medtronic")
  179. return Just([]).eraseToAnyPublisher()
  180. }
  181. guard lastFetchGlucoseDate.addingTimeInterval(5.minutes.timeInterval) < Date() else {
  182. return Just([]).eraseToAnyPublisher()
  183. }
  184. medtronic.cgmManagerDelegate = self
  185. return Future<[BloodGlucose], Error> { promise in
  186. self.processQueue.async {
  187. medtronic.fetchNewDataIfNeeded { result in
  188. switch result {
  189. case .noData:
  190. debug(.deviceManager, "Minilink glucose is empty")
  191. promise(.success([]))
  192. case .unreliableData:
  193. debug(.deviceManager, "Unreliable data received")
  194. promise(.success([]))
  195. case let .newData(glucose):
  196. let directions: [BloodGlucose.Direction?] = [nil]
  197. + glucose.windows(ofCount: 2).map { window -> BloodGlucose.Direction? in
  198. let pair = Array(window)
  199. guard pair.count == 2 else { return nil }
  200. let firstValue = Int(pair[0].quantity.doubleValue(for: .milligramsPerDeciliter))
  201. let secondValue = Int(pair[1].quantity.doubleValue(for: .milligramsPerDeciliter))
  202. return .init(trend: secondValue - firstValue)
  203. }
  204. let results = glucose.enumerated().map { index, sample -> BloodGlucose in
  205. let value = Int(sample.quantity.doubleValue(for: .milligramsPerDeciliter))
  206. return BloodGlucose(
  207. _id: sample.syncIdentifier,
  208. sgv: value,
  209. direction: directions[index],
  210. date: Decimal(Int(sample.date.timeIntervalSince1970 * 1000)),
  211. dateString: sample.date,
  212. unfiltered: Decimal(value),
  213. filtered: nil,
  214. noise: nil,
  215. glucose: value,
  216. type: "sgv"
  217. )
  218. }
  219. if let lastDate = results.last?.dateString {
  220. self.lastFetchGlucoseDate = lastDate
  221. }
  222. promise(.success(results))
  223. case let .error(error):
  224. warning(.deviceManager, "Fetch minilink glucose failed", error: error)
  225. promise(.failure(error))
  226. }
  227. }
  228. }
  229. }
  230. .timeout(60 * 3, scheduler: processQueue, options: nil, customError: nil)
  231. .replaceError(with: [])
  232. .replaceEmpty(with: [])
  233. .eraseToAnyPublisher()
  234. }
  235. }
  236. // MARK: - PumpManagerDelegate
  237. extension BaseDeviceDataManager: PumpManagerDelegate {
  238. var automaticDosingEnabled: Bool {
  239. settingsManager.settings.closedLoop // Take if close or open loop
  240. }
  241. func pumpManager(
  242. _: LoopKit.PumpManager,
  243. didRequestBasalRateScheduleChange _: LoopKit.BasalRateSchedule,
  244. completion _: @escaping (Error?) -> Void
  245. ) {
  246. debug(.deviceManager, "pumpManagerBasalRateChange")
  247. }
  248. func pumpManagerPumpWasReplaced(_: PumpManager) {
  249. debug(.deviceManager, "pumpManagerPumpWasReplaced")
  250. }
  251. var detectedSystemTimeOffset: TimeInterval {
  252. // trustedTimeChecker.detectedSystemTimeOffset
  253. 0
  254. }
  255. func pumpManager(_: PumpManager, didAdjustPumpClockBy adjustment: TimeInterval) {
  256. debug(.deviceManager, "didAdjustPumpClockBy \(adjustment)")
  257. }
  258. func pumpManagerDidUpdateState(_ pumpManager: PumpManager) {
  259. rawPumpManager = pumpManager.rawValue
  260. if self.pumpManager == nil, let newPumpManager = pumpManager as? PumpManagerUI {
  261. self.pumpManager = newPumpManager
  262. }
  263. pumpName.send(pumpManager.localizedTitle)
  264. }
  265. /// heartbeat with pump occurs some issues in the backgroundtask - so never used
  266. func pumpManagerBLEHeartbeatDidFire(_: PumpManager) {
  267. debug(.deviceManager, "Pump Heartbeat: do nothing. Pump connection is OK")
  268. }
  269. func pumpManagerMustProvideBLEHeartbeat(_: PumpManager) -> Bool {
  270. true
  271. }
  272. func pumpManager(_ pumpManager: PumpManager, didUpdate status: PumpManagerStatus, oldStatus: PumpManagerStatus) {
  273. dispatchPrecondition(condition: .onQueue(processQueue))
  274. debug(.deviceManager, "New pump status Bolus: \(status.bolusState)")
  275. debug(.deviceManager, "New pump status Basal: \(String(describing: status.basalDeliveryState))")
  276. if case .inProgress = status.bolusState {
  277. bolusTrigger.send(true)
  278. } else if status.bolusState != .canceling {
  279. bolusTrigger.send(false)
  280. }
  281. if status.insulinType != oldStatus.insulinType {
  282. settingsManager.updateInsulinCurve(status.insulinType)
  283. }
  284. let batteryPercent = Int((status.pumpBatteryChargeRemaining ?? 1) * 100)
  285. let battery = Battery(
  286. percent: batteryPercent,
  287. voltage: nil,
  288. string: batteryPercent >= 10 ? .normal : .low,
  289. display: pumpManager.status.pumpBatteryChargeRemaining != nil
  290. )
  291. storage.save(battery, as: OpenAPS.Monitor.battery)
  292. broadcaster.notify(PumpBatteryObserver.self, on: processQueue) {
  293. $0.pumpBatteryDidChange(battery)
  294. }
  295. if let omnipod = pumpManager as? OmnipodPumpManager {
  296. let reservoirVal = omnipod.state.podState?.lastInsulinMeasurements?.reservoirLevel ?? 0xDEAD_BEEF
  297. // TODO: find the value Pod.maximumReservoirReading
  298. let reservoir = Decimal(reservoirVal) > 50.0 ? 0xDEAD_BEEF : reservoirVal
  299. storage.save(Decimal(reservoir), as: OpenAPS.Monitor.reservoir)
  300. broadcaster.notify(PumpReservoirObserver.self, on: processQueue) {
  301. $0.pumpReservoirDidChange(Decimal(reservoir))
  302. }
  303. if let tempBasal = omnipod.state.podState?.unfinalizedTempBasal, !tempBasal.isFinished(),
  304. !tempBasal.automatic
  305. {
  306. // the manual basal temp is launch - block every thing
  307. debug(.deviceManager, "manual temp basal")
  308. manualTempBasal.send(true)
  309. } else {
  310. // no more manual Temp Basal !
  311. manualTempBasal.send(false)
  312. }
  313. guard let endTime = omnipod.state.podState?.expiresAt else {
  314. pumpExpiresAtDate.send(nil)
  315. return
  316. }
  317. pumpExpiresAtDate.send(endTime)
  318. if let startTime = omnipod.state.podState?.activatedAt {
  319. storage.save(startTime, as: OpenAPS.Monitor.podAge)
  320. }
  321. }
  322. if let omnipodBLE = pumpManager as? OmniBLEPumpManager {
  323. let reservoirVal = omnipodBLE.state.podState?.lastInsulinMeasurements?.reservoirLevel ?? 0xDEAD_BEEF
  324. // TODO: find the value Pod.maximumReservoirReading
  325. let reservoir = Decimal(reservoirVal) > 50.0 ? 0xDEAD_BEEF : reservoirVal
  326. storage.save(Decimal(reservoir), as: OpenAPS.Monitor.reservoir)
  327. broadcaster.notify(PumpReservoirObserver.self, on: processQueue) {
  328. $0.pumpReservoirDidChange(Decimal(reservoir))
  329. }
  330. // manual temp basal on
  331. if let tempBasal = omnipodBLE.state.podState?.unfinalizedTempBasal, !tempBasal.isFinished(),
  332. !tempBasal.automatic
  333. {
  334. // the manual basal temp is launch - block every thing
  335. debug(.deviceManager, "manual temp basal")
  336. manualTempBasal.send(true)
  337. } else {
  338. // no more manual Temp Basal !
  339. manualTempBasal.send(false)
  340. }
  341. guard let endTime = omnipodBLE.state.podState?.expiresAt else {
  342. pumpExpiresAtDate.send(nil)
  343. return
  344. }
  345. pumpExpiresAtDate.send(endTime)
  346. if let startTime = omnipodBLE.state.podState?.activatedAt {
  347. storage.save(startTime, as: OpenAPS.Monitor.podAge)
  348. }
  349. }
  350. }
  351. func pumpManagerWillDeactivate(_: PumpManager) {
  352. dispatchPrecondition(condition: .onQueue(processQueue))
  353. pumpManager = nil
  354. broadcaster.notify(PumpDeactivatedObserver.self, on: processQueue) {
  355. $0.pumpDeactivatedDidChange()
  356. }
  357. }
  358. func pumpManager(_: PumpManager, didUpdatePumpRecordsBasalProfileStartEvents _: Bool) {}
  359. func pumpManager(_: PumpManager, didError error: PumpManagerError) {
  360. dispatchPrecondition(condition: .onQueue(processQueue))
  361. debug(.deviceManager, "error: \(error.localizedDescription), reason: \(String(describing: error.failureReason))")
  362. errorSubject.send(error)
  363. }
  364. func pumpManager(
  365. _: PumpManager,
  366. hasNewPumpEvents events: [NewPumpEvent],
  367. lastReconciliation _: Date?,
  368. replacePendingEvents _: Bool,
  369. completion: @escaping (_ error: Error?) -> Void
  370. ) {
  371. dispatchPrecondition(condition: .onQueue(processQueue))
  372. debug(.deviceManager, "New pump events:\n\(events.map(\.title).joined(separator: "\n"))")
  373. // filter buggy TBRs > maxBasal from MDT
  374. let events = events.filter {
  375. // type is optional...
  376. guard let type = $0.type, type == .tempBasal else { return true }
  377. return $0.dose?.unitsPerHour ?? 0 <= Double(settingsManager.pumpSettings.maxBasal)
  378. }
  379. pumpHistoryStorage.storePumpEvents(events)
  380. lastEventDate = events.last?.date
  381. completion(nil)
  382. }
  383. func pumpManager(
  384. _: PumpManager,
  385. didReadReservoirValue units: Double,
  386. at date: Date,
  387. completion: @escaping (Result<
  388. (newValue: ReservoirValue, lastValue: ReservoirValue?, areStoredValuesContinuous: Bool),
  389. Error
  390. >) -> Void
  391. ) {
  392. dispatchPrecondition(condition: .onQueue(processQueue))
  393. debug(.deviceManager, "Reservoir Value \(units), at: \(date)")
  394. storage.save(Decimal(units), as: OpenAPS.Monitor.reservoir)
  395. broadcaster.notify(PumpReservoirObserver.self, on: processQueue) {
  396. $0.pumpReservoirDidChange(Decimal(units))
  397. }
  398. completion(.success((
  399. newValue: Reservoir(startDate: Date(), unitVolume: units),
  400. lastValue: nil,
  401. areStoredValuesContinuous: true
  402. )))
  403. }
  404. func pumpManagerRecommendsLoop(_: PumpManager) {
  405. dispatchPrecondition(condition: .onQueue(processQueue))
  406. debug(.deviceManager, "Pump recommends loop")
  407. guard let promise = pumpUpdatePromise else {
  408. warning(.deviceManager, "We do not waiting for loop recommendation at this time.")
  409. return
  410. }
  411. promise(.success(true))
  412. }
  413. func startDateToFilterNewPumpEvents(for _: PumpManager) -> Date {
  414. lastEventDate?.addingTimeInterval(-15.minutes.timeInterval) ?? Date().addingTimeInterval(-2.hours.timeInterval)
  415. }
  416. }
  417. // MARK: - DeviceManagerDelegate
  418. extension BaseDeviceDataManager: DeviceManagerDelegate {
  419. func issueAlert(_ alert: Alert) {
  420. alertHistoryStorage.storeAlert(
  421. AlertEntry(
  422. alertIdentifier: alert.identifier.alertIdentifier,
  423. primitiveInterruptionLevel: alert.interruptionLevel.storedValue as? Decimal,
  424. issuedDate: Date(),
  425. managerIdentifier: alert.identifier.managerIdentifier,
  426. triggerType: alert.trigger.storedType,
  427. triggerInterval: alert.trigger.storedInterval as? Decimal,
  428. contentTitle: alert.foregroundContent?.title,
  429. contentBody: alert.foregroundContent?.body
  430. )
  431. )
  432. }
  433. func retractAlert(identifier: Alert.Identifier) {
  434. alertHistoryStorage.deleteAlert(identifier: identifier.alertIdentifier)
  435. }
  436. func doesIssuedAlertExist(identifier _: Alert.Identifier, completion _: @escaping (Result<Bool, Error>) -> Void) {
  437. debug(.deviceManager, "doesIssueAlertExist")
  438. }
  439. func lookupAllUnretracted(managerIdentifier _: String, completion _: @escaping (Result<[PersistedAlert], Error>) -> Void) {
  440. debug(.deviceManager, "lookupAllUnretracted")
  441. }
  442. func lookupAllUnacknowledgedUnretracted(
  443. managerIdentifier _: String,
  444. completion _: @escaping (Result<[PersistedAlert], Error>) -> Void
  445. ) {}
  446. func recordRetractedAlert(_: Alert, at _: Date) {}
  447. func removeNotificationRequests(for _: DeviceManager, identifiers: [String]) {
  448. DispatchQueue.main.async {
  449. UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: identifiers)
  450. }
  451. }
  452. func deviceManager(
  453. _: DeviceManager,
  454. logEventForDeviceIdentifier _: String?,
  455. type _: DeviceLogEntryType,
  456. message: String,
  457. completion _: ((Error?) -> Void)?
  458. ) {
  459. debug(.deviceManager, "Device message: \(message)")
  460. }
  461. }
  462. // MARK: - CGMManagerDelegate
  463. extension BaseDeviceDataManager: CGMManagerDelegate {
  464. func startDateToFilterNewData(for _: CGMManager) -> Date? {
  465. glucoseStorage.syncDate().addingTimeInterval(-10.minutes.timeInterval) // additional time to calculate directions
  466. }
  467. func cgmManager(_: CGMManager, hasNew _: CGMReadingResult) {}
  468. func cgmManager(_: LoopKit.CGMManager, hasNew _: [LoopKit.PersistedCgmEvent]) {}
  469. func cgmManagerWantsDeletion(_: CGMManager) {}
  470. func cgmManagerDidUpdateState(_: CGMManager) {}
  471. func credentialStoragePrefix(for _: CGMManager) -> String { "BaseDeviceDataManager" }
  472. func cgmManager(_: CGMManager, didUpdate _: CGMManagerStatus) {}
  473. }
  474. // MARK: - AlertPresenter
  475. extension BaseDeviceDataManager: AlertObserver {
  476. func AlertDidUpdate(_ alerts: [AlertEntry]) {
  477. alerts.forEach { alert in
  478. if alert.acknowledgedDate == nil {
  479. ackAlert(alert: alert)
  480. }
  481. }
  482. }
  483. private func ackAlert(alert: AlertEntry) {
  484. let typeMessage: MessageType
  485. let alertUp = alert.alertIdentifier.uppercased()
  486. if alertUp.contains("FAULT") || alertUp.contains("ERROR") {
  487. typeMessage = .errorPump
  488. } else {
  489. typeMessage = .warning
  490. }
  491. let messageCont = MessageContent(content: alert.contentBody ?? "Unknown", type: typeMessage)
  492. let alertIssueDate = alert.issuedDate
  493. processQueue.async {
  494. // if not alert in OmniPod/BLE, the acknowledgeAlert didn't do callbacks- Hack to manage this case
  495. if let omnipodBLE = self.pumpManager as? OmniBLEPumpManager {
  496. if omnipodBLE.state.activeAlerts.isEmpty {
  497. // force to ack alert in the alertStorage
  498. self.alertHistoryStorage.ackAlert(alertIssueDate, nil)
  499. }
  500. }
  501. if let omniPod = self.pumpManager as? OmnipodPumpManager {
  502. if omniPod.state.activeAlerts.isEmpty {
  503. // force to ack alert in the alertStorage
  504. self.alertHistoryStorage.ackAlert(alertIssueDate, nil)
  505. }
  506. }
  507. self.pumpManager?.acknowledgeAlert(alertIdentifier: alert.alertIdentifier) { error in
  508. self.router.alertMessage.send(messageCont)
  509. if let error = error {
  510. self.alertHistoryStorage.ackAlert(alertIssueDate, error.localizedDescription)
  511. debug(.deviceManager, "acknowledge not succeeded with error \(error.localizedDescription)")
  512. } else {
  513. self.alertHistoryStorage.ackAlert(alertIssueDate, nil)
  514. }
  515. }
  516. self.broadcaster.notify(pumpNotificationObserver.self, on: self.processQueue) {
  517. $0.pumpNotification(alert: alert)
  518. }
  519. }
  520. }
  521. }
  522. // extension BaseDeviceDataManager: AlertPresenter {
  523. // func issueAlert(_: Alert) {}
  524. // func retractAlert(identifier _: Alert.Identifier) {}
  525. // }
  526. // MARK: Others
  527. protocol PumpReservoirObserver {
  528. func pumpReservoirDidChange(_ reservoir: Decimal)
  529. }
  530. protocol PumpBatteryObserver {
  531. func pumpBatteryDidChange(_ battery: Battery)
  532. }
  533. protocol PumpDeactivatedObserver {
  534. func pumpDeactivatedDidChange()
  535. }