FetchGlucoseManager.swift 7.5 KB

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