DeviceDataManager.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. import Combine
  2. import Foundation
  3. import LoopKit
  4. import LoopKitUI
  5. import MinimedKit
  6. import MockKit
  7. import OmniKit
  8. import SwiftDate
  9. import Swinject
  10. import UserNotifications
  11. protocol DeviceDataManager {
  12. var pumpManager: PumpManagerUI? { get set }
  13. var pumpDisplayState: CurrentValueSubject<PumpDisplayState?, Never> { get }
  14. var recommendsLoop: PassthroughSubject<Void, Never> { get }
  15. var errorSubject: PassthroughSubject<Error, Never> { get }
  16. var pumpName: CurrentValueSubject<String, Never> { get }
  17. var pumpExpiresAtDate: CurrentValueSubject<Date?, Never> { get }
  18. func heartbeat(date: Date, force: Bool)
  19. func createBolusProgressReporter() -> DoseProgressReporter?
  20. }
  21. private let staticPumpManagers: [PumpManagerUI.Type] = [
  22. MinimedPumpManager.self,
  23. OmnipodPumpManager.self,
  24. MockPumpManager.self
  25. ]
  26. private let staticPumpManagersByIdentifier: [String: PumpManagerUI.Type] = staticPumpManagers.reduce(into: [:]) { map, Type in
  27. map[Type.managerIdentifier] = Type
  28. }
  29. private let accessLock = NSRecursiveLock(label: "BaseDeviceDataManager.accessLock")
  30. final class BaseDeviceDataManager: DeviceDataManager, Injectable {
  31. private let processQueue = DispatchQueue(label: "BaseDeviceDataManager.processQueue")
  32. @Injected() private var pumpHistoryStorage: PumpHistoryStorage!
  33. @Injected() private var storage: FileStorage!
  34. @Injected() private var broadcaster: Broadcaster!
  35. @Injected() private var glucoseStorage: GlucoseStorage!
  36. @Persisted(key: "BaseDeviceDataManager.lastEventDate") var lastEventDate: Date? = nil
  37. @SyncAccess(lock: accessLock) @Persisted(key: "BaseDeviceDataManager.lastHeartBeatTime") var lastHeartBeatTime: Date =
  38. .distantPast
  39. let recommendsLoop = PassthroughSubject<Void, Never>()
  40. let errorSubject = PassthroughSubject<Error, Never>()
  41. var pumpManager: PumpManagerUI? {
  42. didSet {
  43. pumpManager?.pumpManagerDelegate = self
  44. pumpManager?.delegateQueue = processQueue
  45. UserDefaults.standard.pumpManagerRawValue = pumpManager?.rawValue
  46. if let pumpManager = pumpManager {
  47. pumpDisplayState.value = PumpDisplayState(name: pumpManager.localizedTitle, image: pumpManager.smallImage)
  48. pumpName.send(pumpManager.localizedTitle)
  49. if let omnipod = pumpManager as? OmnipodPumpManager {
  50. guard let endTime = omnipod.state.podState?.expiresAt else {
  51. pumpExpiresAtDate.send(nil)
  52. return
  53. }
  54. pumpExpiresAtDate.send(endTime)
  55. }
  56. pumpManager.setMustProvideBLEHeartbeat(true)
  57. } else {
  58. pumpDisplayState.value = nil
  59. }
  60. }
  61. }
  62. let pumpDisplayState = CurrentValueSubject<PumpDisplayState?, Never>(nil)
  63. let pumpExpiresAtDate = CurrentValueSubject<Date?, Never>(nil)
  64. let pumpName = CurrentValueSubject<String, Never>("Pump")
  65. init(resolver: Resolver) {
  66. injectServices(resolver)
  67. setupPumpManager()
  68. UIDevice.current.isBatteryMonitoringEnabled = true
  69. }
  70. func setupPumpManager() {
  71. if let pumpManagerRawValue = UserDefaults.standard.pumpManagerRawValue {
  72. pumpManager = pumpManagerFromRawValue(pumpManagerRawValue)
  73. }
  74. }
  75. @SyncAccess(lock: accessLock) private var pumpUpdateInProgress = false
  76. func createBolusProgressReporter() -> DoseProgressReporter? {
  77. pumpManager?.createBolusProgressReporter(reportingOn: processQueue)
  78. }
  79. func heartbeat(date: Date, force: Bool) {
  80. if force {
  81. updatePumpData()
  82. return
  83. }
  84. var updateInterval: TimeInterval = 5.minutes.timeInterval
  85. switch lastHeartBeatTime.timeIntervalSince(date) {
  86. case let interval where interval < -10.minutes.timeInterval:
  87. break
  88. case let interval where interval < -5.minutes.timeInterval:
  89. updateInterval = 1.minutes.timeInterval
  90. default:
  91. break
  92. }
  93. let interval = date.timeIntervalSince(lastHeartBeatTime)
  94. guard interval >= updateInterval else {
  95. debug(.deviceManager, "Last hearbeat \(interval / 60) min ago, skip updating the pump data")
  96. return
  97. }
  98. lastHeartBeatTime = date
  99. updatePumpData()
  100. }
  101. private func updatePumpData() {
  102. guard let pumpManager = pumpManager else {
  103. debug(.deviceManager, "Pump is not set, skip updating")
  104. return
  105. }
  106. guard !pumpUpdateInProgress else {
  107. debug(.deviceManager, "Pump update in progress, skip updating")
  108. return
  109. }
  110. debug(.deviceManager, "Start updating the pump data")
  111. pumpUpdateInProgress = true
  112. pumpManager.ensureCurrentPumpData {
  113. debug(.deviceManager, "Pump Data updated")
  114. self.pumpUpdateInProgress = false
  115. }
  116. }
  117. private func pumpManagerFromRawValue(_ rawValue: [String: Any]) -> PumpManagerUI? {
  118. guard let rawState = rawValue["state"] as? PumpManager.RawStateValue,
  119. let Manager = pumpManagerTypeFromRawValue(rawValue)
  120. else {
  121. return nil
  122. }
  123. return Manager.init(rawState: rawState) as? PumpManagerUI
  124. }
  125. private func pumpManagerTypeFromRawValue(_ rawValue: [String: Any]) -> PumpManager.Type? {
  126. guard let managerIdentifier = rawValue["managerIdentifier"] as? String else {
  127. return nil
  128. }
  129. return staticPumpManagersByIdentifier[managerIdentifier]
  130. }
  131. }
  132. extension BaseDeviceDataManager: PumpManagerDelegate {
  133. func pumpManager(_: PumpManager, didAdjustPumpClockBy adjustment: TimeInterval) {
  134. debug(.deviceManager, "didAdjustPumpClockBy \(adjustment)")
  135. }
  136. func pumpManagerDidUpdateState(_ pumpManager: PumpManager) {
  137. UserDefaults.standard.pumpManagerRawValue = pumpManager.rawValue
  138. if self.pumpManager == nil, let newPumpManager = pumpManager as? PumpManagerUI {
  139. self.pumpManager = newPumpManager
  140. }
  141. pumpName.send(pumpManager.localizedTitle)
  142. }
  143. func pumpManagerBLEHeartbeatDidFire(_: PumpManager) {
  144. debug(.deviceManager, "Pump Heartbeat")
  145. pumpUpdateInProgress = false
  146. heartbeat(date: Date(), force: false)
  147. }
  148. func pumpManagerMustProvideBLEHeartbeat(_: PumpManager) -> Bool {
  149. true
  150. }
  151. func pumpManager(_ pumpManager: PumpManager, didUpdate status: PumpManagerStatus, oldStatus _: PumpManagerStatus) {
  152. debug(.deviceManager, "New pump status Bolus: \(status.bolusState)")
  153. debug(.deviceManager, "New pump status Basal: \(String(describing: status.basalDeliveryState))")
  154. let batteryPercent = Int((status.pumpBatteryChargeRemaining ?? 1) * 100)
  155. let battery = Battery(
  156. percent: batteryPercent,
  157. voltage: nil,
  158. string: batteryPercent >= 10 ? .normal : .low,
  159. display: pumpManager.status.pumpBatteryChargeRemaining != nil
  160. )
  161. storage.save(battery, as: OpenAPS.Monitor.battery)
  162. broadcaster.notify(PumpBatteryObserver.self, on: processQueue) {
  163. $0.pumpBatteryDidChange(battery)
  164. }
  165. if let omnipod = pumpManager as? OmnipodPumpManager {
  166. let reservoir = omnipod.state.podState?.lastInsulinMeasurements?.reservoirLevel ?? 0xDEAD_BEEF
  167. storage.save(Decimal(reservoir), as: OpenAPS.Monitor.reservoir)
  168. broadcaster.notify(PumpReservoirObserver.self, on: processQueue) {
  169. $0.pumpReservoirDidChange(Decimal(reservoir))
  170. }
  171. guard let endTime = omnipod.state.podState?.expiresAt else {
  172. pumpExpiresAtDate.send(nil)
  173. return
  174. }
  175. pumpExpiresAtDate.send(endTime)
  176. }
  177. }
  178. func pumpManagerWillDeactivate(_: PumpManager) {
  179. pumpManager = nil
  180. }
  181. func pumpManager(_: PumpManager, didUpdatePumpRecordsBasalProfileStartEvents _: Bool) {}
  182. func pumpManager(_: PumpManager, didError error: PumpManagerError) {
  183. debug(.deviceManager, "error: \(error.localizedDescription), reason: \(String(describing: error.failureReason))")
  184. errorSubject.send(error)
  185. pumpUpdateInProgress = false
  186. }
  187. func pumpManager(
  188. _: PumpManager,
  189. hasNewPumpEvents events: [NewPumpEvent],
  190. lastReconciliation _: Date?,
  191. completion: @escaping (_ error: Error?) -> Void
  192. ) {
  193. dispatchPrecondition(condition: .onQueue(processQueue))
  194. pumpHistoryStorage.storePumpEvents(events)
  195. lastEventDate = events.last?.date
  196. completion(nil)
  197. }
  198. func pumpManager(
  199. _: PumpManager,
  200. didReadReservoirValue units: Double,
  201. at date: Date,
  202. completion: @escaping (Result<
  203. (newValue: ReservoirValue, lastValue: ReservoirValue?, areStoredValuesContinuous: Bool),
  204. Error
  205. >) -> Void
  206. ) {
  207. dispatchPrecondition(condition: .onQueue(processQueue))
  208. debug(.deviceManager, "Reservoir Value \(units), at: \(date)")
  209. storage.save(Decimal(units), as: OpenAPS.Monitor.reservoir)
  210. broadcaster.notify(PumpReservoirObserver.self, on: processQueue) {
  211. $0.pumpReservoirDidChange(Decimal(units))
  212. }
  213. completion(.success((
  214. newValue: Reservoir(startDate: Date(), unitVolume: units),
  215. lastValue: nil,
  216. areStoredValuesContinuous: true
  217. )))
  218. }
  219. func pumpManagerRecommendsLoop(_: PumpManager) {
  220. dispatchPrecondition(condition: .onQueue(processQueue))
  221. pumpUpdateInProgress = false
  222. debug(.deviceManager, "Recomends loop")
  223. recommendsLoop.send()
  224. }
  225. func startDateToFilterNewPumpEvents(for _: PumpManager) -> Date {
  226. lastEventDate?.addingTimeInterval(-15.minutes.timeInterval) ?? Date().addingTimeInterval(-2.hours.timeInterval)
  227. }
  228. }
  229. // MARK: - DeviceManagerDelegate
  230. extension BaseDeviceDataManager: DeviceManagerDelegate {
  231. func scheduleNotification(
  232. for _: DeviceManager,
  233. identifier: String,
  234. content: UNNotificationContent,
  235. trigger: UNNotificationTrigger?
  236. ) {
  237. let request = UNNotificationRequest(
  238. identifier: identifier,
  239. content: content,
  240. trigger: trigger
  241. )
  242. DispatchQueue.main.async {
  243. UNUserNotificationCenter.current().add(request)
  244. }
  245. }
  246. func clearNotification(for _: DeviceManager, identifier: String) {
  247. DispatchQueue.main.async {
  248. UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [identifier])
  249. }
  250. }
  251. func removeNotificationRequests(for _: DeviceManager, identifiers: [String]) {
  252. DispatchQueue.main.async {
  253. UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: identifiers)
  254. }
  255. }
  256. func deviceManager(
  257. _: DeviceManager,
  258. logEventForDeviceIdentifier _: String?,
  259. type _: DeviceLogEntryType,
  260. message: String,
  261. completion _: ((Error?) -> Void)?
  262. ) {
  263. debug(.deviceManager, message)
  264. }
  265. }
  266. // MARK: - AlertPresenter
  267. extension BaseDeviceDataManager: AlertPresenter {
  268. func issueAlert(_: Alert) {}
  269. func retractAlert(identifier _: Alert.Identifier) {}
  270. }
  271. // MARK: Others
  272. protocol PumpReservoirObserver {
  273. func pumpReservoirDidChange(_ reservoir: Decimal)
  274. }
  275. protocol PumpBatteryObserver {
  276. func pumpBatteryDidChange(_ battery: Battery)
  277. }