DexcomSource.swift 5.0 KB

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