|
|
@@ -108,9 +108,7 @@ final class BaseAPSManager: APSManager, Injectable {
|
|
|
lastLoopDateSubject.send(lastLoopDate)
|
|
|
|
|
|
isLooping
|
|
|
- .sink { value in
|
|
|
- self.deviceDataManager.loopInProgress = value
|
|
|
- }
|
|
|
+ .weakAssign(to: \.deviceDataManager.loopInProgress, on: self)
|
|
|
.store(in: &lifetime)
|
|
|
}
|
|
|
|
|
|
@@ -147,6 +145,7 @@ final class BaseAPSManager: APSManager, Injectable {
|
|
|
deviceDataManager.heartbeat(date: date)
|
|
|
}
|
|
|
|
|
|
+ // Loop entry point
|
|
|
private func loop() {
|
|
|
guard !isLooping.value else {
|
|
|
warning(.apsManager, "Already looping, skip")
|
|
|
@@ -156,51 +155,68 @@ final class BaseAPSManager: APSManager, Injectable {
|
|
|
debug(.apsManager, "Starting loop")
|
|
|
isLooping.send(true)
|
|
|
determineBasal()
|
|
|
- .sink { [weak self] ok in
|
|
|
- guard let self = self else { return }
|
|
|
+ .replaceEmpty(with: false)
|
|
|
+ .flatMap { [weak self] success -> AnyPublisher<Void, Error> in
|
|
|
+ guard let self = self, success else {
|
|
|
+ return Fail(error: APSError.apsError(message: "Determine basal failed")).eraseToAnyPublisher()
|
|
|
+ }
|
|
|
+ self.nightscout.uploadStatus()
|
|
|
|
|
|
- if ok {
|
|
|
- self.nightscout.uploadStatus()
|
|
|
- if self.settings.closedLoop {
|
|
|
- self.enactSuggested()
|
|
|
- } else {
|
|
|
- self.isLooping.send(false)
|
|
|
- self.lastLoopDate = Date()
|
|
|
- }
|
|
|
+ // Open loop completed
|
|
|
+ guard self.settings.closedLoop else {
|
|
|
+ return Just(()).setFailureType(to: Error.self).eraseToAnyPublisher()
|
|
|
+ }
|
|
|
+
|
|
|
+ // Closed loop - enact suggested
|
|
|
+ return self.enactSuggested()
|
|
|
+ }
|
|
|
+ .sink { [weak self] completion in
|
|
|
+ guard let self = self else { return }
|
|
|
+ if case let .failure(error) = completion {
|
|
|
+ self.loopCompleted(error: error)
|
|
|
} else {
|
|
|
- self.isLooping.send(false)
|
|
|
+ self.loopCompleted()
|
|
|
}
|
|
|
- }.store(in: &lifetime)
|
|
|
+ } receiveValue: {}
|
|
|
+ .store(in: &lifetime)
|
|
|
}
|
|
|
|
|
|
- private func verifyStatus() -> Bool {
|
|
|
+ // Loop exit point
|
|
|
+ private func loopCompleted(error: Error? = nil) {
|
|
|
+ isLooping.send(false)
|
|
|
+
|
|
|
+ if let error = error {
|
|
|
+ warning(.apsManager, "Loop failed with error: \(error.localizedDescription)")
|
|
|
+ processError(error)
|
|
|
+ } else {
|
|
|
+ debug(.apsManager, "Loop succeeded")
|
|
|
+ lastLoopDate = Date()
|
|
|
+ lastError.send(nil)
|
|
|
+ }
|
|
|
+
|
|
|
+ reportEnacted(received: error == nil)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func verifyStatus() -> Error? {
|
|
|
guard let pump = pumpManager else {
|
|
|
- debug(.apsManager, "Pump is not set")
|
|
|
- processError(APSError.invalidPumpState(message: "Pump not set"))
|
|
|
- return false
|
|
|
+ return APSError.invalidPumpState(message: "Pump not set")
|
|
|
}
|
|
|
let status = pump.status.pumpStatus
|
|
|
|
|
|
guard !status.bolusing else {
|
|
|
- debug(.apsManager, "Pump is bolusing")
|
|
|
- processError(APSError.invalidPumpState(message: "Pump is bolusing"))
|
|
|
- return false
|
|
|
+ return APSError.invalidPumpState(message: "Pump is bolusing")
|
|
|
}
|
|
|
|
|
|
guard !status.suspended else {
|
|
|
- debug(.apsManager, "Pump suspended")
|
|
|
- processError(APSError.invalidPumpState(message: "Pump suspended"))
|
|
|
- return false
|
|
|
+ return APSError.invalidPumpState(message: "Pump suspended")
|
|
|
}
|
|
|
|
|
|
let reservoir = storage.retrieve(OpenAPS.Monitor.reservoir, as: Decimal.self) ?? 100
|
|
|
guard reservoir > 0 else {
|
|
|
- debug(.apsManager, "Reservoir is empty")
|
|
|
- processError(APSError.invalidPumpState(message: "Reservoir is empty"))
|
|
|
- return false
|
|
|
+ return APSError.invalidPumpState(message: "Reservoir is empty")
|
|
|
}
|
|
|
|
|
|
- return true
|
|
|
+ return nil
|
|
|
}
|
|
|
|
|
|
private func autosens() -> AnyPublisher<Bool, Never> {
|
|
|
@@ -301,7 +317,17 @@ final class BaseAPSManager: APSManager, Injectable {
|
|
|
private var bolusReporter: DoseProgressReporter?
|
|
|
|
|
|
func enactBolus(amount: Double, isSMB: Bool) {
|
|
|
- guard let pump = pumpManager, verifyStatus() else { return }
|
|
|
+ if let error = verifyStatus() {
|
|
|
+ processError(error)
|
|
|
+ processQueue.async {
|
|
|
+ self.broadcaster.notify(BolusFailureObserver.self, on: self.processQueue) {
|
|
|
+ $0.bolusDidFail()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ guard let pump = pumpManager else { return }
|
|
|
|
|
|
let roundedAmout = pump.roundToSupportedBolusVolume(units: amount)
|
|
|
|
|
|
@@ -348,7 +374,12 @@ final class BaseAPSManager: APSManager, Injectable {
|
|
|
}
|
|
|
|
|
|
func enactTempBasal(rate: Double, duration: TimeInterval) {
|
|
|
- guard let pump = pumpManager, verifyStatus() else { return }
|
|
|
+ if let error = verifyStatus() {
|
|
|
+ processError(error)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ guard let pump = pumpManager else { return }
|
|
|
debug(.apsManager, "Enact temp basal \(rate) - \(duration)")
|
|
|
|
|
|
let roundedAmout = pump.roundToSupportedBasalRate(unitsPerHour: rate)
|
|
|
@@ -402,7 +433,8 @@ final class BaseAPSManager: APSManager, Injectable {
|
|
|
|
|
|
switch action {
|
|
|
case let .bolus(amount):
|
|
|
- guard verifyStatus() else {
|
|
|
+ if let error = verifyStatus() {
|
|
|
+ processError(error)
|
|
|
return
|
|
|
}
|
|
|
let roundedAmount = pump.roundToSupportedBolusVolume(units: Double(amount))
|
|
|
@@ -419,7 +451,8 @@ final class BaseAPSManager: APSManager, Injectable {
|
|
|
case let .pump(pumpAction):
|
|
|
switch pumpAction {
|
|
|
case .suspend:
|
|
|
- guard verifyStatus(), !pump.status.pumpStatus.suspended else {
|
|
|
+ if let error = verifyStatus() {
|
|
|
+ processError(error)
|
|
|
return
|
|
|
}
|
|
|
pump.suspendDelivery { error in
|
|
|
@@ -450,7 +483,11 @@ final class BaseAPSManager: APSManager, Injectable {
|
|
|
debug(.apsManager, "Closed loop \(closedLoop) by Announcement")
|
|
|
announcementsStorage.storeAnnouncements([announcement], enacted: true)
|
|
|
case let .tempbasal(rate, duration):
|
|
|
- guard verifyStatus(), !settings.closedLoop else {
|
|
|
+ if let error = verifyStatus() {
|
|
|
+ processError(error)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ guard !settings.closedLoop else {
|
|
|
return
|
|
|
}
|
|
|
let roundedRate = pump.roundToSupportedBasalRate(unitsPerHour: Double(rate))
|
|
|
@@ -489,30 +526,27 @@ final class BaseAPSManager: APSManager, Injectable {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private func enactSuggested() {
|
|
|
+ private func enactSuggested() -> AnyPublisher<Void, Error> {
|
|
|
guard let suggested = storage.retrieve(OpenAPS.Enact.suggested, as: Suggestion.self) else {
|
|
|
- isLooping.send(false)
|
|
|
- warning(.apsManager, "Suggestion not found")
|
|
|
- processError(APSError.apsError(message: "Suggestion not found"))
|
|
|
- return
|
|
|
+ return Fail(error: APSError.apsError(message: "Suggestion not found")).eraseToAnyPublisher()
|
|
|
}
|
|
|
|
|
|
guard Date().timeIntervalSince(suggested.deliverAt ?? .distantPast) < Config.eхpirationInterval else {
|
|
|
- isLooping.send(false)
|
|
|
- warning(.apsManager, "Suggestion expired")
|
|
|
- processError(APSError.apsError(message: "Suggestion expired"))
|
|
|
- return
|
|
|
+ return Fail(error: APSError.apsError(message: "Suggestion expired")).eraseToAnyPublisher()
|
|
|
}
|
|
|
|
|
|
guard let pump = pumpManager else {
|
|
|
- isLooping.send(false)
|
|
|
- warning(.apsManager, "Pump not set")
|
|
|
- processError(APSError.invalidPumpState(message: "Pump not set"))
|
|
|
- return
|
|
|
+ return Fail(error: APSError.apsError(message: "Pump not set")).eraseToAnyPublisher()
|
|
|
}
|
|
|
|
|
|
let basalPublisher: AnyPublisher<Void, Error> = Deferred { () -> AnyPublisher<Void, Error> in
|
|
|
- guard let rate = suggested.rate, let duration = suggested.duration, self.verifyStatus() else {
|
|
|
+ if let error = self.verifyStatus() {
|
|
|
+ return Fail(error: error).eraseToAnyPublisher()
|
|
|
+ }
|
|
|
+
|
|
|
+ guard let rate = suggested.rate, let duration = suggested.duration else {
|
|
|
+ // It is OK, no temp required
|
|
|
+ debug(.apsManager, "No temp required")
|
|
|
return Just(()).setFailureType(to: Error.self)
|
|
|
.eraseToAnyPublisher()
|
|
|
}
|
|
|
@@ -525,7 +559,12 @@ final class BaseAPSManager: APSManager, Injectable {
|
|
|
}.eraseToAnyPublisher()
|
|
|
|
|
|
let bolusPublisher: AnyPublisher<Void, Error> = Deferred { () -> AnyPublisher<Void, Error> in
|
|
|
- guard let units = suggested.units, self.verifyStatus() else {
|
|
|
+ if let error = self.verifyStatus() {
|
|
|
+ return Fail(error: error).eraseToAnyPublisher()
|
|
|
+ }
|
|
|
+ guard let units = suggested.units else {
|
|
|
+ // It is OK, no bolus required
|
|
|
+ debug(.apsManager, "No bolus required")
|
|
|
return Just(()).setFailureType(to: Error.self)
|
|
|
.eraseToAnyPublisher()
|
|
|
}
|
|
|
@@ -536,26 +575,11 @@ final class BaseAPSManager: APSManager, Injectable {
|
|
|
.eraseToAnyPublisher()
|
|
|
}.eraseToAnyPublisher()
|
|
|
|
|
|
- basalPublisher
|
|
|
- .flatMap { bolusPublisher }
|
|
|
- .sink { [weak self] completion in
|
|
|
- if case let .failure(error) = completion {
|
|
|
- warning(.apsManager, "Loop failed with error: \(error.localizedDescription)")
|
|
|
- self?.reportEnacted(suggestion: suggested, received: false)
|
|
|
- self?.processError(APSError.pumpError(error))
|
|
|
- } else {
|
|
|
- self?.reportEnacted(suggestion: suggested, received: true)
|
|
|
- }
|
|
|
- self?.isLooping.send(false)
|
|
|
- } receiveValue: {
|
|
|
- debug(.apsManager, "Loop succeeded")
|
|
|
- self.lastError.send(nil)
|
|
|
- self.lastLoopDate = Date()
|
|
|
- }.store(in: &lifetime)
|
|
|
+ return basalPublisher.flatMap { bolusPublisher }.eraseToAnyPublisher()
|
|
|
}
|
|
|
|
|
|
- private func reportEnacted(suggestion: Suggestion, received: Bool) {
|
|
|
- if suggestion.deliverAt != nil {
|
|
|
+ private func reportEnacted(received: Bool) {
|
|
|
+ if let suggestion = storage.retrieve(OpenAPS.Enact.suggested, as: Suggestion.self), suggestion.deliverAt != nil {
|
|
|
var enacted = suggestion
|
|
|
enacted.timestamp = Date()
|
|
|
enacted.recieved = received
|
|
|
@@ -602,7 +626,9 @@ private extension PumpManager {
|
|
|
promise(.failure(error))
|
|
|
}
|
|
|
}
|
|
|
- }.eraseToAnyPublisher()
|
|
|
+ }
|
|
|
+ .mapError { APSError.pumpError($0) }
|
|
|
+ .eraseToAnyPublisher()
|
|
|
}
|
|
|
|
|
|
func enactBolus(units: Double, automatic: Bool) -> AnyPublisher<DoseEntry, Error> {
|
|
|
@@ -617,7 +643,9 @@ private extension PumpManager {
|
|
|
promise(.failure(error))
|
|
|
}
|
|
|
}
|
|
|
- }.eraseToAnyPublisher()
|
|
|
+ }
|
|
|
+ .mapError { APSError.pumpError($0) }
|
|
|
+ .eraseToAnyPublisher()
|
|
|
}
|
|
|
|
|
|
func cancelBolus() -> AnyPublisher<DoseEntry?, Error> {
|
|
|
@@ -633,6 +661,7 @@ private extension PumpManager {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+ .mapError { APSError.pumpError($0) }
|
|
|
.eraseToAnyPublisher()
|
|
|
}
|
|
|
|
|
|
@@ -645,7 +674,9 @@ private extension PumpManager {
|
|
|
promise(.success(()))
|
|
|
}
|
|
|
}
|
|
|
- }.eraseToAnyPublisher()
|
|
|
+ }
|
|
|
+ .mapError { APSError.pumpError($0) }
|
|
|
+ .eraseToAnyPublisher()
|
|
|
}
|
|
|
|
|
|
func resumeDelivery() -> AnyPublisher<Void, Error> {
|
|
|
@@ -657,7 +688,9 @@ private extension PumpManager {
|
|
|
promise(.success(()))
|
|
|
}
|
|
|
}
|
|
|
- }.eraseToAnyPublisher()
|
|
|
+ }
|
|
|
+ .mapError { APSError.pumpError($0) }
|
|
|
+ .eraseToAnyPublisher()
|
|
|
}
|
|
|
}
|
|
|
|