DeviceDataManager.swift 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  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 pumpName: CurrentValueSubject<String, Never> { get }
  16. var pumpExpiresAtDate: CurrentValueSubject<Date?, Never> { get }
  17. }
  18. private let staticPumpManagers: [PumpManagerUI.Type] = [
  19. MinimedPumpManager.self,
  20. OmnipodPumpManager.self,
  21. MockPumpManager.self
  22. ]
  23. private let staticPumpManagersByIdentifier: [String: PumpManagerUI.Type] = staticPumpManagers.reduce(into: [:]) { map, Type in
  24. map[Type.managerIdentifier] = Type
  25. }
  26. final class BaseDeviceDataManager: DeviceDataManager, Injectable {
  27. private let processQueue = DispatchQueue(label: "BaseDeviceDataManager.processQueue")
  28. @Injected() private var pumpHistoryStorage: PumpHistoryStorage!
  29. @Injected() private var storage: FileStorage!
  30. @Injected() private var broadcaster: Broadcaster!
  31. @Persisted(key: "BaseDeviceDataManager.lastEventDate") var lastEventDate: Date? = nil
  32. @Persisted(key: "BaseDeviceDataManager.lastHeartBeatTime") var lastHeartBeatTime: Date = .distantPast
  33. let recommendsLoop = PassthroughSubject<Void, Never>()
  34. var pumpManager: PumpManagerUI? {
  35. didSet {
  36. pumpManager?.pumpManagerDelegate = self
  37. pumpManager?.delegateQueue = processQueue
  38. UserDefaults.standard.pumpManagerRawValue = pumpManager?.rawValue
  39. if let pumpManager = pumpManager {
  40. pumpDisplayState.value = PumpDisplayState(name: pumpManager.localizedTitle, image: pumpManager.smallImage)
  41. pumpName.send(pumpManager.localizedTitle)
  42. if let omnipod = pumpManager as? OmnipodPumpManager {
  43. guard let endTime = omnipod.state.podState?.expiresAt else {
  44. pumpExpiresAtDate.send(nil)
  45. return
  46. }
  47. pumpExpiresAtDate.send(endTime)
  48. }
  49. } else {
  50. pumpDisplayState.value = nil
  51. }
  52. }
  53. }
  54. let pumpDisplayState = CurrentValueSubject<PumpDisplayState?, Never>(nil)
  55. let pumpExpiresAtDate = CurrentValueSubject<Date?, Never>(nil)
  56. let pumpName = CurrentValueSubject<String, Never>("Pump")
  57. var heartBeat: Timer!
  58. init(resolver: Resolver) {
  59. injectServices(resolver)
  60. setupPumpManager()
  61. UIDevice.current.isBatteryMonitoringEnabled = true
  62. if pumpManager is MockPumpManager {
  63. heartBeat = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) { _ in
  64. debug(.deviceManager, "Timer Heartbeat")
  65. self.updatePumpData()
  66. }
  67. }
  68. updatePumpData()
  69. }
  70. func setupPumpManager() {
  71. if let pumpManagerRawValue = UserDefaults.standard.pumpManagerRawValue {
  72. pumpManager = pumpManagerFromRawValue(pumpManagerRawValue)
  73. }
  74. }
  75. private func updatePumpData() {
  76. let now = Date()
  77. guard now.timeIntervalSince(lastHeartBeatTime) >= Config.loopInterval else { return }
  78. pumpManager?.ensureCurrentPumpData {
  79. debug(.deviceManager, "Pump Data updated")
  80. self.lastHeartBeatTime = now
  81. }
  82. }
  83. private func pumpManagerFromRawValue(_ rawValue: [String: Any]) -> PumpManagerUI? {
  84. guard let rawState = rawValue["state"] as? PumpManager.RawStateValue,
  85. let Manager = pumpManagerTypeFromRawValue(rawValue)
  86. else {
  87. return nil
  88. }
  89. return Manager.init(rawState: rawState) as? PumpManagerUI
  90. }
  91. private func pumpManagerTypeFromRawValue(_ rawValue: [String: Any]) -> PumpManager.Type? {
  92. guard let managerIdentifier = rawValue["managerIdentifier"] as? String else {
  93. return nil
  94. }
  95. return staticPumpManagersByIdentifier[managerIdentifier]
  96. }
  97. }
  98. extension BaseDeviceDataManager: PumpManagerDelegate {
  99. func pumpManager(_: PumpManager, didAdjustPumpClockBy adjustment: TimeInterval) {
  100. debug(.deviceManager, "didAdjustPumpClockBy \(adjustment)")
  101. }
  102. func pumpManagerDidUpdateState(_ pumpManager: PumpManager) {
  103. UserDefaults.standard.pumpManagerRawValue = pumpManager.rawValue
  104. pumpName.send(pumpManager.localizedTitle)
  105. }
  106. func pumpManagerBLEHeartbeatDidFire(_: PumpManager) {
  107. debug(.deviceManager, "Pump Heartbeat")
  108. updatePumpData()
  109. }
  110. func pumpManagerMustProvideBLEHeartbeat(_: PumpManager) -> Bool {
  111. true
  112. }
  113. func pumpManager(_: PumpManager, didUpdate status: PumpManagerStatus, oldStatus _: PumpManagerStatus) {
  114. debug(.deviceManager, "New pump status Bolus: \(status.bolusState)")
  115. debug(.deviceManager, "New pump status Basal: \(String(describing: status.basalDeliveryState))")
  116. let batteryPercent = Int((status.pumpBatteryChargeRemaining ?? 1) * 100)
  117. let battery = Battery(
  118. percent: batteryPercent,
  119. voltage: nil,
  120. string: batteryPercent >= 10 ? .normal : .low,
  121. display: pumpManager?.status.pumpBatteryChargeRemaining != nil
  122. )
  123. storage.save(battery, as: OpenAPS.Monitor.battery)
  124. broadcaster.notify(PumpBatteryObserver.self, on: processQueue) {
  125. $0.pumpBatteryDidChange(battery)
  126. }
  127. if let omnipod = pumpManager as? OmnipodPumpManager {
  128. guard let endTime = omnipod.state.podState?.expiresAt else {
  129. pumpExpiresAtDate.send(nil)
  130. return
  131. }
  132. pumpExpiresAtDate.send(endTime)
  133. }
  134. }
  135. func pumpManagerWillDeactivate(_: PumpManager) {
  136. pumpManager = nil
  137. }
  138. func pumpManager(_: PumpManager, didUpdatePumpRecordsBasalProfileStartEvents _: Bool) {}
  139. func pumpManager(_: PumpManager, didError error: PumpManagerError) {
  140. info(.deviceManager, "error: \(error.localizedDescription)")
  141. }
  142. func pumpManager(
  143. _: PumpManager,
  144. hasNewPumpEvents events: [NewPumpEvent],
  145. lastReconciliation _: Date?,
  146. completion: @escaping (_ error: Error?) -> Void
  147. ) {
  148. dispatchPrecondition(condition: .onQueue(processQueue))
  149. pumpHistoryStorage.storePumpEvents(events)
  150. lastEventDate = events.last?.date
  151. completion(nil)
  152. }
  153. func pumpManager(
  154. _: PumpManager,
  155. didReadReservoirValue units: Double,
  156. at date: Date,
  157. completion: @escaping (Result<
  158. (newValue: ReservoirValue, lastValue: ReservoirValue?, areStoredValuesContinuous: Bool),
  159. Error
  160. >) -> Void
  161. ) {
  162. dispatchPrecondition(condition: .onQueue(processQueue))
  163. debug(.deviceManager, "Reservoir Value \(units), at: \(date)")
  164. storage.save(Decimal(units), as: OpenAPS.Monitor.reservoir)
  165. broadcaster.notify(PumpReservoirObserver.self, on: processQueue) {
  166. $0.pumpReservoirDidChange(Decimal(units))
  167. }
  168. completion(.success((
  169. newValue: Reservoir(startDate: Date(), unitVolume: units),
  170. lastValue: nil,
  171. areStoredValuesContinuous: true
  172. )))
  173. }
  174. func pumpManagerRecommendsLoop(_: PumpManager) {
  175. dispatchPrecondition(condition: .onQueue(processQueue))
  176. debug(.deviceManager, "Recomends loop")
  177. recommendsLoop.send()
  178. }
  179. func startDateToFilterNewPumpEvents(for _: PumpManager) -> Date {
  180. lastEventDate ?? Date().addingTimeInterval(-2.hours.timeInterval)
  181. }
  182. }
  183. // MARK: - DeviceManagerDelegate
  184. extension BaseDeviceDataManager: DeviceManagerDelegate {
  185. func scheduleNotification(
  186. for _: DeviceManager,
  187. identifier: String,
  188. content: UNNotificationContent,
  189. trigger: UNNotificationTrigger?
  190. ) {
  191. let request = UNNotificationRequest(
  192. identifier: identifier,
  193. content: content,
  194. trigger: trigger
  195. )
  196. DispatchQueue.main.async {
  197. UNUserNotificationCenter.current().add(request)
  198. }
  199. }
  200. func clearNotification(for _: DeviceManager, identifier: String) {
  201. DispatchQueue.main.async {
  202. UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [identifier])
  203. }
  204. }
  205. func removeNotificationRequests(for _: DeviceManager, identifiers: [String]) {
  206. DispatchQueue.main.async {
  207. UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: identifiers)
  208. }
  209. }
  210. func deviceManager(
  211. _: DeviceManager,
  212. logEventForDeviceIdentifier _: String?,
  213. type _: DeviceLogEntryType,
  214. message _: String,
  215. completion _: ((Error?) -> Void)?
  216. ) {}
  217. }
  218. // MARK: - AlertPresenter
  219. extension BaseDeviceDataManager: AlertPresenter {
  220. func issueAlert(_: Alert) {}
  221. func retractAlert(identifier _: Alert.Identifier) {}
  222. }
  223. // MARK: Others
  224. protocol PumpReservoirObserver {
  225. func pumpReservoirDidChange(_ reservoir: Decimal)
  226. }
  227. protocol PumpBatteryObserver {
  228. func pumpBatteryDidChange(_ battery: Battery)
  229. }