DexcomSourceG6.swift 5.6 KB

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