PluginSource.swift 7.3 KB

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