FetchGlucoseManager.swift 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import Combine
  2. import Foundation
  3. import SwiftDate
  4. import Swinject
  5. protocol FetchGlucoseManager: SourceInfoProvider {
  6. func updateGlucoseStore(newBloodGlucose: [BloodGlucose])
  7. func refreshCGM()
  8. }
  9. final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
  10. private let processQueue = DispatchQueue(label: "BaseGlucoseManager.processQueue")
  11. @Injected() var glucoseStorage: GlucoseStorage!
  12. @Injected() var nightscoutManager: NightscoutManager!
  13. @Injected() var apsManager: APSManager!
  14. @Injected() var settingsManager: SettingsManager!
  15. @Injected() var libreTransmitter: LibreTransmitterSource!
  16. @Injected() var healthKitManager: HealthKitManager!
  17. @Injected() var deviceDataManager: DeviceDataManager!
  18. private var lifetime = Lifetime()
  19. private let timer = DispatchTimer(timeInterval: 1.minutes.timeInterval)
  20. private lazy var dexcomSourceG5 = DexcomSourceG5(glucoseStorage: glucoseStorage, glucoseManager: self)
  21. private lazy var dexcomSourceG6 = DexcomSourceG6(glucoseStorage: glucoseStorage, glucoseManager: self)
  22. private lazy var dexcomSourceG7 = DexcomSourceG7(glucoseStorage: glucoseStorage, glucoseManager: self)
  23. private lazy var simulatorSource = GlucoseSimulatorSource()
  24. init(resolver: Resolver) {
  25. injectServices(resolver)
  26. updateGlucoseSource()
  27. subscribe()
  28. /// listen if require CGM update
  29. deviceDataManager.requireCGMRefresh
  30. .receive(on: processQueue)
  31. .sink { _ in
  32. self.refreshCGM()
  33. }
  34. .store(in: &lifetime)
  35. }
  36. var glucoseSource: GlucoseSource!
  37. private func updateGlucoseSource() {
  38. switch settingsManager.settings.cgm {
  39. case .xdrip:
  40. glucoseSource = AppGroupSource(from: "xDrip")
  41. case .dexcomG5:
  42. glucoseSource = dexcomSourceG5
  43. case .dexcomG6:
  44. glucoseSource = dexcomSourceG6
  45. case .dexcomG7:
  46. glucoseSource = dexcomSourceG7
  47. case .nightscout:
  48. glucoseSource = nightscoutManager
  49. case .simulator:
  50. glucoseSource = simulatorSource
  51. case .libreTransmitter:
  52. glucoseSource = libreTransmitter
  53. case .glucoseDirect:
  54. glucoseSource = AppGroupSource(from: "GlucoseDirect")
  55. case .enlite:
  56. glucoseSource = deviceDataManager
  57. }
  58. if settingsManager.settings.cgm != .libreTransmitter {
  59. libreTransmitter.manager = nil
  60. } else {
  61. libreTransmitter.glucoseManager = self
  62. }
  63. }
  64. /// function called when a callback is fired by CGM BLE
  65. public func updateGlucoseStore(newBloodGlucose: [BloodGlucose]) {
  66. let syncDate = glucoseStorage.syncDate()
  67. debug(.deviceManager, "CGM BLE FETCHGLUCOSE : SyncDate is \(syncDate)")
  68. glucoseStoreAndHeartDecision(syncDate: syncDate, glucose: newBloodGlucose)
  69. }
  70. /// function to try to force the refresh of the CGM - generally provide by the pump heartbeat
  71. public func refreshCGM() {
  72. debug(.deviceManager, "refreshCGM by pump")
  73. updateGlucoseSource()
  74. Publishers.CombineLatest3(
  75. Just(glucoseStorage.syncDate()),
  76. healthKitManager.fetch(nil),
  77. glucoseSource.fetchIfNeeded()
  78. )
  79. .eraseToAnyPublisher()
  80. .receive(on: processQueue)
  81. .sink { syncDate, glucoseFromHealth, glucose in
  82. debug(.nightscout, "refreshCGM FETCHGLUCOSE : SyncDate is \(syncDate)")
  83. self.glucoseStoreAndHeartDecision(syncDate: syncDate, glucose: glucose, glucoseFromHealth: glucoseFromHealth)
  84. }
  85. .store(in: &lifetime)
  86. }
  87. private func glucoseStoreAndHeartDecision(syncDate: Date, glucose: [BloodGlucose], glucoseFromHealth: [BloodGlucose] = []) {
  88. let allGlucose = glucose + glucoseFromHealth
  89. var filteredByDate: [BloodGlucose] = []
  90. var filtered: [BloodGlucose] = []
  91. if allGlucose.isNotEmpty {
  92. filteredByDate = allGlucose.filter { $0.dateString > syncDate }
  93. filtered = glucoseStorage.filterTooFrequentGlucose(filteredByDate, at: syncDate)
  94. if filtered.isNotEmpty {
  95. debug(.nightscout, "New glucose found")
  96. glucoseStorage.storeGlucose(filtered)
  97. }
  98. }
  99. if filtered.isEmpty {
  100. let lastGlucoseDate = glucoseStorage.lastGlucoseDate()
  101. guard lastGlucoseDate >= Date().addingTimeInterval(Config.eхpirationInterval) else {
  102. debug(.nightscout, "Glucose is too old - \(lastGlucoseDate)")
  103. return
  104. }
  105. }
  106. apsManager.heartbeat(date: Date())
  107. // no need to go next step if no new data
  108. guard filteredByDate.isNotEmpty else {
  109. return
  110. }
  111. nightscoutManager.uploadGlucose()
  112. let glucoseForHealth = filteredByDate.filter { !glucoseFromHealth.contains($0) }
  113. guard glucoseForHealth.isNotEmpty else { return }
  114. healthKitManager.saveIfNeeded(bloodGlucose: glucoseForHealth)
  115. }
  116. /// The function used to start the timer sync - Function of the variable defined in config
  117. private func subscribe() {
  118. timer.publisher
  119. .receive(on: processQueue)
  120. .flatMap { date -> AnyPublisher<(Date, Date, [BloodGlucose], [BloodGlucose]), Never> in
  121. debug(.nightscout, "FetchGlucoseManager heartbeat")
  122. self.updateGlucoseSource()
  123. return Publishers.CombineLatest4(
  124. Just(date),
  125. Just(self.glucoseStorage.syncDate()),
  126. self.glucoseSource.fetch(self.timer),
  127. self.healthKitManager.fetch(nil)
  128. )
  129. .eraseToAnyPublisher()
  130. }
  131. .sink { _, syncDate, glucose, glucoseFromHealth in
  132. debug(.nightscout, "FETCHGLUCOSE : SyncDate is \(syncDate)")
  133. self.glucoseStoreAndHeartDecision(syncDate: syncDate, glucose: glucose, glucoseFromHealth: glucoseFromHealth)
  134. }
  135. .store(in: &lifetime)
  136. timer.fire()
  137. timer.resume()
  138. UserDefaults.standard
  139. .publisher(for: \.dexcomTransmitterID)
  140. .removeDuplicates()
  141. .sink { id in
  142. if self.settingsManager.settings.cgm == .dexcomG5 {
  143. if id != self.dexcomSourceG5.transmitterID {
  144. self.dexcomSourceG5 = DexcomSourceG5(glucoseStorage: self.glucoseStorage, glucoseManager: self)
  145. }
  146. } else if self.settingsManager.settings.cgm == .dexcomG6 {
  147. if id != self.dexcomSourceG6.transmitterID {
  148. self.dexcomSourceG6 = DexcomSourceG6(glucoseStorage: self.glucoseStorage, glucoseManager: self)
  149. }
  150. }
  151. }
  152. .store(in: &lifetime)
  153. }
  154. func sourceInfo() -> [String: Any]? {
  155. glucoseSource.sourceInfo()
  156. }
  157. }
  158. extension UserDefaults {
  159. @objc var dexcomTransmitterID: String? {
  160. get {
  161. string(forKey: "DexcomSource.transmitterID")?.nonEmpty
  162. }
  163. set {
  164. set(newValue, forKey: "DexcomSource.transmitterID")
  165. }
  166. }
  167. }