DexcomSourceG5.swift 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  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: G5CGMManager?
  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 = G5CGMManager
  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. // dexcomManager.transmitter.resumeScanning()
  26. Just([]).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, tickBLE: false) {
  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 DexcomSourceG5: CGMManagerDelegate {
  49. func deviceManager(
  50. _: LoopKit.DeviceManager,
  51. logEventForDeviceIdentifier _: String?,
  52. type _: LoopKit.DeviceLogEntryType,
  53. message _: String,
  54. completion _: ((Error?) -> Void)?
  55. ) {}
  56. func issueAlert(_: LoopKit.Alert) {}
  57. func retractAlert(identifier _: LoopKit.Alert.Identifier) {}
  58. func doesIssuedAlertExist(identifier _: LoopKit.Alert.Identifier, completion _: @escaping (Result<Bool, Error>) -> Void) {}
  59. func lookupAllUnretracted(
  60. managerIdentifier _: String,
  61. completion _: @escaping (Result<[LoopKit.PersistedAlert], Error>) -> Void
  62. ) {}
  63. func lookupAllUnacknowledgedUnretracted(
  64. managerIdentifier _: String,
  65. completion _: @escaping (Result<[LoopKit.PersistedAlert], Error>) -> Void
  66. ) {}
  67. func recordRetractedAlert(_: LoopKit.Alert, at _: Date) {}
  68. func cgmManagerWantsDeletion(_: CGMManager) {}
  69. func cgmManager(_ manager: CGMManager, hasNew readingResult: CGMReadingResult) {
  70. dispatchPrecondition(condition: .onQueue(.main))
  71. processCGMReadingResult(manager, readingResult: readingResult, tickBLE: true) {
  72. debug(.deviceManager, "DEXCOM - Direct return")
  73. }
  74. }
  75. func startDateToFilterNewData(for _: CGMManager) -> Date? {
  76. dispatchPrecondition(condition: .onQueue(.main))
  77. return glucoseStorage.lastGlucoseDate()
  78. // return glucoseStore.latestGlucose?.startDate
  79. }
  80. func cgmManagerDidUpdateState(_: CGMManager) {}
  81. func credentialStoragePrefix(for _: CGMManager) -> String {
  82. // return string unique to this instance of the CGMManager
  83. UUID().uuidString
  84. }
  85. func cgmManager(_: CGMManager, didUpdate status: CGMManagerStatus) {
  86. DispatchQueue.main.async {
  87. if self.cgmHasValidSensorSession != status.hasValidSensorSession {
  88. self.cgmHasValidSensorSession = status.hasValidSensorSession
  89. }
  90. }
  91. }
  92. private func processCGMReadingResult(
  93. _: CGMManager,
  94. readingResult: CGMReadingResult,
  95. tickBLE: Bool,
  96. completion: @escaping () -> Void
  97. ) {
  98. debug(.deviceManager, "DEXCOM - Process CGM Reading Result launched")
  99. switch readingResult {
  100. case let .newData(values):
  101. let bloodGlucose = values.compactMap { newGlucoseSample -> BloodGlucose? in
  102. let quantity = newGlucoseSample.quantity
  103. let value = Int(quantity.doubleValue(for: .milligramsPerDeciliter))
  104. return BloodGlucose(
  105. _id: newGlucoseSample.syncIdentifier,
  106. sgv: value,
  107. direction: .init(trendType: newGlucoseSample.trend),
  108. date: Decimal(Int(newGlucoseSample.date.timeIntervalSince1970 * 1000)),
  109. dateString: newGlucoseSample.date,
  110. unfiltered: nil,
  111. filtered: nil,
  112. noise: nil,
  113. glucose: value,
  114. type: "sgv",
  115. transmitterID: self.transmitterID
  116. )
  117. }
  118. if tickBLE {
  119. glucoseManager?.updateGlucoseStore(newBloodGlucose: bloodGlucose)
  120. } else {
  121. promise?(.success(bloodGlucose))
  122. }
  123. completion()
  124. case .unreliableData:
  125. // loopManager.receivedUnreliableCGMReading()
  126. promise?(.failure(GlucoseDataError.unreliableData))
  127. completion()
  128. case .noData:
  129. promise?(.failure(GlucoseDataError.noData))
  130. completion()
  131. case let .error(error):
  132. promise?(.failure(error))
  133. completion()
  134. }
  135. }
  136. }
  137. extension DexcomSourceG5 {
  138. func sourceInfo() -> [String: Any]? {
  139. [GlucoseSourceKey.description.rawValue: "Dexcom tramsmitter ID: \(transmitterID)"]
  140. }
  141. }