| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475 |
- //
- // MinimedPumpIDSetupViewController.swift
- // Loop
- //
- // Copyright © 2018 LoopKit Authors. All rights reserved.
- //
- import UIKit
- import LoopKit
- import LoopKitUI
- import MinimedKit
- import RileyLinkKit
- class MinimedPumpIDSetupViewController: SetupTableViewController {
- var rileyLinkPumpManager: RileyLinkPumpManager!
- private enum RegionCode: String {
- case northAmerica = "NA"
- case canada = "CA/CM"
- case worldWide = "WW"
- var region: PumpRegion {
- switch self {
- case .northAmerica:
- return .northAmerica
- case .canada:
- return .canada
- case .worldWide:
- return .worldWide
- }
- }
- }
- private var pumpRegionCode: RegionCode? {
- didSet {
- regionAndColorPickerCell.regionLabel.text = pumpRegionCode?.region.description
- regionAndColorPickerCell.regionLabel.textColor = nil
- updateStateForSettings()
- }
- }
- private var pumpColor: PumpColor? {
- didSet {
- regionAndColorPickerCell.pumpImageView.image = .pumpImage(in: pumpColor, isLargerModel: true, isSmallImage: true)
- updateStateForSettings()
- }
- }
- private var pumpID: String? {
- get {
- return pumpIDTextField.text
- }
- set {
- pumpIDTextField.text = newValue
- }
- }
- private var pumpOps: PumpOps?
- private var pumpState: PumpState?
-
- private var pumpFirmwareVersion: String?
- var maxBasalRateUnitsPerHour: Double!
- var maxBolusUnits: Double!
- var basalSchedule: BasalRateSchedule!
- private var isSentrySetUpNeeded: Bool = false
- var pumpManagerState: MinimedPumpManagerState? {
- get {
- guard
- let navVC = navigationController as? MinimedPumpManagerSetupViewController,
- let insulinType = navVC.insulinType,
- let pumpColor = pumpColor,
- let pumpID = pumpID,
- let pumpModel = pumpState?.pumpModel,
- let pumpRegion = pumpRegionCode?.region,
- let timeZone = pumpState?.timeZone,
- let pumpFirmwareVersion = pumpFirmwareVersion
- else {
- return nil
- }
-
- return MinimedPumpManagerState(
- isOnboarded: false,
- useMySentry: pumpState?.useMySentry ?? true,
- pumpColor: pumpColor,
- pumpID: pumpID,
- pumpModel: pumpModel,
- pumpFirmwareVersion: pumpFirmwareVersion,
- pumpRegion: pumpRegion,
- rileyLinkConnectionState: rileyLinkPumpManager.rileyLinkConnectionManagerState,
- timeZone: timeZone,
- suspendState: .resumed(Date()),
- insulinType: insulinType,
- lastTuned: pumpState?.lastTuned,
- lastValidFrequency: pumpState?.lastValidFrequency
- )
- }
- }
- var pumpManager: MinimedPumpManager? {
- guard let pumpManagerState = pumpManagerState else {
- return nil
- }
- return MinimedPumpManager(
- state: pumpManagerState,
- rileyLinkDeviceProvider: rileyLinkPumpManager.rileyLinkDeviceProvider)
- }
- // MARK: -
- @IBOutlet weak var pumpIDTextField: UITextField!
-
- @IBOutlet fileprivate weak var regionAndColorPickerCell: RegionAndColorPickerTableViewCell!
- @IBOutlet weak var activityIndicator: SetupIndicatorView!
- @IBOutlet weak var loadingLabel: UILabel!
- override func viewDidLoad() {
- super.viewDidLoad()
- regionAndColorPickerCell.pickerView.delegate = self
- regionAndColorPickerCell.pickerView.dataSource = self
- continueState = .inputSettings
- NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidHide), name: UIResponder.keyboardDidHideNotification, object: nil)
- }
- override func setEditing(_ editing: Bool, animated: Bool) {
- super.setEditing(editing, animated: animated)
- }
- // MARK: - UITableViewDelegate
- override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
- guard continueState != .reading else {
- return
- }
- if let cell = tableView.cellForRow(at: indexPath) as? RegionAndColorPickerTableViewCell {
- cell.becomeFirstResponder()
- // Apply initial values to match the picker
- if pumpRegionCode == nil {
- pumpRegionCode = MinimedPumpIDSetupViewController.regionRows[0]
- }
- if pumpColor == nil {
- pumpColor = MinimedPumpIDSetupViewController.colorRows[0]
- }
- }
- tableView.deselectRow(at: indexPath, animated: true)
- }
- // MARK: - Navigation
- private enum State {
- case loadingView
- case inputSettings
- case readyToRead
- case reading
- case completed
- }
- private var continueState: State = .loadingView {
- didSet {
- switch continueState {
- case .loadingView:
- updateStateForSettings()
- case .inputSettings:
- pumpIDTextField.isEnabled = true
- activityIndicator.state = .hidden
- footerView.primaryButton.isEnabled = false
- footerView.primaryButton.setConnectTitle()
- lastError = nil
- case .readyToRead:
- pumpIDTextField.isEnabled = true
- activityIndicator.state = .hidden
- footerView.primaryButton.isEnabled = true
- footerView.primaryButton.setConnectTitle()
- case .reading:
- pumpIDTextField.isEnabled = false
- activityIndicator.state = .indeterminantProgress
- footerView.primaryButton.isEnabled = false
- footerView.primaryButton.setConnectTitle()
- lastError = nil
- case .completed:
- pumpIDTextField.isEnabled = true
- activityIndicator.state = .completed
- footerView.primaryButton.isEnabled = true
- footerView.primaryButton.resetTitle()
- lastError = nil
- }
- }
- }
- private var lastError: Error? {
- didSet {
- guard oldValue != nil || lastError != nil else {
- return
- }
- var errorText = lastError?.localizedDescription
- if let error = lastError as? LocalizedError {
- let localizedText = [error.errorDescription, error.failureReason, error.recoverySuggestion].compactMap({ $0 }).joined(separator: ". ")
- if !localizedText.isEmpty {
- errorText = localizedText
- }
- }
- tableView.beginUpdates()
- loadingLabel.text = errorText
- let isHidden = (errorText == nil)
- loadingLabel.isHidden = isHidden
- tableView.endUpdates()
- // If we changed the error text, update the continue state
- if !isHidden {
- updateStateForSettings()
- }
- }
- }
- private func updateStateForSettings() {
- let isReadyToRead = pumpRegionCode != nil && pumpColor != nil && pumpID?.count == 6
- if isReadyToRead {
- continueState = .readyToRead
- } else {
- continueState = .inputSettings
- }
- }
- private func setupPump(with settings: PumpSettings) {
- continueState = .reading
- let pumpOps = MinimedPumpOps(pumpSettings: settings, pumpState: pumpState, delegate: self)
- self.pumpOps = pumpOps
- pumpOps.runSession(withName: "Pump ID Setup", usingSelector: rileyLinkPumpManager.rileyLinkDeviceProvider.firstConnectedDevice, { (session) in
- guard let session = session else {
- DispatchQueue.main.async {
- self.lastError = PumpManagerError.connection(MinimedPumpManagerError.noRileyLink)
- }
- return
- }
- do {
- _ = try session.tuneRadio()
- let model = try session.getPumpModel()
- var isSentrySetUpNeeded = false
-
- self.pumpFirmwareVersion = try session.getPumpFirmwareVersion()
- // Radio
- if model.hasMySentry {
- let isSentryEnabled = try session.getOtherDevicesEnabled()
- if isSentryEnabled {
- let sentryIDCount = try session.getOtherDevicesIDs().ids.count
- isSentrySetUpNeeded = (sentryIDCount == 0)
- } else {
- isSentrySetUpNeeded = true
- }
- } else {
- // Pre-sentry models need a remote ID to decrease the radio wake interval
- let remoteIDCount = try session.getRemoteControlIDs().ids.count
- if remoteIDCount == 0 {
- try session.setRemoteControlID(Data([9, 9, 9, 9, 9, 9]), atIndex: 2)
- }
- try session.setRemoteControlEnabled(true)
- }
- // Settings
- let newSchedule = BasalSchedule(repeatingScheduleValues: self.basalSchedule.items)
- try session.setBasalSchedule(newSchedule, for: .standard)
- try session.setMaxBolus(units: self.maxBolusUnits)
- try session.setMaxBasalRate(unitsPerHour: self.maxBasalRateUnitsPerHour)
- try session.selectBasalProfile(.standard)
- try session.setTimeToNow(in: .current)
- DispatchQueue.main.async {
- self.isSentrySetUpNeeded = isSentrySetUpNeeded
- if self.pumpState != nil {
- self.continueState = .completed
- } else {
- self.lastError = PumpManagerError.connection(MinimedPumpManagerError.noRileyLink)
- }
- }
- } catch let error {
- DispatchQueue.main.async {
- self.lastError = error
- }
- }
- })
- }
- override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
- return continueState == .completed
- }
- override func continueButtonPressed(_ sender: Any) {
- if case .completed = continueState {
- if let setupViewController = navigationController as? MinimedPumpManagerSetupViewController,
- let pumpManager = pumpManager // create mdt 1
- {
- setupViewController.pumpManagerSetupComplete(pumpManager)
- }
- if isSentrySetUpNeeded {
- performSegue(withIdentifier: "Sentry", sender: sender)
- } else {
- super.continueButtonPressed(sender)
- }
- } else if case .readyToRead = continueState, let pumpID = pumpID, let pumpRegion = pumpRegionCode?.region {
- #if targetEnvironment(simulator)
- self.continueState = .completed
- self.pumpState = PumpState(timeZone: .currentFixed, pumpModel: PumpModel(rawValue:
- "523")!, useMySentry: false)
- self.pumpFirmwareVersion = "2.4Mock"
- #else
- setupPump(with: PumpSettings(pumpID: pumpID, pumpRegion: pumpRegion))
- #endif
- }
- }
- override func cancelButtonPressed(_ sender: Any) {
- if regionAndColorPickerCell.isFirstResponder {
- regionAndColorPickerCell.resignFirstResponder()
- } else if pumpIDTextField.isFirstResponder {
- pumpIDTextField.resignFirstResponder()
- } else {
- super.cancelButtonPressed(sender)
- }
- }
- @objc func keyboardDidHide() {
- regionAndColorPickerCell.resignFirstResponder()
- }
- }
- extension MinimedPumpIDSetupViewController: UIPickerViewDelegate, UIPickerViewDataSource {
- private static let regionRows: [RegionCode] = [.northAmerica, .canada, .worldWide]
- private static let colorRows: [PumpColor] = [.blue, .clear, .purple, .smoke, .pink]
- private enum PickerViewComponent: Int {
- case region
- case color
- static let count = 2
- }
- func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
- switch PickerViewComponent(rawValue: component)! {
- case .region:
- return MinimedPumpIDSetupViewController.regionRows[row].rawValue
- case .color:
- return MinimedPumpIDSetupViewController.colorRows[row].rawValue
- }
- }
- func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
- switch PickerViewComponent(rawValue: component)! {
- case .region:
- pumpRegionCode = MinimedPumpIDSetupViewController.regionRows[row]
- case .color:
- pumpColor = MinimedPumpIDSetupViewController.colorRows[row]
- }
- }
- func numberOfComponents(in pickerView: UIPickerView) -> Int {
- return PickerViewComponent.count
- }
- func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
- switch PickerViewComponent(rawValue: component)! {
- case .region:
- return MinimedPumpIDSetupViewController.regionRows.count
- case .color:
- return MinimedPumpIDSetupViewController.colorRows.count
- }
- }
- }
- extension MinimedPumpIDSetupViewController: UITextFieldDelegate {
- func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
- guard let text = textField.text, let stringRange = Range(range, in: text) else {
- updateStateForSettings()
- return true
- }
- let newText = text.replacingCharacters(in: stringRange, with: string)
- if newText.count >= 6 {
- if newText.count == 6 {
- textField.text = newText
- textField.resignFirstResponder()
- }
- updateStateForSettings()
- return false
- }
- textField.text = newText
- updateStateForSettings()
- return false
- }
- func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
- return true
- }
- func textFieldShouldReturn(_ textField: UITextField) -> Bool {
- textField.resignFirstResponder()
- return true
- }
- }
- extension MinimedPumpIDSetupViewController: PumpOpsDelegate {
- // TODO: create PumpManager and report it to Loop before pump setup
- // No pumpManager available yet, so no device logs.
- func willSend(_ message: String) {}
- func didReceive(_ message: String) {}
- func didError(_ message: String) {}
- func pumpOps(_ pumpOps: PumpOps, didChange state: PumpState) {
- DispatchQueue.main.async {
- self.pumpState = state
- }
- }
- }
- class RegionAndColorPickerTableViewCell: UITableViewCell {
- override var canBecomeFirstResponder: Bool {
- return true
- }
- fileprivate private(set) lazy var pickerView = UIPickerView()
- override var inputView: UIView? {
- return pickerView
- }
- @IBOutlet weak var regionLabel: UILabel!
- @IBOutlet weak var pumpImageView: UIImageView!
- }
- private extension SetupButton {
- func setConnectTitle() {
- setTitle(LocalizedString("Connect", comment: "Button title to connect to pump during setup"), for: .normal)
- }
- }
|