Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unacknowledged command handling fixes and PodCommsSession improvements #45

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 84 additions & 8 deletions OmniKit/PumpManager/PodCommsSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -251,15 +251,23 @@ public class PodCommsSession {
/// - PodCommsError.unexpectedResponse
/// - PodCommsError.rejectedMessage
/// - PodCommsError.nonceResyncFailed
/// - PodCommsError.unexpectedPacketType
/// - PodCommsError.emptyResponse
/// - PodCommsError.unacknowledgedMessage
/// - MessageError
/// - RileyLinkDeviceError
func send<T: MessageBlock>(_ messageBlocks: [MessageBlock], beepBlock: MessageBlock? = nil, expectFollowOnMessage: Bool = false) throws -> T {

var triesRemaining = 2 // Retries only happen for nonce resync
var blocksToSend = messageBlocks

// If a beep block was specified & pod isn't faulted, append the beep block to emit the confirmation beep
if let beepBlock = beepBlock, podState.isFaulted == false {
// If a beep block was specified & the pod isn't faulted AND there isn't an unacknowledged
// command for a getStatus command, append the beep block to emit the confirmation beep.
// Since a beep command changes lastProgrammingMessageSeqNum, we need skip appending a beep
// block while still trying to resolve an unacknowldged delivery command with getStatus calls.
if let beepBlock = beepBlock, podState.isFaulted == false &&
!(podState.unacknowledgedCommand != nil && blocksToSend[0].blockType == .getStatus)
{
blocksToSend += [beepBlock]
}

Expand Down Expand Up @@ -396,6 +404,12 @@ public class PodCommsSession {
// 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
}

let configurations = alerts.map { $0.configuration }
let configureAlerts = ConfigureAlertsCommand(nonce: podState.currentNonce, configurations: configurations)
let blocksToSend: [MessageBlock]
Expand All @@ -421,6 +435,11 @@ 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)
}

let beepConfigCommand = BeepConfigCommand(beepType: beepType, tempBasalCompletionBeep: tempBasalCompletionBeep, bolusCompletionBeep: bolusCompletionBeep)
do {
let statusResponse: StatusResponse = try send([beepConfigCommand])
Expand Down Expand Up @@ -699,7 +718,6 @@ public class PodCommsSession {
}

// Cancels any suspend related alerts, called when setting a basal schedule with active suspend alerts
@discardableResult
private func cancelSuspendAlerts() throws -> StatusResponse {

do {
Expand Down Expand Up @@ -821,7 +839,6 @@ public class PodCommsSession {

// use cancelDelivery with .none to get status as well as to validate & advance the nonce
// Throws PodCommsError
@discardableResult
public func cancelNone(beepBlock: MessageBlock? = nil) throws -> StatusResponse {
var statusResponse: StatusResponse

Expand All @@ -839,7 +856,6 @@ public class PodCommsSession {
}

// Throws PodCommsError
@discardableResult
public func getStatus(beepBlock: MessageBlock? = nil) throws -> StatusResponse {
let statusResponse: StatusResponse = try send([GetStatusCommand()], beepBlock: beepBlock)

Expand All @@ -850,7 +866,6 @@ public class PodCommsSession {
return statusResponse
}

@discardableResult
public func getDetailedStatus(beepBlock: MessageBlock? = nil) throws -> DetailedStatus {
let infoResponse: PodInfoResponse = try send([GetStatusCommand(podInfoType: .detailedStatus)], beepBlock: beepBlock)

Expand All @@ -870,14 +885,13 @@ public class PodCommsSession {
return detailedStatus
}

@discardableResult
public func readPodInfo(podInfoResponseSubType: PodInfoResponseSubType, beepBlock: MessageBlock? = nil) throws -> PodInfoResponse {
let podInfoCommand = GetStatusCommand(podInfoType: podInfoResponseSubType)
let podInfoResponse: PodInfoResponse = try send([podInfoCommand], beepBlock: beepBlock)
return podInfoResponse
}

// Reconnected to the pod, and we know program was successful
// Reconnected to the pod, and we know program was successful based on lastProgrammingMessageSeqNum
private func unacknowledgedCommandWasReceived(pendingCommand: PendingCommand, podStatus: StatusResponse) {
switch pendingCommand {
case .program(let program, _, let commandDate, _):
Expand Down Expand Up @@ -908,13 +922,69 @@ public class PodCommsSession {
}
}

// Reconnected to the pod and we didn't match lastProgrammingMessageSeqNum which indicates
// that the command was not received. Now verify the pendingCommand against the current pod
// delivery status to decide whether the delivery related command might have been received or not.
// Returns true if the command was received based on the pod delivery status and podState was updated.
private func checkCommandAgainstStatus(pendingCommand: PendingCommand, podStatus: StatusResponse) -> Bool {
let deliveryStatus = podStatus.deliveryStatus
var podStatusMatched = false
switch pendingCommand {
case .program(let program, _, let commandDate, _):
if let dose = program.unfinalizedDose(at: commandDate, withCertainty: .certain, insulinType: podState.insulinType) {
switch dose.doseType {
case .bolus:
if deliveryStatus.bolusing {
podState.unfinalizedBolus = dose
podStatusMatched = true
}
case .tempBasal:
if deliveryStatus.tempBasalRunning {
podState.unfinalizedTempBasal = dose
podStatusMatched = true
}
case .resume:
if !deliveryStatus.suspended {
podState.suspendState = .resumed(commandDate)
podStatusMatched = true
}
default:
break
}
}
case .stopProgram(let stopProgram, _, let commandDate, _):
if stopProgram.contains(.bolus), let bolus = podState.unfinalizedBolus, !bolus.isFinished(at: commandDate) {
if !deliveryStatus.bolusing {
podState.unfinalizedBolus?.cancel(at: commandDate, withRemaining: podStatus.bolusNotDelivered)
podStatusMatched = true
}
}
if stopProgram.contains(.tempBasal), let tempBasal = podState.unfinalizedTempBasal, !tempBasal.isFinished(at: commandDate) {
if !deliveryStatus.tempBasalRunning {
podState.unfinalizedTempBasal?.cancel(at: commandDate)
podStatusMatched = true
}
}
if stopProgram.contains(.basal) {
if !deliveryStatus.suspended {
podState.finalizedDoses.append(UnfinalizedDose(suspendStartTime: commandDate, scheduledCertainty: .certain))
podState.suspendState = .suspended(commandDate)
podStatusMatched = true
}
}
}
return podStatusMatched
}

public func recoverUnacknowledgedCommand(using status: StatusResponse) {
if let pendingCommand = podState.unacknowledgedCommand {
self.log.default("Recovering from unacknowledged command %{public}@, status = %{public}@", String(describing: pendingCommand), String(describing: status))

if status.lastProgrammingMessageSeqNum == pendingCommand.sequence {
self.log.default("Unacknowledged command was received by pump")
unacknowledgedCommandWasReceived(pendingCommand: pendingCommand, podStatus: status)
} else if checkCommandAgainstStatus(pendingCommand: pendingCommand, podStatus: status) {
self.log.default("Accepted unacknowledged command was received based on pod delivery status of ${public}@", String(describing: status.deliveryStatus))
} else {
self.log.default("Unacknowledged command was not received by pump")
}
Expand Down Expand Up @@ -970,6 +1040,12 @@ 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
}

let cmd = AcknowledgeAlertCommand(nonce: podState.currentNonce, alerts: alerts)
let status: StatusResponse = try send([cmd], beepBlock: beepBlock)
podState.updateFromStatusResponse(status, at: currentDate)
Expand Down