dexcomSourceG7.swift 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import Combine
  2. import Foundation
  3. import G7SensorKit
  4. import LoopKit
  5. import LoopKitUI
  6. final class DexcomSourceG7: GlucoseSource {
  7. private let processQueue = DispatchQueue(label: "DexcomSource.processQueue")
  8. private var glucoseStorage: GlucoseStorage!
  9. var glucoseManager: FetchGlucoseManager?
  10. var cgmManager: G7CGMManager?
  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 = G7CGMManager()
  17. cgmManager?.cgmManagerDelegate = self
  18. }
  19. func fetch(_: DispatchTimer?) -> AnyPublisher<[BloodGlucose], Never> {
  20. // dexcomManager.transmitter.resumeScanning()
  21. return Future<[BloodGlucose], Error> { [weak self] promise in
  22. self?.promise = promise
  23. }
  24. .timeout(60, 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, tickBLE: false) {
  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 DexcomSourceG7: CGMManagerDelegate {
  50. func deviceManager(
  51. _: LoopKit.DeviceManager,
  52. logEventForDeviceIdentifier _: String?,
  53. type _: LoopKit.DeviceLogEntryType,
  54. message _: String,
  55. completion _: ((Error?) -> Void)?
  56. ) {}
  57. func issueAlert(_: LoopKit.Alert) {}
  58. func retractAlert(identifier _: LoopKit.Alert.Identifier) {}
  59. func doesIssuedAlertExist(identifier _: LoopKit.Alert.Identifier, completion _: @escaping (Result<Bool, Error>) -> Void) {}
  60. func lookupAllUnretracted(
  61. managerIdentifier _: String,
  62. completion _: @escaping (Result<[LoopKit.PersistedAlert], Error>) -> Void
  63. ) {}
  64. func lookupAllUnacknowledgedUnretracted(
  65. managerIdentifier _: String,
  66. completion _: @escaping (Result<[LoopKit.PersistedAlert], Error>) -> Void
  67. ) {}
  68. func recordRetractedAlert(_: LoopKit.Alert, at _: Date) {}
  69. func cgmManagerWantsDeletion(_: CGMManager) {}
  70. func cgmManager(_ manager: CGMManager, hasNew readingResult: CGMReadingResult) {
  71. dispatchPrecondition(condition: .onQueue(.main))
  72. processCGMReadingResult(manager, readingResult: readingResult, tickBLE: true) {
  73. // nothing to do
  74. }
  75. }
  76. func startDateToFilterNewData(for _: CGMManager) -> Date? {
  77. dispatchPrecondition(condition: .onQueue(.main))
  78. return glucoseStorage.lastGlucoseDate()
  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, "DEXCOMG7 - 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. )
  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 DexcomSourceG7 {
  137. // func sourceInfo() -> [String: Any]? {
  138. // [GlucoseSourceKey.description.rawValue: "Dexcom tramsmitter ID: \(transmitterID)"]
  139. // }
  140. // }