NightscoutManager.swift 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. import Combine
  2. import Foundation
  3. import Swinject
  4. import UIKit
  5. protocol NightscoutManager {
  6. func fetchGlucose() -> AnyPublisher<Void, Never>
  7. func fetchCarbs() -> AnyPublisher<Void, Never>
  8. func fetchTempTargets() -> AnyPublisher<Void, Never>
  9. func fetchAnnouncements() -> AnyPublisher<Void, Never>
  10. func uploadStatus()
  11. }
  12. final class BaseNightscoutManager: NightscoutManager, Injectable {
  13. @Injected() private var keychain: Keychain!
  14. @Injected() private var glucoseStorage: GlucoseStorage!
  15. @Injected() private var tempTargetsStorage: TempTargetsStorage!
  16. @Injected() private var carbsStorage: CarbsStorage!
  17. @Injected() private var pumpHistoryStorage: PumpHistoryStorage!
  18. @Injected() private var storage: FileStorage!
  19. @Injected() private var announcementsStorage: AnnouncementsStorage!
  20. @Injected() private var settingsManager: SettingsManager!
  21. @Injected() private var broadcaster: Broadcaster!
  22. private let processQueue = DispatchQueue(label: "BaseNetworkManager.processQueue")
  23. private var lifetime = Set<AnyCancellable>()
  24. private var nightscoutAPI: NightscoutAPI? {
  25. guard let urlString = keychain.getValue(String.self, forKey: NightscoutConfig.Config.urlKey),
  26. let url = URL(string: urlString),
  27. let secret = keychain.getValue(String.self, forKey: NightscoutConfig.Config.secretKey)
  28. else {
  29. return nil
  30. }
  31. return NightscoutAPI(url: url, secret: secret)
  32. }
  33. init(resolver: Resolver) {
  34. injectServices(resolver)
  35. subscribe()
  36. }
  37. private func subscribe() {
  38. broadcaster.register(PumpHistoryObserver.self, observer: self)
  39. broadcaster.register(CarbsObserver.self, observer: self)
  40. broadcaster.register(TempTargetsObserver.self, observer: self)
  41. }
  42. func fetchGlucose() -> AnyPublisher<Void, Never> {
  43. guard let nightscout = nightscoutAPI else {
  44. return Just(()).eraseToAnyPublisher()
  45. }
  46. let since = glucoseStorage.syncDate()
  47. return nightscout.fetchLastGlucose(288, sinceDate: since)
  48. .replaceError(with: [])
  49. .map {
  50. self.glucoseStorage.storeGlucose($0)
  51. return ()
  52. }
  53. .eraseToAnyPublisher()
  54. }
  55. func fetchCarbs() -> AnyPublisher<Void, Never> {
  56. guard let nightscout = nightscoutAPI else {
  57. return Just(()).eraseToAnyPublisher()
  58. }
  59. let since = carbsStorage.syncDate()
  60. return nightscout.fetchCarbs(sinceDate: since)
  61. .replaceError(with: [])
  62. .map {
  63. self.carbsStorage.storeCarbs($0)
  64. return ()
  65. }.eraseToAnyPublisher()
  66. }
  67. func fetchTempTargets() -> AnyPublisher<Void, Never> {
  68. guard let nightscout = nightscoutAPI else {
  69. return Just(()).eraseToAnyPublisher()
  70. }
  71. let since = tempTargetsStorage.syncDate()
  72. return nightscout.fetchTempTargets(sinceDate: since)
  73. .replaceError(with: [])
  74. .map {
  75. self.tempTargetsStorage.storeTempTargets($0)
  76. return ()
  77. }.eraseToAnyPublisher()
  78. }
  79. func fetchAnnouncements() -> AnyPublisher<Void, Never> {
  80. guard let nightscout = nightscoutAPI else {
  81. return Just(()).eraseToAnyPublisher()
  82. }
  83. let since = announcementsStorage.syncDate()
  84. return nightscout.fetchAnnouncement(sinceDate: since)
  85. .replaceError(with: [])
  86. .map {
  87. self.announcementsStorage.storeAnnouncements($0, enacted: false)
  88. return ()
  89. }.eraseToAnyPublisher()
  90. }
  91. func uploadStatus() {
  92. let iob = (try? storage.retrieve(OpenAPS.Monitor.iob, as: [IOBEntry].self))?.first
  93. var suggested = try? storage.retrieve(OpenAPS.Enact.suggested, as: Suggestion.self)
  94. var enacted = try? storage.retrieve(OpenAPS.Enact.enacted, as: Suggestion.self)
  95. if (suggested?.timestamp ?? .distantPast) > (enacted?.timestamp ?? .distantPast) {
  96. enacted?.predictions = nil
  97. } else {
  98. suggested?.predictions = nil
  99. }
  100. let openapsStatus = OpenAPSStatus(
  101. iob: iob,
  102. suggested: suggested,
  103. enacted: enacted,
  104. version: "0.7.0"
  105. )
  106. let battery = try? storage.retrieve(OpenAPS.Monitor.battery, as: Battery.self)
  107. let reservoir = Decimal(from: storage.retrieveRaw(OpenAPS.Monitor.reservoir) ?? "0")
  108. let pumpStatus = try? storage.retrieve(OpenAPS.Monitor.status, as: PumpStatus.self)
  109. let pump = NSPumpStatus(clock: Date(), battery: battery, reservoir: reservoir, status: pumpStatus)
  110. let preferences = settingsManager.preferences
  111. let device = UIDevice.current
  112. let uploader = Uploader(batteryVoltage: nil, battery: Int(device.batteryLevel * 100))
  113. let status = NightscoutStatus(
  114. device: "freeaps-x://" + device.name,
  115. openaps: openapsStatus,
  116. pump: pump,
  117. preferences: preferences,
  118. uploader: uploader
  119. )
  120. try? storage.save(status, as: OpenAPS.Upload.nsStatus)
  121. guard let nightscout = nightscoutAPI else {
  122. return
  123. }
  124. processQueue.async {
  125. nightscout.uploadStatus(status)
  126. .sink { completion in
  127. switch completion {
  128. case .finished:
  129. debug(.nightscout, "Status uploaded")
  130. case let .failure(error):
  131. debug(.nightscout, error.localizedDescription)
  132. }
  133. } receiveValue: {}
  134. .store(in: &self.lifetime)
  135. }
  136. }
  137. private func uploadPumpHistory() {
  138. uploadTreatments(pumpHistoryStorage.nightscoutTretmentsNotUploaded(), fileToSave: OpenAPS.Nightscout.uploadedPumphistory)
  139. }
  140. private func uploadCarbs() {
  141. uploadTreatments(carbsStorage.nightscoutTretmentsNotUploaded(), fileToSave: OpenAPS.Nightscout.uploadedCarbs)
  142. }
  143. private func uploadTempTargets() {
  144. uploadTreatments(tempTargetsStorage.nightscoutTretmentsNotUploaded(), fileToSave: OpenAPS.Nightscout.uploadedTempTargets)
  145. }
  146. private func uploadTreatments(_ treatments: [NigtscoutTreatment], fileToSave: String) {
  147. guard !treatments.isEmpty, let nightscout = nightscoutAPI else {
  148. return
  149. }
  150. processQueue.async {
  151. nightscout.uploadTreatments(treatments)
  152. .sink { completion in
  153. switch completion {
  154. case .finished:
  155. try? self.storage.save(treatments, as: fileToSave)
  156. case let .failure(error):
  157. debug(.nightscout, error.localizedDescription)
  158. }
  159. } receiveValue: {}
  160. .store(in: &self.lifetime)
  161. }
  162. }
  163. }
  164. extension BaseNightscoutManager: PumpHistoryObserver {
  165. func pumpHistoryDidUpdate(_: [PumpHistoryEvent]) {
  166. uploadPumpHistory()
  167. }
  168. }
  169. extension BaseNightscoutManager: CarbsObserver {
  170. func carbsDidUpdate(_: [CarbsEntry]) {
  171. uploadCarbs()
  172. }
  173. }
  174. extension BaseNightscoutManager: TempTargetsObserver {
  175. func tempTargetsDidUpdate(_: [TempTarget]) {
  176. uploadTempTargets()
  177. }
  178. }