Sfoglia il codice sorgente

Backport Omni{BLE/Kit} Dependencies from LoopKit (#18)

* Backport LoopKit OmniBLE #110 and OmniKit #24 (iAPS #542)

* Backport OmniBLE #113 and OmniKit #27 (fixes Loop issue #2117) (iAPS #554)
Deniz Cengiz 2 anni fa
parent
commit
08281fe45d
19 ha cambiato i file con 406 aggiunte e 302 eliminazioni
  1. 7 3
      Dependencies/OmniBLE/OmniBLE/OmnipodCommon/Pod.swift
  2. 116 86
      Dependencies/OmniBLE/OmniBLE/PumpManager/OmniBLEPumpManager.swift
  3. 38 18
      Dependencies/OmniBLE/OmniBLE/PumpManager/PodCommsSession.swift
  4. 18 17
      Dependencies/OmniBLE/OmniBLE/PumpManager/PodState.swift
  5. 8 8
      Dependencies/OmniBLE/OmniBLE/PumpManagerUI/ViewControllers/DashUICoordinator.swift
  6. 16 27
      Dependencies/OmniBLE/OmniBLE/PumpManagerUI/ViewModels/InsertCannulaViewModel.swift
  7. 29 22
      Dependencies/OmniBLE/OmniBLE/PumpManagerUI/ViewModels/PairPodViewModel.swift
  8. 1 1
      Dependencies/OmniBLE/OmniBLE/PumpManagerUI/ViewModels/PodLifeState.swift
  9. 4 1
      Dependencies/OmniBLE/OmniBLE/PumpManagerUI/Views/InsertCannulaView.swift
  10. 7 3
      Dependencies/OmniKit/OmniKit/OmnipodCommon/Pod.swift
  11. 46 22
      Dependencies/OmniKit/OmniKit/PumpManager/OmnipodPumpManager.swift
  12. 38 18
      Dependencies/OmniKit/OmniKit/PumpManager/PodCommsSession.swift
  13. 16 16
      Dependencies/OmniKit/OmniKit/PumpManager/PodState.swift
  14. 7 3
      Dependencies/OmniKit/OmniKitTests/PodCommsSessionTests.swift
  15. 7 7
      Dependencies/OmniKit/OmniKitUI/ViewControllers/OmnipodUICoordinator.swift
  16. 16 27
      Dependencies/OmniKit/OmniKitUI/ViewModels/InsertCannulaViewModel.swift
  17. 28 21
      Dependencies/OmniKit/OmniKitUI/ViewModels/PairPodViewModel.swift
  18. 1 1
      Dependencies/OmniKit/OmniKitUI/ViewModels/PodLifeState.swift
  19. 3 1
      Dependencies/OmniKit/OmniKitUI/Views/InsertCannulaView.swift

+ 7 - 3
Dependencies/OmniBLE/OmniBLE/OmnipodCommon/Pod.swift

@@ -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
     }
 

+ 116 - 86
Dependencies/OmniBLE/OmniBLE/PumpManager/OmniBLEPumpManager.swift

@@ -32,7 +32,6 @@ public enum OmniBLEPumpManagerError: Error {
     case insulinTypeNotConfigured
     case notReadyForCannulaInsertion
     case invalidSetting
-    case setupNotComplete
     case communication(Error)
     case state(Error)
 }
@@ -45,13 +44,11 @@ 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 .invalidSetting:
             return LocalizedString("Invalid Setting", comment: "Error description for invalid setting")
-        case .setupNotComplete:
-            return LocalizedString("Pod setup is not complete", comment: "Error description when pod setup is not complete")
         case .communication(let error):
             if let error = error as? LocalizedError {
                 return error.errorDescription
@@ -458,7 +455,7 @@ extension OmniBLEPumpManager {
         } else if !podState.isSetupComplete {
             return .activating
         }
-        return .deactivating
+        return .deactivating // Can't be reached and thus will never be returned
     }
 
     public var podCommState: PodCommState {
@@ -599,7 +596,7 @@ extension OmniBLEPumpManager {
         switch podCommState(for: state) {
         case .activating:
             return PumpStatusHighlight(
-                localizedMessage: LocalizedString("Finish Pairing", comment: "Status highlight that when pod is activating."),
+                localizedMessage: LocalizedString("Finish Setup", comment: "Status highlight that when pod is activating."),
                 imageName: "exclamationmark.circle.fill",
                 state: .warning)
         case .deactivating:
@@ -710,6 +707,25 @@ extension OmniBLEPumpManager {
         }
     }
 
+    // Reset all the per pod state kept in pump manager state which doesn't span pods
+    fileprivate func resetPerPodPumpManagerState() {
+
+        // Reset any residual per pod slot based pump manager alerts
+        // (i.e., all but timeOffsetChangeDetected which isn't actually used)
+        let podAlerts = state.activeAlerts.filter { $0 != .timeOffsetChangeDetected }
+        for alert in podAlerts {
+            self.retractAlert(alert: alert)
+        }
+
+        self.setState { (state) in
+            // Reset alertsWithPendingAcknowledgment which are all pod slot based
+            state.alertsWithPendingAcknowledgment = []
+
+            // Reset other miscellaneous state variables that are actually per pod
+            state.podAttachmentConfirmed = false
+            state.acknowledgedTimeOffsetAlert = false
+        }
+    }
 
     // MARK: - Pod comms
 
@@ -737,13 +753,14 @@ extension OmniBLEPumpManager {
 
         self.podComms.forgetPod()
 
+        self.resetPerPodPumpManagerState()
+
         if let dosesToStore = state.podState?.dosesToStore {
             store(doses: dosesToStore, completion: { error in
                 self.setState({ (state) in
                     if error != nil {
                         state.unstoredDoses.append(contentsOf: dosesToStore)
                     }
-                    state.alertsWithPendingAcknowledgment = []
                 })
                 self.prepForNewPod()
                 completion()
@@ -778,6 +795,8 @@ extension OmniBLEPumpManager {
         self.podComms.delegate = self
         self.podComms.messageLogger = self
 
+        self.resetPerPodPumpManagerState()
+
         setState({ (state) in
             state.updatePodStateFromPodComms(podState)
             state.scheduledExpirationReminderOffset = state.defaultExpirationReminderOffset
@@ -875,6 +894,8 @@ extension OmniBLEPumpManager {
                     self.podComms.pairAndSetupPod(timeZone: .currentFixed, insulinType: insulinType, messageLogger: self)
                     { (result) in
 
+                        self.resetPerPodPumpManagerState()
+
                         // Calls completion
                         primeSession(result)
                     }
@@ -1087,7 +1108,7 @@ extension OmniBLEPumpManager {
 
         guard state.podState?.setupProgress == .completed else {
             // A cancel delivery command before pod setup is complete will fault the pod
-            completion(.state(OmniBLEPumpManagerError.setupNotComplete))
+            completion(.state(PodCommsError.setupNotComplete))
             return
         }
 
@@ -1127,7 +1148,7 @@ extension OmniBLEPumpManager {
 
             guard state.podState?.setupProgress == .completed else {
                 // A cancel delivery command before pod setup is complete will fault the pod
-                return .failure(PumpManagerError.deviceState(OmniBLEPumpManagerError.setupNotComplete))
+                return .failure(PumpManagerError.deviceState(PodCommsError.setupNotComplete))
             }
 
             guard state.podState?.unfinalizedBolus?.isFinished() != false else {
@@ -1822,7 +1843,7 @@ extension OmniBLEPumpManager: PumpManager {
 
         guard state.podState?.setupProgress == .completed else {
             // A cancel delivery command before pod setup is complete will fault the pod
-            completion(.failure(PumpManagerError.deviceState(OmniBLEPumpManagerError.setupNotComplete)))
+            completion(.failure(PumpManagerError.deviceState(PodCommsError.setupNotComplete)))
             return
         }
 
@@ -1883,8 +1904,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
         }
 
@@ -1917,95 +1945,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
-                }
-
-                // A resume scheduled basal delivery request is denoted by a 0 duration that cancels any existing temp basal.
-                let resumingScheduledBasal = duration < .ulpOfOne
+            if case (.suspended) = podState.suspendState {
+                self.log.info("Not enacting temp basal because podState indicates pod is suspended.")
+                completion(.deviceState(PodCommsError.podSuspended))
+                return
+            }
 
-                // 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
-                }
+            // A resume scheduled basal delivery request is denoted by a 0 duration that cancels any existing temp basal.
+            let resumingScheduledBasal = duration < .ulpOfOne
 
-                // Did the last message have comms issues or is the last delivery status not verified correctly?
-                let uncertainDeliveryStatus = self.state.podState?.lastCommsOK == false || self.state.podState?.deliveryStatusVerified == false
+            // 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 cancel temp basal command if currently running a temp basal OR
-                // if resuming scheduled basal delivery OR if the delivery status is uncertain.
-                if self.state.podState?.unfinalizedTempBasal != nil || resumingScheduledBasal || uncertainDeliveryStatus {
-                    let status: StatusResponse
+            // 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
-                    case .unacknowledged(let error):
-                        throw error
-                    case .success(let cancelTempStatus, _):
-                        status = cancelTempStatus
-                    }
+                // 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):
+                    completion(.communication(error))
+                    return
+                case .unacknowledged(let 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
-                    }
+                // If pod is bolusing, fail if not resuming the scheduled basal
+                guard !status.deliveryStatus.bolusing || resumingScheduledBasal else {
+                    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
-                    }
-                } else {
-                    self.log.info("Skipped Cancel TB command before enacting temp basal")
+                guard status.deliveryStatus != .suspended else {
+                    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))
             }
         }
     }

+ 38 - 18
Dependencies/OmniBLE/OmniBLE/PumpManager/PodCommsSession.swift

@@ -38,6 +38,7 @@ public enum PodCommsError: Error {
     case podIncompatible(str: String)
     case noPodsFound
     case tooManyPodsFound
+    case setupNotComplete
 }
 
 extension PodCommsError: LocalizedError {
@@ -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")
         }
     }
     
@@ -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
         }
     }
 
@@ -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
@@ -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
             }
 
@@ -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
@@ -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)
@@ -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 {
@@ -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)
             }
         }
 
@@ -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
@@ -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)
@@ -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

+ 18 - 17
Dependencies/OmniBLE/OmniBLE/PumpManager/PodState.swift

@@ -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.
@@ -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
@@ -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 {
@@ -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)
@@ -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 {

+ 8 - 8
Dependencies/OmniBLE/OmniBLE/PumpManagerUI/ViewControllers/DashUICoordinator.swift

@@ -19,7 +19,7 @@ enum DashUIScreen {
     case expirationReminderSetup
     case lowReservoirReminderSetup
     case insulinTypeSelection
-    case pairPod
+    case pairAndPrime
     case insertCannula
     case confirmAttachment
     case checkInsertedCannula
@@ -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
@@ -54,7 +54,7 @@ enum DashUIScreen {
         case .uncertaintyRecovered:
             return nil
         case .deactivate:
-            return .pairPod
+            return .pairAndPrime
         case .settings:
             return nil
         }
@@ -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)
@@ -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
@@ -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
         }

+ 16 - 27
Dependencies/OmniBLE/OmniBLE/PumpManagerUI/ViewModels/InsertCannulaViewModel.swift

@@ -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 {
 
@@ -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")
@@ -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 {
@@ -171,7 +169,7 @@ class InsertCannulaViewModel: ObservableObject, Identifiable {
     
     private func insertCannula() {
         state = .startingInsertion
-        
+
         cannulaInserter.insertCannula { (result) in
             DispatchQueue.main.async {
                 switch(result) {
@@ -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)
-//            }
         }
     }
     
@@ -214,7 +204,6 @@ class InsertCannulaViewModel: ObservableObject, Identifiable {
             insertCannula()
         }
     }
-    
 }
 
 public extension OmniBLEPumpManagerError {

+ 29 - 22
Dependencies/OmniBLE/OmniBLE/PumpManagerUI/ViewModels/PairPodViewModel.swift

@@ -38,7 +38,7 @@ class PairPodViewModel: ObservableObject, Identifiable {
     enum PairPodViewModelState {
         case ready
         case pairing
-        case priming(finishTime: CFTimeInterval)
+        case priming(finishTime: CFTimeInterval?)
         case error(DashPairingError)
         case finished
         
@@ -84,14 +84,6 @@ class PairPodViewModel: ObservableObject, Identifiable {
         }
         
         var navBarButtonAction: NavBarButtonAction {
-//            switch self {
-//            case .error(_, let podCommState):
-//                if podCommState == .activating {
-//                    return .discard
-//                }
-//            default:
-//                break
-//            }
             return .cancel
         }
         
@@ -118,7 +110,11 @@ class PairPodViewModel: ObservableObject, Identifiable {
             case .pairing:
                 return .indeterminantProgress
             case .priming(let finishTime):
-                return .timedProgress(finishTime: finishTime)
+                if let finishTime {
+                    return .timedProgress(finishTime: finishTime)
+                } else {
+                    return .indeterminantProgress
+                }
             case .finished:
                 return .completed
             }
@@ -151,9 +147,9 @@ class PairPodViewModel: ObservableObject, Identifiable {
     @Published var state: PairPodViewModelState = .ready
     
     var podIsActivated: Bool {
-        return false // podPairer.podCommState != .noPod
+        return podPairer.podCommState != .noPod
     }
-    
+
     var backButtonHidden: Bool {
         if case .pairing = state {
             return true
@@ -174,12 +170,22 @@ class PairPodViewModel: ObservableObject, Identifiable {
 
     init(podPairer: PodPairer) {
         self.podPairer = podPairer
+
+        // If resuming, don't wait for the button action
+        if podPairer.podCommState == .activating {
+            pairAndPrime()
+        }
     }
-        
-    private func pair() {
-        state = .pairing
-        
-        podPairer.pair { (status) in
+
+    private func pairAndPrime() {
+        if podPairer.podCommState == .noPod {
+            state = .pairing
+        } else {
+            // Already paired, so resume with the prime
+            state = .priming(finishTime: nil)
+        }
+
+        podPairer.pairAndPrimePod { (status) in
             DispatchQueue.main.async {
                 switch status {
                 case .failure(let error):
@@ -207,14 +213,14 @@ class PairPodViewModel: ObservableObject, Identifiable {
                 self.didRequestDeactivation?()
             } else {
                 // Retry
-                pair()
+                pairAndPrime()
             }
         case .finished:
             didFinish?()
         default:
-            pair()
+            pairAndPrime()
         }
-    }    
+    }
 }
 
 // Pairing recovery suggestions
@@ -245,15 +251,16 @@ enum DashPairingError : LocalizedError {
 }
 
 public protocol PodPairer {
-    func pair(completion: @escaping (PumpManagerResult<TimeInterval>) -> Void)
+    func pairAndPrimePod(completion: @escaping (PumpManagerResult<TimeInterval>) -> Void)
     func discardPod(completion: @escaping (Bool) -> ())
+    var podCommState: PodCommState { get }
 }
 
 extension OmniBLEPumpManager: PodPairer {
     public func discardPod(completion: @escaping (Bool) -> ()) {
     }
     
-    public func pair(completion: @escaping (PumpManagerResult<TimeInterval>) -> Void) {
+    public func pairAndPrimePod(completion: @escaping (PumpManagerResult<TimeInterval>) -> Void) {
         pairAndPrime(completion: completion)
     }
 }

+ 1 - 1
Dependencies/OmniBLE/OmniBLE/PumpManagerUI/ViewModels/PodLifeState.swift

@@ -72,7 +72,7 @@ enum PodLifeState {
     var nextPodLifecycleAction: DashUIScreen {
         switch self {
         case .podActivating, .noPod:
-            return .pairPod
+            return .pairAndPrime
         default:
             return .deactivate
         }

+ 4 - 1
Dependencies/OmniBLE/OmniBLE/PumpManagerUI/Views/InsertCannulaView.swift

@@ -120,10 +120,11 @@ struct InsertCannulaView: View {
             secondaryButton: .default(FrameworkLocalText("No, Continue With Pod", comment: "Continue pairing button title of in pairing cancel modal"))
         )
     }
+
 }
 
 class MockCannulaInserter: CannulaInserter {
-    public func insertCannula(completion: @escaping (Result<TimeInterval,OmniBLEPumpManagerError>) -> Void) {
+    func insertCannula(completion: @escaping (Result<TimeInterval,OmniBLEPumpManagerError>) -> Void) {
         let mockDelay = TimeInterval(seconds: 3)
         let result :Result<TimeInterval, OmniBLEPumpManagerError> = .success(mockDelay)
         completion(result)
@@ -132,6 +133,8 @@ class MockCannulaInserter: CannulaInserter {
     func checkCannulaInsertionFinished(completion: @escaping (OmniBLEPumpManagerError?) -> Void) {
         completion(nil)
     }
+
+    var cannulaInsertionSuccessfullyStarted: Bool = false
 }
 
 struct InsertCannulaView_Previews: PreviewProvider {

+ 7 - 3
Dependencies/OmniKit/OmniKit/OmnipodCommon/Pod.swift

@@ -108,16 +108,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
     }
 

+ 46 - 22
Dependencies/OmniKit/OmniKit/PumpManager/OmnipodPumpManager.swift

@@ -44,7 +44,6 @@ public enum OmnipodPumpManagerError: Error {
     case insulinTypeNotConfigured
     case notReadyForCannulaInsertion
     case invalidSetting
-    case setupNotComplete
     case communication(Error)
     case state(Error)
 }
@@ -57,13 +56,11 @@ extension OmnipodPumpManagerError: 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 .invalidSetting:
             return LocalizedString("Invalid Setting", comment: "Error description for invalid setting")
-        case .setupNotComplete:
-            return LocalizedString("Pod setup is not complete", comment: "Error description when pod setup is not complete")
         case .communication(let error):
             if let error = error as? LocalizedError {
                 return error.errorDescription
@@ -323,7 +320,7 @@ extension OmnipodPumpManager {
         switch podCommState(for: state) {
         case .activating:
             return PumpStatusHighlight(
-                localizedMessage: LocalizedString("Finish Pairing", comment: "Status highlight that when pod is activating."),
+                localizedMessage: LocalizedString("Finish Setup", comment: "Status highlight that when pod is activating."),
                 imageName: "exclamationmark.circle.fill",
                 state: .warning)
         case .deactivating:
@@ -586,7 +583,7 @@ extension OmnipodPumpManager {
         } else if !podState.isSetupComplete {
             return .activating
         }
-        return .deactivating
+        return .deactivating // Can't be reached and thus will never be returned
     }
 
     public var podCommState: PodCommState {
@@ -669,6 +666,25 @@ extension OmnipodPumpManager {
         return date
     }
 
+    // Reset all the per pod state kept in pump manager state which doesn't span pods
+    fileprivate func resetPerPodPumpManagerState() {
+
+        // Reset any residual per pod slot based pump manager alerts
+        // (i.e., all but timeOffsetChangeDetected which isn't actually used)
+        let podAlerts = state.activeAlerts.filter { $0 != .timeOffsetChangeDetected }
+        for alert in podAlerts {
+            self.retractAlert(alert: alert)
+        }
+
+        self.setState { (state) in
+            // Reset alertsWithPendingAcknowledgment which are all pod slot based
+            state.alertsWithPendingAcknowledgment = []
+
+            // Reset other miscellaneous state variables that are actually per pod
+            state.podAttachmentConfirmed = false
+            state.acknowledgedTimeOffsetAlert = false
+        }
+    }
 
     // MARK: - Pod comms
 
@@ -686,6 +702,8 @@ extension OmnipodPumpManager {
 
         podComms.forgetPod()
 
+        self.resetPerPodPumpManagerState()
+
         if let dosesToStore = self.state.podState?.dosesToStore {
             self.store(doses: dosesToStore, completion: { error in
                 self.setState({ (state) in
@@ -704,7 +722,7 @@ extension OmnipodPumpManager {
             completion()
         }
     }
-    
+
     // MARK: Testing
     #if targetEnvironment(simulator)
     private func jumpStartPod(address: UInt32, lot: UInt32, tid: UInt32, fault: DetailedStatus? = nil, startDate: Date? = nil, mockFault: Bool) {
@@ -719,8 +737,14 @@ extension OmnipodPumpManager {
 
         podComms = PodComms(podState: podState)
 
+        self.podComms.delegate = self
+        self.podComms.messageLogger = self
+
+        self.resetPerPodPumpManagerState()
+
         setState({ (state) in
             state.updatePodStateFromPodComms(podState)
+            state.scheduledExpirationReminderOffset = state.defaultExpirationReminderOffset
         })
     }
     #endif
@@ -814,7 +838,9 @@ extension OmnipodPumpManager {
                         state.pairingAttemptAddress = nil
                     }
                 }
-                
+
+                self.resetPerPodPumpManagerState()
+
                 // Calls completion
                 primeSession(result)
             }
@@ -1026,7 +1052,7 @@ extension OmnipodPumpManager {
 
         guard state.podState?.setupProgress == .completed else {
             // A cancel delivery command before pod setup is complete will fault the pod
-            completion(.state(OmnipodPumpManagerError.setupNotComplete))
+            completion(.state(PodCommsError.setupNotComplete))
             return
         }
 
@@ -1066,7 +1092,7 @@ extension OmnipodPumpManager {
 
             guard state.podState?.setupProgress == .completed else {
                 // A cancel delivery command before pod setup is complete will fault the pod
-                return .failure(PumpManagerError.deviceState(OmnipodPumpManagerError.setupNotComplete))
+                return .failure(PumpManagerError.deviceState(PodCommsError.setupNotComplete))
             }
 
             guard state.podState?.unfinalizedBolus?.isFinished() != false else {
@@ -1815,7 +1841,7 @@ extension OmnipodPumpManager: PumpManager {
 
         guard state.podState?.setupProgress == .completed else {
             // A cancel delivery command before pod setup is complete will fault the pod
-            completion(.failure(PumpManagerError.deviceState(OmnipodPumpManagerError.setupNotComplete)))
+            completion(.failure(PumpManagerError.deviceState(PodCommsError.setupNotComplete)))
             return
         }
 
@@ -1879,14 +1905,14 @@ extension OmnipodPumpManager: PumpManager {
 
     public func runTemporaryBasalProgram(unitsPerHour: Double, for duration: TimeInterval, automatic: Bool, completion: @escaping (PumpManagerError?) -> Void) {
 
-        guard self.hasActivePod else {
+        guard self.hasActivePod, let podState = self.state.podState else {
             completion(.configuration(OmnipodPumpManagerError.noPodPaired))
             return
         }
 
         guard state.podState?.setupProgress == .completed else {
             // A cancel delivery command before pod setup is complete will fault the pod
-            completion(.deviceState(OmnipodPumpManagerError.setupNotComplete))
+            completion(.deviceState(PodCommsError.setupNotComplete))
             return
         }
 
@@ -1920,7 +1946,7 @@ extension OmnipodPumpManager: PumpManager {
                 return
             }
 
-            if case .some(.suspended) = self.state.podState?.suspendState {
+            if case (.suspended) = podState.suspendState {
                 self.log.info("Not enacting temp basal because podState indicates pod is suspended.")
                 completion(.deviceState(PodCommsError.podSuspended))
                 return
@@ -1930,18 +1956,17 @@ extension OmnipodPumpManager: PumpManager {
             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 {
+            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
             }
 
-            // Did the last message have comms issues or is the last delivery status not verified correctly?
-            let uncertainDeliveryStatus = self.state.podState?.lastCommsOK == false || self.state.podState?.deliveryStatusVerified == false
-
-            // Do the cancel temp basal command if currently running a temp basal OR
-            // if resuming scheduled basal delivery OR if the delivery status is uncertain.
-            if self.state.podState?.unfinalizedTempBasal != nil || resumingScheduledBasal || uncertainDeliveryStatus {
+            // 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
@@ -1952,7 +1977,6 @@ extension OmnipodPumpManager: PumpManager {
                     completion(.communication(error))
                     return
                 case .unacknowledged(let error):
-                    // TODO: Return PumpManagerError.uncertainDelivery and implement recovery
                     completion(.communication(error))
                     return
                 case .success(let cancelTempStatus, _):

+ 38 - 18
Dependencies/OmniKit/OmniKit/PumpManager/PodCommsSession.swift

@@ -38,6 +38,7 @@ public enum PodCommsError: Error {
     case podIncompatible(str: String)
     case noPodsFound
     case tooManyPodsFound
+    case setupNotComplete
 }
 
 extension PodCommsError: LocalizedError {
@@ -98,7 +99,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")
         }
     }
 
@@ -162,6 +164,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
         }
     }
 
@@ -279,7 +283,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
@@ -288,7 +294,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
             }
 
@@ -436,7 +441,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
@@ -448,7 +452,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)
@@ -477,7 +482,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 {
@@ -513,16 +518,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)
             }
         }
 
@@ -624,6 +631,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
@@ -704,6 +716,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)
@@ -752,10 +769,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

+ 16 - 16
Dependencies/OmniKit/OmniKit/PumpManager/PodState.swift

@@ -41,6 +41,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.
@@ -107,11 +111,10 @@ 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, pmVersion: String, piVersion: String, lot: UInt32, tid: UInt32, packetNumber: Int = 0, messageNumber: Int = 0, insulinType: InsulinType) {
+    public init(address: UInt32, pmVersion: String, piVersion: String, lot: UInt32, tid: UInt32, packetNumber: Int = 0, messageNumber: Int = 0, insulinType: InsulinType, initialDeliveryStatus: DeliveryStatus? = nil)
+    {
         self.address = address
         self.nonceState = NonceState(lot: lot, tid: tid)
         self.pmVersion = pmVersion
@@ -128,9 +131,8 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl
         self.setupProgress = .addressAssigned
         self.configuredAlerts = [.slot7Expired: .waitingForPairingReminder]
         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 {
@@ -285,21 +287,20 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl
     
     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)
@@ -492,8 +493,7 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl
             self.insulinType = .novolog
         }
 
-        self.deliveryStatusVerified = false
-        self.lastCommsOK = false
+        self.lastDeliveryStatusReceived = nil
     }
     
     public var rawValue: RawValue {

+ 7 - 3
Dependencies/OmniKit/OmniKitTests/PodCommsSessionTests.swift

@@ -20,7 +20,7 @@ class PodCommsSessionTests: XCTestCase {
 
 
     override func setUp() {
-        podState = PodState(address: address, pmVersion: "2.7.0", piVersion: "2.7.0", lot: 43620, tid: 560313, insulinType: .novolog)
+        podState = PodState(address: address, pmVersion: "2.7.0", piVersion: "2.7.0", lot: 43620, tid: 560313, insulinType: .novolog, initialDeliveryStatus: .scheduledBasal)
         mockTransport = MockMessageTransport(address: podState.address, messageNumber: 5)
     }
 
@@ -34,6 +34,9 @@ class PodCommsSessionTests: XCTestCase {
             // 2018-05-26T09:11:08.580347 pod Message(1f16b11e seq:06 [OmniKitPacketParser.ErrorResponse(blockType: OmniKitPacketParser.MessageBlockType.errorResponse, errorReponseType: OmniKitPacketParser.ErrorResponse.ErrorReponseType.badNonce, nonceSearchKey: 43492, data: 5 bytes)])
             mockTransport.addResponse(try ErrorResponse(encodedData: Data(hexadecimalString: "060314a9e403f5")!))
             mockTransport.addResponse(try StatusResponse(encodedData: Data(hexadecimalString: "1d5800d1a8140012e3ff8018")!))
+            // These responses are for session.bolus() which verifies that the pod is not bolusing before sending a bolus command
+            mockTransport.addResponse(try StatusResponse(encodedData: Data(hexadecimalString: "1d180160a800001cd3ff001e")!)) // not bolusing
+            mockTransport.addResponse(try StatusResponse(encodedData: Data(hexadecimalString: "1d580160e014001cd7ff81ce")!)) // bolus successfully started
         } catch (let error) {
             XCTFail("message decoding threw error: \(error)")
             return
@@ -61,11 +64,12 @@ class PodCommsSessionTests: XCTestCase {
             XCTFail("message sending error: \(error)")
         }
 
-        // Try sending another bolus command: nonce should be 676940027
+        // Try sending another bolus command: nonce should be 545302454
         XCTAssertEqual(545302454, lastPodStateUpdate!.currentNonce)
 
         let _ = session.bolus(units: 2, automatic: false)
-        let bolusTry3 = mockTransport.sentMessages[2].messageBlocks[0] as! SetInsulinScheduleCommand
+        let lastSentMessageIndex = mockTransport.sentMessages.endIndex - 1
+        let bolusTry3 = mockTransport.sentMessages[lastSentMessageIndex].messageBlocks[0] as! SetInsulinScheduleCommand
         XCTAssertEqual(545302454, bolusTry3.nonce)
 
     }

+ 7 - 7
Dependencies/OmniKit/OmniKitUI/ViewControllers/OmnipodUICoordinator.swift

@@ -24,7 +24,7 @@ enum OmnipodUIScreen {
     case lowReservoirReminderSetup
     case insulinTypeSelection
     case rileyLinkSetup
-    case pairPod
+    case pairAndPrime
     case insertCannula
     case confirmAttachment
     case checkInsertedCannula
@@ -45,8 +45,8 @@ enum OmnipodUIScreen {
         case .insulinTypeSelection:
             return .rileyLinkSetup
         case .rileyLinkSetup:
-            return .pairPod
-        case .pairPod:
+            return .pairAndPrime
+        case .pairAndPrime:
             return .confirmAttachment
         case .confirmAttachment:
             return .insertCannula
@@ -61,7 +61,7 @@ enum OmnipodUIScreen {
         case .uncertaintyRecovered:
             return nil
         case .deactivate:
-            return .pairPod
+            return .pairAndPrime
         case .settings:
             return nil
         }
@@ -199,7 +199,7 @@ class OmnipodUICoordinator: UINavigationController, PumpManagerOnboarding, Compl
 
             let view = OmnipodSettingsView(viewModel: viewModel, rileyLinkListDataSource: rileyLinkListDataSource, handleRileyLinkSelection: handleRileyLinkSelection, supportedInsulinTypes: allowedInsulinTypes)
             return hostingController(rootView: view)
-        case .pairPod:
+        case .pairAndPrime:
             pumpManagerOnboardingDelegate?.pumpManagerOnboarding(didCreatePumpManager: pumpManager)
 
             let viewModel = PairPodViewModel(podPairer: pumpManager)
@@ -410,13 +410,13 @@ class OmnipodUICoordinator: UINavigationController, PumpManagerOnboarding, Compl
             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
         }

+ 16 - 27
Dependencies/OmniKit/OmniKitUI/ViewModels/InsertCannulaViewModel.swift

@@ -13,9 +13,14 @@ import OmniKit
 public protocol CannulaInserter {
     func insertCannula(completion: @escaping (Result<TimeInterval,OmnipodPumpManagerError>) -> ())
     func checkCannulaInsertionFinished(completion: @escaping (OmnipodPumpManagerError?) -> Void)
+    var cannulaInsertionSuccessfullyStarted: Bool { get }
 }
 
-extension OmnipodPumpManager: CannulaInserter {}
+extension OmnipodPumpManager: CannulaInserter {
+    public var cannulaInsertionSuccessfullyStarted: Bool {
+        return state.podState?.setupProgress.cannulaInsertionSuccessfullyStarted == true
+    }
+}
 
 class InsertCannulaViewModel: ObservableObject, Identifiable {
 
@@ -29,9 +34,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")
@@ -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 {
@@ -171,7 +169,7 @@ class InsertCannulaViewModel: ObservableObject, Identifiable {
     
     private func insertCannula() {
         state = .startingInsertion
-        
+
         cannulaInserter.insertCannula { (result) in
             DispatchQueue.main.async {
                 switch(result) {
@@ -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)
-//            }
         }
     }
     
@@ -214,7 +204,6 @@ class InsertCannulaViewModel: ObservableObject, Identifiable {
             insertCannula()
         }
     }
-    
 }
 
 public extension OmnipodPumpManagerError {

+ 28 - 21
Dependencies/OmniKit/OmniKitUI/ViewModels/PairPodViewModel.swift

@@ -39,7 +39,7 @@ class PairPodViewModel: ObservableObject, Identifiable {
     enum PairPodViewModelState {
         case ready
         case pairing
-        case priming(finishTime: CFTimeInterval)
+        case priming(finishTime: CFTimeInterval?)
         case error(OmnipodPairingError)
         case finished
         
@@ -85,14 +85,6 @@ class PairPodViewModel: ObservableObject, Identifiable {
         }
         
         var navBarButtonAction: NavBarButtonAction {
-//            switch self {
-//            case .error(_, let podCommState):
-//                if podCommState == .activating {
-//                    return .discard
-//                }
-//            default:
-//                break
-//            }
             return .cancel
         }
         
@@ -119,7 +111,11 @@ class PairPodViewModel: ObservableObject, Identifiable {
             case .pairing:
                 return .indeterminantProgress
             case .priming(let finishTime):
-                return .timedProgress(finishTime: finishTime)
+                if let finishTime {
+                    return .timedProgress(finishTime: finishTime)
+                } else {
+                    return .indeterminantProgress
+                }
             case .finished:
                 return .completed
             }
@@ -152,7 +148,7 @@ class PairPodViewModel: ObservableObject, Identifiable {
     @Published var state: PairPodViewModelState = .ready
     
     var podIsActivated: Bool {
-        return false // podPairer.podCommState != .noPod
+        return podPairer.podCommState != .noPod
     }
     
     var backButtonHidden: Bool {
@@ -175,12 +171,22 @@ class PairPodViewModel: ObservableObject, Identifiable {
 
     init(podPairer: PodPairer) {
         self.podPairer = podPairer
+
+        // If resuming, don't wait for the button action
+        if podPairer.podCommState == .activating {
+            pairAndPrime()
+        }
     }
-        
-    private func pair() {
-        state = .pairing
-        
-        podPairer.pair { (status) in
+
+    private func pairAndPrime() {
+        if podPairer.podCommState == .noPod {
+            state = .pairing
+        } else {
+            // Already paired, so resume with the prime
+            state = .priming(finishTime: nil)
+        }
+
+        podPairer.pairAndPrimePod { (status) in
             DispatchQueue.main.async {
                 switch status {
                 case .failure(let error):
@@ -208,14 +214,14 @@ class PairPodViewModel: ObservableObject, Identifiable {
                 self.didRequestDeactivation?()
             } else {
                 // Retry
-                pair()
+                pairAndPrime()
             }
         case .finished:
             didFinish?()
         default:
-            pair()
+            pairAndPrime()
         }
-    }    
+    }
 }
 
 // Pairing recovery suggestions
@@ -246,15 +252,16 @@ enum OmnipodPairingError : LocalizedError {
 }
 
 public protocol PodPairer {
-    func pair(completion: @escaping (PumpManagerResult<TimeInterval>) -> Void)
+    func pairAndPrimePod(completion: @escaping (PumpManagerResult<TimeInterval>) -> Void)
     func discardPod(completion: @escaping (Bool) -> ())
+    var podCommState: PodCommState { get }
 }
 
 extension OmnipodPumpManager: PodPairer {
     public func discardPod(completion: @escaping (Bool) -> ()) {
     }
     
-    public func pair(completion: @escaping (PumpManagerResult<TimeInterval>) -> Void) {
+    public func pairAndPrimePod(completion: @escaping (PumpManagerResult<TimeInterval>) -> Void) {
         pairAndPrime(completion: completion)
     }
 }

+ 1 - 1
Dependencies/OmniKit/OmniKitUI/ViewModels/PodLifeState.swift

@@ -72,7 +72,7 @@ enum PodLifeState {
     var nextPodLifecycleAction: OmnipodUIScreen {
         switch self {
         case .podActivating, .noPod:
-            return .pairPod
+            return .pairAndPrime
         default:
             return .deactivate
         }

+ 3 - 1
Dependencies/OmniKit/OmniKitUI/Views/InsertCannulaView.swift

@@ -124,7 +124,7 @@ struct InsertCannulaView: View {
 }
 
 class MockCannulaInserter: CannulaInserter {
-    public func insertCannula(completion: @escaping (Result<TimeInterval,OmnipodPumpManagerError>) -> Void) {
+    func insertCannula(completion: @escaping (Result<TimeInterval,OmnipodPumpManagerError>) -> Void) {
         let mockDelay = TimeInterval(seconds: 3)
         let result :Result<TimeInterval, OmnipodPumpManagerError> = .success(mockDelay)
         completion(result)
@@ -133,6 +133,8 @@ class MockCannulaInserter: CannulaInserter {
     func checkCannulaInsertionFinished(completion: @escaping (OmnipodPumpManagerError?) -> Void) {
         completion(nil)
     }
+
+    var cannulaInsertionSuccessfullyStarted: Bool = false
 }
 
 struct InsertCannulaView_Previews: PreviewProvider {