Skip to content
Merged
Show file tree
Hide file tree
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
9 changes: 7 additions & 2 deletions OmniKit/OmnipodCommon/MessageBlocks/DetailedStatus.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@ public struct DetailedStatus : PodInfo, Equatable {
}
self.podProgressStatus = PodProgressStatus(rawValue: encodedData[1])!

self.deliveryStatus = DeliveryStatus(rawValue: encodedData[2] & 0xf)!

self.bolusNotDelivered = Double((Int(encodedData[3] & 0x3) << 8) | Int(encodedData[4])) / Pod.pulsesPerUnit

self.lastProgrammingMessageSeqNum = encodedData[5]
Expand All @@ -54,6 +52,13 @@ public struct DetailedStatus : PodInfo, Equatable {

self.faultEventCode = FaultEventCode(rawValue: encodedData[8])

/// Older pod simulators didn't know that all faulted pods are suspended, so handle this here
if self.faultEventCode.faultType != .noFaults {
self.deliveryStatus = .suspended
} else {
self.deliveryStatus = DeliveryStatus(rawValue: encodedData[2] & 0xf)!
}

let minutesSinceActivation = encodedData[9...10].toBigEndian(UInt16.self)
if minutesSinceActivation != 0xffff {
self.faultEventTimeSinceActivation = TimeInterval(minutes: Double(minutesSinceActivation))
Expand Down
4 changes: 3 additions & 1 deletion OmniKit/PumpManager/OmnipodPumpManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -708,7 +708,9 @@ extension OmnipodPumpManager {
state.updatePodStateFromPodComms(nil)
}

podComms.forgetPod()
self.podComms.handleDiscardedPodDosing(podTime: podTime, reservoirLevel: reservoirLevel?.rawValue)

self.podComms.forgetPod()

self.resetPerPodPumpManagerState()

Expand Down
51 changes: 51 additions & 0 deletions OmniKit/PumpManager/PodComms.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,57 @@ class PodComms: CustomDebugStringConvertible {
podStateLock.unlock()
}

/// Handle any dosing and pump event cleanup when discarding a pod without going thru normal pod deactivation
func handleDiscardedPodDosing(podTime: TimeInterval, reservoirLevel: Double?) {
guard podState != nil else {
return
}

/// Any suspended pod either already went through normal pod deactivation that does a cancelDelivery,
/// pod fault handling for the faulted and suspended pod &/or was already suspended by the user.
guard !podState!.isSuspended else {
return
}

/// If the initial basal still needs to be programmed,
/// then don't create suspend pump event without the resume.
guard !podState!.setupProgress.needsInitialBasalSchedule else {
return
}

podStateLock.lock()
let now = Date()

/// Compute the bolusNotDelivered if there was a bolus in progress when the pod was discarded.
var bolusNotDelivered = 0.0
if let bolus = podState!.unfinalizedBolus {
let bolusDelivered = (((bolus.units * bolus.progress(at: now)) / Pod.pulseSize).rounded()) * Pod.pulseSize
log.info("Cancelling unfinished bolus with calculated bolus delivered of %@", bolusDelivered.twoDecimals)
bolusNotDelivered = bolus.units - bolusDelivered
}

/// Use handleCancelDosing() to update the dosing for a cancel all command (assuming the user removes the pod as directed).
/// This includes suspending the pod and handle cancelling any in-progress tempBasal and bolus doses based on the current time.
podState!.handleCancelDosing(deliveryType: .all, bolusNotDelivered: bolusNotDelivered, at: now)

/// Now create a fake cancel all response and use it to update the podState.
/// This has the side effects of updating lastSync as well as finalizing the
/// unfinalizedSuspend and any unfinalized bolus or tempBasal doses.
let fakeSuspendedResponse = StatusResponse(
deliveryStatus: .suspended, // faking a suspended pod response
podProgressStatus: .aboveFiftyUnits, // any nominal value should be fine
timeActive: podTime, // current adjusted pod time as of now
reservoirLevel: reservoirLevel ?? Pod.reservoirLevelAboveThresholdMagicNumber, // re-use last response
insulinDelivered: 0.0, // this value will be ignored when it's less than previous value
bolusNotDelivered: bolusNotDelivered, // might be non-zero for an in-progress bolus
lastProgrammingMessageSeqNum: 0, // not important
alerts: .none
)
podState!.updateFromStatusResponse(fakeSuspendedResponse, at: now)

podStateLock.unlock()
}

func forgetPod() {
podStateLock.lock()
self.podState?.resolveAnyPendingCommandWithUncertainty()
Expand Down
42 changes: 3 additions & 39 deletions OmniKit/PumpManager/PodCommsSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ public class PodCommsSession {
// N.B., recoverUnacknowledgedCommand() skips using bolusNotDelivered for a stopProgram with a faulted pod.
recoverUnacknowledgedCommand(using: derivedStatusResponse)
}
handleCancelDosing(deliveryType: .all, bolusNotDelivered: derivedStatusResponse.bolusNotDelivered)
podState.handleCancelDosing(deliveryType: .all, bolusNotDelivered: derivedStatusResponse.bolusNotDelivered, at: currentDate)
podState.updateFromStatusResponse(derivedStatusResponse, at: currentDate)
}
log.error("Pod Fault: %@", String(describing: fault))
Expand Down Expand Up @@ -644,42 +644,6 @@ public class PodCommsSession {
}
}

@discardableResult
private func handleCancelDosing(deliveryType: CancelDeliveryCommand.DeliveryType, bolusNotDelivered: Double) -> UnfinalizedDose? {
var canceledDose: UnfinalizedDose? = nil
let now = currentDate

if deliveryType.contains(.basal) {
podState.unfinalizedSuspend = UnfinalizedDose(suspendStartTime: now, scheduledCertainty: .certain)
podState.suspendState = .suspended(now)
}

if let unfinalizedTempBasal = podState.unfinalizedTempBasal,
let finishTime = unfinalizedTempBasal.finishTime,
deliveryType.contains(.tempBasal),
finishTime > now
{
podState.unfinalizedTempBasal?.cancel(at: now)
if !deliveryType.contains(.basal) {
podState.suspendState = .resumed(now)
}
canceledDose = podState.unfinalizedTempBasal
log.info("Interrupted temp basal: %@", String(describing: canceledDose))
}

if let unfinalizedBolus = podState.unfinalizedBolus,
let finishTime = unfinalizedBolus.finishTime,
deliveryType.contains(.bolus),
finishTime > now
{
podState.unfinalizedBolus?.cancel(at: now, withRemaining: bolusNotDelivered)
canceledDose = podState.unfinalizedBolus
log.info("Interrupted bolus: %@", String(describing: canceledDose))
}

return canceledDose
}

// Suspends insulin delivery and sets appropriate podSuspendedReminder & suspendTimeExpired alerts.
// A nil suspendReminder is an untimed suspend with no suspend reminders.
// A suspendReminder of 0 is an untimed suspend which only uses podSuspendedReminder alert beeps.
Expand Down Expand Up @@ -736,7 +700,7 @@ public class PodCommsSession {
podState.unacknowledgedCommand = PendingCommand.stopProgram(.all, transport.messageNumber, currentDate)
let status: StatusResponse = try send(commandsToSend, beepBlock: beepBlock)
podState.unacknowledgedCommand = nil
let canceledDose = handleCancelDosing(deliveryType: .all, bolusNotDelivered: status.bolusNotDelivered)
let canceledDose = podState.handleCancelDosing(deliveryType: .all, bolusNotDelivered: status.bolusNotDelivered, at: currentDate)
podState.updateFromStatusResponse(status, at: currentDate)

if let alert = podSuspendedReminderAlert {
Expand Down Expand Up @@ -795,7 +759,7 @@ public class PodCommsSession {
let status: StatusResponse = try send([cancelDeliveryCommand], beepBlock: beepBlock)
podState.unacknowledgedCommand = nil

let canceledDose = handleCancelDosing(deliveryType: deliveryType, bolusNotDelivered: status.bolusNotDelivered)
let canceledDose = podState.handleCancelDosing(deliveryType: deliveryType, bolusNotDelivered: status.bolusNotDelivered, at: currentDate)
podState.updateFromStatusResponse(status, at: currentDate)

return CancelDeliveryResult.success(statusResponse: status, canceledDose: canceledDose)
Expand Down
36 changes: 36 additions & 0 deletions OmniKit/PumpManager/PodState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,42 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl
}
}

@discardableResult
mutating func handleCancelDosing(deliveryType: CancelDeliveryCommand.DeliveryType, bolusNotDelivered: Double, at now: Date = Date()) -> UnfinalizedDose?
{
var canceledDose: UnfinalizedDose? = nil

if deliveryType.contains(.basal) {
unfinalizedSuspend = UnfinalizedDose(suspendStartTime: now, scheduledCertainty: .certain)
suspendState = .suspended(now)
}

if let tempBasal = unfinalizedTempBasal,
let finishTime = tempBasal.finishTime,
deliveryType.contains(.tempBasal),
finishTime > now
{
unfinalizedTempBasal?.cancel(at: now)
if !deliveryType.contains(.basal) {
suspendState = .resumed(now)
}
canceledDose = unfinalizedTempBasal
print("Interrupted temp basal: \(String(describing: canceledDose))")
}

if let bolus = unfinalizedBolus,
let finishTime = bolus.finishTime,
deliveryType.contains(.bolus),
finishTime > now
{
unfinalizedBolus?.cancel(at: now, withRemaining: bolusNotDelivered)
canceledDose = unfinalizedBolus
print("Interrupted bolus: \(String(describing: canceledDose))")
}

return canceledDose
}

// MARK: - RawRepresentable
public init?(rawValue: RawValue) {

Expand Down