| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321 |
- import Combine
- import Foundation
- import LoopKit
- import LoopKitUI
- import MinimedKit
- import MockKit
- import OmniKit
- import SwiftDate
- import Swinject
- import UserNotifications
- protocol DeviceDataManager {
- var pumpManager: PumpManagerUI? { get set }
- var pumpDisplayState: CurrentValueSubject<PumpDisplayState?, Never> { get }
- var recommendsLoop: PassthroughSubject<Void, Never> { get }
- var pumpName: CurrentValueSubject<String, Never> { get }
- var pumpExpiresAtDate: CurrentValueSubject<Date?, Never> { get }
- func heartbeat(force: Bool)
- }
- private let staticPumpManagers: [PumpManagerUI.Type] = [
- MinimedPumpManager.self,
- OmnipodPumpManager.self,
- MockPumpManager.self
- ]
- private let staticPumpManagersByIdentifier: [String: PumpManagerUI.Type] = staticPumpManagers.reduce(into: [:]) { map, Type in
- map[Type.managerIdentifier] = Type
- }
- private let accessLock = NSRecursiveLock(label: "BaseDeviceDataManager.accessLock")
- final class BaseDeviceDataManager: DeviceDataManager, Injectable {
- private let processQueue = DispatchQueue(label: "BaseDeviceDataManager.processQueue")
- @Injected() private var pumpHistoryStorage: PumpHistoryStorage!
- @Injected() private var storage: FileStorage!
- @Injected() private var broadcaster: Broadcaster!
- @Persisted(key: "BaseDeviceDataManager.lastEventDate") var lastEventDate: Date? = nil
- @SyncAccess(lock: accessLock) @Persisted(key: "BaseDeviceDataManager.lastHeartBeatTime") var lastHeartBeatTime: Date =
- .distantPast
- let recommendsLoop = PassthroughSubject<Void, Never>()
- var pumpManager: PumpManagerUI? {
- didSet {
- pumpManager?.pumpManagerDelegate = self
- pumpManager?.delegateQueue = processQueue
- UserDefaults.standard.pumpManagerRawValue = pumpManager?.rawValue
- if let pumpManager = pumpManager {
- pumpDisplayState.value = PumpDisplayState(name: pumpManager.localizedTitle, image: pumpManager.smallImage)
- pumpName.send(pumpManager.localizedTitle)
- if let omnipod = pumpManager as? OmnipodPumpManager {
- guard let endTime = omnipod.state.podState?.expiresAt else {
- pumpExpiresAtDate.send(nil)
- return
- }
- pumpExpiresAtDate.send(endTime)
- }
- pumpManager.setMustProvideBLEHeartbeat(true)
- } else {
- pumpDisplayState.value = nil
- }
- }
- }
- let pumpDisplayState = CurrentValueSubject<PumpDisplayState?, Never>(nil)
- let pumpExpiresAtDate = CurrentValueSubject<Date?, Never>(nil)
- let pumpName = CurrentValueSubject<String, Never>("Pump")
- init(resolver: Resolver) {
- injectServices(resolver)
- setupPumpManager()
- UIDevice.current.isBatteryMonitoringEnabled = true
- }
- func setupPumpManager() {
- if let pumpManagerRawValue = UserDefaults.standard.pumpManagerRawValue {
- pumpManager = pumpManagerFromRawValue(pumpManagerRawValue)
- }
- }
- @SyncAccess(lock: accessLock) private var pumpUpdateInProgress = false
- func heartbeat(force: Bool) {
- if force {
- lastHeartBeatTime = Date()
- updatePumpData()
- return
- }
- let now = Date()
- var updateInterval: TimeInterval = 5.minutes.timeInterval
- switch lastHeartBeatTime.timeIntervalSince(now) {
- case let interval where interval < -10.minutes.timeInterval:
- break
- case let interval where interval < -5.minutes.timeInterval:
- updateInterval = 1.minutes.timeInterval
- default:
- break
- }
- let interval = now.timeIntervalSince(lastHeartBeatTime)
- guard interval >= updateInterval else {
- debug(.deviceManager, "Last hearbeat \(interval / 60) min ago, skip updating the pump data")
- return
- }
- lastHeartBeatTime = Date()
- updatePumpData()
- }
- private func updatePumpData() {
- guard let pumpManager = pumpManager else {
- debug(.deviceManager, "Pump is not set, skip updating")
- return
- }
- guard !pumpUpdateInProgress else {
- debug(.deviceManager, "Pump update in progress, skip updating")
- return
- }
- debug(.deviceManager, "Start updating the pump data")
- pumpUpdateInProgress = true
- pumpManager.ensureCurrentPumpData {
- debug(.deviceManager, "Pump Data updated")
- self.pumpUpdateInProgress = false
- }
- }
- private func pumpManagerFromRawValue(_ rawValue: [String: Any]) -> PumpManagerUI? {
- guard let rawState = rawValue["state"] as? PumpManager.RawStateValue,
- let Manager = pumpManagerTypeFromRawValue(rawValue)
- else {
- return nil
- }
- return Manager.init(rawState: rawState) as? PumpManagerUI
- }
- private func pumpManagerTypeFromRawValue(_ rawValue: [String: Any]) -> PumpManager.Type? {
- guard let managerIdentifier = rawValue["managerIdentifier"] as? String else {
- return nil
- }
- return staticPumpManagersByIdentifier[managerIdentifier]
- }
- }
- extension BaseDeviceDataManager: PumpManagerDelegate {
- func pumpManager(_: PumpManager, didAdjustPumpClockBy adjustment: TimeInterval) {
- debug(.deviceManager, "didAdjustPumpClockBy \(adjustment)")
- }
- func pumpManagerDidUpdateState(_ pumpManager: PumpManager) {
- UserDefaults.standard.pumpManagerRawValue = pumpManager.rawValue
- if self.pumpManager == nil, let newPumpManager = pumpManager as? PumpManagerUI {
- self.pumpManager = newPumpManager
- }
- pumpName.send(pumpManager.localizedTitle)
- }
- func pumpManagerBLEHeartbeatDidFire(_: PumpManager) {
- debug(.deviceManager, "Pump Heartbeat")
- // heartbeat(force: false)
- }
- func pumpManagerMustProvideBLEHeartbeat(_: PumpManager) -> Bool {
- true
- }
- func pumpManager(_ pumpManager: PumpManager, didUpdate status: PumpManagerStatus, oldStatus _: PumpManagerStatus) {
- debug(.deviceManager, "New pump status Bolus: \(status.bolusState)")
- debug(.deviceManager, "New pump status Basal: \(String(describing: status.basalDeliveryState))")
- let batteryPercent = Int((status.pumpBatteryChargeRemaining ?? 1) * 100)
- let battery = Battery(
- percent: batteryPercent,
- voltage: nil,
- string: batteryPercent >= 10 ? .normal : .low,
- display: pumpManager.status.pumpBatteryChargeRemaining != nil
- )
- storage.save(battery, as: OpenAPS.Monitor.battery)
- broadcaster.notify(PumpBatteryObserver.self, on: processQueue) {
- $0.pumpBatteryDidChange(battery)
- }
- if let omnipod = pumpManager as? OmnipodPumpManager {
- let reservoir = omnipod.state.podState?.lastInsulinMeasurements?.reservoirLevel ?? 0xDEAD_BEEF
- storage.save(Decimal(reservoir), as: OpenAPS.Monitor.reservoir)
- broadcaster.notify(PumpReservoirObserver.self, on: processQueue) {
- $0.pumpReservoirDidChange(Decimal(reservoir))
- }
- guard let endTime = omnipod.state.podState?.expiresAt else {
- pumpExpiresAtDate.send(nil)
- return
- }
- pumpExpiresAtDate.send(endTime)
- }
- }
- func pumpManagerWillDeactivate(_: PumpManager) {
- pumpManager = nil
- }
- func pumpManager(_: PumpManager, didUpdatePumpRecordsBasalProfileStartEvents _: Bool) {}
- func pumpManager(_: PumpManager, didError error: PumpManagerError) {
- info(.deviceManager, "error: \(error.localizedDescription)")
- }
- func pumpManager(
- _: PumpManager,
- hasNewPumpEvents events: [NewPumpEvent],
- lastReconciliation _: Date?,
- completion: @escaping (_ error: Error?) -> Void
- ) {
- dispatchPrecondition(condition: .onQueue(processQueue))
- pumpHistoryStorage.storePumpEvents(events)
- lastEventDate = events.last?.date
- completion(nil)
- }
- func pumpManager(
- _: PumpManager,
- didReadReservoirValue units: Double,
- at date: Date,
- completion: @escaping (Result<
- (newValue: ReservoirValue, lastValue: ReservoirValue?, areStoredValuesContinuous: Bool),
- Error
- >) -> Void
- ) {
- dispatchPrecondition(condition: .onQueue(processQueue))
- debug(.deviceManager, "Reservoir Value \(units), at: \(date)")
- storage.save(Decimal(units), as: OpenAPS.Monitor.reservoir)
- broadcaster.notify(PumpReservoirObserver.self, on: processQueue) {
- $0.pumpReservoirDidChange(Decimal(units))
- }
- completion(.success((
- newValue: Reservoir(startDate: Date(), unitVolume: units),
- lastValue: nil,
- areStoredValuesContinuous: true
- )))
- }
- func pumpManagerRecommendsLoop(_: PumpManager) {
- dispatchPrecondition(condition: .onQueue(processQueue))
- pumpUpdateInProgress = false
- debug(.deviceManager, "Recomends loop")
- recommendsLoop.send()
- }
- func startDateToFilterNewPumpEvents(for _: PumpManager) -> Date {
- lastEventDate ?? Date().addingTimeInterval(-2.hours.timeInterval)
- }
- }
- // MARK: - DeviceManagerDelegate
- extension BaseDeviceDataManager: DeviceManagerDelegate {
- func scheduleNotification(
- for _: DeviceManager,
- identifier: String,
- content: UNNotificationContent,
- trigger: UNNotificationTrigger?
- ) {
- let request = UNNotificationRequest(
- identifier: identifier,
- content: content,
- trigger: trigger
- )
- DispatchQueue.main.async {
- UNUserNotificationCenter.current().add(request)
- }
- }
- func clearNotification(for _: DeviceManager, identifier: String) {
- DispatchQueue.main.async {
- UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [identifier])
- }
- }
- func removeNotificationRequests(for _: DeviceManager, identifiers: [String]) {
- DispatchQueue.main.async {
- UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: identifiers)
- }
- }
- func deviceManager(
- _: DeviceManager,
- logEventForDeviceIdentifier _: String?,
- type _: DeviceLogEntryType,
- message _: String,
- completion _: ((Error?) -> Void)?
- ) {}
- }
- // MARK: - AlertPresenter
- extension BaseDeviceDataManager: AlertPresenter {
- func issueAlert(_: Alert) {}
- func retractAlert(identifier _: Alert.Identifier) {}
- }
- // MARK: Others
- protocol PumpReservoirObserver {
- func pumpReservoirDidChange(_ reservoir: Decimal)
- }
- protocol PumpBatteryObserver {
- func pumpBatteryDidChange(_ battery: Battery)
- }
|