diff --git a/OmniBLE.xcodeproj/project.pbxproj b/OmniBLE.xcodeproj/project.pbxproj index d8286bf3..8daa822e 100644 --- a/OmniBLE.xcodeproj/project.pbxproj +++ b/OmniBLE.xcodeproj/project.pbxproj @@ -746,22 +746,25 @@ 8475311E26ED838A009FD801 /* Views */ = { isa = PBXGroup; children = ( - C1C001C327A2351D00533D35 /* OmniBLEReservoirView.swift */, - C1C001C227A2351D00533D35 /* OmniBLEReservoirView.xib */, - C1F67EB227975E710017487F /* DesignElements */, C1F67E7327975B830017487F /* AttachPodView.swift */, C1F67E7A27975B830017487F /* BasalStateView.swift */, + C1ED1E7127BAE44E00FED71C /* BeepPreferenceSelectionView.swift */, C1F67E8027975B830017487F /* CheckInsertedCannulaView.swift */, - C1F67E7727975B830017487F /* OmniBLESettingsView.swift */, C1F67E7927975B830017487F /* DeactivatePodView.swift */, C1F67E8227975B830017487F /* DeliveryUncertaintyRecoveryView.swift */, + C1F67EB227975E710017487F /* DesignElements */, C1F67E7B27975B830017487F /* ExpirationReminderPickerView.swift */, C1F67E7827975B830017487F /* ExpirationReminderSetupView.swift */, C1F67E7D27975B830017487F /* HUDAssets.xcassets */, C1F67E8527975B830017487F /* InsertCannulaView.swift */, + C187C190278FCEC9006E3557 /* InsulinTypeConfirmation.swift */, C1F67E8627975B830017487F /* LowReservoirReminderEditView.swift */, C1F67E7C27975B830017487F /* LowReservoirReminderSetupView.swift */, + C1DBD512282FF79D009FCF74 /* ManualTempBasalEntryView.swift */, C1F67E8127975B830017487F /* NotificationSettingsView.swift */, + C1C001C327A2351D00533D35 /* OmniBLEReservoirView.swift */, + C1C001C227A2351D00533D35 /* OmniBLEReservoirView.xib */, + C1F67E7727975B830017487F /* OmniBLESettingsView.swift */, C1F67E7F27975B830017487F /* PairPodView.swift */, C1F67E8A27975B830017487F /* PodDetailsView.swift */, C1F67E8827975B830017487F /* PodSetupView.swift */, @@ -769,9 +772,6 @@ C1F67E8727975B830017487F /* SetupCompleteView.swift */, C1F67E7627975B830017487F /* TimeView.swift */, C1F67E8427975B830017487F /* UncertaintyRecoveredView.swift */, - C187C190278FCEC9006E3557 /* InsulinTypeConfirmation.swift */, - C1ED1E7127BAE44E00FED71C /* BeepPreferenceSelectionView.swift */, - C1DBD512282FF79D009FCF74 /* ManualTempBasalEntryView.swift */, ); path = Views; sourceTree = ""; diff --git a/OmniBLE/PumpManager/OmniBLEPumpManager.swift b/OmniBLE/PumpManager/OmniBLEPumpManager.swift index b4a107ac..645bfc25 100644 --- a/OmniBLE/PumpManager/OmniBLEPumpManager.swift +++ b/OmniBLE/PumpManager/OmniBLEPumpManager.swift @@ -457,7 +457,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 { @@ -591,7 +591,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: @@ -702,6 +702,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 @@ -729,13 +748,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() @@ -746,7 +766,6 @@ extension OmniBLEPumpManager { } } - // MARK: Testing #if targetEnvironment(simulator) @@ -770,6 +789,8 @@ extension OmniBLEPumpManager { self.podComms.delegate = self self.podComms.messageLogger = self + self.resetPerPodPumpManagerState() + setState({ (state) in state.updatePodStateFromPodComms(podState) state.scheduledExpirationReminderOffset = state.defaultExpirationReminderOffset @@ -867,6 +888,8 @@ extension OmniBLEPumpManager { self.podComms.pairAndSetupPod(timeZone: .currentFixed, insulinType: insulinType, messageLogger: self) { (result) in + self.resetPerPodPumpManagerState() + // Calls completion primeSession(result) } diff --git a/OmniBLE/PumpManager/PodCommsSession.swift b/OmniBLE/PumpManager/PodCommsSession.swift index 2434eb95..3bde7ed1 100644 --- a/OmniBLE/PumpManager/PodCommsSession.swift +++ b/OmniBLE/PumpManager/PodCommsSession.swift @@ -417,7 +417,6 @@ public class PodCommsSession { public func insertCannula(optionalAlerts: [PodAlert] = []) throws -> TimeInterval { let cannulaInsertionUnits = Pod.cannulaInsertionUnits + Pod.cannulaInsertionUnitsExtra - let insertionWait: TimeInterval = .seconds(cannulaInsertionUnits / Pod.primeDeliveryRate) guard let activatedAt = podState.activatedAt else { throw PodCommsError.noPodPaired @@ -429,7 +428,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) @@ -458,7 +458,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 { diff --git a/OmniBLE/PumpManager/PodState.swift b/OmniBLE/PumpManager/PodState.swift index ecc0b92e..64e0dd11 100644 --- a/OmniBLE/PumpManager/PodState.swift +++ b/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. diff --git a/OmniBLE/PumpManagerUI/ViewControllers/DashUICoordinator.swift b/OmniBLE/PumpManagerUI/ViewControllers/DashUICoordinator.swift index a59a8260..a4e87612 100644 --- a/OmniBLE/PumpManagerUI/ViewControllers/DashUICoordinator.swift +++ b/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).onAppear(perform: {UIApplication.shared.isIdleTimerDisabled = true}), onDisappear: {UIApplication.shared.isIdleTimerDisabled = false}) view.navigationItem.title = LocalizedString("Pair Pod", comment: "Title for pod pairing screen") view.navigationItem.backButtonDisplayMode = .generic @@ -351,13 +351,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 } diff --git a/OmniBLE/PumpManagerUI/ViewModels/InsertCannulaViewModel.swift b/OmniBLE/PumpManagerUI/ViewModels/InsertCannulaViewModel.swift index 6747d7d4..984a507b 100644 --- a/OmniBLE/PumpManagerUI/ViewModels/InsertCannulaViewModel.swift +++ b/OmniBLE/PumpManagerUI/ViewModels/InsertCannulaViewModel.swift @@ -12,9 +12,14 @@ import LoopKitUI public protocol CannulaInserter { func insertCannula(completion: @escaping (Result) -> ()) 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") @@ -141,22 +146,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 { @@ -170,7 +168,6 @@ class InsertCannulaViewModel: ObservableObject, Identifiable { private func insertCannula() { state = .startingInsertion - cannulaInserter.insertCannula { (result) in DispatchQueue.main.async { switch(result) { @@ -188,14 +185,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) -// } } } @@ -213,7 +202,6 @@ class InsertCannulaViewModel: ObservableObject, Identifiable { insertCannula() } } - } public extension OmniBLEPumpManagerError { diff --git a/OmniBLE/PumpManagerUI/ViewModels/PairPodViewModel.swift b/OmniBLE/PumpManagerUI/ViewModels/PairPodViewModel.swift index dc6a4b91..5e1ce70b 100644 --- a/OmniBLE/PumpManagerUI/ViewModels/PairPodViewModel.swift +++ b/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) -> Void) + func pairAndPrimePod(completion: @escaping (PumpManagerResult) -> 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) -> Void) { + public func pairAndPrimePod(completion: @escaping (PumpManagerResult) -> Void) { pairAndPrime(completion: completion) } } diff --git a/OmniBLE/PumpManagerUI/ViewModels/PodLifeState.swift b/OmniBLE/PumpManagerUI/ViewModels/PodLifeState.swift index bf0b1ed3..2df28d68 100644 --- a/OmniBLE/PumpManagerUI/ViewModels/PodLifeState.swift +++ b/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 } diff --git a/OmniBLE/PumpManagerUI/Views/InsertCannulaView.swift b/OmniBLE/PumpManagerUI/Views/InsertCannulaView.swift index 0b2ac191..30133eed 100644 --- a/OmniBLE/PumpManagerUI/Views/InsertCannulaView.swift +++ b/OmniBLE/PumpManagerUI/Views/InsertCannulaView.swift @@ -130,8 +130,9 @@ struct InsertCannulaView: View { } } + class MockCannulaInserter: CannulaInserter { - public func insertCannula(completion: @escaping (Result) -> Void) { + func insertCannula(completion: @escaping (Result) -> Void) { let mockDelay = TimeInterval(seconds: 3) let result :Result = .success(mockDelay) completion(result) @@ -141,15 +142,13 @@ class MockCannulaInserter: CannulaInserter { completion(nil) } - + var cannulaInsertionSuccessfullyStarted: Bool = false } + struct InsertCannulaView_Previews: PreviewProvider { static var mockInserter = MockCannulaInserter() static var model = InsertCannulaViewModel(cannulaInserter: mockInserter) static var previews: some View { InsertCannulaView(viewModel: model) - - - } }