DexcomSourceG5.swift 6.2 KB

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