FetchGlucoseManager.swift 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  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. /// listen if require CGM update
  34. deviceDataManager.requireCGMRefresh
  35. .receive(on: processQueue)
  36. .sink { _ in
  37. self.refreshCGM()
  38. }
  39. .store(in: &lifetime)
  40. }
  41. var glucoseSource: GlucoseSource!
  42. func updateGlucoseSource() {
  43. switch settingsManager.settings.cgm {
  44. case .xdrip:
  45. glucoseSource = AppGroupSource(from: "xDrip", cgmType: .xdrip)
  46. case .dexcomG5:
  47. glucoseSource = dexcomSourceG5
  48. case .dexcomG6:
  49. glucoseSource = dexcomSourceG6
  50. case .dexcomG7:
  51. glucoseSource = dexcomSourceG7
  52. case .nightscout:
  53. glucoseSource = nightscoutManager
  54. case .simulator:
  55. glucoseSource = simulatorSource
  56. case .libreTransmitter:
  57. glucoseSource = libreTransmitter
  58. case .glucoseDirect:
  59. glucoseSource = AppGroupSource(from: "GlucoseDirect", cgmType: .glucoseDirect)
  60. case .enlite:
  61. glucoseSource = deviceDataManager
  62. }
  63. // update the config
  64. cgmGlucoseSourceType = settingsManager.settings.cgm
  65. if settingsManager.settings.cgm != .libreTransmitter {
  66. libreTransmitter.manager = nil
  67. } else {
  68. libreTransmitter.glucoseManager = self
  69. }
  70. }
  71. /// function called when a callback is fired by CGM BLE - no more used
  72. public func updateGlucoseStore(newBloodGlucose: [BloodGlucose]) {
  73. let syncDate = glucoseStorage.syncDate()
  74. debug(.deviceManager, "CGM BLE FETCHGLUCOSE : SyncDate is \(syncDate)")
  75. glucoseStoreAndHeartDecision(syncDate: syncDate, glucose: newBloodGlucose)
  76. }
  77. /// function to try to force the refresh of the CGM - generally provide by the pump heartbeat
  78. public func refreshCGM() {
  79. debug(.deviceManager, "refreshCGM by pump")
  80. updateGlucoseSource()
  81. Publishers.CombineLatest3(
  82. Just(glucoseStorage.syncDate()),
  83. healthKitManager.fetch(nil),
  84. glucoseSource.fetchIfNeeded()
  85. )
  86. .eraseToAnyPublisher()
  87. .receive(on: processQueue)
  88. .sink { syncDate, glucoseFromHealth, glucose in
  89. debug(.nightscout, "refreshCGM FETCHGLUCOSE : SyncDate is \(syncDate)")
  90. self.glucoseStoreAndHeartDecision(syncDate: syncDate, glucose: glucose, glucoseFromHealth: glucoseFromHealth)
  91. }
  92. .store(in: &lifetime)
  93. }
  94. private func glucoseStoreAndHeartDecision(syncDate: Date, glucose: [BloodGlucose], glucoseFromHealth: [BloodGlucose] = []) {
  95. let allGlucose = glucose + glucoseFromHealth
  96. var filteredByDate: [BloodGlucose] = []
  97. var filtered: [BloodGlucose] = []
  98. // start background time extension
  99. var backGroundFetchBGTaskID: UIBackgroundTaskIdentifier?
  100. backGroundFetchBGTaskID = UIApplication.shared.beginBackgroundTask(withName: "save BG starting") {
  101. guard let bg = backGroundFetchBGTaskID else { return }
  102. UIApplication.shared.endBackgroundTask(bg)
  103. backGroundFetchBGTaskID = .invalid
  104. }
  105. guard allGlucose.isNotEmpty else {
  106. if let backgroundTask = backGroundFetchBGTaskID {
  107. UIApplication.shared.endBackgroundTask(backgroundTask)
  108. backGroundFetchBGTaskID = .invalid
  109. }
  110. return
  111. }
  112. filteredByDate = allGlucose.filter { $0.dateString > syncDate }
  113. filtered = glucoseStorage.filterTooFrequentGlucose(filteredByDate, at: syncDate)
  114. guard filtered.isNotEmpty else {
  115. // end of the BG tasks
  116. if let backgroundTask = backGroundFetchBGTaskID {
  117. UIApplication.shared.endBackgroundTask(backgroundTask)
  118. backGroundFetchBGTaskID = .invalid
  119. }
  120. return
  121. }
  122. debug(.deviceManager, "New glucose found")
  123. glucoseStorage.storeGlucose(filtered)
  124. deviceDataManager.heartbeat(date: Date())
  125. nightscoutManager.uploadGlucose()
  126. let glucoseForHealth = filteredByDate.filter { !glucoseFromHealth.contains($0) }
  127. guard glucoseForHealth.isNotEmpty else {
  128. // end of the BG tasks
  129. if let backgroundTask = backGroundFetchBGTaskID {
  130. UIApplication.shared.endBackgroundTask(backgroundTask)
  131. backGroundFetchBGTaskID = .invalid
  132. }
  133. return
  134. }
  135. healthKitManager.saveIfNeeded(bloodGlucose: glucoseForHealth)
  136. // end of the BG tasks
  137. if let backgroundTask = backGroundFetchBGTaskID {
  138. UIApplication.shared.endBackgroundTask(backgroundTask)
  139. backGroundFetchBGTaskID = .invalid
  140. }
  141. // if filtered.isEmpty {
  142. // let lastGlucoseDate = glucoseStorage.lastGlucoseDate()
  143. // guard lastGlucoseDate >= Date().addingTimeInterval(-Config.eхpirationInterval) else {
  144. // debug(.nightscout, "Glucose is too old - \(lastGlucoseDate)")
  145. // return
  146. // }
  147. // }
  148. }
  149. /// The function used to start the timer sync - Function of the variable defined in config
  150. private func subscribe() {
  151. timer.publisher
  152. .receive(on: processQueue)
  153. .flatMap { _ -> AnyPublisher<[BloodGlucose], Never> in
  154. debug(.nightscout, "FetchGlucoseManager timer heartbeat")
  155. self.updateGlucoseSource()
  156. return self.glucoseSource.fetch(self.timer).eraseToAnyPublisher()
  157. }
  158. .sink { glucose in
  159. debug(.nightscout, "FetchGlucoseManager callback sensor")
  160. Publishers.CombineLatest3(
  161. Just(glucose),
  162. Just(self.glucoseStorage.syncDate()),
  163. self.healthKitManager.fetch(nil)
  164. )
  165. .eraseToAnyPublisher()
  166. .sink { newGlucose, syncDate, glucoseFromHealth in
  167. self.glucoseStoreAndHeartDecision(
  168. syncDate: syncDate,
  169. glucose: newGlucose,
  170. glucoseFromHealth: glucoseFromHealth
  171. )
  172. }
  173. .store(in: &self.lifetime)
  174. }
  175. .store(in: &lifetime)
  176. timer.fire()
  177. timer.resume()
  178. UserDefaults.standard
  179. .publisher(for: \.dexcomTransmitterID)
  180. .removeDuplicates()
  181. .sink { id in
  182. if self.settingsManager.settings.cgm == .dexcomG5 {
  183. if id != self.dexcomSourceG5.transmitterID {
  184. self.dexcomSourceG5 = DexcomSourceG5(glucoseStorage: self.glucoseStorage, glucoseManager: self)
  185. }
  186. } else if self.settingsManager.settings.cgm == .dexcomG6 {
  187. if id != self.dexcomSourceG6.transmitterID {
  188. self.dexcomSourceG6 = DexcomSourceG6(glucoseStorage: self.glucoseStorage, glucoseManager: self)
  189. }
  190. }
  191. }
  192. .store(in: &lifetime)
  193. }
  194. func sourceInfo() -> [String: Any]? {
  195. glucoseSource.sourceInfo()
  196. }
  197. }
  198. extension UserDefaults {
  199. @objc var dexcomTransmitterID: String? {
  200. get {
  201. string(forKey: "DexcomSource.transmitterID")?.nonEmpty
  202. }
  203. set {
  204. set(newValue, forKey: "DexcomSource.transmitterID")
  205. }
  206. }
  207. }