From b79d85b1e9009eb4516a0cb28779de7b97b84ae4 Mon Sep 17 00:00:00 2001 From: Joe Moran Date: Sun, 22 Dec 2024 21:44:35 -0800 Subject: [PATCH 1/3] Improved & automatic unacknowledged command recovery New resolveUnacknowledgedCommand() attempts a GetStatusCommand to resolve any unacknowledged command or throws .unacknowledgedCommandPending on failure. Have all PodCommSession funcs call resolveUnacknowledgedCommand() instead of failing if called with a pending unacknowledged command. --- OmniKit/PumpManager/PodCommsSession.swift | 98 +++++++++++++++++------ 1 file changed, 74 insertions(+), 24 deletions(-) diff --git a/OmniKit/PumpManager/PodCommsSession.swift b/OmniKit/PumpManager/PodCommsSession.swift index cfe5b31..0f6f942 100644 --- a/OmniKit/PumpManager/PodCommsSession.swift +++ b/OmniKit/PumpManager/PodCommsSession.swift @@ -399,15 +399,47 @@ public class PodCommsSession { podState.finalizedDoses.append(UnfinalizedDose(resumeStartTime: currentDate, scheduledCertainty: .certain, insulinType: podState.insulinType)) } + // + // Attempts to resolve any unacknowledged command by using a GetStatusCommand. + // podState.unacknowledgeCommand is guaranteed to be nil upon successful return. + // Throws PodCommsError.unacknowledgedCommandPending if unsuccessful for any reason. + // + private func resolveUnacknowledgedCommand(function: String = #function) throws { + + guard podState.unacknowledgedCommand != nil else { + return // no unacknowledged command to resolve + } + + let statusResponse: StatusResponse + do { + // Send a GetStatusCommand to try to resolve the unacknowleged command. + statusResponse = try send([GetStatusCommand()]) + } catch let error { + log.info("GetStatus failed with %{public}@ trying to resolve unacknowledged command in %{public}@", String(describing: error), function) + throw PodCommsError.unacknowledgedCommandPending + } + + // Success -- now use the statusResponse to resolve the unacknowledged command & update the podState + recoverUnacknowledgedCommand(using: statusResponse) + podState.updateFromStatusResponse(statusResponse, at: currentDate) + + // recoverUnacknowledgedCommand() should have resolved the unacknowledged command, but check to be sure. + guard podState.unacknowledgedCommand == nil else { + log.error("failed to resolve the unacknowledged command with GetStatus in %{public}@!", function) + throw PodCommsError.unacknowledgedCommandPending + } + + log.info("resolved the unacknowledged command in %{public}@", function) + } + // Configures the given pod alert(s) and registers the newly configured alert slot(s). // When re-configuring all the pod alerts for a silence pod toggle, the optional acknowledgeAll can be // specified to first acknowledge and clear all possible pending pod alerts and pod alert configurations. @discardableResult func configureAlerts(_ alerts: [PodAlert], acknowledgeAll: Bool = false, beepBlock: MessageBlock? = nil) throws -> StatusResponse { - guard podState.unacknowledgedCommand == nil || podState.setupProgress != .completed else { - log.info("Fail configure alerts with unacknowledged command and incomplete pod setup") - throw PodCommsError.unacknowledgedCommandPending + if podState.unacknowledgedCommand != nil { + try resolveUnacknowledgedCommand() } let configurations = alerts.map { $0.configuration } @@ -435,9 +467,12 @@ public class PodCommsSession { return .failure(PodCommsError.podFault(fault: fault)) } - guard podState.unacknowledgedCommand == nil || podState.setupProgress != .completed else { - log.info("Fail beep config with unacknowledged command and incomplete pod setup") - return .failure(PodCommsError.unacknowledgedCommandPending) + if podState.unacknowledgedCommand != nil { + do { + try resolveUnacknowledgedCommand() + } catch let error { + return .failure(error) + } } let beepConfigCommand = BeepConfigCommand(beepType: beepType, tempBasalCompletionBeep: tempBasalCompletionBeep, bolusCompletionBeep: bolusCompletionBeep) @@ -530,8 +565,12 @@ public class PodCommsSession { public func bolus(units: Double, automatic: Bool = false, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0, extendedUnits: Double = 0.0, extendedDuration: TimeInterval = 0) -> DeliveryCommandResult { - guard podState.unacknowledgedCommand == nil else { - return DeliveryCommandResult.certainFailure(error: .unacknowledgedCommandPending) + if podState.unacknowledgedCommand != nil { + do { + try resolveUnacknowledgedCommand() + } catch { + return DeliveryCommandResult.certainFailure(error: .unacknowledgedCommandPending) + } } let timeBetweenPulses = TimeInterval(seconds: Pod.secondsPerBolusPulse) @@ -577,8 +616,12 @@ public class PodCommsSession { public func setTempBasal(rate: Double, duration: TimeInterval, isHighTemp: Bool, automatic: Bool, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0) -> DeliveryCommandResult { - guard podState.unacknowledgedCommand == nil else { - return DeliveryCommandResult.certainFailure(error: .unacknowledgedCommandPending) + if podState.unacknowledgedCommand != nil { + do { + try resolveUnacknowledgedCommand() + } catch { + return DeliveryCommandResult.certainFailure(error: .unacknowledgedCommandPending) + } } let tempBasalCommand = SetInsulinScheduleCommand(nonce: podState.currentNonce, tempBasalRate: rate, duration: duration) @@ -651,8 +694,12 @@ public class PodCommsSession { // The configured alerts will set up as silent pod alerts if silent is true. public func suspendDelivery(suspendReminder: TimeInterval? = nil, silent: Bool, beepBlock: MessageBlock? = nil) -> CancelDeliveryResult { - guard podState.unacknowledgedCommand == nil else { - return .certainFailure(error: .unacknowledgedCommandPending) + if podState.unacknowledgedCommand != nil { + do { + try resolveUnacknowledgedCommand() + } catch { + return .certainFailure(error: .unacknowledgedCommandPending) + } } guard podState.setupProgress == .completed else { @@ -735,8 +782,12 @@ public class PodCommsSession { // N.B., Using the built-in cancel delivery command beepType method when cancelling all insulin delivery will emit 3 different sets of cancel beeps!!! public func cancelDelivery(deliveryType: CancelDeliveryCommand.DeliveryType, beepType: BeepType = .noBeepCancel, beepBlock: MessageBlock? = nil) -> CancelDeliveryResult { - guard podState.unacknowledgedCommand == nil else { - return .certainFailure(error: .unacknowledgedCommandPending) + if podState.unacknowledgedCommand != nil { + do { + try resolveUnacknowledgedCommand() + } catch { + return .certainFailure(error: .unacknowledgedCommandPending) + } } guard podState.setupProgress == .completed else { @@ -765,8 +816,9 @@ public class PodCommsSession { } public func setTime(timeZone: TimeZone, basalSchedule: BasalSchedule, date: Date, acknowledgementBeep: Bool = false) throws -> StatusResponse { - guard podState.unacknowledgedCommand == nil else { - throw PodCommsError.unacknowledgedCommandPending + + if podState.unacknowledgedCommand != nil { + try resolveUnacknowledgedCommand() } let result = cancelDelivery(deliveryType: .all) @@ -784,8 +836,8 @@ public class PodCommsSession { public func setBasalSchedule(schedule: BasalSchedule, scheduleOffset: TimeInterval, acknowledgementBeep: Bool = false, programReminderInterval: TimeInterval = 0) throws -> StatusResponse { - guard podState.unacknowledgedCommand == nil else { - throw PodCommsError.unacknowledgedCommandPending + if podState.unacknowledgedCommand != nil { + try resolveUnacknowledgedCommand() } let basalScheduleCommand = SetInsulinScheduleCommand(nonce: podState.currentNonce, basalSchedule: schedule, scheduleOffset: scheduleOffset) @@ -825,11 +877,10 @@ public class PodCommsSession { public func resumeBasal(schedule: BasalSchedule, scheduleOffset: TimeInterval, acknowledgementBeep: Bool = false, programReminderInterval: TimeInterval = 0) throws -> StatusResponse { - guard podState.unacknowledgedCommand == nil else { - throw PodCommsError.unacknowledgedCommandPending + if podState.unacknowledgedCommand != nil { + try resolveUnacknowledgedCommand() } - let status = try setBasalSchedule(schedule: schedule, scheduleOffset: scheduleOffset, acknowledgementBeep: acknowledgementBeep, programReminderInterval: programReminderInterval) podState.suspendState = .resumed(currentDate) @@ -1041,9 +1092,8 @@ public class PodCommsSession { public func acknowledgeAlerts(alerts: AlertSet, beepBlock: MessageBlock? = nil) throws -> AlertSet { - guard podState.unacknowledgedCommand == nil || podState.setupProgress != .completed else { - log.info("Fail acknowledge alerts with unacknowledged command and pod setup complete") - throw PodCommsError.unacknowledgedCommandPending + if podState.unacknowledgedCommand != nil { + try resolveUnacknowledgedCommand() } let cmd = AcknowledgeAlertCommand(nonce: podState.currentNonce, alerts: alerts) From 2d9959c107d41d5c8c64b1fa69049c103874cc02 Mon Sep 17 00:00:00 2001 From: Joe Moran Date: Mon, 23 Dec 2024 23:40:52 -0800 Subject: [PATCH 2/3] Use getStatus() more for unacknowledged command handling & simplified code Fix deactivatePod() logic error to correctly handle an unacknowledged command Simplify logging by not including the calling function's name --- OmniKit/PumpManager/PodCommsSession.swift | 43 +++++++++-------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/OmniKit/PumpManager/PodCommsSession.swift b/OmniKit/PumpManager/PodCommsSession.swift index 0f6f942..ee47a83 100644 --- a/OmniKit/PumpManager/PodCommsSession.swift +++ b/OmniKit/PumpManager/PodCommsSession.swift @@ -357,8 +357,7 @@ public class PodCommsSession { try configureAlerts([finishSetupReminder]) } else { // Not the first time through, check to see if prime bolus was successfully started - let status: StatusResponse = try send([GetStatusCommand()]) - podState.updateFromStatusResponse(status, at: currentDate) + let status = try getStatus() if status.podProgressStatus == .priming || status.podProgressStatus == .primingCompleted { podState.setupProgress = .priming return podState.primeFinishTime?.timeIntervalSinceNow ?? primeDuration @@ -383,8 +382,7 @@ public class PodCommsSession { public func programInitialBasalSchedule(_ basalSchedule: BasalSchedule, scheduleOffset: TimeInterval) throws { if podState.setupProgress == .settingInitialBasalSchedule { // We started basal schedule programming, but didn't get confirmation somehow, so check status - let status: StatusResponse = try send([GetStatusCommand()]) - podState.updateFromStatusResponse(status, at: currentDate) + let status = try getStatus() if status.podProgressStatus == .basalInitialized { podState.setupProgress = .initialBasalScheduleSet podState.finalizedDoses.append(UnfinalizedDose(resumeStartTime: currentDate, scheduledCertainty: .certain, insulinType: podState.insulinType)) @@ -400,36 +398,30 @@ public class PodCommsSession { } // - // Attempts to resolve any unacknowledged command by using a GetStatusCommand. + // Attempts to resolve any unacknowledged command by calling getStatus(). // podState.unacknowledgeCommand is guaranteed to be nil upon successful return. // Throws PodCommsError.unacknowledgedCommandPending if unsuccessful for any reason. // - private func resolveUnacknowledgedCommand(function: String = #function) throws { + private func resolveUnacknowledgedCommand() throws { guard podState.unacknowledgedCommand != nil else { return // no unacknowledged command to resolve } - let statusResponse: StatusResponse do { - // Send a GetStatusCommand to try to resolve the unacknowleged command. - statusResponse = try send([GetStatusCommand()]) + _ = try getStatus() // should resolve the unacknowledged command if successful } catch let error { - log.info("GetStatus failed with %{public}@ trying to resolve unacknowledged command in %{public}@", String(describing: error), function) + log.error("GetStatus failed trying to resolve unacknowledged command: %{public}@", String(describing: error)) throw PodCommsError.unacknowledgedCommandPending } - // Success -- now use the statusResponse to resolve the unacknowledged command & update the podState - recoverUnacknowledgedCommand(using: statusResponse) - podState.updateFromStatusResponse(statusResponse, at: currentDate) - - // recoverUnacknowledgedCommand() should have resolved the unacknowledged command, but check to be sure. + // Verify that getStatus successfully resolved the unacknowledged command. guard podState.unacknowledgedCommand == nil else { - log.error("failed to resolve the unacknowledged command with GetStatus in %{public}@!", function) + log.error("Successful getStatus didn't resolve the unacknowledged command!") throw PodCommsError.unacknowledgedCommandPending } - log.info("resolved the unacknowledged command in %{public}@", function) + log.info("Successfully resolved pending unacknowledged command") } // Configures the given pod alert(s) and registers the newly configured alert slot(s). @@ -502,19 +494,16 @@ public class PodCommsSession { if podState.setupProgress == .startingInsertCannula || podState.setupProgress == .cannulaInserting { // We started cannula insertion, but didn't get confirmation somehow, so check status - let status: StatusResponse = try send([GetStatusCommand()]) + let status = try getStatus() if status.podProgressStatus == .insertingCannula { podState.setupProgress = .cannulaInserting - podState.updateFromStatusResponse(status, at: currentDate) // return a non-zero wait time based on the bolus not yet delivered return (status.bolusNotDelivered / Pod.primeDeliveryRate) + 1 } if status.podProgressStatus.readyForDelivery { markSetupProgressCompleted(statusResponse: status) - podState.updateFromStatusResponse(status, at: currentDate) return TimeInterval(0) // Already done; no need to wait } - podState.updateFromStatusResponse(status, at: currentDate) } else { let elapsed: TimeInterval = -(podState.podTimeUpdated?.timeIntervalSinceNow ?? 0) let podTime = podState.podTime + elapsed @@ -541,11 +530,10 @@ public class PodCommsSession { public func checkInsertionCompleted() throws { if podState.setupProgress == .cannulaInserting { - let response: StatusResponse = try send([GetStatusCommand()]) + let response = try getStatus() if response.podProgressStatus.readyForDelivery { markSetupProgressCompleted(statusResponse: response) } - podState.updateFromStatusResponse(response, at: currentDate) } } @@ -1068,13 +1056,14 @@ public class PodCommsSession { } do { - let deactivatePod = DeactivatePodCommand(nonce: podState.currentNonce) - let status: StatusResponse = try send([deactivatePod]) - if podState.unacknowledgedCommand != nil { - recoverUnacknowledgedCommand(using: status) + // Try to resolve the unacknowledged command now as DeactivatePodCommand + // destroys any chance of correctly handling the unacknowledged command. + try? resolveUnacknowledgedCommand() } + let deactivatePod = DeactivatePodCommand(nonce: podState.currentNonce) + let status: StatusResponse = try send([deactivatePod]) podState.updateFromStatusResponse(status, at: currentDate) if podState.activeTime == nil, let activatedAt = podState.activatedAt { From 2d41740c31859919d1aa1f8ce84d2b175a389526 Mon Sep 17 00:00:00 2001 From: Joe Moran Date: Tue, 24 Dec 2024 22:58:30 -0800 Subject: [PATCH 3/3] Rename resolveUnacknowledgedCommand() to tryToResolvePendingCommand() --- OmniKit/PumpManager/PodCommsSession.swift | 36 +++++++++++------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/OmniKit/PumpManager/PodCommsSession.swift b/OmniKit/PumpManager/PodCommsSession.swift index ee47a83..7fb0df1 100644 --- a/OmniKit/PumpManager/PodCommsSession.swift +++ b/OmniKit/PumpManager/PodCommsSession.swift @@ -398,26 +398,26 @@ public class PodCommsSession { } // - // Attempts to resolve any unacknowledged command by calling getStatus(). + // Attempts to resolve any pending unacknowledged command by calling getStatus(). // podState.unacknowledgeCommand is guaranteed to be nil upon successful return. // Throws PodCommsError.unacknowledgedCommandPending if unsuccessful for any reason. // - private func resolveUnacknowledgedCommand() throws { + private func tryToResolvePendingCommand() throws { guard podState.unacknowledgedCommand != nil else { - return // no unacknowledged command to resolve + return // no pending unacknowledged command to resolve } do { - _ = try getStatus() // should resolve the unacknowledged command if successful + _ = try getStatus() // should resolve the pending unacknowledged command if successful } catch let error { - log.error("GetStatus failed trying to resolve unacknowledged command: %{public}@", String(describing: error)) + log.error("GetStatus failed trying to resolve pending unacknowledged command: %{public}@", String(describing: error)) throw PodCommsError.unacknowledgedCommandPending } - // Verify that getStatus successfully resolved the unacknowledged command. + // Verify that getStatus successfully resolved the pending unacknowledged command. guard podState.unacknowledgedCommand == nil else { - log.error("Successful getStatus didn't resolve the unacknowledged command!") + log.error("Successful getStatus didn't resolve the pending unacknowledged command!") throw PodCommsError.unacknowledgedCommandPending } @@ -431,7 +431,7 @@ public class PodCommsSession { func configureAlerts(_ alerts: [PodAlert], acknowledgeAll: Bool = false, beepBlock: MessageBlock? = nil) throws -> StatusResponse { if podState.unacknowledgedCommand != nil { - try resolveUnacknowledgedCommand() + try tryToResolvePendingCommand() } let configurations = alerts.map { $0.configuration } @@ -461,7 +461,7 @@ public class PodCommsSession { if podState.unacknowledgedCommand != nil { do { - try resolveUnacknowledgedCommand() + try tryToResolvePendingCommand() } catch let error { return .failure(error) } @@ -555,7 +555,7 @@ public class PodCommsSession { if podState.unacknowledgedCommand != nil { do { - try resolveUnacknowledgedCommand() + try tryToResolvePendingCommand() } catch { return DeliveryCommandResult.certainFailure(error: .unacknowledgedCommandPending) } @@ -606,7 +606,7 @@ public class PodCommsSession { if podState.unacknowledgedCommand != nil { do { - try resolveUnacknowledgedCommand() + try tryToResolvePendingCommand() } catch { return DeliveryCommandResult.certainFailure(error: .unacknowledgedCommandPending) } @@ -684,7 +684,7 @@ public class PodCommsSession { if podState.unacknowledgedCommand != nil { do { - try resolveUnacknowledgedCommand() + try tryToResolvePendingCommand() } catch { return .certainFailure(error: .unacknowledgedCommandPending) } @@ -772,7 +772,7 @@ public class PodCommsSession { if podState.unacknowledgedCommand != nil { do { - try resolveUnacknowledgedCommand() + try tryToResolvePendingCommand() } catch { return .certainFailure(error: .unacknowledgedCommandPending) } @@ -806,7 +806,7 @@ public class PodCommsSession { public func setTime(timeZone: TimeZone, basalSchedule: BasalSchedule, date: Date, acknowledgementBeep: Bool = false) throws -> StatusResponse { if podState.unacknowledgedCommand != nil { - try resolveUnacknowledgedCommand() + try tryToResolvePendingCommand() } let result = cancelDelivery(deliveryType: .all) @@ -825,7 +825,7 @@ public class PodCommsSession { public func setBasalSchedule(schedule: BasalSchedule, scheduleOffset: TimeInterval, acknowledgementBeep: Bool = false, programReminderInterval: TimeInterval = 0) throws -> StatusResponse { if podState.unacknowledgedCommand != nil { - try resolveUnacknowledgedCommand() + try tryToResolvePendingCommand() } let basalScheduleCommand = SetInsulinScheduleCommand(nonce: podState.currentNonce, basalSchedule: schedule, scheduleOffset: scheduleOffset) @@ -866,7 +866,7 @@ public class PodCommsSession { public func resumeBasal(schedule: BasalSchedule, scheduleOffset: TimeInterval, acknowledgementBeep: Bool = false, programReminderInterval: TimeInterval = 0) throws -> StatusResponse { if podState.unacknowledgedCommand != nil { - try resolveUnacknowledgedCommand() + try tryToResolvePendingCommand() } let status = try setBasalSchedule(schedule: schedule, scheduleOffset: scheduleOffset, acknowledgementBeep: acknowledgementBeep, programReminderInterval: programReminderInterval) @@ -1059,7 +1059,7 @@ public class PodCommsSession { if podState.unacknowledgedCommand != nil { // Try to resolve the unacknowledged command now as DeactivatePodCommand // destroys any chance of correctly handling the unacknowledged command. - try? resolveUnacknowledgedCommand() + try? tryToResolvePendingCommand() } let deactivatePod = DeactivatePodCommand(nonce: podState.currentNonce) @@ -1082,7 +1082,7 @@ public class PodCommsSession { public func acknowledgeAlerts(alerts: AlertSet, beepBlock: MessageBlock? = nil) throws -> AlertSet { if podState.unacknowledgedCommand != nil { - try resolveUnacknowledgedCommand() + try tryToResolvePendingCommand() } let cmd = AcknowledgeAlertCommand(nonce: podState.currentNonce, alerts: alerts)