DeviceDataManager.swift 30 KB

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