Skip to content

Commit

Permalink
Backport Omni{BLE/Kit} Dependencies from LoopKit (nightscout#18)
Browse files Browse the repository at this point in the history
* Backport LoopKit OmniBLE nightscout#110 and OmniKit nightscout#24 (iAPS #542)

* Backport OmniBLE nightscout#113 and OmniKit nightscout#27 (fixes Loop issue #2117) (iAPS #554)
  • Loading branch information
dnzxy committed Mar 1, 2024
1 parent dcde330 commit 08281fe
Show file tree
Hide file tree
Showing 19 changed files with 406 additions and 302 deletions.
10 changes: 7 additions & 3 deletions Dependencies/OmniBLE/OmniBLE/OmnipodCommon/Pod.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,16 +106,20 @@ public enum DeliveryStatus: UInt8, CustomStringConvertible {
case bolusAndTempBasal = 6
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
}

public var tempBasalRunning: Bool {
return self == .tempBasalRunning || self == .bolusAndTempBasal || self == .extendedBolusAndTempBasal
}

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

Expand Down
202 changes: 116 additions & 86 deletions Dependencies/OmniBLE/OmniBLE/PumpManager/OmniBLEPumpManager.swift

Large diffs are not rendered by default.

56 changes: 38 additions & 18 deletions Dependencies/OmniBLE/OmniBLE/PumpManager/PodCommsSession.swift
Original file line number Diff line number Diff line change
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,7 +278,9 @@ public class PodCommsSession {

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

self.podState.lastCommsOK = false // mark last comms as not OK until we get the expected response
// 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 All @@ -283,7 +289,6 @@ public class PodCommsSession {

if let responseMessageBlock = response.messageBlocks[0] as? T {
log.info("POD Response: %{public}@", String(describing: responseMessageBlock))
self.podState.lastCommsOK = true // message successfully sent and expected response received
return responseMessageBlock
}

Expand Down Expand Up @@ -431,7 +436,6 @@ public class PodCommsSession {

public func insertCannula(optionalAlerts: [PodAlert] = [], silent: Bool) throws -> TimeInterval {
let cannulaInsertionUnits = Pod.cannulaInsertionUnits + Pod.cannulaInsertionUnitsExtra
let insertionWait: TimeInterval = .seconds(cannulaInsertionUnits / Pod.primeDeliveryRate)

guard podState.activatedAt != nil else {
throw PodCommsError.noPodPaired
Expand All @@ -443,7 +447,8 @@ public class PodCommsSession {
if status.podProgressStatus == .insertingCannula {
podState.setupProgress = .cannulaInserting
podState.updateFromStatusResponse(status, at: currentDate)
return insertionWait // Not sure when it started, wait full time to be sure
// 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)
Expand Down Expand Up @@ -472,7 +477,7 @@ public class PodCommsSession {
podState.updateFromStatusResponse(status2, at: currentDate)

podState.setupProgress = .cannulaInserting
return insertionWait
return status2.bolusNotDelivered / Pod.primeDeliveryRate // seconds for the cannula insert bolus to finish
}

public func checkInsertionCompleted() throws {
Expand Down Expand Up @@ -508,16 +513,18 @@ public class PodCommsSession {
let timeBetweenPulses = TimeInterval(seconds: Pod.secondsPerBolusPulse)
let bolusScheduleCommand = SetInsulinScheduleCommand(nonce: podState.currentNonce, units: units, timeBetweenPulses: timeBetweenPulses, extendedUnits: extendedUnits, extendedDuration: extendedDuration)

// Do a getstatus to verify that there isn't an on-going bolus in progress if the last bolus command is still
// finalized, if the last delivery status wasn't successfully verified or the last comms attempt wasn't OK
if podState.unfinalizedBolus != nil || !podState.deliveryStatusVerified || !podState.lastCommsOK {
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 @@ -619,6 +626,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 @@ -699,6 +711,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 @@ -747,10 +764,13 @@ public class PodCommsSession {
let basalExtraCommand = BasalScheduleExtraCommand.init(schedule: schedule, scheduleOffset: scheduleOffset, acknowledgementBeep: acknowledgementBeep, programReminderInterval: programReminderInterval)

do {
if podState.setupProgress == .completed && !(podState.lastCommsOK && podState.deliveryStatusVerified) {
// The pod setup is complete and the current delivery state can't be trusted so
// do a cancel all to be sure that setting the basal program won't fault the pod.
let _: StatusResponse = try send([CancelDeliveryCommand(nonce: podState.currentNonce, deliveryType: .all, beepType: .noBeepCancel)])
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
Expand Down
35 changes: 18 additions & 17 deletions Dependencies/OmniBLE/OmniBLE/PumpManager/PodState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ public enum SetupProgress: Int {
public var needsCannulaInsertion: Bool {
return self.rawValue < SetupProgress.completed.rawValue
}

public var cannulaInsertionSuccessfullyStarted: Bool {
return self.rawValue > SetupProgress.startingInsertCannula.rawValue
}
}

// TODO: Mutating functions aren't guaranteed to synchronize read/write calls.
Expand Down Expand Up @@ -111,11 +115,12 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl
return false
}

// the following two vars are not persistent across app restarts
public var deliveryStatusVerified: Bool
public var lastCommsOK: Bool
var lastDeliveryStatusReceived: DeliveryStatus? // this variable is not persistent across app restarts


public init(address: UInt32, ltk: Data, firmwareVersion: String, bleFirmwareVersion: String, lotNo: UInt32, lotSeq: UInt32, productId: UInt8, messageTransportState: MessageTransportState? = nil, bleIdentifier: String, insulinType: InsulinType) {
public init(address: UInt32, ltk: Data, firmwareVersion: String, bleFirmwareVersion: String, lotNo: UInt32, lotSeq: UInt32, productId: UInt8,
messageTransportState: MessageTransportState? = nil, bleIdentifier: String, insulinType: InsulinType, initialDeliveryStatus: DeliveryStatus? = nil)
{
self.address = address
self.ltk = ltk
self.firmwareVersion = firmwareVersion
Expand All @@ -134,9 +139,8 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl
self.configuredAlerts = [.slot7Expired: .waitingForPairingReminder]
self.bleIdentifier = bleIdentifier
self.insulinType = insulinType
self.deliveryStatusVerified = false
self.lastCommsOK = false
self.podTime = 0
self.lastDeliveryStatusReceived = initialDeliveryStatus // can be non-nil when testing
}

public var unfinishedSetup: Bool {
Expand Down Expand Up @@ -293,24 +297,22 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl
self.unacknowledgedCommand = nil
}


private mutating func updateDeliveryStatus(deliveryStatus: DeliveryStatus, podProgressStatus: PodProgressStatus, bolusNotDelivered: Double, at date: Date) {

deliveryStatusVerified = true
// See if the pod deliveryStatus indicates an active bolus or temp basal that the PodState isn't tracking (possible Loop restart)
if deliveryStatus.bolusing && unfinalizedBolus == nil { // active bolus that Loop doesn't know about?
// save the current pod delivery state for verification before any insulin delivery command
self.lastDeliveryStatusReceived = deliveryStatus

// See if the pod's deliveryStatus indicates some insulin delivery that podState isn't tracking
if deliveryStatus.bolusing && unfinalizedBolus == nil { // active bolus that we aren't tracking
if podProgressStatus.readyForDelivery {
deliveryStatusVerified = false // remember that we had inconsistent (bolus) delivery status
// Create an unfinalizedBolus with the remaining bolus amount to capture what we can.
unfinalizedBolus = UnfinalizedDose(bolusAmount: bolusNotDelivered, startTime: date, scheduledCertainty: .certain, insulinType: insulinType, automatic: false)
}
}
if deliveryStatus.tempBasalRunning && unfinalizedTempBasal == nil { // active temp basal that app isn't tracking
deliveryStatusVerified = false // remember that we had inconsistent (temp basal) delivery status
if deliveryStatus.tempBasalRunning && unfinalizedTempBasal == nil { // active temp basal that we aren't tracking
// unfinalizedTempBasal = UnfinalizedDose(tempBasalRate: 0, startTime: Date(), duration: .minutes(30), isHighTemp: false, scheduledCertainty: .certain, insulinType: insulinType)
}
if deliveryStatus != .suspended && isSuspended { // active basal that app isn't tracking
deliveryStatusVerified = false // remember that we had inconsistent (basal) delivery status
if deliveryStatus != .suspended && isSuspended { // active basal that we aren't tracking
let resumeStartTime = Date()
suspendState = .resumed(resumeStartTime)
unfinalizedResume = UnfinalizedDose(resumeStartTime: resumeStartTime, scheduledCertainty: .certain, insulinType: insulinType)
Expand Down Expand Up @@ -511,8 +513,7 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl
self.insulinType = .novolog
}

self.deliveryStatusVerified = false
self.lastCommsOK = false
self.lastDeliveryStatusReceived = nil
}

public var rawValue: RawValue {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ enum DashUIScreen {
case expirationReminderSetup
case lowReservoirReminderSetup
case insulinTypeSelection
case pairPod
case pairAndPrime
case insertCannula
case confirmAttachment
case checkInsertedCannula
Expand All @@ -38,8 +38,8 @@ enum DashUIScreen {
case .lowReservoirReminderSetup:
return .insulinTypeSelection
case .insulinTypeSelection:
return .pairPod
case .pairPod:
return .pairAndPrime
case .pairAndPrime:
return .confirmAttachment
case .confirmAttachment:
return .insertCannula
Expand All @@ -54,7 +54,7 @@ enum DashUIScreen {
case .uncertaintyRecovered:
return nil
case .deactivate:
return .pairPod
return .pairAndPrime
case .settings:
return nil
}
Expand Down Expand Up @@ -171,7 +171,7 @@ class DashUICoordinator: UINavigationController, PumpManagerOnboarding, Completi
}
let view = OmniBLESettingsView(viewModel: viewModel, supportedInsulinTypes: allowedInsulinTypes)
return hostingController(rootView: view)
case .pairPod:
case .pairAndPrime:
pumpManagerOnboardingDelegate?.pumpManagerOnboarding(didCreatePumpManager: pumpManager)

let viewModel = PairPodViewModel(podPairer: pumpManager)
Expand All @@ -185,7 +185,7 @@ class DashUICoordinator: UINavigationController, PumpManagerOnboarding, Completi
viewModel.didRequestDeactivation = { [weak self] in
self?.navigateTo(.deactivate)
}

let view = hostingController(rootView: PairPodView(viewModel: viewModel))
view.navigationItem.title = LocalizedString("Pair Pod", comment: "Title for pod pairing screen")
view.navigationItem.backButtonDisplayMode = .generic
Expand Down Expand Up @@ -350,13 +350,13 @@ class DashUICoordinator: UINavigationController, PumpManagerOnboarding, Completi
if pumpManager.podAttachmentConfirmed {
return .insertCannula
} else {
return .confirmAttachment
return .pairAndPrime // need to finish the priming
}
} else if !pumpManager.isOnboarded {
if !pumpManager.initialConfigurationCompleted {
return .firstRunScreen
}
return .pairPod
return .pairAndPrime // pair and prime a new pod
} else {
return .settings
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,14 @@ import LoopKitUI
public protocol CannulaInserter {
func insertCannula(completion: @escaping (Result<TimeInterval,OmniBLEPumpManagerError>) -> ())
func checkCannulaInsertionFinished(completion: @escaping (OmniBLEPumpManagerError?) -> Void)
var cannulaInsertionSuccessfullyStarted: Bool { get }
}

extension OmniBLEPumpManager: CannulaInserter { }
extension OmniBLEPumpManager: CannulaInserter {
public var cannulaInsertionSuccessfullyStarted: Bool {
return state.podState?.setupProgress.cannulaInsertionSuccessfullyStarted == true
}
}

class InsertCannulaViewModel: ObservableObject, Identifiable {

Expand All @@ -28,9 +33,9 @@ class InsertCannulaViewModel: ObservableObject, Identifiable {

var actionButtonAccessibilityLabel: String {
switch self {
case .ready, .startingInsertion:
case .ready:
return LocalizedString("Slide Button to insert Cannula", comment: "Insert cannula slider button accessibility label while ready to pair")
case .inserting:
case .inserting, .startingInsertion:
return LocalizedString("Inserting. Please wait.", comment: "Insert cannula action button accessibility label while pairing")
case .checkingInsertion:
return LocalizedString("Checking Insertion", comment: "Insert cannula action button accessibility label checking insertion")
Expand Down Expand Up @@ -142,22 +147,15 @@ class InsertCannulaViewModel: ObservableObject, Identifiable {

init(cannulaInserter: CannulaInserter) {
self.cannulaInserter = cannulaInserter

// If resuming, don't wait for the button action
if cannulaInserter.cannulaInsertionSuccessfullyStarted {
insertCannula()
}
}

// private func handleEvent(_ event: ActivationStep2Event) {
// switch event {
// case .insertingCannula:
// let finishTime = TimeInterval(Pod.estimatedCannulaInsertionDuration)
// state = .inserting(finishTime: CACurrentMediaTime() + finishTime)
// case .step2Completed:
// state = .finished
// default:
// break
// }
// }


private func checkCannulaInsertionFinished() {
state = .startingInsertion
state = .checkingInsertion
cannulaInserter.checkCannulaInsertionFinished() { (error) in
DispatchQueue.main.async {
if let error = error {
Expand All @@ -171,7 +169,7 @@ class InsertCannulaViewModel: ObservableObject, Identifiable {

private func insertCannula() {
state = .startingInsertion

cannulaInserter.insertCannula { (result) in
DispatchQueue.main.async {
switch(result) {
Expand All @@ -189,14 +187,6 @@ class InsertCannulaViewModel: ObservableObject, Identifiable {
self.state = .error(error)
}
}


// switch status {
// case .error(let error):
// self.state = .error(error)
// case .event(let event):
// self.handleEvent(event)
// }
}
}

Expand All @@ -214,7 +204,6 @@ class InsertCannulaViewModel: ObservableObject, Identifiable {
insertCannula()
}
}

}

public extension OmniBLEPumpManagerError {
Expand Down
Loading

0 comments on commit 08281fe

Please sign in to comment.