PluginSource.swift 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. import Combine
  2. import Foundation
  3. import LoopKit
  4. import LoopKitUI
  5. final class PluginSource: GlucoseSource {
  6. private let processQueue = DispatchQueue(label: "DexcomSource.processQueue")
  7. private let glucoseStorage: GlucoseStorage!
  8. var glucoseManager: FetchGlucoseManager?
  9. var cgmManager: CGMManagerUI?
  10. var cgmHasValidSensorSession: Bool = false
  11. private var promise: Future<[BloodGlucose], Error>.Promise?
  12. init(glucoseStorage: GlucoseStorage, glucoseManager: FetchGlucoseManager) {
  13. self.glucoseStorage = glucoseStorage
  14. self.glucoseManager = glucoseManager
  15. cgmManager = glucoseManager.cgmManager
  16. cgmManager?.delegateQueue = processQueue
  17. cgmManager?.cgmManagerDelegate = self
  18. }
  19. func fetch(_: DispatchTimer?) -> AnyPublisher<[BloodGlucose], Never> {
  20. Future<[BloodGlucose], Error> { [weak self] promise in
  21. self?.promise = promise
  22. }
  23. .timeout(60 * 5, scheduler: processQueue, options: nil, customError: nil)
  24. .replaceError(with: [])
  25. .replaceEmpty(with: [])
  26. .eraseToAnyPublisher()
  27. }
  28. func fetchIfNeeded() -> AnyPublisher<[BloodGlucose], Never> {
  29. Future<[BloodGlucose], Error> { _ in
  30. self.processQueue.async {
  31. guard let cgmManager = self.cgmManager else { return }
  32. cgmManager.fetchNewDataIfNeeded { result in
  33. self.processCGMReadingResult(cgmManager, readingResult: result) {
  34. // nothing to do
  35. }
  36. }
  37. }
  38. }
  39. .timeout(60, scheduler: processQueue, options: nil, customError: nil)
  40. .replaceError(with: [])
  41. .replaceEmpty(with: [])
  42. .eraseToAnyPublisher()
  43. }
  44. deinit {
  45. // dexcomManager.transmitter.stopScanning()
  46. }
  47. }
  48. extension PluginSource: CGMManagerDelegate {
  49. func deviceManager(
  50. _: LoopKit.DeviceManager,
  51. logEventForDeviceIdentifier deviceIdentifier: String?,
  52. type _: LoopKit.DeviceLogEntryType,
  53. message: String,
  54. completion _: ((Error?) -> Void)?
  55. ) {
  56. debug(.deviceManager, "device Manager for \(String(describing: deviceIdentifier)) : \(message)")
  57. }
  58. func issueAlert(_: LoopKit.Alert) {}
  59. func retractAlert(identifier _: LoopKit.Alert.Identifier) {}
  60. func doesIssuedAlertExist(identifier _: LoopKit.Alert.Identifier, completion _: @escaping (Result<Bool, Error>) -> Void) {}
  61. func lookupAllUnretracted(
  62. managerIdentifier _: String,
  63. completion _: @escaping (Result<[LoopKit.PersistedAlert], Error>) -> Void
  64. ) {}
  65. func lookupAllUnacknowledgedUnretracted(
  66. managerIdentifier _: String,
  67. completion _: @escaping (Result<[LoopKit.PersistedAlert], Error>) -> Void
  68. ) {}
  69. func recordRetractedAlert(_: LoopKit.Alert, at _: Date) {}
  70. func cgmManagerWantsDeletion(_ manager: CGMManager) {
  71. dispatchPrecondition(condition: .onQueue(processQueue))
  72. debug(.deviceManager, " CGM Manager with identifier \(manager.pluginIdentifier) wants deletion")
  73. // TODO:
  74. glucoseManager?.cgmGlucoseSourceType = .none
  75. }
  76. func cgmManager(_ manager: CGMManager, hasNew readingResult: CGMReadingResult) {
  77. dispatchPrecondition(condition: .onQueue(processQueue))
  78. processCGMReadingResult(manager, readingResult: readingResult) {
  79. debug(.deviceManager, "CGM PLUGIN - Direct return done")
  80. }
  81. }
  82. func cgmManager(_: LoopKit.CGMManager, hasNew events: [LoopKit.PersistedCgmEvent]) {
  83. dispatchPrecondition(condition: .onQueue(processQueue))
  84. // TODO: Events in APS ?
  85. // currently only display in log the date of the event
  86. events.forEach { event in
  87. debug(.deviceManager, "events from CGM at \(event.date)")
  88. if event.type == .sensorStart {
  89. self.glucoseManager?.removeCalibrations()
  90. }
  91. }
  92. }
  93. func startDateToFilterNewData(for _: CGMManager) -> Date? {
  94. dispatchPrecondition(condition: .onQueue(processQueue))
  95. return glucoseStorage.lastGlucoseDate()
  96. }
  97. func cgmManagerDidUpdateState(_: CGMManager) {
  98. dispatchPrecondition(condition: .onQueue(processQueue))
  99. // guard let g6Manager = manager as? TransmitterManager else {
  100. // return
  101. // }
  102. // glucoseManager?.settingsManager.settings.uploadGlucose = g6Manager.shouldSyncToRemoteService
  103. // UserDefaults.standard.dexcomTransmitterID = g6Manager.rawState["transmitterID"] as? String
  104. }
  105. func credentialStoragePrefix(for _: CGMManager) -> String {
  106. // return string unique to this instance of the CGMManager
  107. UUID().uuidString
  108. }
  109. func cgmManager(_: CGMManager, didUpdate status: CGMManagerStatus) {
  110. debug(.deviceManager, "DEBUG DID UPDATE STATE")
  111. processQueue.async {
  112. if self.cgmHasValidSensorSession != status.hasValidSensorSession {
  113. self.cgmHasValidSensorSession = status.hasValidSensorSession
  114. }
  115. }
  116. }
  117. private func processCGMReadingResult(
  118. _: CGMManager,
  119. readingResult: CGMReadingResult,
  120. completion: @escaping () -> Void
  121. ) {
  122. debug(.deviceManager, "PLUGIN CGM - Process CGM Reading Result launched with \(readingResult)")
  123. switch readingResult {
  124. case let .newData(values):
  125. let bloodGlucose = values.compactMap { newGlucoseSample -> BloodGlucose? in
  126. let quantity = newGlucoseSample.quantity
  127. let value = Int(quantity.doubleValue(for: .milligramsPerDeciliter))
  128. return BloodGlucose(
  129. _id: UUID().uuidString,
  130. sgv: value,
  131. direction: .init(trendType: newGlucoseSample.trend),
  132. date: Decimal(Int(newGlucoseSample.date.timeIntervalSince1970 * 1000)),
  133. dateString: newGlucoseSample.date,
  134. unfiltered: Decimal(value),
  135. filtered: nil,
  136. noise: nil,
  137. glucose: value,
  138. type: "sgv"
  139. // activationDate: activationDate,
  140. // sessionStartDate: sessionStartDate
  141. // transmitterID: self.transmitterID
  142. )
  143. }
  144. promise?(.success(bloodGlucose))
  145. completion()
  146. case .unreliableData:
  147. // loopManager.receivedUnreliableCGMReading()
  148. promise?(.failure(GlucoseDataError.unreliableData))
  149. completion()
  150. case .noData:
  151. promise?(.failure(GlucoseDataError.noData))
  152. completion()
  153. case let .error(error):
  154. promise?(.failure(error))
  155. completion()
  156. }
  157. }
  158. }
  159. extension PluginSource {
  160. func sourceInfo() -> [String: Any]? {
  161. [GlucoseSourceKey.description.rawValue: "Plugin CGM source"]
  162. }
  163. }