From 5f4c77774ac9acaaaeadbc25822552f75539793e Mon Sep 17 00:00:00 2001 From: Joe Moran Date: Thu, 7 Mar 2024 23:57:42 -0800 Subject: [PATCH 1/3] Eliminate user retries on pod setup resumes (Loop issue #2117 follow up) + Have PairAndPrime ViewModel do an automatic retry on error + Have InsertCannula ViewModel do an automatic retry on error + Add resumingPodSetup func to attempt a getStatus and sleep on errors + Added some improved and updated pumpManager comments + Have pumpManager detect pod setup resumes to invoke resumingPodSetup() + Include additional isConnected handling to OmniBLE resumingPodSetup() --- OmniBLE/PumpManager/OmniBLEPumpManager.swift | 45 ++++++++++++++++--- .../ViewModels/InsertCannulaViewModel.swift | 23 ++++++++-- .../ViewModels/PairPodViewModel.swift | 19 +++++++- 3 files changed, 76 insertions(+), 11 deletions(-) diff --git a/OmniBLE/PumpManager/OmniBLEPumpManager.swift b/OmniBLE/PumpManager/OmniBLEPumpManager.swift index 35b400ca..a9cea9c3 100644 --- a/OmniBLE/PumpManager/OmniBLEPumpManager.swift +++ b/OmniBLE/PumpManager/OmniBLEPumpManager.swift @@ -900,7 +900,11 @@ extension OmniBLEPumpManager { } else { self.log.default("Pod already paired. Continuing.") + // Resuming the pod setup, try to ensure pod comms will work right away + self.resumingPodSetup() + self.podComms.runSession(withName: "Prime pod") { (result) in + // Calls completion primeSession(result) } @@ -916,10 +920,10 @@ extension OmniBLEPumpManager { let mockFaultDuringInsertCannula = false DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + mockDelay) { let result = self.setStateWithResult({ (state) -> Result in - if mockFaultDuringInsertCannula { - let fault = try! DetailedStatus(encodedData: Data(hexadecimalString: "020d0000000e00c36a020703ff020900002899080082")!) - var podState = state.podState - podState?.fault = fault + if mockFaultDuringInsertCannula { + let fault = try! DetailedStatus(encodedData: Data(hexadecimalString: "020d0000000e00c36a020703ff020900002899080082")!) + var podState = state.podState + podState?.fault = fault state.updatePodStateFromPodComms(podState) return .failure(OmniBLEPumpManagerError.communication(PodCommsError.podFault(fault: fault))) } @@ -959,6 +963,10 @@ extension OmniBLEPumpManager { self.podComms.runSession(withName: "Insert cannula") { (result) in switch result { case .success(let session): + if self.state.podState?.setupProgress.cannulaInsertionSuccessfullyStarted == true { + // Resuming the pod setup, try to ensure pod comms will work right away + self.resumingPodSetup() + } do { if self.state.podState?.setupProgress.needsInitialBasalSchedule == true { let scheduleOffset = timeZone.scheduleOffset(forDate: Date()) @@ -1011,6 +1019,33 @@ extension OmniBLEPumpManager { #endif } + // Called when resuming a pod setup operation which sometimes can fail on the first pod command in various situations. + // Attempting a getStatus and sleeping a couple of seconds on errors greatly improves the odds for first pod command success. + public func resumingPodSetup() { + let sleepTime:UInt32 = 2 + + if !isConnected { + self.log.debug("### Pod setup resume pod not connected, sleeping %d seconds", sleepTime) + sleep(sleepTime) + } + + podComms.runSession(withName: "Resuming pod setup") { (result) in + switch result { + case .success(let session): + let status = try? session.getStatus() + if status == nil { + self.log.debug("### Pod setup resume getStatus failed, sleeping %d seconds", sleepTime) + sleep(sleepTime) + } + case .failure(let error): + self.log.debug("### Pod setup resume session failure, sleeping %d seconds: %@", sleepTime, error.localizedDescription) + sleep(sleepTime) + } + } + } + + // MARK: - Pump Commands + public func getPodStatus(completion: ((_ result: PumpManagerResult) -> Void)? = nil) { guard state.hasActivePod else { completion?(.failure(PumpManagerError.configuration(OmniBLEPumpManagerError.noPodPaired))) @@ -1038,8 +1073,6 @@ extension OmniBLEPumpManager { } } - // MARK: - Pump Commands - public func acknowledgePodAlerts(_ alertsToAcknowledge: AlertSet, completion: @escaping (_ alerts: [AlertSlot: PodAlert]?) -> Void) { guard self.hasActivePod else { completion(nil) diff --git a/OmniBLE/PumpManagerUI/ViewModels/InsertCannulaViewModel.swift b/OmniBLE/PumpManagerUI/ViewModels/InsertCannulaViewModel.swift index 984a507b..733fc36b 100644 --- a/OmniBLE/PumpManagerUI/ViewModels/InsertCannulaViewModel.swift +++ b/OmniBLE/PumpManagerUI/ViewModels/InsertCannulaViewModel.swift @@ -129,6 +129,7 @@ class InsertCannulaViewModel: ObservableObject, Identifiable { } @Published var state: InsertCannulaViewModelState = .ready + public var stateNeedsDeliberateUserAcceptance : Bool { switch state { case .ready: @@ -143,16 +144,19 @@ class InsertCannulaViewModel: ObservableObject, Identifiable { var didRequestDeactivation: (() -> Void)? var cannulaInserter: CannulaInserter - + + var autoRetryAttempted: Bool + init(cannulaInserter: CannulaInserter) { self.cannulaInserter = cannulaInserter + self.autoRetryAttempted = false // If resuming, don't wait for the button action if cannulaInserter.cannulaInsertionSuccessfullyStarted { insertCannula() } } - + private func checkCannulaInsertionFinished() { state = .checkingInsertion cannulaInserter.checkCannulaInsertionFinished() { (error) in @@ -168,6 +172,7 @@ class InsertCannulaViewModel: ObservableObject, Identifiable { private func insertCannula() { state = .startingInsertion + cannulaInserter.insertCannula { (result) in DispatchQueue.main.async { switch(result) { @@ -182,7 +187,19 @@ class InsertCannulaViewModel: ObservableObject, Identifiable { self.state = .finished } case .failure(let error): - self.state = .error(error) + if self.autoRetryAttempted { + self.autoRetryAttempted = false // allow for an auto retry on the next user attempt + self.state = .error(error) + } else { + self.autoRetryAttempted = true + let autoRetryPauseTime = TimeInterval(seconds: 3) + print("### insertCannula encountered error \(error.localizedDescription), retrying after \(autoRetryPauseTime) seconds") + DispatchQueue.global(qos: .utility).async { + Thread.sleep(forTimeInterval: autoRetryPauseTime) + + self.insertCannula() + } + } } } } diff --git a/OmniBLE/PumpManagerUI/ViewModels/PairPodViewModel.swift b/OmniBLE/PumpManagerUI/ViewModels/PairPodViewModel.swift index 5e1ce70b..4e3ec324 100644 --- a/OmniBLE/PumpManagerUI/ViewModels/PairPodViewModel.swift +++ b/OmniBLE/PumpManagerUI/ViewModels/PairPodViewModel.swift @@ -168,8 +168,11 @@ class PairPodViewModel: ObservableObject, Identifiable { var podPairer: PodPairer + var autoRetryAttempted: Bool + init(podPairer: PodPairer) { self.podPairer = podPairer + self.autoRetryAttempted = false // If resuming, don't wait for the button action if podPairer.podCommState == .activating { @@ -189,8 +192,20 @@ class PairPodViewModel: ObservableObject, Identifiable { DispatchQueue.main.async { switch status { case .failure(let error): - let pairingError = DashPairingError.pumpManagerError(error) - self.state = .error(pairingError) + if self.autoRetryAttempted { + self.autoRetryAttempted = false // allow for an auto retry on the next user attempt + let pairAndPrimeError = DashPairingError.pumpManagerError(error) + self.state = .error(pairAndPrimeError) + } else { + self.autoRetryAttempted = true + let autoRetryPauseTime = TimeInterval(seconds: 3) + print("### pairAndPrimePod encountered error \(error.localizedDescription), retrying after \(autoRetryPauseTime) seconds") + DispatchQueue.global(qos: .utility).async { + Thread.sleep(forTimeInterval: autoRetryPauseTime) + + self.pairAndPrime() // handles both pairing or priming failures + } + } case .success(let duration): if duration > 0 { From 534532bc8bcafc85e238927e2ef23460a4a8c532 Mon Sep 17 00:00:00 2001 From: Joe Moran Date: Fri, 8 Mar 2024 00:32:57 -0800 Subject: [PATCH 2/3] Indentation fix --- OmniBLE/PumpManager/OmniBLEPumpManager.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/OmniBLE/PumpManager/OmniBLEPumpManager.swift b/OmniBLE/PumpManager/OmniBLEPumpManager.swift index a9cea9c3..2ddf0ba3 100644 --- a/OmniBLE/PumpManager/OmniBLEPumpManager.swift +++ b/OmniBLE/PumpManager/OmniBLEPumpManager.swift @@ -920,10 +920,10 @@ extension OmniBLEPumpManager { let mockFaultDuringInsertCannula = false DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + mockDelay) { let result = self.setStateWithResult({ (state) -> Result in - if mockFaultDuringInsertCannula { - let fault = try! DetailedStatus(encodedData: Data(hexadecimalString: "020d0000000e00c36a020703ff020900002899080082")!) - var podState = state.podState - podState?.fault = fault + if mockFaultDuringInsertCannula { + let fault = try! DetailedStatus(encodedData: Data(hexadecimalString: "020d0000000e00c36a020703ff020900002899080082")!) + var podState = state.podState + podState?.fault = fault state.updatePodStateFromPodComms(podState) return .failure(OmniBLEPumpManagerError.communication(PodCommsError.podFault(fault: fault))) } From aa6d276de80101f9e9abc378c0494e69bcfbe2a5 Mon Sep 17 00:00:00 2001 From: Joe Moran Date: Fri, 8 Mar 2024 00:50:56 -0800 Subject: [PATCH 3/3] Add missing comment --- OmniBLE/PumpManager/OmniBLEPumpManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OmniBLE/PumpManager/OmniBLEPumpManager.swift b/OmniBLE/PumpManager/OmniBLEPumpManager.swift index 2ddf0ba3..ada8e522 100644 --- a/OmniBLE/PumpManager/OmniBLEPumpManager.swift +++ b/OmniBLE/PumpManager/OmniBLEPumpManager.swift @@ -888,6 +888,7 @@ extension OmniBLEPumpManager { self.podComms.pairAndSetupPod(timeZone: .currentFixed, insulinType: insulinType, messageLogger: self) { (result) in + // Have new podState, reset all the per pod pump manager state self.resetPerPodPumpManagerState() // Calls completion @@ -904,7 +905,6 @@ extension OmniBLEPumpManager { self.resumingPodSetup() self.podComms.runSession(withName: "Prime pod") { (result) in - // Calls completion primeSession(result) }