dexcomSourceG7.swift 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  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: CGMManagerUI?
  11. var cgmType: CGMType = .dexcomG7
  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 = G7CGMManager()
  18. cgmManager?.cgmManagerDelegate = self
  19. cgmManager?.delegateQueue = processQueue
  20. }
  21. func fetch(_: DispatchTimer?) -> AnyPublisher<[BloodGlucose], Never> {
  22. Future<[BloodGlucose], Error> { [weak self] promise in
  23. self?.promise = promise
  24. }
  25. .timeout(60 * 5, scheduler: processQueue, options: nil, customError: nil)
  26. .replaceError(with: [])
  27. .replaceEmpty(with: [])
  28. .eraseToAnyPublisher()
  29. }
  30. func fetchIfNeeded() -> AnyPublisher<[BloodGlucose], Never> {
  31. Future<[BloodGlucose], Error> { _ in
  32. self.processQueue.async {
  33. guard let cgmManager = self.cgmManager else { return }
  34. cgmManager.fetchNewDataIfNeeded { result in
  35. self.processCGMReadingResult(cgmManager, readingResult: result) {
  36. // nothing to do
  37. }
  38. }
  39. }
  40. }
  41. .timeout(60, scheduler: processQueue, options: nil, customError: nil)
  42. .replaceError(with: [])
  43. .replaceEmpty(with: [])
  44. .eraseToAnyPublisher()
  45. }
  46. deinit {
  47. // dexcomManager.transmitter.stopScanning()
  48. }
  49. }
  50. extension DexcomSourceG7: CGMManagerDelegate {
  51. func deviceManager(
  52. _: LoopKit.DeviceManager,
  53. logEventForDeviceIdentifier deviceIdentifier: String?,
  54. type _: LoopKit.DeviceLogEntryType,
  55. message: String,
  56. completion _: ((Error?) -> Void)?
  57. ) {
  58. debug(.deviceManager, "device Manager for \(String(describing: deviceIdentifier)) : \(message)")
  59. }
  60. func issueAlert(_: LoopKit.Alert) {}
  61. func retractAlert(identifier _: LoopKit.Alert.Identifier) {}
  62. func doesIssuedAlertExist(identifier _: LoopKit.Alert.Identifier, completion _: @escaping (Result<Bool, Error>) -> Void) {}
  63. func lookupAllUnretracted(
  64. managerIdentifier _: String,
  65. completion _: @escaping (Result<[LoopKit.PersistedAlert], Error>) -> Void
  66. ) {}
  67. func lookupAllUnacknowledgedUnretracted(
  68. managerIdentifier _: String,
  69. completion _: @escaping (Result<[LoopKit.PersistedAlert], Error>) -> Void
  70. ) {}
  71. func recordRetractedAlert(_: LoopKit.Alert, at _: Date) {}
  72. func cgmManagerWantsDeletion(_ manager: CGMManager) {
  73. dispatchPrecondition(condition: .onQueue(processQueue))
  74. debug(.deviceManager, " CGM Manager with identifier \(manager.managerIdentifier) wants deletion")
  75. glucoseManager?.cgmGlucoseSourceType = nil
  76. }
  77. func cgmManager(_ manager: CGMManager, hasNew readingResult: CGMReadingResult) {
  78. dispatchPrecondition(condition: .onQueue(processQueue))
  79. processCGMReadingResult(manager, readingResult: readingResult) {
  80. debug(.deviceManager, "DEXCOM - Direct return done")
  81. }
  82. }
  83. func startDateToFilterNewData(for _: CGMManager) -> Date? {
  84. dispatchPrecondition(condition: .onQueue(processQueue))
  85. return glucoseStorage.lastGlucoseDate()
  86. }
  87. func cgmManagerDidUpdateState(_: CGMManager) {}
  88. func credentialStoragePrefix(for _: CGMManager) -> String {
  89. // return string unique to this instance of the CGMManager
  90. UUID().uuidString
  91. }
  92. func cgmManager(_: CGMManager, didUpdate status: CGMManagerStatus) {
  93. processQueue.async {
  94. if self.cgmHasValidSensorSession != status.hasValidSensorSession {
  95. self.cgmHasValidSensorSession = status.hasValidSensorSession
  96. }
  97. }
  98. }
  99. private func processCGMReadingResult(
  100. _: CGMManager,
  101. readingResult: CGMReadingResult,
  102. completion: @escaping () -> Void
  103. ) {
  104. debug(.deviceManager, "DEXCOMG7 - Process CGM Reading Result launched")
  105. switch readingResult {
  106. case let .newData(values):
  107. let bloodGlucose = values.compactMap { newGlucoseSample -> BloodGlucose? in
  108. let quantity = newGlucoseSample.quantity
  109. let value = Int(quantity.doubleValue(for: .milligramsPerDeciliter))
  110. return BloodGlucose(
  111. _id: newGlucoseSample.syncIdentifier,
  112. sgv: value,
  113. direction: .init(trendType: newGlucoseSample.trend),
  114. date: Decimal(Int(newGlucoseSample.date.timeIntervalSince1970 * 1000)),
  115. dateString: newGlucoseSample.date,
  116. unfiltered: nil,
  117. filtered: nil,
  118. noise: nil,
  119. glucose: value,
  120. type: "sgv"
  121. )
  122. }
  123. promise?(.success(bloodGlucose))
  124. completion()
  125. case .unreliableData:
  126. // loopManager.receivedUnreliableCGMReading()
  127. promise?(.failure(GlucoseDataError.unreliableData))
  128. completion()
  129. case .noData:
  130. promise?(.failure(GlucoseDataError.noData))
  131. completion()
  132. case let .error(error):
  133. promise?(.failure(error))
  134. completion()
  135. }
  136. }
  137. }