APSManager.swift 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import Combine
  2. import Foundation
  3. import LoopKit
  4. import LoopKitUI
  5. import Swinject
  6. protocol APSManager {
  7. func loop()
  8. func autosense()
  9. func autotune()
  10. var pumpManager: PumpManagerUI? { get set }
  11. var pumpDisplayState: CurrentValueSubject<PumpDisplayState?, Never> { get }
  12. }
  13. final class BaseAPSManager: APSManager, Injectable {
  14. @Injected() private var storage: FileStorage!
  15. @Injected() private var pumpHistoryStorage: PumpHistoryStorage!
  16. @Injected() private var glucoseStorage: GlucoseStorage!
  17. @Injected() private var tempTargetsStorage: TempTargetsStorage!
  18. @Injected() private var deviceDataManager: DeviceDataManager!
  19. @Injected() private var networkManager: NetworkManager!
  20. @Injected() private var settingsManager: SettingsManager!
  21. private var openAPS: OpenAPS!
  22. private var loopCancellable: AnyCancellable?
  23. private var pumpCancellable: AnyCancellable?
  24. private var enactCancellable: AnyCancellable?
  25. var pumpManager: PumpManagerUI? {
  26. get { deviceDataManager.pumpManager }
  27. set { deviceDataManager.pumpManager = newValue }
  28. }
  29. var pumpDisplayState: CurrentValueSubject<PumpDisplayState?, Never> {
  30. deviceDataManager.pumpDisplayState
  31. }
  32. var settings: FreeAPSSettings {
  33. get { settingsManager.settings }
  34. set { settingsManager.settings = newValue }
  35. }
  36. init(resolver: Resolver) {
  37. injectServices(resolver)
  38. openAPS = OpenAPS(storage: storage)
  39. subscribe()
  40. }
  41. private func subscribe() {
  42. pumpCancellable = deviceDataManager.recommendsLoop
  43. .sink { [weak self] in
  44. self?.loop()
  45. }
  46. }
  47. func loop() {
  48. loopCancellable = networkManager
  49. .fetchGlucose()
  50. .flatMap { [weak self] glucose -> AnyPublisher<Bool, Never> in
  51. guard let self = self else { return Just(false).eraseToAnyPublisher() }
  52. self.glucoseStorage.storeGlucose(glucose)
  53. return self.determineBasal()
  54. }
  55. .sink { _ in } receiveValue: { [weak self] ok in
  56. guard let self = self else { return }
  57. if ok, self.settings.closedLoop {
  58. self.enactSuggested()
  59. }
  60. }
  61. }
  62. func determineBasal() -> AnyPublisher<Bool, Never> {
  63. guard let glucose = try? storage.retrieve(OpenAPS.Monitor.glucose, as: [BloodGlucose].self), glucose.count >= 36 else {
  64. print("Not enough glucose data")
  65. return Just(false).eraseToAnyPublisher()
  66. }
  67. let now = Date()
  68. guard let temp = currentTemp(date: now) else {
  69. return Just(false).eraseToAnyPublisher()
  70. }
  71. return openAPS.makeProfiles()
  72. .flatMap { _ in
  73. self.openAPS.determineBasal(currentTemp: temp, clock: now)
  74. }
  75. .map { true }
  76. .eraseToAnyPublisher()
  77. }
  78. func autosense() {
  79. _ = openAPS.autosense()
  80. }
  81. func autotune() {
  82. _ = openAPS.autotune()
  83. }
  84. private func currentTemp(date: Date) -> TempBasal? {
  85. guard let state = pumpManager?.status.basalDeliveryState else { return nil }
  86. switch state {
  87. case .active:
  88. return TempBasal(duration: 0, rate: 0, temp: .absolute)
  89. case let .tempBasal(dose):
  90. let rate = Decimal(dose.unitsPerHour)
  91. let durationMin = max(0, Int((dose.endDate.timeIntervalSince1970 - date.timeIntervalSince1970) / 60))
  92. return TempBasal(duration: durationMin, rate: rate, temp: .absolute)
  93. default: return nil
  94. }
  95. }
  96. private func enactSuggested() {
  97. guard let pump = pumpManager,
  98. let suggested = try? storage.retrieve(
  99. OpenAPS.Enact.suggested,
  100. as: Suggestion.self
  101. )
  102. else {
  103. return
  104. }
  105. let basalPublisher: AnyPublisher<Void, Error> = {
  106. guard let rate = suggested.rate, let duration = suggested.duration else {
  107. return Just(()).setFailureType(to: Error.self)
  108. .eraseToAnyPublisher()
  109. }
  110. return pump.enactTempBasal(unitsPerHour: Double(rate), for: TimeInterval(duration * 60)).map { _ in () }
  111. .eraseToAnyPublisher()
  112. }()
  113. let bolusPublisher: AnyPublisher<Void, Error> = {
  114. guard let units = suggested.units else {
  115. return Just(()).setFailureType(to: Error.self)
  116. .eraseToAnyPublisher()
  117. }
  118. return pump.enactBolus(units: Double(units), automatic: true).map { _ in () }
  119. .eraseToAnyPublisher()
  120. }()
  121. enactCancellable = basalPublisher
  122. .flatMap { bolusPublisher }
  123. .sink { completion in
  124. if case let .failure(error) = completion {
  125. print("Loop failed with error: \(error.localizedDescription)")
  126. }
  127. } receiveValue: { [weak self] in
  128. print("Loop succeeded")
  129. if let rawSuggested = self?.storage.retrieveRaw(OpenAPS.Enact.suggested) {
  130. try? self?.storage.save(rawSuggested, as: OpenAPS.Enact.enacted)
  131. }
  132. }
  133. }
  134. }
  135. private extension PumpManager {
  136. func enactTempBasal(unitsPerHour: Double, for duration: TimeInterval) -> AnyPublisher<DoseEntry, Error> {
  137. Future { promise in
  138. self.enactTempBasal(unitsPerHour: unitsPerHour, for: duration) { result in
  139. switch result {
  140. case let .success(dose):
  141. promise(.success(dose))
  142. case let .failure(error):
  143. promise(.failure(error))
  144. }
  145. }
  146. }.eraseToAnyPublisher()
  147. }
  148. func enactBolus(units: Double, automatic: Bool) -> AnyPublisher<DoseEntry, Error> {
  149. Future { promise in
  150. self.enactBolus(units: units, automatic: automatic) { result in
  151. switch result {
  152. case let .success(dose):
  153. promise(.success(dose))
  154. case let .failure(error):
  155. promise(.failure(error))
  156. }
  157. }
  158. }.eraseToAnyPublisher()
  159. }
  160. }