dexcomSourceG7.swift 6.5 KB

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