FetchGlucoseManager.swift 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. import Combine
  2. import Foundation
  3. import SwiftDate
  4. import Swinject
  5. import UIKit
  6. protocol FetchGlucoseManager: SourceInfoProvider {
  7. func updateGlucoseStore(newBloodGlucose: [BloodGlucose])
  8. func refreshCGM()
  9. func updateGlucoseSource()
  10. var glucoseSource: GlucoseSource! { get }
  11. var cgmGlucoseSourceType: CGMType? { get set }
  12. }
  13. final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
  14. private let processQueue = DispatchQueue(label: "BaseGlucoseManager.processQueue")
  15. @Injected() var glucoseStorage: GlucoseStorage!
  16. @Injected() var nightscoutManager: NightscoutManager!
  17. @Injected() var apsManager: APSManager!
  18. @Injected() var settingsManager: SettingsManager!
  19. @Injected() var libreTransmitter: LibreTransmitterSource!
  20. @Injected() var healthKitManager: HealthKitManager!
  21. @Injected() var deviceDataManager: DeviceDataManager!
  22. private var lifetime = Lifetime()
  23. private let timer = DispatchTimer(timeInterval: 1.minutes.timeInterval)
  24. var cgmGlucoseSourceType: CGMType?
  25. private lazy var dexcomSourceG5 = DexcomSourceG5(glucoseStorage: glucoseStorage, glucoseManager: self)
  26. private lazy var dexcomSourceG6 = DexcomSourceG6(glucoseStorage: glucoseStorage, glucoseManager: self)
  27. private lazy var dexcomSourceG7 = DexcomSourceG7(glucoseStorage: glucoseStorage, glucoseManager: self)
  28. private lazy var simulatorSource = GlucoseSimulatorSource()
  29. init(resolver: Resolver) {
  30. injectServices(resolver)
  31. updateGlucoseSource()
  32. subscribe()
  33. }
  34. var glucoseSource: GlucoseSource!
  35. func updateGlucoseSource() {
  36. switch settingsManager.settings.cgm {
  37. case .xdrip:
  38. glucoseSource = AppGroupSource(from: "xDrip", cgmType: .xdrip)
  39. case .dexcomG5:
  40. glucoseSource = dexcomSourceG5
  41. case .dexcomG6:
  42. glucoseSource = dexcomSourceG6
  43. case .dexcomG7:
  44. glucoseSource = dexcomSourceG7
  45. case .nightscout:
  46. glucoseSource = nightscoutManager
  47. case .simulator:
  48. glucoseSource = simulatorSource
  49. case .libreTransmitter:
  50. glucoseSource = libreTransmitter
  51. case .glucoseDirect:
  52. glucoseSource = AppGroupSource(from: "GlucoseDirect", cgmType: .glucoseDirect)
  53. case .enlite:
  54. glucoseSource = deviceDataManager
  55. }
  56. // update the config
  57. cgmGlucoseSourceType = settingsManager.settings.cgm
  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 - no more used
  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. // start background time extension
  92. var backGroundFetchBGTaskID: UIBackgroundTaskIdentifier?
  93. backGroundFetchBGTaskID = UIApplication.shared.beginBackgroundTask(withName: "save BG starting") {
  94. guard let bg = backGroundFetchBGTaskID else { return }
  95. UIApplication.shared.endBackgroundTask(bg)
  96. backGroundFetchBGTaskID = .invalid
  97. }
  98. guard allGlucose.isNotEmpty else {
  99. if let backgroundTask = backGroundFetchBGTaskID {
  100. UIApplication.shared.endBackgroundTask(backgroundTask)
  101. backGroundFetchBGTaskID = .invalid
  102. }
  103. return
  104. }
  105. filteredByDate = allGlucose.filter { $0.dateString > syncDate }
  106. filtered = glucoseStorage.filterTooFrequentGlucose(filteredByDate, at: syncDate)
  107. guard filtered.isNotEmpty else {
  108. // end of the BG tasks
  109. if let backgroundTask = backGroundFetchBGTaskID {
  110. UIApplication.shared.endBackgroundTask(backgroundTask)
  111. backGroundFetchBGTaskID = .invalid
  112. }
  113. return
  114. }
  115. debug(.deviceManager, "New glucose found")
  116. glucoseStorage.storeGlucose(filtered)
  117. deviceDataManager.heartbeat(date: Date())
  118. nightscoutManager.uploadGlucose()
  119. let glucoseForHealth = filteredByDate.filter { !glucoseFromHealth.contains($0) }
  120. guard glucoseForHealth.isNotEmpty else {
  121. // end of the BG tasks
  122. if let backgroundTask = backGroundFetchBGTaskID {
  123. UIApplication.shared.endBackgroundTask(backgroundTask)
  124. backGroundFetchBGTaskID = .invalid
  125. }
  126. return
  127. }
  128. healthKitManager.saveIfNeeded(bloodGlucose: glucoseForHealth)
  129. // end of the BG tasks
  130. if let backgroundTask = backGroundFetchBGTaskID {
  131. UIApplication.shared.endBackgroundTask(backgroundTask)
  132. backGroundFetchBGTaskID = .invalid
  133. }
  134. }
  135. /// The function used to start the timer sync - Function of the variable defined in config
  136. private func subscribe() {
  137. timer.publisher
  138. .receive(on: processQueue)
  139. .flatMap { _ -> AnyPublisher<[BloodGlucose], Never> in
  140. debug(.nightscout, "FetchGlucoseManager timer heartbeat")
  141. self.updateGlucoseSource()
  142. return self.glucoseSource.fetch(self.timer).eraseToAnyPublisher()
  143. }
  144. .sink { glucose in
  145. debug(.nightscout, "FetchGlucoseManager callback sensor")
  146. Publishers.CombineLatest3(
  147. Just(glucose),
  148. Just(self.glucoseStorage.syncDate()),
  149. self.healthKitManager.fetch(nil)
  150. )
  151. .eraseToAnyPublisher()
  152. .sink { newGlucose, syncDate, glucoseFromHealth in
  153. self.glucoseStoreAndHeartDecision(
  154. syncDate: syncDate,
  155. glucose: newGlucose,
  156. glucoseFromHealth: glucoseFromHealth
  157. )
  158. }
  159. .store(in: &self.lifetime)
  160. }
  161. .store(in: &lifetime)
  162. timer.fire()
  163. timer.resume()
  164. UserDefaults.standard
  165. .publisher(for: \.dexcomTransmitterID)
  166. .removeDuplicates()
  167. .sink { id in
  168. if self.settingsManager.settings.cgm == .dexcomG5 {
  169. if id != self.dexcomSourceG5.transmitterID {
  170. self.dexcomSourceG5 = DexcomSourceG5(glucoseStorage: self.glucoseStorage, glucoseManager: self)
  171. }
  172. } else if self.settingsManager.settings.cgm == .dexcomG6 {
  173. if id != self.dexcomSourceG6.transmitterID {
  174. self.dexcomSourceG6 = DexcomSourceG6(glucoseStorage: self.glucoseStorage, glucoseManager: self)
  175. }
  176. }
  177. }
  178. .store(in: &lifetime)
  179. }
  180. func sourceInfo() -> [String: Any]? {
  181. glucoseSource.sourceInfo()
  182. }
  183. }
  184. extension UserDefaults {
  185. @objc var dexcomTransmitterID: String? {
  186. get {
  187. string(forKey: "DexcomSource.transmitterID")?.nonEmpty
  188. }
  189. set {
  190. set(newValue, forKey: "DexcomSource.transmitterID")
  191. }
  192. }
  193. }