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
6 changes: 5 additions & 1 deletion OmniBLE/OmnipodCommon/Pod.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ public enum DeliveryStatus: UInt8, CustomStringConvertible {
case extendedBolusRunning = 9
case extendedBolusAndTempBasal = 10

public var suspended: Bool {
return self == .suspended
}

public var bolusing: Bool {
return self == .bolusInProgress || self == .bolusAndTempBasal || self == .extendedBolusRunning || self == .extendedBolusAndTempBasal
}
Expand All @@ -115,7 +119,7 @@ public enum DeliveryStatus: UInt8, CustomStringConvertible {
return self == .tempBasalRunning || self == .bolusAndTempBasal || self == .extendedBolusAndTempBasal
}

public var extendedBolusRunninng: Bool {
public var extendedBolusRunning: Bool {
return self == .extendedBolusRunning || self == .extendedBolusAndTempBasal
}

Expand Down
145 changes: 90 additions & 55 deletions OmniBLE/PumpManager/OmniBLEPumpManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ extension OmniBLEPumpManagerError: LocalizedError {
case .podAlreadyPaired:
return LocalizedString("Pod already paired", comment: "Error message shown when user cannot pair because pod is already paired")
case .insulinTypeNotConfigured:
return LocalizedString("Insulin type not configured", comment: "Error description for OmniBLEPumpManagerError.insulinTypeNotConfigured")
return LocalizedString("Insulin type not configured", comment: "Error description for insulin type not configured")
case .notReadyForCannulaInsertion:
return LocalizedString("Pod is not in a state ready for cannula insertion.", comment: "Error message when cannula insertion fails because the pod is in an unexpected state")
case .communication(let error):
Expand All @@ -60,7 +60,7 @@ extension OmniBLEPumpManagerError: LocalizedError {
return String(describing: error)
}
case .invalidSetting:
return LocalizedString("Invalid Setting", comment: "Error description for OmniBLEPumpManagerError.invalidSetting")
return LocalizedString("Invalid Setting", comment: "Error description for invalid setting")
}
}

Expand Down Expand Up @@ -1047,6 +1047,12 @@ extension OmniBLEPumpManager {
return
}

guard state.podState?.setupProgress == .completed else {
// A cancel delivery command before pod setup is complete will fault the pod
completion(.state(PodCommsError.setupNotComplete))
return
}

guard state.podState?.unfinalizedBolus?.isFinished() != false else {
completion(.state(PodCommsError.unfinalizedBolus))
return
Expand Down Expand Up @@ -1081,6 +1087,11 @@ extension OmniBLEPumpManager {
return .success(false)
}

guard state.podState?.setupProgress == .completed else {
// A cancel delivery command before pod setup is complete will fault the pod
return .failure(PumpManagerError.deviceState(PodCommsError.setupNotComplete))
}

guard state.podState?.unfinalizedBolus?.isFinished() != false else {
return .failure(.deviceState(PodCommsError.unfinalizedBolus))
}
Expand Down Expand Up @@ -1603,6 +1614,12 @@ extension OmniBLEPumpManager: PumpManager {
return
}

guard state.podState?.setupProgress == .completed else {
// A cancel delivery command before pod setup is complete will fault the pod
completion(.failure(PumpManagerError.deviceState(PodCommsError.setupNotComplete)))
return
}

self.podComms.runSession(withName: "Cancel Bolus") { (result) in

let session: PodCommsSession
Expand Down Expand Up @@ -1660,8 +1677,15 @@ extension OmniBLEPumpManager: PumpManager {
}

public func runTemporaryBasalProgram(unitsPerHour: Double, for duration: TimeInterval, automatic: Bool, completion: @escaping (PumpManagerError?) -> Void) {
guard self.hasActivePod else {
completion(.deviceState(OmniBLEPumpManagerError.noPodPaired))

guard self.hasActivePod, let podState = self.state.podState else {
completion(.configuration(OmniBLEPumpManagerError.noPodPaired))
return
}

guard state.podState?.setupProgress == .completed else {
// A cancel delivery command before pod setup is complete will fault the pod
completion(.deviceState(PodCommsError.setupNotComplete))
return
}

Expand All @@ -1682,86 +1706,97 @@ extension OmniBLEPumpManager: PumpManager {
return
}

do {
if case .some(.suspended) = self.state.podState?.suspendState {
self.log.info("Not enacting temp basal because podState indicates pod is suspended.")
throw PodCommsError.podSuspended
}
if case (.suspended) = podState.suspendState {
self.log.info("Not enacting temp basal because podState indicates pod is suspended.")
completion(.deviceState(PodCommsError.podSuspended))
return
}

// A resume scheduled basal delivery request is denoted by a 0 duration that cancels any existing temp basal.
let resumingScheduledBasal = duration < .ulpOfOne
// A resume scheduled basal delivery request is denoted by a 0 duration that cancels any existing temp basal.
let resumingScheduledBasal = duration < .ulpOfOne

// If a bolus is not finished, fail if not resuming the scheduled basal
guard self.state.podState?.unfinalizedBolus?.isFinished() != false || resumingScheduledBasal else {
self.log.info("Not enacting temp basal because podState indicates unfinalized bolus in progress.")
throw PodCommsError.unfinalizedBolus
}
// If a bolus is not finished, fail if not resuming the scheduled basal
guard podState.unfinalizedBolus?.isFinished() != false || resumingScheduledBasal else {
self.log.info("Not enacting temp basal because podState indicates unfinalized bolus in progress.")
completion(.deviceState(PodCommsError.unfinalizedBolus))
return
}

// Do the safe cancel TB command when resuming scheduled basal delivery OR if unfinalizedTempBasal indicates a
// running a temp basal OR if we don't have the last pod delivery status confirming that no temp basal is running.
if resumingScheduledBasal || podState.unfinalizedTempBasal != nil ||
podState.lastDeliveryStatusReceived == nil || podState.lastDeliveryStatusReceived!.tempBasalRunning
{
let status: StatusResponse

// if resuming scheduled basal delivery & an acknowledgement beep is needed, use the cancel TB beep
let beepType: BeepType = resumingScheduledBasal && acknowledgementBeep ? .beep : .noBeepCancel
let result = session.cancelDelivery(deliveryType: .tempBasal, beepType: beepType)
switch result {
case .certainFailure(let error):
throw error
completion(.communication(error))
return
case .unacknowledged(let error):
throw error
completion(.communication(error))
return
case .success(let cancelTempStatus, _):
status = cancelTempStatus
}

// If pod is bolusing, fail if not resuming the scheduled basal
guard !status.deliveryStatus.bolusing || resumingScheduledBasal else {
throw PodCommsError.unfinalizedBolus
self.log.info("Canceling temp basal because status return indicates bolus in progress.")
completion(.communication(PodCommsError.unfinalizedBolus))
return
}

guard status.deliveryStatus != .suspended else {
self.log.info("Canceling temp basal because status return indicates pod is suspended.")
throw PodCommsError.podSuspended
self.log.info("Canceling temp basal because status return indicates pod is suspended!")
completion(.communication(PodCommsError.podSuspended))
return
}
} else {
self.log.info("Skipped Cancel TB command before enacting temp basal")
}

defer {
self.setState({ (state) in
state.tempBasalEngageState = .stable
})
defer {
self.setState({ (state) in
state.tempBasalEngageState = .stable
})
}

if resumingScheduledBasal {
self.setState({ (state) in
state.tempBasalEngageState = .disengaging
})
session.dosesForStorage() { (doses) -> Bool in
return self.store(doses: doses, in: session)
}
completion(nil)
} else {
self.setState({ (state) in
state.tempBasalEngageState = .engaging
})

if resumingScheduledBasal {
self.setState({ (state) in
state.tempBasalEngageState = .disengaging
})
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = self.state.timeZone
let scheduledRate = self.state.basalSchedule.currentRate(using: calendar, at: self.dateGenerator())
let isHighTemp = rate > scheduledRate

let result = session.setTempBasal(rate: rate, duration: duration, isHighTemp: isHighTemp, automatic: automatic, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep)
switch result {
case .success:
session.dosesForStorage() { (doses) -> Bool in
return self.store(doses: doses, in: session)
}
completion(nil)
} else {
self.setState({ (state) in
state.tempBasalEngageState = .engaging
})

var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = self.state.timeZone
let scheduledRate = self.state.basalSchedule.currentRate(using: calendar, at: self.dateGenerator())
let isHighTemp = rate > scheduledRate

let result = session.setTempBasal(rate: rate, duration: duration, isHighTemp: isHighTemp, automatic: automatic, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep)

switch result {
case .success:
session.dosesForStorage() { (doses) -> Bool in
return self.store(doses: doses, in: session)
}
completion(nil)
case .unacknowledged(let error):
throw error
case .certainFailure(let error):
throw error
}
case .unacknowledged(let error):
self.log.error("Temp basal uncertain error: %@", String(describing: error))
completion(nil)
case .certainFailure(let error):
self.log.error("setTempBasal failed: %{public}@", String(describing: error))
completion(.communication(error))
}
} catch let error {
self.log.error("Error during temp basal: %{public}@", String(describing: error))
completion(.communication(error as? LocalizedError))
}
}
}
Expand Down
50 changes: 40 additions & 10 deletions OmniBLE/PumpManager/PodCommsSession.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// PodCommsSession.swift
// OmnipodKit
// OmniBLE
//
// From OmniKit/PumpManager/PodCommsSession.swift
// Created by Pete Schwamb on 10/13/17.
Expand Down Expand Up @@ -38,6 +38,7 @@ public enum PodCommsError: Error {
case podIncompatible(str: String)
case noPodsFound
case tooManyPodsFound
case setupNotComplete
}

extension PodCommsError: LocalizedError {
Expand Down Expand Up @@ -96,7 +97,8 @@ extension PodCommsError: LocalizedError {
return LocalizedString("No pods found", comment: "Error message for PodCommsError.noPodsFound")
case .tooManyPodsFound:
return LocalizedString("Too many pods found", comment: "Error message for PodCommsError.tooManyPodsFound")

case .setupNotComplete:
return LocalizedString("Pod setup is not complete", comment: "Error description when pod setup is not complete")
}
}

Expand Down Expand Up @@ -158,6 +160,8 @@ extension PodCommsError: LocalizedError {
return LocalizedString("Make sure your pod is filled and nearby.", comment: "Recovery suggestion for PodCommsError.noPodsFound")
case .tooManyPodsFound:
return LocalizedString("Move to a new area away from any other pods and try again.", comment: "Recovery suggestion for PodCommsError.tooManyPodsFound")
case .setupNotComplete:
return nil
}
}

Expand Down Expand Up @@ -274,6 +278,9 @@ public class PodCommsSession {

let message = Message(address: podState.address, messageBlocks: blocksToSend, sequenceNum: messageNumber, expectFollowOnMessage: expectFollowOnMessage)

// Clear the lastDeliveryStatusReceived variable which is used to guard against possible 0x31 pod faults
podState.lastDeliveryStatusReceived = nil

let response = try transport.sendMessage(message)

// Simulate fault
Expand Down Expand Up @@ -367,6 +374,7 @@ public class PodCommsSession {
podState.updateFromStatusResponse(status, at: currentDate)
if status.podProgressStatus == .basalInitialized {
podState.setupProgress = .initialBasalScheduleSet
podState.finalizedDoses.append(UnfinalizedDose(resumeStartTime: currentDate, scheduledCertainty: .certain, insulinType: podState.insulinType))
return
}
}
Expand Down Expand Up @@ -494,14 +502,18 @@ public class PodCommsSession {
let timeBetweenPulses = TimeInterval(seconds: Pod.secondsPerBolusPulse)
let bolusScheduleCommand = SetInsulinScheduleCommand(nonce: podState.currentNonce, units: units, timeBetweenPulses: timeBetweenPulses, extendedUnits: extendedUnits, extendedDuration: extendedDuration)

if podState.unfinalizedBolus != nil {
var ongoingBolus = true
// Do a get status here to verify that there isn't an on-going bolus in progress if the last bolus command
// is still not finalized OR we don't have the last pod delivery status confirming that no bolus is active.
if podState.unfinalizedBolus != nil || podState.lastDeliveryStatusReceived == nil || podState.lastDeliveryStatusReceived!.bolusing {
if let statusResponse: StatusResponse = try? send([GetStatusCommand()]) {
podState.updateFromStatusResponse(statusResponse, at: currentDate)
ongoingBolus = podState.unfinalizedBolus != nil
}
guard !ongoingBolus else {
return DeliveryCommandResult.certainFailure(error: .unfinalizedBolus)
guard podState.unfinalizedBolus == nil else {
log.default("bolus: pod is still bolusing")
return DeliveryCommandResult.certainFailure(error: .unfinalizedBolus)
}
} else {
log.default("bolus: failed to read pod status to verify there is no bolus running")
return DeliveryCommandResult.certainFailure(error: .noResponse)
}
}

Expand Down Expand Up @@ -602,6 +614,11 @@ public class PodCommsSession {
return .certainFailure(error: .unacknowledgedCommandPending)
}

guard podState.setupProgress == .completed else {
// A cancel delivery command before pod setup is complete will fault the pod
return .certainFailure(error: PodCommsError.setupNotComplete)
}

do {
var alertConfigurations: [AlertConfiguration] = []
var podSuspendedReminderAlert: PodAlert? = nil
Expand Down Expand Up @@ -679,6 +696,11 @@ public class PodCommsSession {
return .certainFailure(error: .unacknowledgedCommandPending)
}

guard podState.setupProgress == .completed else {
// A cancel delivery command before pod setup is complete will fault the pod
return .certainFailure(error: PodCommsError.setupNotComplete)
}

do {
podState.unacknowledgedCommand = PendingCommand.stopProgram(deliveryType, transport.messageNumber, currentDate)
let cancelDeliveryCommand = CancelDeliveryCommand(nonce: podState.currentNonce, deliveryType: deliveryType, beepType: beepType)
Expand Down Expand Up @@ -727,6 +749,14 @@ public class PodCommsSession {
let basalExtraCommand = BasalScheduleExtraCommand.init(schedule: schedule, scheduleOffset: scheduleOffset, acknowledgementBeep: acknowledgementBeep, programReminderInterval: programReminderInterval)

do {
if !podState.isSuspended || podState.lastDeliveryStatusReceived == nil || !podState.lastDeliveryStatusReceived!.suspended {
// The podState or the last pod delivery status return indicates that the pod is not currently suspended.
// So execute a cancel all command here before setting the basal to prevent a possible 0x31 pod fault,
// but only when the pod startup is complete as a cancel command during pod setup also fault the pod!
if podState.setupProgress == .completed {
let _: StatusResponse = try send([CancelDeliveryCommand(nonce: podState.currentNonce, deliveryType: .all, beepType: .noBeepCancel)])
}
}
var status: StatusResponse = try send([basalScheduleCommand, basalExtraCommand])
let now = currentDate
podState.suspendState = .resumed(now)
Expand Down Expand Up @@ -856,10 +886,10 @@ public class PodCommsSession {
self.log.default("Recovering from unacknowledged command %{public}@, status = %{public}@", String(describing: pendingCommand), String(describing: status))

if status.lastProgrammingMessageSeqNum == pendingCommand.sequence {
self.log.debug("Unacknowledged command was received by pump")
self.log.default("Unacknowledged command was received by pump")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

unacknowledgedCommandWasReceived(pendingCommand: pendingCommand, podStatus: status)
} else {
self.log.debug("Unacknowledged command was not received by pump")
self.log.default("Unacknowledged command was not received by pump")
}
podState.unacknowledgedCommand = nil
}
Expand Down
Loading