瀏覽代碼

Rl ema orange updates

Jon Mårtensson 4 年之前
父節點
當前提交
f4a83daaad
共有 28 個文件被更改,包括 1073 次插入970 次删除
  1. 9 1
      Dependencies/rileylink_ios/Common/NumberFormatter.swift
  2. 1 1
      Dependencies/rileylink_ios/MinimedKit/Models/PumpModel.swift
  3. 37 0
      Dependencies/rileylink_ios/MinimedKit/PumpManager/MinimedPumpManager.swift
  4. 8 0
      Dependencies/rileylink_ios/MinimedKit/PumpManager/MinimedPumpManagerState.swift
  5. 1 1
      Dependencies/rileylink_ios/MinimedKit/PumpManager/PumpMessageSender.swift
  6. 1 1
      Dependencies/rileylink_ios/MinimedKit/PumpManager/RileyLinkDevice.swift
  7. 1 1
      Dependencies/rileylink_ios/MinimedKitTests/PumpOpsSynchronousTests.swift
  8. 0 20
      Dependencies/rileylink_ios/MinimedKitUI/CommandResponseViewController.swift
  9. 10 2
      Dependencies/rileylink_ios/MinimedKitUI/MinimedPumpSettingsViewController.swift
  10. 10 192
      Dependencies/rileylink_ios/MinimedKitUI/RileyLinkMinimedDeviceTableViewController.swift
  11. 38 1
      Dependencies/rileylink_ios/OmniKit/PumpManager/OmnipodPumpManager.swift
  12. 12 1
      Dependencies/rileylink_ios/OmniKit/PumpManager/OmnipodPumpManagerState.swift
  13. 14 1
      Dependencies/rileylink_ios/OmniKitUI/ViewControllers/OmnipodSettingsViewController.swift
  14. 0 12
      Dependencies/rileylink_ios/OmniKitUI/sv.lproj/Localizable.strings
  15. 34 26
      Dependencies/rileylink_ios/RileyLink.xcodeproj/project.pbxproj
  16. 13 4
      Dependencies/rileylink_ios/RileyLinkBLEKit/CommandSession.swift
  17. 142 99
      Dependencies/rileylink_ios/RileyLinkBLEKit/PeripheralManager+RileyLink.swift
  18. 19 49
      Dependencies/rileylink_ios/RileyLinkBLEKit/PeripheralManager.swift
  19. 16 7
      Dependencies/rileylink_ios/RileyLinkBLEKit/PeripheralManagerError.swift
  20. 199 141
      Dependencies/rileylink_ios/RileyLinkBLEKit/RileyLinkDevice.swift
  21. 6 1
      Dependencies/rileylink_ios/RileyLinkBLEKit/RileyLinkDeviceError.swift
  22. 3 7
      Dependencies/rileylink_ios/RileyLinkKit/PumpOpsSession.swift
  23. 0 16
      Dependencies/rileylink_ios/RileyLinkKit/RileyLinkDevice.swift
  24. 2 0
      Dependencies/rileylink_ios/RileyLinkKit/RileyLinkPumpManager.swift
  25. 3 39
      Dependencies/rileylink_ios/RileyLinkKitUI/Base.lproj/Localizable.strings
  26. 62 0
      Dependencies/rileylink_ios/RileyLinkKitUI/CommandResponseViewController.swift
  27. 429 308
      Dependencies/rileylink_ios/RileyLinkKitUI/RileyLinkDeviceTableViewController.swift
  28. 3 39
      Dependencies/rileylink_ios/RileyLinkKitUI/sv.lproj/Localizable.strings

+ 9 - 1
Dependencies/rileylink_ios/Common/NumberFormatter.swift

@@ -15,7 +15,15 @@ extension NumberFormatter {
             return nil
             return nil
         }
         }
     }
     }
-    
+
+    func percentString(from percent: Int?) -> String? {
+        if let percent = percent, let formatted = string(from: NSNumber(value: percent)) {
+            return String(format: LocalizedString("%@%%", comment: "Unit format string for an value in percent"), formatted)
+        } else {
+            return nil
+        }
+    }
+
     func string(from number: Double) -> String? {
     func string(from number: Double) -> String? {
         return string(from: NSNumber(value: number))
         return string(from: NSNumber(value: number))
     }
     }

+ 1 - 1
Dependencies/rileylink_ios/MinimedKit/Models/PumpModel.swift

@@ -51,7 +51,7 @@ public enum PumpModel: String {
     }
     }
     
     
     public var hasMySentry: Bool {
     public var hasMySentry: Bool {
-        return false
+        return generation >= 23
     }
     }
     
     
     var hasLowSuspend: Bool {
     var hasLowSuspend: Bool {

+ 37 - 0
Dependencies/rileylink_ios/MinimedKit/PumpManager/MinimedPumpManager.swift

@@ -207,6 +207,43 @@ public class MinimedPumpManager: RileyLinkPumpManager {
         }
         }
     }
     }
 
 
+    public var rileyLinkBatteryAlertLevel: Int? {
+        get {
+            return state.rileyLinkBatteryAlertLevel
+        }
+        set {
+            setState { state in
+                state.rileyLinkBatteryAlertLevel = newValue
+            }
+        }
+    }
+    
+    public override func device(_ device: RileyLinkDevice, didUpdateBattery level: Int) {
+        let repeatInterval: TimeInterval = .hours(1)
+        
+        if let alertLevel = state.rileyLinkBatteryAlertLevel,
+           level <= alertLevel,
+           state.lastRileyLinkBatteryAlertDate.addingTimeInterval(repeatInterval) < Date()
+        {
+            self.setState { state in
+                state.lastRileyLinkBatteryAlertDate = Date()
+            }
+            
+            // HACK Alert. This is temporary for the 2.2.5 release. Dev and newer releases will use the new Loop Alert facility
+            let notification = UNMutableNotificationContent()
+            notification.body = String(format: LocalizedString("\"%1$@\" has a low battery", comment: "Format string for low battery alert body for RileyLink. (1: device name)"), device.name ?? "unnamed")
+            notification.title = LocalizedString("Low RileyLink Battery", comment: "Title for RileyLink low battery alert")
+            notification.sound = .default
+            notification.categoryIdentifier = LoopNotificationCategory.loopNotRunning.rawValue
+            notification.threadIdentifier = LoopNotificationCategory.loopNotRunning.rawValue
+            let request = UNNotificationRequest(
+                identifier: "batteryalert.rileylink",
+                content: notification,
+                trigger: nil)
+            UNUserNotificationCenter.current().add(request)
+        }
+    }
+
     // MARK: - CustomDebugStringConvertible
     // MARK: - CustomDebugStringConvertible
 
 
     override public var debugDescription: String {
     override public var debugDescription: String {

File diff suppressed because it is too large
+ 8 - 0
Dependencies/rileylink_ios/MinimedKit/PumpManager/MinimedPumpManagerState.swift


+ 1 - 1
Dependencies/rileylink_ios/MinimedKit/PumpManager/PumpMessageSender.swift

@@ -43,7 +43,7 @@ protocol PumpMessageSender {
     func send(_ data: Data, onChannel channel: Int, timeout: TimeInterval) throws
     func send(_ data: Data, onChannel channel: Int, timeout: TimeInterval) throws
     
     
     /// - Throws: LocalizedError
     /// - Throws: LocalizedError
-    func enableCCLEDs() throws
+    func setCCLEDMode(_ mode: RileyLinkLEDMode) throws
     
     
     /// - Throws: LocalizedError
     /// - Throws: LocalizedError
     func getRileyLinkStatistics() throws -> RileyLinkStatistics
     func getRileyLinkStatistics() throws -> RileyLinkStatistics

+ 1 - 1
Dependencies/rileylink_ios/MinimedKit/PumpManager/RileyLinkDevice.swift

@@ -16,7 +16,7 @@ extension RileyLinkDevice.Status {
             manufacturer: "Medtronic",
             manufacturer: "Medtronic",
             model: pumpModel.rawValue,
             model: pumpModel.rawValue,
             hardwareVersion: nil,
             hardwareVersion: nil,
-            firmwareVersion: radioFirmwareVersion?.description,
+            firmwareVersion: version,
             softwareVersion: String(MinimedKitVersionNumber),
             softwareVersion: String(MinimedKitVersionNumber),
             localIdentifier: pumpID,
             localIdentifier: pumpID,
             udiDeviceIdentifier: nil
             udiDeviceIdentifier: nil

+ 1 - 1
Dependencies/rileylink_ios/MinimedKitTests/PumpOpsSynchronousTests.swift

@@ -377,7 +377,7 @@ class PumpMessageSenderStub: PumpMessageSender {
         throw PumpOpsError.noResponse(during: "Tests")
         throw PumpOpsError.noResponse(during: "Tests")
     }
     }
     
     
-    func enableCCLEDs() throws {
+    func setCCLEDMode(_ mode: RileyLinkLEDMode) throws {
         throw PumpOpsError.noResponse(during: "Tests")
         throw PumpOpsError.noResponse(during: "Tests")
     }
     }
     
     

+ 0 - 20
Dependencies/rileylink_ios/MinimedKitUI/CommandResponseViewController.swift

@@ -251,26 +251,6 @@ extension CommandResponseViewController {
         }
         }
     }
     }
 
 
-    static func enableLEDs(ops: PumpOps?, device: RileyLinkDevice) -> T {
-        return T { (completionHandler) -> String in
-            device.enableBLELEDs()
-            ops?.runSession(withName: "Enable LEDs", using: device) { (session) in
-                let response: String
-                do {
-                    try session.enableCCLEDs()
-                    response = "OK"
-                } catch let error {
-                    response = String(describing: error)
-                }
-
-                DispatchQueue.main.async {
-                    completionHandler(response)
-                }
-            }
-
-            return LocalizedString("Enabled Diagnostic LEDs", comment: "Progress message for enabling diagnostic LEDs")
-        }
-    }
 
 
     static func readPumpStatus(ops: PumpOps?, device: RileyLinkDevice, measurementFormatter: MeasurementFormatter) -> T {
     static func readPumpStatus(ops: PumpOps?, device: RileyLinkDevice, measurementFormatter: MeasurementFormatter) -> T {
         return T { (completionHandler) -> String in
         return T { (completionHandler) -> String in

+ 10 - 2
Dependencies/rileylink_ios/MinimedKitUI/MinimedPumpSettingsViewController.swift

@@ -272,10 +272,18 @@ class MinimedPumpSettingsViewController: RileyLinkSettingsViewController {
             }
             }
         case .rileyLinks:
         case .rileyLinks:
             let device = devicesDataSource.devices[indexPath.row]
             let device = devicesDataSource.devices[indexPath.row]
+            
+            guard device.hardwareType != nil else {
+                tableView.deselectRow(at: indexPath, animated: true)
+                return
+            }
 
 
-            let vc = RileyLinkMinimedDeviceTableViewController(
+            let vc = RileyLinkDeviceTableViewController(
                 device: device,
                 device: device,
-                pumpOps: pumpManager.pumpOps
+                batteryAlertLevel: pumpManager.rileyLinkBatteryAlertLevel,
+                batteryAlertLevelChanged: { [weak self] value in
+                    self?.pumpManager.rileyLinkBatteryAlertLevel = value
+                }
             )
             )
 
 
             self.show(vc, sender: sender)
             self.show(vc, sender: sender)

+ 10 - 192
Dependencies/rileylink_ios/MinimedKitUI/RileyLinkMinimedDeviceTableViewController.swift

@@ -53,22 +53,6 @@ public class RileyLinkMinimedDeviceTableViewController: UITableViewController {
         }
         }
     }
     }
     
     
-    private var fw_hw: String? {
-        didSet {
-            guard isViewLoaded else {
-                return
-            }
-            
-            cellForRow(.orl)?.detailTextLabel?.text = fw_hw
-        }
-    }
-    
-    private var disconnectLed: Bool = false
-    private var disconnectVibration: Bool = false
-    
-    private var connectLed: Bool = false
-    private var connectVibration: Bool = false
-    
     private var uptime: TimeInterval? {
     private var uptime: TimeInterval? {
         didSet {
         didSet {
             guard isViewLoaded else {
             guard isViewLoaded else {
@@ -78,17 +62,6 @@ public class RileyLinkMinimedDeviceTableViewController: UITableViewController {
             cellForRow(.uptime)?.setDetailAge(uptime)
             cellForRow(.uptime)?.setDetailAge(uptime)
         }
         }
     }
     }
-    
-    private var battery: String? {
-        didSet {
-            guard isViewLoaded else {
-                return
-            }
-            
-            cellForRow(.battery)?.setDetailBatteryLevel(battery)
-        }
-    }
-
 
 
     private var lastIdle: Date? {
     private var lastIdle: Date? {
         didSet {
         didSet {
@@ -144,57 +117,12 @@ public class RileyLinkMinimedDeviceTableViewController: UITableViewController {
             } catch { }
             } catch { }
         }
         }
     }
     }
-    
-    func updateBatteryLevel() {
-        device.runSession(withName: "Get battery level") { (session) in
-            let batteryLevel = self.device.getBatterylevel()
-            DispatchQueue.main.async {
-                self.battery = batteryLevel
-            }
-        }
-    }
-    
-    func orangeClose() {
-        device.runSession(withName: "Orange Action Close") { (session) in
-            self.device.orangeClose()
-        }
-    }
-    
-    func orangeReadSet() {
-        device.runSession(withName: "orange Read Set") { (session) in
-            self.device.orangeReadSet()
-        }
-    }
 
 
-    func writePSW() {
-        device.runSession(withName: "Orange Action PSW") { (session) in
-            self.device.orangeWritePwd()
-        }
-    }
-    
-    func orangeAction(index: Int) {
-        device.runSession(withName: "Orange Action \(index)") { (session) in
-            self.device.orangeAction(mode: index)
-        }
-    }
-    
-    func orangeAction(index: Int, open: Bool) {
-        device.runSession(withName: "Orange Set Action \(index)") { (session) in
-            self.device.orangeSetAction(index: index, open: open)
-        }
-    }
-    
     private func updateDeviceStatus() {
     private func updateDeviceStatus() {
         device.getStatus { (status) in
         device.getStatus { (status) in
             DispatchQueue.main.async {
             DispatchQueue.main.async {
-                self.firmwareVersion = status.firmwareDescription
-                self.fw_hw = status.fw_hw
-                self.connectLed = status.ledOn
-                self.connectVibration = status.vibrationOn
-                self.disconnectLed = status.ledOn
-                self.disconnectVibration = status.vibrationOn
-                
-                self.tableView.reloadData()
+                self.lastIdle = status.lastIdle
+                self.firmwareVersion = status.version
             }
             }
         }
         }
     }
     }
@@ -239,10 +167,7 @@ public class RileyLinkMinimedDeviceTableViewController: UITableViewController {
                 if let state = note.userInfo?[PumpOps.notificationPumpStateKey] as? PumpState {
                 if let state = note.userInfo?[PumpOps.notificationPumpStateKey] as? PumpState {
                     self?.pumpState = state
                     self?.pumpState = state
                 }
                 }
-            },
-            center.addObserver(forName: .DeviceFW_HWChange, object: device, queue: mainQueue) { [weak self] (note) in
-                self?.updateDeviceStatus()
-            },
+            }
         ]
         ]
     }
     }
     
     
@@ -260,19 +185,6 @@ public class RileyLinkMinimedDeviceTableViewController: UITableViewController {
         updateRSSI()
         updateRSSI()
         
         
         updateUptime()
         updateUptime()
-        
-        updateBatteryLevel()
-        
-        writePSW()
-        
-        orangeAction(index: 9)
-        
-        orangeReadSet()
-    }
-    
-    public override func viewDidDisappear(_ animated: Bool) {
-        super.viewDidDisappear(animated)
-        orangeClose()
     }
     }
     
     
     public override func viewWillDisappear(_ animated: Bool) {
     public override func viewWillDisappear(_ animated: Bool) {
@@ -326,8 +238,6 @@ public class RileyLinkMinimedDeviceTableViewController: UITableViewController {
         case connection
         case connection
         case uptime
         case uptime
         case idleStatus
         case idleStatus
-        case battery
-        case orl
     }
     }
 
 
     private enum PumpRow: Int, CaseCountable {
     private enum PumpRow: Int, CaseCountable {
@@ -349,33 +259,8 @@ public class RileyLinkMinimedDeviceTableViewController: UITableViewController {
         case enableLED
         case enableLED
         case discoverCommands
         case discoverCommands
         case getStatistics
         case getStatistics
-        case yellow
-        case red
-        case off
-        case shake
-        case shakeOff
-        case disconnectLed
-        case disconnectVibration
-        case connectLed
-        case connectVibration
-    }
-
-    @objc
-    func switchAction(sender: RileyLinkSwitch) {
-        switch CommandRow(rawValue: sender.index)! {
-        case .connectLed:
-            orangeAction(index: 4, open: sender.isOn)
-        case .connectVibration:
-            orangeAction(index: 5, open: sender.isOn)
-        case .disconnectLed:
-            orangeAction(index: 2, open: sender.isOn)
-        case .disconnectVibration:
-            orangeAction(index: 3, open: sender.isOn)
-        default:
-            break
-        }
     }
     }
-    
+
     private func cellForRow(_ row: DeviceRow) -> UITableViewCell? {
     private func cellForRow(_ row: DeviceRow) -> UITableViewCell? {
         return tableView.cellForRow(at: IndexPath(row: row.rawValue, section: Section.device.rawValue))
         return tableView.cellForRow(at: IndexPath(row: row.rawValue, section: Section.device.rawValue))
     }
     }
@@ -402,10 +287,6 @@ public class RileyLinkMinimedDeviceTableViewController: UITableViewController {
             return CommandRow.count
             return CommandRow.count
         }
         }
     }
     }
-    
-    public override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
-        return 45
-    }
 
 
     public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
     public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
         let cell: UITableViewCell
         let cell: UITableViewCell
@@ -414,17 +295,8 @@ public class RileyLinkMinimedDeviceTableViewController: UITableViewController {
             cell = reusableCell
             cell = reusableCell
         } else {
         } else {
             cell = UITableViewCell(style: .value1, reuseIdentifier: CellIdentifier)
             cell = UITableViewCell(style: .value1, reuseIdentifier: CellIdentifier)
-            let switchView = RileyLinkSwitch()
-            switchView.tag = 10000
-            switchView.addTarget(self, action: #selector(switchAction(sender:)), for: .valueChanged)
-            switchView.frame = CGRect(x: tableView.frame.width - 51 - 20, y: 7, width: 51, height: 31)
-            cell.contentView.addSubview(switchView)
         }
         }
-        
-        let switchView = cell.contentView.viewWithTag(10000) as? RileyLinkSwitch
-        switchView?.isHidden = true
-        switchView?.index = indexPath.row
-        
+
         cell.accessoryType = .none
         cell.accessoryType = .none
 
 
         switch Section(rawValue: indexPath.section)! {
         switch Section(rawValue: indexPath.section)! {
@@ -449,12 +321,6 @@ public class RileyLinkMinimedDeviceTableViewController: UITableViewController {
             case .idleStatus:
             case .idleStatus:
                 cell.textLabel?.text = LocalizedString("On Idle", comment: "The title of the cell showing the last idle")
                 cell.textLabel?.text = LocalizedString("On Idle", comment: "The title of the cell showing the last idle")
                 cell.setDetailDate(lastIdle, formatter: dateFormatter)
                 cell.setDetailDate(lastIdle, formatter: dateFormatter)
-            case .battery:
-                cell.textLabel?.text = NSLocalizedString("Battery Level", comment: "The title of the cell showing battery level")
-                cell.setDetailBatteryLevel(battery)
-            case .orl:
-                cell.textLabel?.text = NSLocalizedString("ORL", comment: "The title of the cell showing ORL")
-                cell.detailTextLabel?.text = fw_hw
             }
             }
         case .pump:
         case .pump:
             switch PumpRow(rawValue: indexPath.row)! {
             switch PumpRow(rawValue: indexPath.row)! {
@@ -519,36 +385,6 @@ public class RileyLinkMinimedDeviceTableViewController: UITableViewController {
                 
                 
             case .getStatistics:
             case .getStatistics:
                 cell.textLabel?.text = LocalizedString("RileyLink Statistics", comment: "The title of the command to fetch RileyLink statistics")
                 cell.textLabel?.text = LocalizedString("RileyLink Statistics", comment: "The title of the command to fetch RileyLink statistics")
-            case .yellow:
-                cell.textLabel?.text = NSLocalizedString("Lighten Yellow LED", comment: "The title of the cell showing Lighten Yellow LED")
-            case .red:
-                cell.textLabel?.text = NSLocalizedString("Lighten Red LED", comment: "The title of the cell showing Lighten Red LED")
-            case .off:
-                cell.textLabel?.text = NSLocalizedString("Turn Off LED", comment: "The title of the cell showing Turn Off LED")
-            case .shake:
-                cell.textLabel?.text = NSLocalizedString("Test Vibrator", comment: "The title of the cell showing Test Vibrator")
-            case .shakeOff:
-                cell.textLabel?.text = NSLocalizedString("Stop Vibrator", comment: "The title of the cell showing Stop Vibrator")
-            case .disconnectLed:
-                switchView?.isHidden = false
-                switchView?.isOn = disconnectLed
-                cell.accessoryType = .none
-                cell.textLabel?.text = NSLocalizedString("Disconnect Led", comment: "The title of the cell showing Stop Vibrator")
-            case .disconnectVibration:
-                switchView?.isHidden = false
-                switchView?.isOn = disconnectVibration
-                cell.accessoryType = .none
-                cell.textLabel?.text = NSLocalizedString("Disconnect Vibrator", comment: "The title of the cell showing Stop Vibrator")
-            case .connectLed:
-                switchView?.isHidden = false
-                switchView?.isOn = connectLed
-                cell.accessoryType = .none
-                cell.textLabel?.text = NSLocalizedString("Connect Led", comment: "The title of the cell showing Stop Vibrator")
-            case .connectVibration:
-                switchView?.isHidden = false
-                switchView?.isOn = connectVibration
-                cell.accessoryType = .none
-                cell.textLabel?.text = NSLocalizedString("Connect Vibrator", comment: "The title of the cell showing Stop Vibrator")
             }
             }
         }
         }
 
 
@@ -602,7 +438,7 @@ public class RileyLinkMinimedDeviceTableViewController: UITableViewController {
                 break
                 break
             }
             }
         case .commands:
         case .commands:
-            var vc: CommandResponseViewController?
+            let vc: CommandResponseViewController
 
 
             switch CommandRow(rawValue: indexPath.row)! {
             switch CommandRow(rawValue: indexPath.row)! {
             case .tune:
             case .tune:
@@ -624,27 +460,19 @@ public class RileyLinkMinimedDeviceTableViewController: UITableViewController {
             case .readBasalSchedule:
             case .readBasalSchedule:
                 vc = .readBasalSchedule(ops: ops, device: device, integerFormatter: integerFormatter)
                 vc = .readBasalSchedule(ops: ops, device: device, integerFormatter: integerFormatter)
             case .enableLED:
             case .enableLED:
-                vc = .enableLEDs(ops: ops, device: device)
+//                vc = .enableLEDs(ops: ops, device: device)
+                vc = .getStatistics(ops: ops, device: device)
             case .discoverCommands:
             case .discoverCommands:
                 vc = .discoverCommands(ops: ops, device: device)
                 vc = .discoverCommands(ops: ops, device: device)
             case .getStatistics:
             case .getStatistics:
                 vc = .getStatistics(ops: ops, device: device)
                 vc = .getStatistics(ops: ops, device: device)
-            case .yellow: orangeAction(index: 1)
-            case .red: orangeAction(index: 2)
-            case .off: orangeAction(index: 3)
-            case .shake: orangeAction(index: 4)
-            case .shakeOff: orangeAction(index: 5)
-            default:
-                break
             }
             }
 
 
             if let cell = tableView.cellForRow(at: indexPath) {
             if let cell = tableView.cellForRow(at: indexPath) {
-                vc?.title = cell.textLabel?.text
+                vc.title = cell.textLabel?.text
             }
             }
 
 
-            if let vc = vc {
-                show(vc, sender: indexPath)
-            }
+            show(vc, sender: indexPath)
         case .pump:
         case .pump:
             break
             break
         }
         }
@@ -689,16 +517,6 @@ private extension TimeInterval {
 
 
 
 
 private extension UITableViewCell {
 private extension UITableViewCell {
-    
-    func setDetailBatteryLevel(_ batteryLevel: String?) {
-        if let unwrappedBatteryLevel = batteryLevel {
-            detailTextLabel?.text = unwrappedBatteryLevel + " %"
-        } else {
-            detailTextLabel?.text = ""
-        }
-    }
-    
-    
     func setDetailDate(_ date: Date?, formatter: DateFormatter) {
     func setDetailDate(_ date: Date?, formatter: DateFormatter) {
         if let date = date {
         if let date = date {
             detailTextLabel?.text = formatter.string(from: date)
             detailTextLabel?.text = formatter.string(from: date)

+ 38 - 1
Dependencies/rileylink_ios/OmniKit/PumpManager/OmnipodPumpManager.swift

@@ -20,7 +20,7 @@ public enum ReservoirAlertState {
     case empty
     case empty
 }
 }
 
 
-public protocol PodStateObserver: class {
+public protocol PodStateObserver: AnyObject {
     func podStateDidUpdate(_ state: PodState?)
     func podStateDidUpdate(_ state: PodState?)
 }
 }
 
 
@@ -229,6 +229,43 @@ public class OmnipodPumpManager: RileyLinkPumpManager {
         }
         }
     }
     }
 
 
+    public var rileyLinkBatteryAlertLevel: Int? {
+        get {
+            return state.rileyLinkBatteryAlertLevel
+        }
+        set {
+            setState { state in
+                state.rileyLinkBatteryAlertLevel = newValue
+            }
+        }
+    }
+
+    public override func device(_ device: RileyLinkDevice, didUpdateBattery level: Int) {
+        let repeatInterval: TimeInterval = .hours(1)
+
+        if let alertLevel = state.rileyLinkBatteryAlertLevel,
+           level <= alertLevel,
+           state.lastRileyLinkBatteryAlertDate.addingTimeInterval(repeatInterval) < Date()
+        {
+            self.setState { state in
+                state.lastRileyLinkBatteryAlertDate = Date()
+            }
+
+            // HACK Alert. This is temporary for the v2.2.5 & v2.2.6 releases. Dev and newer releases will use the new Loop Alert facility
+            let notification = UNMutableNotificationContent()
+            notification.body = String(format: LocalizedString("\"%1$@\" has a low battery", comment: "Format string for low battery alert body for RileyLink. (1: device name)"), device.name ?? "unnamed")
+            notification.title = LocalizedString("Low RileyLink Battery", comment: "Title for RileyLink low battery alert")
+            notification.sound = .default
+            notification.categoryIdentifier = LoopNotificationCategory.loopNotRunning.rawValue
+            notification.threadIdentifier = LoopNotificationCategory.loopNotRunning.rawValue
+            let request = UNNotificationRequest(
+                identifier: "batteryalert.rileylink",
+                content: notification,
+                trigger: nil)
+            UNUserNotificationCenter.current().add(request)
+        }
+    }
+
     // MARK: - CustomDebugStringConvertible
     // MARK: - CustomDebugStringConvertible
 
 
     override public var debugDescription: String {
     override public var debugDescription: String {

+ 12 - 1
Dependencies/rileylink_ios/OmniKit/PumpManager/OmnipodPumpManagerState.swift

@@ -52,6 +52,10 @@ public struct OmnipodPumpManagerState: RawRepresentable, Equatable {
     
     
     internal var insulinType: InsulinType
     internal var insulinType: InsulinType
 
 
+    public var rileyLinkBatteryAlertLevel: Int?
+
+    public var lastRileyLinkBatteryAlertDate: Date = .distantPast
+
     // MARK: -
     // MARK: -
 
 
     public init(podState: PodState?, timeZone: TimeZone, basalSchedule: BasalSchedule, rileyLinkConnectionManagerState: RileyLinkConnectionManagerState?, insulinType: InsulinType) {
     public init(podState: PodState?, timeZone: TimeZone, basalSchedule: BasalSchedule, rileyLinkConnectionManagerState: RileyLinkConnectionManagerState?, insulinType: InsulinType) {
@@ -146,6 +150,9 @@ public struct OmnipodPumpManagerState: RawRepresentable, Equatable {
         if let pairingAttemptAddress = rawValue["pairingAttemptAddress"] as? UInt32 {
         if let pairingAttemptAddress = rawValue["pairingAttemptAddress"] as? UInt32 {
             self.pairingAttemptAddress = pairingAttemptAddress
             self.pairingAttemptAddress = pairingAttemptAddress
         }
         }
+
+        rileyLinkBatteryAlertLevel = rawValue["rileyLinkBatteryAlertLevel"] as? Int
+        lastRileyLinkBatteryAlertDate = rawValue["lastRileyLinkBatteryAlertDate"] as? Date ?? Date.distantPast
     }
     }
     
     
     public var rawValue: RawValue {
     public var rawValue: RawValue {
@@ -174,7 +181,9 @@ public struct OmnipodPumpManagerState: RawRepresentable, Equatable {
         if let pairingAttemptAddress = pairingAttemptAddress {
         if let pairingAttemptAddress = pairingAttemptAddress {
             value["pairingAttemptAddress"] = pairingAttemptAddress
             value["pairingAttemptAddress"] = pairingAttemptAddress
         }
         }
-        
+        value["rileyLinkBatteryAlertLevel"] = rileyLinkBatteryAlertLevel
+        value["lastRileyLinkBatteryAlertDate"] = lastRileyLinkBatteryAlertDate
+
         return value
         return value
     }
     }
 }
 }
@@ -213,6 +222,8 @@ extension OmnipodPumpManagerState: CustomDebugStringConvertible {
             "* automaticBolusBeeps: \(String(describing: automaticBolusBeeps))",
             "* automaticBolusBeeps: \(String(describing: automaticBolusBeeps))",
             "* pairingAttemptAddress: \(String(describing: pairingAttemptAddress))",
             "* pairingAttemptAddress: \(String(describing: pairingAttemptAddress))",
             "* insulinType: \(String(describing: insulinType))",
             "* insulinType: \(String(describing: insulinType))",
+            "* rileyLinkBatteryAlertLevel: \(String(describing: rileyLinkBatteryAlertLevel))",
+            "* lastRileyLinkBatteryAlertDate \(String(describing: lastRileyLinkBatteryAlertDate))",
             String(reflecting: podState),
             String(reflecting: podState),
             String(reflecting: rileyLinkConnectionManagerState),
             String(reflecting: rileyLinkConnectionManagerState),
         ].joined(separator: "\n")
         ].joined(separator: "\n")

+ 14 - 1
Dependencies/rileylink_ios/OmniKitUI/ViewControllers/OmnipodSettingsViewController.swift

@@ -632,7 +632,20 @@ class OmnipodSettingsViewController: RileyLinkSettingsViewController {
             }
             }
         case .rileyLinks:
         case .rileyLinks:
             let device = devicesDataSource.devices[indexPath.row]
             let device = devicesDataSource.devices[indexPath.row]
-            let vc = RileyLinkDeviceTableViewController(device: device)
+            
+            guard device.hardwareType != nil else {
+                tableView.deselectRow(at: indexPath, animated: true)
+                return
+            }
+
+            let vc = RileyLinkDeviceTableViewController(
+                device: device,
+                batteryAlertLevel: pumpManager.rileyLinkBatteryAlertLevel,
+                batteryAlertLevelChanged: { [weak self] value in
+                    self?.pumpManager.rileyLinkBatteryAlertLevel = value
+                }
+            )
+            
             self.show(vc, sender: sender)
             self.show(vc, sender: sender)
         case .deletePumpManager:
         case .deletePumpManager:
             let confirmVC = UIAlertController(pumpManagerDeletionHandler: {
             let confirmVC = UIAlertController(pumpManagerDeletionHandler: {

+ 0 - 12
Dependencies/rileylink_ios/OmniKitUI/sv.lproj/Localizable.strings

@@ -93,12 +93,6 @@
 /* Title text for button to enable bolus beeps */
 /* Title text for button to enable bolus beeps */
 "Enable Bolus Beeps" = "Slå på bolusljud";
 "Enable Bolus Beeps" = "Slå på bolusljud";
 
 
-/* Title text for button to enable confirmation beeps */
-"Enable Confirmation Beeps" = "Sätt på bekräftelseljud";
-
-/* Title text for button to disable confirmation beeps */
-"Disable Confirmation Beeps" = "Stäng av bekräftelseljud";
-
 /* The alert title for disable bolus beeps error */
 /* The alert title for disable bolus beeps error */
 "Error disabling bolus beeps" = "Fel vid avstängning av bolusljud";
 "Error disabling bolus beeps" = "Fel vid avstängning av bolusljud";
 
 
@@ -224,12 +218,6 @@
 /* The title of the command to run the test command */
 /* The title of the command to run the test command */
 "Test Command" = "Kommandotest";
 "Test Command" = "Kommandotest";
 
 
-/* The title of the command to read pod status */
-"Read Pod Status" = "Läs poddstatus";
-
-/* The title of the command to read the puls log */
-"Read Pulse Log" = "Läs pulslogg";
-
 /* Progress message for testing commands. */
 /* Progress message for testing commands. */
 "Testing Commands…" = "Testar att skicka kommandon…";
 "Testing Commands…" = "Testar att skicka kommandon…";
 
 

+ 34 - 26
Dependencies/rileylink_ios/RileyLink.xcodeproj/project.pbxproj

@@ -7,12 +7,7 @@
 	objects = {
 	objects = {
 
 
 /* Begin PBXBuildFile section */
 /* Begin PBXBuildFile section */
-		19E9EBAD264A86F100000232 /* PeripheralManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19E9EBAC264A86F100000232 /* PeripheralManager.swift */; };
-		19E9EBB5264A88BD00000232 /* PeripheralManager+RileyLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19E9EBB4264A88BD00000232 /* PeripheralManager+RileyLink.swift */; };
-		19E9EBB7264A88C900000232 /* RileyLinkDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19E9EBB6264A88C900000232 /* RileyLinkDevice.swift */; };
-		19E9EBB9264A88F500000232 /* RileyLinkDeviceTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19E9EBB8264A88F500000232 /* RileyLinkDeviceTableViewController.swift */; };
-		19E9EBBB264A890F00000232 /* RileyLinkMinimedDeviceTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19E9EBBA264A890F00000232 /* RileyLinkMinimedDeviceTableViewController.swift */; };
-		19E9EBBD264A897A00000232 /* PumpModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19E9EBBC264A897A00000232 /* PumpModel.swift */; };
+		198AA26227295EDC00FB87CE /* CommandResponseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 198AA26127295EDC00FB87CE /* CommandResponseViewController.swift */; };
 		2B19B9881DF3EF68006AB65F /* NewTimePumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B19B9871DF3EF68006AB65F /* NewTimePumpEvent.swift */; };
 		2B19B9881DF3EF68006AB65F /* NewTimePumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B19B9871DF3EF68006AB65F /* NewTimePumpEvent.swift */; };
 		2F962EC11E6872170070EFBD /* TimestampedHistoryEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F962EC01E6872170070EFBD /* TimestampedHistoryEventTests.swift */; };
 		2F962EC11E6872170070EFBD /* TimestampedHistoryEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F962EC01E6872170070EFBD /* TimestampedHistoryEventTests.swift */; };
 		2F962EC81E7074E60070EFBD /* BolusNormalPumpEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F962EC71E7074E60070EFBD /* BolusNormalPumpEventTests.swift */; };
 		2F962EC81E7074E60070EFBD /* BolusNormalPumpEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F962EC71E7074E60070EFBD /* BolusNormalPumpEventTests.swift */; };
@@ -26,13 +21,16 @@
 		431CE7811F98564200255374 /* RileyLinkBLEKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 431CE7711F98564100255374 /* RileyLinkBLEKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		431CE7811F98564200255374 /* RileyLinkBLEKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 431CE7711F98564100255374 /* RileyLinkBLEKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		431CE7841F98564200255374 /* RileyLinkBLEKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 431CE76F1F98564100255374 /* RileyLinkBLEKit.framework */; };
 		431CE7841F98564200255374 /* RileyLinkBLEKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 431CE76F1F98564100255374 /* RileyLinkBLEKit.framework */; };
 		431CE7851F98564200255374 /* RileyLinkBLEKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 431CE76F1F98564100255374 /* RileyLinkBLEKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
 		431CE7851F98564200255374 /* RileyLinkBLEKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 431CE76F1F98564100255374 /* RileyLinkBLEKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+		431CE78D1F985B5400255374 /* PeripheralManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE78C1F985B5400255374 /* PeripheralManager.swift */; };
 		431CE78F1F985B6E00255374 /* CBPeripheral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE78E1F985B6E00255374 /* CBPeripheral.swift */; };
 		431CE78F1F985B6E00255374 /* CBPeripheral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE78E1F985B6E00255374 /* CBPeripheral.swift */; };
 		431CE7911F985D8D00255374 /* RileyLinkDeviceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE7901F985D8D00255374 /* RileyLinkDeviceManager.swift */; };
 		431CE7911F985D8D00255374 /* RileyLinkDeviceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE7901F985D8D00255374 /* RileyLinkDeviceManager.swift */; };
+		431CE7931F985DE700255374 /* PeripheralManager+RileyLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE7921F985DE700255374 /* PeripheralManager+RileyLink.swift */; };
 		431CE7961F9B0F0200255374 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE7941F9B0DAE00255374 /* OSLog.swift */; };
 		431CE7961F9B0F0200255374 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE7941F9B0DAE00255374 /* OSLog.swift */; };
 		431CE7971F9B0F0200255374 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE7941F9B0DAE00255374 /* OSLog.swift */; };
 		431CE7971F9B0F0200255374 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE7941F9B0DAE00255374 /* OSLog.swift */; };
 		431CE7981F9B0F0200255374 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE7941F9B0DAE00255374 /* OSLog.swift */; };
 		431CE7981F9B0F0200255374 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE7941F9B0DAE00255374 /* OSLog.swift */; };
 		431CE7991F9B0F0200255374 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE7941F9B0DAE00255374 /* OSLog.swift */; };
 		431CE7991F9B0F0200255374 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE7941F9B0DAE00255374 /* OSLog.swift */; };
 		431CE79A1F9B0F1600255374 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE7941F9B0DAE00255374 /* OSLog.swift */; };
 		431CE79A1F9B0F1600255374 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE7941F9B0DAE00255374 /* OSLog.swift */; };
+		431CE79C1F9B21BA00255374 /* RileyLinkDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE79B1F9B21BA00255374 /* RileyLinkDevice.swift */; };
 		431CE79E1F9BE73900255374 /* BLEFirmwareVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE79D1F9BE73900255374 /* BLEFirmwareVersion.swift */; };
 		431CE79E1F9BE73900255374 /* BLEFirmwareVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE79D1F9BE73900255374 /* BLEFirmwareVersion.swift */; };
 		431CE79F1F9C670600255374 /* TimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43EBE4501EAD238C0073A0B5 /* TimeInterval.swift */; };
 		431CE79F1F9C670600255374 /* TimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43EBE4501EAD238C0073A0B5 /* TimeInterval.swift */; };
 		431CE7A11F9D195600255374 /* CBCentralManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE7A01F9D195600255374 /* CBCentralManager.swift */; };
 		431CE7A11F9D195600255374 /* CBCentralManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE7A01F9D195600255374 /* CBCentralManager.swift */; };
@@ -68,6 +66,7 @@
 		4352A73220DEC9D600CAC200 /* CommandResponseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4352A73120DEC9D600CAC200 /* CommandResponseViewController.swift */; };
 		4352A73220DEC9D600CAC200 /* CommandResponseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4352A73120DEC9D600CAC200 /* CommandResponseViewController.swift */; };
 		4352A73320DEC9FC00CAC200 /* RileyLinkKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43D5E78E1FAF7BFB004ACDB7 /* RileyLinkKitUI.framework */; };
 		4352A73320DEC9FC00CAC200 /* RileyLinkKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43D5E78E1FAF7BFB004ACDB7 /* RileyLinkKitUI.framework */; };
 		4352A73420DECAE000CAC200 /* LoopKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43FB610A20DDF55E002B996B /* LoopKitUI.framework */; };
 		4352A73420DECAE000CAC200 /* LoopKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43FB610A20DDF55E002B996B /* LoopKitUI.framework */; };
+		4352A73920DECBAA00CAC200 /* RileyLinkMinimedDeviceTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4352A73820DECBAA00CAC200 /* RileyLinkMinimedDeviceTableViewController.swift */; };
 		4352A73A20DECDB300CAC200 /* MinimedKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C10D9BC11C8269D500378342 /* MinimedKit.framework */; };
 		4352A73A20DECDB300CAC200 /* MinimedKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C10D9BC11C8269D500378342 /* MinimedKit.framework */; };
 		4352A73F20DED02C00CAC200 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4352A73D20DED01700CAC200 /* HKUnit.swift */; };
 		4352A73F20DED02C00CAC200 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4352A73D20DED01700CAC200 /* HKUnit.swift */; };
 		4352A74120DED23100CAC200 /* RileyLinkDeviceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4352A74020DED23000CAC200 /* RileyLinkDeviceManager.swift */; };
 		4352A74120DED23100CAC200 /* RileyLinkDeviceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4352A74020DED23000CAC200 /* RileyLinkDeviceManager.swift */; };
@@ -150,6 +149,7 @@
 		43D5E7951FAF7BFB004ACDB7 /* RileyLinkKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43D5E78E1FAF7BFB004ACDB7 /* RileyLinkKitUI.framework */; };
 		43D5E7951FAF7BFB004ACDB7 /* RileyLinkKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43D5E78E1FAF7BFB004ACDB7 /* RileyLinkKitUI.framework */; };
 		43D5E7961FAF7BFB004ACDB7 /* RileyLinkKitUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 43D5E78E1FAF7BFB004ACDB7 /* RileyLinkKitUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
 		43D5E7961FAF7BFB004ACDB7 /* RileyLinkKitUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 43D5E78E1FAF7BFB004ACDB7 /* RileyLinkKitUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
 		43D5E79A1FAF7C47004ACDB7 /* RileyLinkDeviceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 439731261CF21C3C00F474E5 /* RileyLinkDeviceTableViewCell.swift */; };
 		43D5E79A1FAF7C47004ACDB7 /* RileyLinkDeviceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 439731261CF21C3C00F474E5 /* RileyLinkDeviceTableViewCell.swift */; };
+		43D5E79C1FAF7C47004ACDB7 /* RileyLinkDeviceTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C170C9981CECD80000F3D8E5 /* RileyLinkDeviceTableViewController.swift */; };
 		43D5E79F1FAF7C98004ACDB7 /* IdentifiableClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431185AE1CF25A590059ED98 /* IdentifiableClass.swift */; };
 		43D5E79F1FAF7C98004ACDB7 /* IdentifiableClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431185AE1CF25A590059ED98 /* IdentifiableClass.swift */; };
 		43D5E7A01FAF7CCA004ACDB7 /* NumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43323EA61FA81A0F003FB0FA /* NumberFormatter.swift */; };
 		43D5E7A01FAF7CCA004ACDB7 /* NumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43323EA61FA81A0F003FB0FA /* NumberFormatter.swift */; };
 		43D5E7A11FAF7CE0004ACDB7 /* UITableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1B4A9581D1E6357003B8985 /* UITableViewCell.swift */; };
 		43D5E7A11FAF7CE0004ACDB7 /* UITableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1B4A9581D1E6357003B8985 /* UITableViewCell.swift */; };
@@ -250,6 +250,7 @@
 		7D9BF00123369910005DCFD6 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D9BF00323369910005DCFD6 /* Localizable.strings */; };
 		7D9BF00123369910005DCFD6 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D9BF00323369910005DCFD6 /* Localizable.strings */; };
 		7D9BF03D2336AE0B005DCFD6 /* OmnipodPumpManager.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7D9BF03F2336AE0B005DCFD6 /* OmnipodPumpManager.storyboard */; };
 		7D9BF03D2336AE0B005DCFD6 /* OmnipodPumpManager.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7D9BF03F2336AE0B005DCFD6 /* OmnipodPumpManager.storyboard */; };
 		7DEFE05322ED1C2400FCD378 /* OverrideStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEFE05222ED1C2300FCD378 /* OverrideStatus.swift */; };
 		7DEFE05322ED1C2400FCD378 /* OverrideStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEFE05222ED1C2300FCD378 /* OverrideStatus.swift */; };
+		B62DBD572725B9EB0050C038 /* CommandResponseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62DBD562725B9EB0050C038 /* CommandResponseViewController.swift */; };
 		C104A9C1217E603E006E3C3E /* OmnipodReservoirView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C104A9BF217E603E006E3C3E /* OmnipodReservoirView.swift */; };
 		C104A9C1217E603E006E3C3E /* OmnipodReservoirView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C104A9BF217E603E006E3C3E /* OmnipodReservoirView.swift */; };
 		C104A9C2217E603E006E3C3E /* OmnipodReservoirView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C104A9C0217E603E006E3C3E /* OmnipodReservoirView.xib */; };
 		C104A9C2217E603E006E3C3E /* OmnipodReservoirView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C104A9C0217E603E006E3C3E /* OmnipodReservoirView.xib */; };
 		C104A9C3217E611F006E3C3E /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14FFC5A1D3D74F90049CF85 /* NibLoadable.swift */; };
 		C104A9C3217E611F006E3C3E /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14FFC5A1D3D74F90049CF85 /* NibLoadable.swift */; };
@@ -359,6 +360,7 @@
 		C17EDC4F2134D0CC0031D9F0 /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4345D1CD1DA16AF300BAAD22 /* TimeZone.swift */; };
 		C17EDC4F2134D0CC0031D9F0 /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4345D1CD1DA16AF300BAAD22 /* TimeZone.swift */; };
 		C1814B88225F0A3D008D2D8E /* ExpirationReminderDateTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1814B87225F0A3D008D2D8E /* ExpirationReminderDateTableViewCell.swift */; };
 		C1814B88225F0A3D008D2D8E /* ExpirationReminderDateTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1814B87225F0A3D008D2D8E /* ExpirationReminderDateTableViewCell.swift */; };
 		C1814B8A225FADFA008D2D8E /* ExpirationReminderDateTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C1814B89225FADFA008D2D8E /* ExpirationReminderDateTableViewCell.xib */; };
 		C1814B8A225FADFA008D2D8E /* ExpirationReminderDateTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C1814B89225FADFA008D2D8E /* ExpirationReminderDateTableViewCell.xib */; };
+		C1842BBB1C8E184300DB42AC /* PumpModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1842BBA1C8E184300DB42AC /* PumpModel.swift */; };
 		C1842BBD1C8E7C6E00DB42AC /* PumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1842BBC1C8E7C6E00DB42AC /* PumpEvent.swift */; };
 		C1842BBD1C8E7C6E00DB42AC /* PumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1842BBC1C8E7C6E00DB42AC /* PumpEvent.swift */; };
 		C1842BBF1C8E855A00DB42AC /* PumpEventType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1842BBE1C8E855A00DB42AC /* PumpEventType.swift */; };
 		C1842BBF1C8E855A00DB42AC /* PumpEventType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1842BBE1C8E855A00DB42AC /* PumpEventType.swift */; };
 		C1842BC11C8E8B2500DB42AC /* UnabsorbedInsulinPumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1842BC01C8E8B2500DB42AC /* UnabsorbedInsulinPumpEvent.swift */; };
 		C1842BC11C8E8B2500DB42AC /* UnabsorbedInsulinPumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1842BC01C8E8B2500DB42AC /* UnabsorbedInsulinPumpEvent.swift */; };
@@ -879,13 +881,7 @@
 /* End PBXCopyFilesBuildPhase section */
 /* End PBXCopyFilesBuildPhase section */
 
 
 /* Begin PBXFileReference section */
 /* Begin PBXFileReference section */
-		1971A859268B21C70083CF2D /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = "<group>"; };
-		19E9EBAC264A86F100000232 /* PeripheralManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeripheralManager.swift; sourceTree = "<group>"; };
-		19E9EBB4264A88BD00000232 /* PeripheralManager+RileyLink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PeripheralManager+RileyLink.swift"; sourceTree = "<group>"; };
-		19E9EBB6264A88C900000232 /* RileyLinkDevice.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RileyLinkDevice.swift; sourceTree = "<group>"; };
-		19E9EBB8264A88F500000232 /* RileyLinkDeviceTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RileyLinkDeviceTableViewController.swift; sourceTree = "<group>"; };
-		19E9EBBA264A890F00000232 /* RileyLinkMinimedDeviceTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RileyLinkMinimedDeviceTableViewController.swift; sourceTree = "<group>"; };
-		19E9EBBC264A897A00000232 /* PumpModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PumpModel.swift; sourceTree = "<group>"; };
+		198AA26127295EDC00FB87CE /* CommandResponseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommandResponseViewController.swift; sourceTree = "<group>"; };
 		2B19B9871DF3EF68006AB65F /* NewTimePumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewTimePumpEvent.swift; sourceTree = "<group>"; };
 		2B19B9871DF3EF68006AB65F /* NewTimePumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewTimePumpEvent.swift; sourceTree = "<group>"; };
 		2F962EBE1E678BAA0070EFBD /* PumpOpsSynchronousTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = PumpOpsSynchronousTests.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
 		2F962EBE1E678BAA0070EFBD /* PumpOpsSynchronousTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = PumpOpsSynchronousTests.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
 		2F962EC01E6872170070EFBD /* TimestampedHistoryEventTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimestampedHistoryEventTests.swift; sourceTree = "<group>"; };
 		2F962EC01E6872170070EFBD /* TimestampedHistoryEventTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimestampedHistoryEventTests.swift; sourceTree = "<group>"; };
@@ -902,9 +898,12 @@
 		431CE7721F98564100255374 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		431CE7721F98564100255374 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		431CE7771F98564200255374 /* RileyLinkBLEKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RileyLinkBLEKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
 		431CE7771F98564200255374 /* RileyLinkBLEKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RileyLinkBLEKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
 		431CE7801F98564200255374 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		431CE7801F98564200255374 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		431CE78C1F985B5400255374 /* PeripheralManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralManager.swift; sourceTree = "<group>"; };
 		431CE78E1F985B6E00255374 /* CBPeripheral.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CBPeripheral.swift; sourceTree = "<group>"; };
 		431CE78E1F985B6E00255374 /* CBPeripheral.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CBPeripheral.swift; sourceTree = "<group>"; };
 		431CE7901F985D8D00255374 /* RileyLinkDeviceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RileyLinkDeviceManager.swift; sourceTree = "<group>"; };
 		431CE7901F985D8D00255374 /* RileyLinkDeviceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RileyLinkDeviceManager.swift; sourceTree = "<group>"; };
+		431CE7921F985DE700255374 /* PeripheralManager+RileyLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PeripheralManager+RileyLink.swift"; sourceTree = "<group>"; };
 		431CE7941F9B0DAE00255374 /* OSLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = "<group>"; };
 		431CE7941F9B0DAE00255374 /* OSLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = "<group>"; };
+		431CE79B1F9B21BA00255374 /* RileyLinkDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RileyLinkDevice.swift; sourceTree = "<group>"; };
 		431CE79D1F9BE73900255374 /* BLEFirmwareVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEFirmwareVersion.swift; sourceTree = "<group>"; };
 		431CE79D1F9BE73900255374 /* BLEFirmwareVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEFirmwareVersion.swift; sourceTree = "<group>"; };
 		431CE7A01F9D195600255374 /* CBCentralManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CBCentralManager.swift; sourceTree = "<group>"; };
 		431CE7A01F9D195600255374 /* CBCentralManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CBCentralManager.swift; sourceTree = "<group>"; };
 		431CE7A21F9D737F00255374 /* Command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = "<group>"; };
 		431CE7A21F9D737F00255374 /* Command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = "<group>"; };
@@ -925,6 +924,7 @@
 		4352A72720DEC9B700CAC200 /* MinimedKitUI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MinimedKitUI.h; sourceTree = "<group>"; };
 		4352A72720DEC9B700CAC200 /* MinimedKitUI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MinimedKitUI.h; sourceTree = "<group>"; };
 		4352A72820DEC9B700CAC200 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		4352A72820DEC9B700CAC200 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		4352A73120DEC9D600CAC200 /* CommandResponseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandResponseViewController.swift; sourceTree = "<group>"; };
 		4352A73120DEC9D600CAC200 /* CommandResponseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandResponseViewController.swift; sourceTree = "<group>"; };
+		4352A73820DECBAA00CAC200 /* RileyLinkMinimedDeviceTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RileyLinkMinimedDeviceTableViewController.swift; sourceTree = "<group>"; };
 		4352A73D20DED01700CAC200 /* HKUnit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HKUnit.swift; sourceTree = "<group>"; };
 		4352A73D20DED01700CAC200 /* HKUnit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HKUnit.swift; sourceTree = "<group>"; };
 		4352A74020DED23000CAC200 /* RileyLinkDeviceManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RileyLinkDeviceManager.swift; sourceTree = "<group>"; };
 		4352A74020DED23000CAC200 /* RileyLinkDeviceManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RileyLinkDeviceManager.swift; sourceTree = "<group>"; };
 		435535D51FB6D98400CE5A23 /* UserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaults.swift; sourceTree = "<group>"; };
 		435535D51FB6D98400CE5A23 /* UserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaults.swift; sourceTree = "<group>"; };
@@ -1155,6 +1155,7 @@
 		7D9BF01E2336A2EA005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = "<group>"; };
 		7D9BF01E2336A2EA005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = "<group>"; };
 		7D9BF01F2336A2EA005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = "<group>"; };
 		7D9BF01F2336A2EA005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = "<group>"; };
 		7D9BF0202336A2EA005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = "<group>"; };
 		7D9BF0202336A2EA005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = "<group>"; };
+		7D9BF0212336A2EA005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = "<group>"; };
 		7D9BF0222336A2EA005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = "<group>"; };
 		7D9BF0222336A2EA005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = "<group>"; };
 		7D9BF0232336A2EA005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = "<group>"; };
 		7D9BF0232336A2EA005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = "<group>"; };
 		7D9BF0242336A2EA005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = "<group>"; };
 		7D9BF0242336A2EA005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = "<group>"; };
@@ -1209,6 +1210,7 @@
 		7D9BF15423371408005DCFD6 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = "<group>"; };
 		7D9BF15423371408005DCFD6 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = "<group>"; };
 		7D9BF15523371408005DCFD6 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = "<group>"; };
 		7D9BF15523371408005DCFD6 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = "<group>"; };
 		7DEFE05222ED1C2300FCD378 /* OverrideStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverrideStatus.swift; sourceTree = "<group>"; };
 		7DEFE05222ED1C2300FCD378 /* OverrideStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverrideStatus.swift; sourceTree = "<group>"; };
+		B62DBD562725B9EB0050C038 /* CommandResponseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommandResponseViewController.swift; sourceTree = "<group>"; };
 		C104A9BF217E603E006E3C3E /* OmnipodReservoirView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OmnipodReservoirView.swift; sourceTree = "<group>"; };
 		C104A9BF217E603E006E3C3E /* OmnipodReservoirView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OmnipodReservoirView.swift; sourceTree = "<group>"; };
 		C104A9C0217E603E006E3C3E /* OmnipodReservoirView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = OmnipodReservoirView.xib; sourceTree = "<group>"; };
 		C104A9C0217E603E006E3C3E /* OmnipodReservoirView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = OmnipodReservoirView.xib; sourceTree = "<group>"; };
 		C104A9C4217E645C006E3C3E /* HUDAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = HUDAssets.xcassets; sourceTree = "<group>"; };
 		C104A9C4217E645C006E3C3E /* HUDAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = HUDAssets.xcassets; sourceTree = "<group>"; };
@@ -1299,6 +1301,7 @@
 		C16E61212208C7A80069F357 /* ReservoirReading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReservoirReading.swift; sourceTree = "<group>"; };
 		C16E61212208C7A80069F357 /* ReservoirReading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReservoirReading.swift; sourceTree = "<group>"; };
 		C16E61252208EC580069F357 /* MinimedHUDProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MinimedHUDProvider.swift; sourceTree = "<group>"; };
 		C16E61252208EC580069F357 /* MinimedHUDProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MinimedHUDProvider.swift; sourceTree = "<group>"; };
 		C170C98D1CECD6F300F3D8E5 /* CBPeripheralState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CBPeripheralState.swift; sourceTree = "<group>"; };
 		C170C98D1CECD6F300F3D8E5 /* CBPeripheralState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CBPeripheralState.swift; sourceTree = "<group>"; };
+		C170C9981CECD80000F3D8E5 /* RileyLinkDeviceTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RileyLinkDeviceTableViewController.swift; sourceTree = "<group>"; };
 		C1711A551C94F13400CB25BD /* ButtonPressCarelinkMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonPressCarelinkMessageBody.swift; sourceTree = "<group>"; };
 		C1711A551C94F13400CB25BD /* ButtonPressCarelinkMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonPressCarelinkMessageBody.swift; sourceTree = "<group>"; };
 		C1711A591C952D2900CB25BD /* GetPumpModelCarelinkMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetPumpModelCarelinkMessageBody.swift; sourceTree = "<group>"; };
 		C1711A591C952D2900CB25BD /* GetPumpModelCarelinkMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetPumpModelCarelinkMessageBody.swift; sourceTree = "<group>"; };
 		C1711A5B1C953F3000CB25BD /* GetBatteryCarelinkMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetBatteryCarelinkMessageBody.swift; sourceTree = "<group>"; };
 		C1711A5B1C953F3000CB25BD /* GetBatteryCarelinkMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetBatteryCarelinkMessageBody.swift; sourceTree = "<group>"; };
@@ -1308,6 +1311,7 @@
 		C17884601D519F1E00405663 /* BatteryIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BatteryIndicator.swift; sourceTree = "<group>"; };
 		C17884601D519F1E00405663 /* BatteryIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BatteryIndicator.swift; sourceTree = "<group>"; };
 		C1814B87225F0A3D008D2D8E /* ExpirationReminderDateTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpirationReminderDateTableViewCell.swift; sourceTree = "<group>"; };
 		C1814B87225F0A3D008D2D8E /* ExpirationReminderDateTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpirationReminderDateTableViewCell.swift; sourceTree = "<group>"; };
 		C1814B89225FADFA008D2D8E /* ExpirationReminderDateTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ExpirationReminderDateTableViewCell.xib; sourceTree = "<group>"; };
 		C1814B89225FADFA008D2D8E /* ExpirationReminderDateTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ExpirationReminderDateTableViewCell.xib; sourceTree = "<group>"; };
+		C1842BBA1C8E184300DB42AC /* PumpModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PumpModel.swift; sourceTree = "<group>"; };
 		C1842BBC1C8E7C6E00DB42AC /* PumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PumpEvent.swift; sourceTree = "<group>"; };
 		C1842BBC1C8E7C6E00DB42AC /* PumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PumpEvent.swift; sourceTree = "<group>"; };
 		C1842BBE1C8E855A00DB42AC /* PumpEventType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = PumpEventType.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
 		C1842BBE1C8E855A00DB42AC /* PumpEventType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = PumpEventType.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
 		C1842BC01C8E8B2500DB42AC /* UnabsorbedInsulinPumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = UnabsorbedInsulinPumpEvent.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
 		C1842BC01C8E8B2500DB42AC /* UnabsorbedInsulinPumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = UnabsorbedInsulinPumpEvent.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
@@ -1733,14 +1737,14 @@
 				431CE78E1F985B6E00255374 /* CBPeripheral.swift */,
 				431CE78E1F985B6E00255374 /* CBPeripheral.swift */,
 				431CE7A21F9D737F00255374 /* Command.swift */,
 				431CE7A21F9D737F00255374 /* Command.swift */,
 				431CE7A61F9D98F700255374 /* CommandSession.swift */,
 				431CE7A61F9D98F700255374 /* CommandSession.swift */,
+				431CE78C1F985B5400255374 /* PeripheralManager.swift */,
 				43D5E7871FAEDAC4004ACDB7 /* PeripheralManagerError.swift */,
 				43D5E7871FAEDAC4004ACDB7 /* PeripheralManagerError.swift */,
-				19E9EBAC264A86F100000232 /* PeripheralManager.swift */,
-				19E9EBB4264A88BD00000232 /* PeripheralManager+RileyLink.swift */,
+				431CE7921F985DE700255374 /* PeripheralManager+RileyLink.swift */,
 				431CE7A41F9D78F500255374 /* RFPacket.swift */,
 				431CE7A41F9D78F500255374 /* RFPacket.swift */,
 				432847C21FA57C0F00CDE69C /* RadioFirmwareVersion.swift */,
 				432847C21FA57C0F00CDE69C /* RadioFirmwareVersion.swift */,
 				43BA719A202591A70058961E /* Response.swift */,
 				43BA719A202591A70058961E /* Response.swift */,
 				43BA719C2026C9B00058961E /* ResponseBuffer.swift */,
 				43BA719C2026C9B00058961E /* ResponseBuffer.swift */,
-				19E9EBB6264A88C900000232 /* RileyLinkDevice.swift */,
+				431CE79B1F9B21BA00255374 /* RileyLinkDevice.swift */,
 				433ABFFB2016FDF700E6C1FF /* RileyLinkDeviceError.swift */,
 				433ABFFB2016FDF700E6C1FF /* RileyLinkDeviceError.swift */,
 				431CE7901F985D8D00255374 /* RileyLinkDeviceManager.swift */,
 				431CE7901F985D8D00255374 /* RileyLinkDeviceManager.swift */,
 				C12572972125FA390061BA2F /* RileyLinkConnectionManager.swift */,
 				C12572972125FA390061BA2F /* RileyLinkConnectionManager.swift */,
@@ -1765,7 +1769,7 @@
 			children = (
 			children = (
 				C1C73F1C1DE6306A0022FC89 /* BatteryChemistryType.swift */,
 				C1C73F1C1DE6306A0022FC89 /* BatteryChemistryType.swift */,
 				43D8708A20DE1BC9006B549E /* PumpColor.swift */,
 				43D8708A20DE1BC9006B549E /* PumpColor.swift */,
-				19E9EBBC264A897A00000232 /* PumpModel.swift */,
+				C1842BBA1C8E184300DB42AC /* PumpModel.swift */,
 				C1274F851D8242BE0002912B /* PumpRegion.swift */,
 				C1274F851D8242BE0002912B /* PumpRegion.swift */,
 			);
 			);
 			path = Models;
 			path = Models;
@@ -1809,9 +1813,9 @@
 				43709AE020DF1D5400F941B3 /* MinimedPumpManager.storyboard */,
 				43709AE020DF1D5400F941B3 /* MinimedPumpManager.storyboard */,
 				43709ACC20DF1CC900F941B3 /* Setup */,
 				43709ACC20DF1CC900F941B3 /* Setup */,
 				4352A73120DEC9D600CAC200 /* CommandResponseViewController.swift */,
 				4352A73120DEC9D600CAC200 /* CommandResponseViewController.swift */,
+				4352A73820DECBAA00CAC200 /* RileyLinkMinimedDeviceTableViewController.swift */,
 				43709AC320DF1C8B00F941B3 /* MinimedPumpManager+UI.swift */,
 				43709AC320DF1C8B00F941B3 /* MinimedPumpManager+UI.swift */,
 				43709AC220DF1C8B00F941B3 /* MinimedPumpSettingsViewController.swift */,
 				43709AC220DF1C8B00F941B3 /* MinimedPumpSettingsViewController.swift */,
-				19E9EBBA264A890F00000232 /* RileyLinkMinimedDeviceTableViewController.swift */,
 				43709AC020DF1C8B00F941B3 /* PumpModel.swift */,
 				43709AC020DF1C8B00F941B3 /* PumpModel.swift */,
 				43709AC120DF1C8B00F941B3 /* RadioSelectionTableViewController.swift */,
 				43709AC120DF1C8B00F941B3 /* RadioSelectionTableViewController.swift */,
 				C16E61252208EC580069F357 /* MinimedHUDProvider.swift */,
 				C16E61252208EC580069F357 /* MinimedHUDProvider.swift */,
@@ -1877,8 +1881,10 @@
 				7D23679521252EBC0028B67D /* Localizable.strings */,
 				7D23679521252EBC0028B67D /* Localizable.strings */,
 				43709AEB20E0056F00F941B3 /* RileyLinkKitUI.xcassets */,
 				43709AEB20E0056F00F941B3 /* RileyLinkKitUI.xcassets */,
 				C170C98D1CECD6F300F3D8E5 /* CBPeripheralState.swift */,
 				C170C98D1CECD6F300F3D8E5 /* CBPeripheralState.swift */,
+				198AA26127295EDC00FB87CE /* CommandResponseViewController.swift */,
 				439731261CF21C3C00F474E5 /* RileyLinkDeviceTableViewCell.swift */,
 				439731261CF21C3C00F474E5 /* RileyLinkDeviceTableViewCell.swift */,
-				19E9EBB8264A88F500000232 /* RileyLinkDeviceTableViewController.swift */,
+				C170C9981CECD80000F3D8E5 /* RileyLinkDeviceTableViewController.swift */,
+				B62DBD562725B9EB0050C038 /* CommandResponseViewController.swift */,
 				435D26B320DA0AAE00891C17 /* RileyLinkDevicesHeaderView.swift */,
 				435D26B320DA0AAE00891C17 /* RileyLinkDevicesHeaderView.swift */,
 				435D26B520DA0BCC00891C17 /* RileyLinkDevicesTableViewDataSource.swift */,
 				435D26B520DA0BCC00891C17 /* RileyLinkDevicesTableViewDataSource.swift */,
 				43709ABB20DF1C6400F941B3 /* RileyLinkManagerSetupViewController.swift */,
 				43709ABB20DF1C6400F941B3 /* RileyLinkManagerSetupViewController.swift */,
@@ -3527,18 +3533,18 @@
 				43BA719B202591A70058961E /* Response.swift in Sources */,
 				43BA719B202591A70058961E /* Response.swift in Sources */,
 				43D5E7881FAEDAC4004ACDB7 /* PeripheralManagerError.swift in Sources */,
 				43D5E7881FAEDAC4004ACDB7 /* PeripheralManagerError.swift in Sources */,
 				C12572982125FA390061BA2F /* RileyLinkConnectionManager.swift in Sources */,
 				C12572982125FA390061BA2F /* RileyLinkConnectionManager.swift in Sources */,
+				431CE79C1F9B21BA00255374 /* RileyLinkDevice.swift in Sources */,
 				431CE7A71F9D98F700255374 /* CommandSession.swift in Sources */,
 				431CE7A71F9D98F700255374 /* CommandSession.swift in Sources */,
 				431CE7A31F9D737F00255374 /* Command.swift in Sources */,
 				431CE7A31F9D737F00255374 /* Command.swift in Sources */,
-				19E9EBAD264A86F100000232 /* PeripheralManager.swift in Sources */,
 				431CE7A11F9D195600255374 /* CBCentralManager.swift in Sources */,
 				431CE7A11F9D195600255374 /* CBCentralManager.swift in Sources */,
 				431CE7911F985D8D00255374 /* RileyLinkDeviceManager.swift in Sources */,
 				431CE7911F985D8D00255374 /* RileyLinkDeviceManager.swift in Sources */,
+				431CE78D1F985B5400255374 /* PeripheralManager.swift in Sources */,
 				431CE79E1F9BE73900255374 /* BLEFirmwareVersion.swift in Sources */,
 				431CE79E1F9BE73900255374 /* BLEFirmwareVersion.swift in Sources */,
 				432847C31FA57C0F00CDE69C /* RadioFirmwareVersion.swift in Sources */,
 				432847C31FA57C0F00CDE69C /* RadioFirmwareVersion.swift in Sources */,
+				431CE7931F985DE700255374 /* PeripheralManager+RileyLink.swift in Sources */,
 				431CE79A1F9B0F1600255374 /* OSLog.swift in Sources */,
 				431CE79A1F9B0F1600255374 /* OSLog.swift in Sources */,
 				43047FC91FAECA8700508343 /* Data.swift in Sources */,
 				43047FC91FAECA8700508343 /* Data.swift in Sources */,
-				19E9EBB7264A88C900000232 /* RileyLinkDevice.swift in Sources */,
 				431CE7A51F9D78F500255374 /* RFPacket.swift in Sources */,
 				431CE7A51F9D78F500255374 /* RFPacket.swift in Sources */,
-				19E9EBB5264A88BD00000232 /* PeripheralManager+RileyLink.swift in Sources */,
 			);
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 		};
@@ -3576,8 +3582,8 @@
 				4352A74320DED3F200CAC200 /* NumberFormatter.swift in Sources */,
 				4352A74320DED3F200CAC200 /* NumberFormatter.swift in Sources */,
 				4352A73220DEC9D600CAC200 /* CommandResponseViewController.swift in Sources */,
 				4352A73220DEC9D600CAC200 /* CommandResponseViewController.swift in Sources */,
 				C1DD51D525A0BC6000DE27AE /* InsulinTypeConfirmation.swift in Sources */,
 				C1DD51D525A0BC6000DE27AE /* InsulinTypeConfirmation.swift in Sources */,
-				19E9EBBB264A890F00000232 /* RileyLinkMinimedDeviceTableViewController.swift in Sources */,
 				43709AC520DF1C8B00F941B3 /* RadioSelectionTableViewController.swift in Sources */,
 				43709AC520DF1C8B00F941B3 /* RadioSelectionTableViewController.swift in Sources */,
+				4352A73920DECBAA00CAC200 /* RileyLinkMinimedDeviceTableViewController.swift in Sources */,
 				4352A74220DED3D200CAC200 /* CaseCountable.swift in Sources */,
 				4352A74220DED3D200CAC200 /* CaseCountable.swift in Sources */,
 				43709AC620DF1C8B00F941B3 /* MinimedPumpSettingsViewController.swift in Sources */,
 				43709AC620DF1C8B00F941B3 /* MinimedPumpSettingsViewController.swift in Sources */,
 				43709AC720DF1C8B00F941B3 /* MinimedPumpManager+UI.swift in Sources */,
 				43709AC720DF1C8B00F941B3 /* MinimedPumpManager+UI.swift in Sources */,
@@ -3614,12 +3620,12 @@
 			buildActionMask = 2147483647;
 			buildActionMask = 2147483647;
 			files = (
 			files = (
 				7D2366F6212527DA0028B67D /* LocalizedString.swift in Sources */,
 				7D2366F6212527DA0028B67D /* LocalizedString.swift in Sources */,
-				19E9EBB9264A88F500000232 /* RileyLinkDeviceTableViewController.swift in Sources */,
 				43BF58B31FF6079600499C46 /* TimeInterval.swift in Sources */,
 				43BF58B31FF6079600499C46 /* TimeInterval.swift in Sources */,
 				43D5E7A11FAF7CE0004ACDB7 /* UITableViewCell.swift in Sources */,
 				43D5E7A11FAF7CE0004ACDB7 /* UITableViewCell.swift in Sources */,
 				43709AEE20E008F300F941B3 /* SetupImageTableViewCell.swift in Sources */,
 				43709AEE20E008F300F941B3 /* SetupImageTableViewCell.swift in Sources */,
 				43709AF120E0127000F941B3 /* NibLoadable.swift in Sources */,
 				43709AF120E0127000F941B3 /* NibLoadable.swift in Sources */,
 				43D5E7A31FAF7D05004ACDB7 /* CBPeripheralState.swift in Sources */,
 				43D5E7A31FAF7D05004ACDB7 /* CBPeripheralState.swift in Sources */,
+				198AA26227295EDC00FB87CE /* CommandResponseViewController.swift in Sources */,
 				43D5E7A21FAF7CF2004ACDB7 /* CaseCountable.swift in Sources */,
 				43D5E7A21FAF7CF2004ACDB7 /* CaseCountable.swift in Sources */,
 				435D26B620DA0BCC00891C17 /* RileyLinkDevicesTableViewDataSource.swift in Sources */,
 				435D26B620DA0BCC00891C17 /* RileyLinkDevicesTableViewDataSource.swift in Sources */,
 				43709AE820DF22E700F941B3 /* UIColor.swift in Sources */,
 				43709AE820DF22E700F941B3 /* UIColor.swift in Sources */,
@@ -3629,9 +3635,11 @@
 				435D26B420DA0AAE00891C17 /* RileyLinkDevicesHeaderView.swift in Sources */,
 				435D26B420DA0AAE00891C17 /* RileyLinkDevicesHeaderView.swift in Sources */,
 				43709ABF20DF1C6400F941B3 /* RileyLinkSettingsViewController.swift in Sources */,
 				43709ABF20DF1C6400F941B3 /* RileyLinkSettingsViewController.swift in Sources */,
 				43709ABD20DF1C6400F941B3 /* RileyLinkSetupTableViewController.swift in Sources */,
 				43709ABD20DF1C6400F941B3 /* RileyLinkSetupTableViewController.swift in Sources */,
+				43D5E79C1FAF7C47004ACDB7 /* RileyLinkDeviceTableViewController.swift in Sources */,
 				43709ABE20DF1C6400F941B3 /* RileyLinkManagerSetupViewController.swift in Sources */,
 				43709ABE20DF1C6400F941B3 /* RileyLinkManagerSetupViewController.swift in Sources */,
 				43D5E7A01FAF7CCA004ACDB7 /* NumberFormatter.swift in Sources */,
 				43D5E7A01FAF7CCA004ACDB7 /* NumberFormatter.swift in Sources */,
 				43709AE420DF20D500F941B3 /* OSLog.swift in Sources */,
 				43709AE420DF20D500F941B3 /* OSLog.swift in Sources */,
+				B62DBD572725B9EB0050C038 /* CommandResponseViewController.swift in Sources */,
 			);
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 		};
@@ -3745,7 +3753,6 @@
 				C1A721661EC4BCE30080FAD7 /* PartialDecode.swift in Sources */,
 				C1A721661EC4BCE30080FAD7 /* PartialDecode.swift in Sources */,
 				43B0ADC21D12454700AAD278 /* TimestampedHistoryEvent.swift in Sources */,
 				43B0ADC21D12454700AAD278 /* TimestampedHistoryEvent.swift in Sources */,
 				C1EAD6C91C826B92006DBA60 /* MySentryAckMessageBody.swift in Sources */,
 				C1EAD6C91C826B92006DBA60 /* MySentryAckMessageBody.swift in Sources */,
-				19E9EBBD264A897A00000232 /* PumpModel.swift in Sources */,
 				C1F000521EBE73F400F65163 /* BasalSchedule.swift in Sources */,
 				C1F000521EBE73F400F65163 /* BasalSchedule.swift in Sources */,
 				C1EAD6CE1C826B92006DBA60 /* ReadSettingsCarelinkMessageBody.swift in Sources */,
 				C1EAD6CE1C826B92006DBA60 /* ReadSettingsCarelinkMessageBody.swift in Sources */,
 				C1F0004C1EBE68A600F65163 /* DataFrameMessageBody.swift in Sources */,
 				C1F0004C1EBE68A600F65163 /* DataFrameMessageBody.swift in Sources */,
@@ -3765,6 +3772,7 @@
 				C1842BC71C8F8DC200DB42AC /* CalBGForPHPumpEvent.swift in Sources */,
 				C1842BC71C8F8DC200DB42AC /* CalBGForPHPumpEvent.swift in Sources */,
 				C1842C251C8FA45100DB42AC /* AlarmSensorPumpEvent.swift in Sources */,
 				C1842C251C8FA45100DB42AC /* AlarmSensorPumpEvent.swift in Sources */,
 				43D8709320DE1C80006B549E /* PumpOpsSession+LoopKit.swift in Sources */,
 				43D8709320DE1C80006B549E /* PumpOpsSession+LoopKit.swift in Sources */,
+				C1842BBB1C8E184300DB42AC /* PumpModel.swift in Sources */,
 				C16E61222208C7A80069F357 /* ReservoirReading.swift in Sources */,
 				C16E61222208C7A80069F357 /* ReservoirReading.swift in Sources */,
 				C14D2B091C9F5EDA00C98E4C /* ChangeTempBasalTypePumpEvent.swift in Sources */,
 				C14D2B091C9F5EDA00C98E4C /* ChangeTempBasalTypePumpEvent.swift in Sources */,
 				C1842C1E1C8FA45100DB42AC /* ChangeBasalProfilePumpEvent.swift in Sources */,
 				C1842C1E1C8FA45100DB42AC /* ChangeBasalProfilePumpEvent.swift in Sources */,
@@ -4381,11 +4389,11 @@
 				7D199DB2212A159A00241026 /* pl */,
 				7D199DB2212A159A00241026 /* pl */,
 				7D9BF0112336A2D3005DCFD6 /* vi */,
 				7D9BF0112336A2D3005DCFD6 /* vi */,
 				7D9BF0192336A2E3005DCFD6 /* ja */,
 				7D9BF0192336A2E3005DCFD6 /* ja */,
+				7D9BF0212336A2EA005DCFD6 /* sv */,
 				7D9BF0292336A2F2005DCFD6 /* da */,
 				7D9BF0292336A2F2005DCFD6 /* da */,
 				7D9BF0312336A2FB005DCFD6 /* fi */,
 				7D9BF0312336A2FB005DCFD6 /* fi */,
 				7D9BF0392336A304005DCFD6 /* pt-BR */,
 				7D9BF0392336A304005DCFD6 /* pt-BR */,
 				7D9BF15223371408005DCFD6 /* ro */,
 				7D9BF15223371408005DCFD6 /* ro */,
-				1971A859268B21C70083CF2D /* sv */,
 			);
 			);
 			name = Localizable.strings;
 			name = Localizable.strings;
 			sourceTree = "<group>";
 			sourceTree = "<group>";

+ 13 - 4
Dependencies/rileylink_ios/RileyLinkBLEKit/CommandSession.swift

@@ -146,10 +146,19 @@ public struct CommandSession {
 
 
     
     
     /// - Throws: RileyLinkDeviceError
     /// - Throws: RileyLinkDeviceError
-    public func enableCCLEDs() throws {
-        let enableBlue = SetLEDMode(.blue, mode: .auto)
+    public func setCCLEDMode(_ mode: RileyLinkLEDMode) throws {
+        let ccMode: RileyLinkLEDMode
+        
+        switch mode {
+        case .on:
+            ccMode = .auto
+        default:
+            ccMode = .off
+        }
+        
+        let enableBlue = SetLEDMode(.blue, mode: ccMode)
         _ = try writeCommand(enableBlue, timeout: 0)
         _ = try writeCommand(enableBlue, timeout: 0)
-        let enableGreen = SetLEDMode(.green, mode: .auto)
+        let enableGreen = SetLEDMode(.green, mode: ccMode)
         _ = try writeCommand(enableGreen, timeout: 0)
         _ = try writeCommand(enableGreen, timeout: 0)
     }
     }
 
 
@@ -177,7 +186,7 @@ public struct CommandSession {
 
 
         return Measurement<UnitFrequency>(value: frequency, unit: .hertz).converted(to: .megahertz)
         return Measurement<UnitFrequency>(value: frequency, unit: .hertz).converted(to: .megahertz)
     }
     }
-
+    
     /// Sends data to the pump, listening for a reply
     /// Sends data to the pump, listening for a reply
     ///
     ///
     /// - Parameters:
     /// - Parameters:

+ 142 - 99
Dependencies/rileylink_ios/RileyLinkBLEKit/PeripheralManager+RileyLink.swift

@@ -18,9 +18,11 @@ extension CBUUIDRawValue where RawValue == String {
 
 
 
 
 enum RileyLinkServiceUUID: String, CBUUIDRawValue {
 enum RileyLinkServiceUUID: String, CBUUIDRawValue {
-    case main    = "0235733B-99C5-4197-B856-69219C2A3845"
-    case battery = "180F"
-    case orange  = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
+    case main
+            = "0235733B-99C5-4197-B856-69219C2A3845"
+    case battery   = "180F"
+    case orange    = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
+    case secureDFU = "FE59"
 }
 }
 
 
 enum MainServiceCharacteristicUUID: String, CBUUIDRawValue {
 enum MainServiceCharacteristicUUID: String, CBUUIDRawValue {
@@ -37,20 +39,37 @@ enum BatteryServiceCharacteristicUUID: String, CBUUIDRawValue {
 }
 }
 
 
 enum OrangeServiceCharacteristicUUID: String, CBUUIDRawValue {
 enum OrangeServiceCharacteristicUUID: String, CBUUIDRawValue {
-    case orange         = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
-    case orangeNotif    = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
+    case orangeRX = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
+    case orangeTX = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
 }
 }
 
 
-enum RileyLinkOrangeMode: UInt8 {
-    case yellow  = 0x1
-    case red   = 0x2
-    case off = 0x3
-    case shake = 0x4
+enum SecureDFUCharacteristicUUID: String, CBUUIDRawValue {
+    case control = "8EC90001-F315-4F60-9FB8-838830DAEA50"
+}
+
+
+public enum OrangeLinkCommand: UInt8 {
+    case yellow   = 0x1
+    case red      = 0x2
+    case off      = 0x3
+    case shake    = 0x4
     case shakeOff = 0x5
     case shakeOff = 0x5
-    case fw_hw = 0x9
+    case fw_hw    = 0x9
+}
+
+public enum OrangeLinkRequestType: UInt8 {
+    case fctStartLoop = 0xaa // Fct_StartLoop
+    case fctHeader = 0xbb    // Fct_PutReq
+    case fctStopLoop = 0xcc  // Fct_StopLoop
+    case cfgHeader = 0xdd    // Cfg_PutReq
 }
 }
 
 
-enum RileyLinkLEDMode: UInt8 {
+public enum OrangeLinkConfigurationSetting: UInt8 {
+    case connectionLED     = 0x00
+    case connectionVibrate = 0x01
+}
+
+public enum RileyLinkLEDMode: UInt8 {
     case off  = 0x00
     case off  = 0x00
     case on   = 0x01
     case on   = 0x01
     case auto = 0x02
     case auto = 0x02
@@ -73,16 +92,20 @@ extension PeripheralManager.Configuration {
                     BatteryServiceCharacteristicUUID.battery_level.cbUUID
                     BatteryServiceCharacteristicUUID.battery_level.cbUUID
                 ],
                 ],
                 RileyLinkServiceUUID.orange.cbUUID: [
                 RileyLinkServiceUUID.orange.cbUUID: [
-                    OrangeServiceCharacteristicUUID.orange.cbUUID,
-                    OrangeServiceCharacteristicUUID.orangeNotif.cbUUID,
+                    OrangeServiceCharacteristicUUID.orangeRX.cbUUID,
+                    OrangeServiceCharacteristicUUID.orangeTX.cbUUID,
+                ],
+                RileyLinkServiceUUID.secureDFU.cbUUID: [
+                    SecureDFUCharacteristicUUID.control.cbUUID,
                 ]
                 ]
+
             ],
             ],
             notifyingCharacteristics: [
             notifyingCharacteristics: [
                 RileyLinkServiceUUID.main.cbUUID: [
                 RileyLinkServiceUUID.main.cbUUID: [
                     MainServiceCharacteristicUUID.responseCount.cbUUID
                     MainServiceCharacteristicUUID.responseCount.cbUUID
                 ],
                 ],
                 RileyLinkServiceUUID.orange.cbUUID: [
                 RileyLinkServiceUUID.orange.cbUUID: [
-                    OrangeServiceCharacteristicUUID.orangeNotif.cbUUID,
+                    OrangeServiceCharacteristicUUID.orangeTX.cbUUID,
                 ]
                 ]
             ],
             ],
             valueUpdateMacros: [
             valueUpdateMacros: [
@@ -100,29 +123,24 @@ extension PeripheralManager.Configuration {
     }
     }
 }
 }
 
 
-
 fileprivate extension CBPeripheral {
 fileprivate extension CBPeripheral {
-    func getCharacteristicWithUUID(_ uuid: MainServiceCharacteristicUUID, serviceUUID: RileyLinkServiceUUID = .main) -> CBCharacteristic? {
-        guard let service = services?.itemWithUUID(serviceUUID.cbUUID) else {
+    func getBatteryCharacteristic(_ uuid: BatteryServiceCharacteristicUUID) -> CBCharacteristic? {
+        guard let service = services?.itemWithUUID(RileyLinkServiceUUID.battery.cbUUID) else {
             return nil
             return nil
         }
         }
 
 
         return service.characteristics?.itemWithUUID(uuid.cbUUID)
         return service.characteristics?.itemWithUUID(uuid.cbUUID)
     }
     }
-}
-
-fileprivate extension CBPeripheral {
-    func getBatteryCharacteristic(_ uuid: BatteryServiceCharacteristicUUID, serviceUUID: RileyLinkServiceUUID = .battery) -> CBCharacteristic? {
-        guard let service = services?.itemWithUUID(serviceUUID.cbUUID) else {
+    
+    func getOrangeCharacteristic(_ uuid: OrangeServiceCharacteristicUUID) -> CBCharacteristic? {
+        guard let service = services?.itemWithUUID(RileyLinkServiceUUID.orange.cbUUID) else {
             return nil
             return nil
         }
         }
 
 
         return service.characteristics?.itemWithUUID(uuid.cbUUID)
         return service.characteristics?.itemWithUUID(uuid.cbUUID)
     }
     }
-}
-
-fileprivate extension CBPeripheral {
-    func getOrangeCharacteristic(_ uuid: OrangeServiceCharacteristicUUID, serviceUUID: RileyLinkServiceUUID = .orange) -> CBCharacteristic? {
+    
+    func getCharacteristicWithUUID(_ uuid: MainServiceCharacteristicUUID, serviceUUID: RileyLinkServiceUUID = .main) -> CBCharacteristic? {
         guard let service = services?.itemWithUUID(serviceUUID.cbUUID) else {
         guard let service = services?.itemWithUUID(serviceUUID.cbUUID) else {
             return nil
             return nil
         }
         }
@@ -162,6 +180,44 @@ private let log = OSLog(category: "PeripheralManager+RileyLink")
 
 
 extension PeripheralManager {
 extension PeripheralManager {
     static let expectedMaxBLELatency: TimeInterval = 2
     static let expectedMaxBLELatency: TimeInterval = 2
+    
+    func readBatteryLevel(completion: @escaping (Int?) -> Void) {
+        perform { (manager) in
+            guard let characteristic = self.peripheral.getBatteryCharacteristic(.battery_level) else {
+                completion(nil)
+                return
+            }
+            
+            do {
+                guard let data = try self.readValue(for: characteristic, timeout: PeripheralManager.expectedMaxBLELatency) else {
+                    completion(nil)
+                    return
+                }
+                
+                completion(Int(data[0]))
+            } catch {
+                completion(nil)
+            }
+        }
+    }
+    
+    func readDiagnosticLEDMode(completion: @escaping (RileyLinkLEDMode?) -> Void) {
+        perform { (manager) in
+            do {
+                guard
+                    let characteristic = self.peripheral.getCharacteristicWithUUID(.ledMode),
+                    let data = try self.readValue(for: characteristic, timeout: PeripheralManager.expectedMaxBLELatency),
+                    let mode = RileyLinkLEDMode(rawValue: data[0]) else
+                {
+                    completion(nil)
+                    return
+                }
+                completion(mode)
+            } catch {
+                completion(nil)
+            }
+        }
+    }
 
 
     var timerTickEnabled: Bool {
     var timerTickEnabled: Bool {
         return peripheral.getCharacteristicWithUUID(.timerTick)?.isNotifying ?? false
         return peripheral.getCharacteristicWithUUID(.timerTick)?.isNotifying ?? false
@@ -171,7 +227,7 @@ extension PeripheralManager {
         perform { (manager) in
         perform { (manager) in
             do {
             do {
                 guard let characteristic = manager.peripheral.getCharacteristicWithUUID(.timerTick) else {
                 guard let characteristic = manager.peripheral.getCharacteristicWithUUID(.timerTick) else {
-                    throw PeripheralManagerError.unknownCharacteristic
+                    throw PeripheralManagerError.unknownCharacteristic(MainServiceCharacteristicUUID.timerTick.cbUUID)
                 }
                 }
 
 
                 try manager.setNotifyValue(enabled, for: characteristic, timeout: timeout)
                 try manager.setNotifyValue(enabled, for: characteristic, timeout: timeout)
@@ -188,7 +244,7 @@ extension PeripheralManager {
         perform { (manager) in
         perform { (manager) in
             do {
             do {
                 guard let characteristic = manager.peripheral.getCharacteristicWithUUID(.ledMode) else {
                 guard let characteristic = manager.peripheral.getCharacteristicWithUUID(.ledMode) else {
-                    throw PeripheralManagerError.unknownCharacteristic
+                    throw PeripheralManagerError.unknownCharacteristic(MainServiceCharacteristicUUID.ledMode.cbUUID)
                 }
                 }
                 let value = Data([mode.rawValue])
                 let value = Data([mode.rawValue])
                 try manager.writeValue(value, for: characteristic, type: .withResponse, timeout: PeripheralManager.expectedMaxBLELatency)
                 try manager.writeValue(value, for: characteristic, type: .withResponse, timeout: PeripheralManager.expectedMaxBLELatency)
@@ -224,7 +280,7 @@ extension PeripheralManager {
         perform { (manager) in
         perform { (manager) in
             do {
             do {
                 guard let characteristic = manager.peripheral.getCharacteristicWithUUID(.customName) else {
                 guard let characteristic = manager.peripheral.getCharacteristicWithUUID(.customName) else {
-                    throw PeripheralManagerError.unknownCharacteristic
+                    throw PeripheralManagerError.unknownCharacteristic(MainServiceCharacteristicUUID.customName.cbUUID)
                 }
                 }
 
 
                 try manager.writeValue(value, for: characteristic, type: .withResponse, timeout: timeout)
                 try manager.writeValue(value, for: characteristic, type: .withResponse, timeout: timeout)
@@ -260,7 +316,7 @@ extension PeripheralManager {
     ///     - RileyLinkDeviceError.writeSizeLimitExceeded
     ///     - RileyLinkDeviceError.writeSizeLimitExceeded
     func writeCommand<C: Command>(_ command: C, timeout: TimeInterval, responseType: ResponseType) throws -> C.ResponseType {
     func writeCommand<C: Command>(_ command: C, timeout: TimeInterval, responseType: ResponseType) throws -> C.ResponseType {
         guard let characteristic = peripheral.getCharacteristicWithUUID(.data) else {
         guard let characteristic = peripheral.getCharacteristicWithUUID(.data) else {
-            throw RileyLinkDeviceError.peripheralManagerError(.unknownCharacteristic)
+            throw RileyLinkDeviceError.peripheralManagerError(PeripheralManagerError.unknownCharacteristic(MainServiceCharacteristicUUID.data.cbUUID))
         }
         }
 
 
         let value = try command.writableData()
         let value = try command.writableData()
@@ -294,7 +350,7 @@ extension PeripheralManager {
     ///     - RileyLinkDeviceError.writeSizeLimitExceeded
     ///     - RileyLinkDeviceError.writeSizeLimitExceeded
     fileprivate func writeCommandWithoutResponse<C: Command>(_ command: C, timeout: TimeInterval) throws {
     fileprivate func writeCommandWithoutResponse<C: Command>(_ command: C, timeout: TimeInterval) throws {
         guard let characteristic = peripheral.getCharacteristicWithUUID(.data) else {
         guard let characteristic = peripheral.getCharacteristicWithUUID(.data) else {
-            throw RileyLinkDeviceError.peripheralManagerError(.unknownCharacteristic)
+            throw RileyLinkDeviceError.peripheralManagerError(PeripheralManagerError.unknownCharacteristic(MainServiceCharacteristicUUID.data.cbUUID))
         }
         }
 
 
         let value = try command.writableData()
         let value = try command.writableData()
@@ -321,13 +377,12 @@ extension PeripheralManager {
     ///     - RileyLinkDeviceError.peripheralManagerError
     ///     - RileyLinkDeviceError.peripheralManagerError
     func readBluetoothFirmwareVersion(timeout: TimeInterval) throws -> String {
     func readBluetoothFirmwareVersion(timeout: TimeInterval) throws -> String {
         guard let characteristic = peripheral.getCharacteristicWithUUID(.firmwareVersion) else {
         guard let characteristic = peripheral.getCharacteristicWithUUID(.firmwareVersion) else {
-            throw RileyLinkDeviceError.peripheralManagerError(.unknownCharacteristic)
+            throw RileyLinkDeviceError.peripheralManagerError(PeripheralManagerError.unknownCharacteristic(MainServiceCharacteristicUUID.firmwareVersion.cbUUID))
         }
         }
 
 
         do {
         do {
             guard let data = try readValue(for: characteristic, timeout: timeout) else {
             guard let data = try readValue(for: characteristic, timeout: timeout) else {
-                // TODO: This is an "unknown value" issue, not a timeout
-                throw RileyLinkDeviceError.peripheralManagerError(.timeout)
+                throw RileyLinkDeviceError.peripheralManagerError(PeripheralManagerError.emptyValue)
             }
             }
 
 
             guard let version = String(bytes: data, encoding: .utf8) else {
             guard let version = String(bytes: data, encoding: .utf8) else {
@@ -345,81 +400,64 @@ extension PeripheralManager {
 // MARK: - Lower-level helper operations
 // MARK: - Lower-level helper operations
 extension PeripheralManager {
 extension PeripheralManager {
     
     
-    func readBatteryLevel(timeout: TimeInterval) throws -> String {
-        guard let characteristic = peripheral.getBatteryCharacteristic(.battery_level) else {
-            throw RileyLinkDeviceError.peripheralManagerError(.unknownCharacteristic)
-        }
-        
-        do {
-            guard let data = try readValue(for: characteristic, timeout: timeout) else {
-                // TODO: This is an "unknown value" issue, not a timeout
-                throw RileyLinkDeviceError.peripheralManagerError(.timeout)
-            }
-            
-            let battery_level = "\(data[0])"
-            
-            return battery_level
-        } catch let error as PeripheralManagerError {
-            throw RileyLinkDeviceError.peripheralManagerError(error)
-        }
-    }
-    
     func setOrangeNotifyOn() throws {
     func setOrangeNotifyOn() throws {
         perform { [self] (manager) in
         perform { [self] (manager) in
-            guard let characteristicNotif = peripheral.getOrangeCharacteristic(.orangeNotif) else {
+            guard let characteristicNotif = peripheral.getOrangeCharacteristic(.orangeTX) else {
                 return
                 return
             }
             }
             
             
-            add(log: "setOrangeNotifyOn: \(characteristicNotif.uuid.uuidString)")
             do {
             do {
                 try setNotifyValue(true, for: characteristicNotif, timeout: 2)
                 try setNotifyValue(true, for: characteristicNotif, timeout: 2)
             } catch {
             } catch {
-                add(log: "setOrangeNotifyOn Error: \(error.localizedDescription)")
+                log.error("setOrangeNotifyOn failed: %@", error.localizedDescription)
             }
             }
         }
         }
     }
     }
     
     
-    func orangeAction(mode: RileyLinkOrangeMode) {
-        if mode != .off, mode != .shakeOff {
+    func orangeAction(_ command: OrangeLinkCommand) {
+        if command != .off, command != .shakeOff {
             orangeWritePwd()
             orangeWritePwd()
         }
         }
         perform { [self] (manager) in
         perform { [self] (manager) in
             do {
             do {
-                guard let characteristic = peripheral.getOrangeCharacteristic(.orange) else {
-                    throw PeripheralManagerError.unknownCharacteristic
+                guard let characteristic = peripheral.getOrangeCharacteristic(.orangeRX) else {
+                    throw PeripheralManagerError.unknownCharacteristic(OrangeServiceCharacteristicUUID.orangeRX.cbUUID)
                 }
                 }
-                let value = Data([0xbb, mode.rawValue])
-                add(log: "write: \(value.hexadecimalString)")
+                let value = Data([OrangeLinkRequestType.fctHeader.rawValue, command.rawValue])
                 try writeValue(value, for: characteristic, type: .withResponse, timeout: PeripheralManager.expectedMaxBLELatency)
                 try writeValue(value, for: characteristic, type: .withResponse, timeout: PeripheralManager.expectedMaxBLELatency)
             } catch (_) {
             } catch (_) {
-                add(log: "orangeAction failed")
+                log.debug("orangeAction failed")
             }
             }
         }
         }
-        if mode == .off, mode == .shakeOff {
+        if command == .off, command == .shakeOff {
             orangeClose()
             orangeClose()
         }
         }
     }
     }
     
     
-    
-    
-    func setAction(index: Int, open: Bool) {
+    func findDevice() {
         perform { [self] (manager) in
         perform { [self] (manager) in
             do {
             do {
-                guard let characteristic = peripheral.getOrangeCharacteristic(.orange) else {
-                    throw PeripheralManagerError.unknownCharacteristic
+                guard let characteristic = peripheral.getOrangeCharacteristic(.orangeRX) else {
+                    throw PeripheralManagerError.unknownCharacteristic(OrangeServiceCharacteristicUUID.orangeRX.cbUUID)
                 }
                 }
-                if index == 0 {
-                    setDatas[2] = 0
-                    setDatas[3] = open ? 1 : 0
-                } else if index == 1 {
-                    setDatas[2] = 1
-                    setDatas[3] = open ? 1 : 0
+                let value = Data([OrangeLinkRequestType.cfgHeader.rawValue, 0x04])
+                try writeValue(value, for: characteristic, type: .withResponse, timeout: PeripheralManager.expectedMaxBLELatency)
+            } catch (_) {
+                log.debug("findDevice failed")
+            }
+        }
+    }
+    
+    func setOrangeConfig(_ config: OrangeLinkConfigurationSetting, isOn: Bool) {
+        perform { [self] (manager) in
+            do {
+                guard let characteristic = peripheral.getOrangeCharacteristic(.orangeRX) else {
+                    throw PeripheralManagerError.unknownCharacteristic(OrangeServiceCharacteristicUUID.orangeRX.cbUUID)
                 }
                 }
-                let value = Data(setDatas)
-                add(log: "write: \(value.hexadecimalString)")
+                let value = Data([OrangeLinkRequestType.cfgHeader.rawValue, 0x02, config.rawValue, isOn ? 1 : 0])
                 try writeValue(value, for: characteristic, type: .withResponse, timeout: PeripheralManager.expectedMaxBLELatency)
                 try writeValue(value, for: characteristic, type: .withResponse, timeout: PeripheralManager.expectedMaxBLELatency)
             } catch (_) {
             } catch (_) {
-                add(log: "setAction failed")
+                log.debug("setOrangeConfig failed")
             }
             }
         }
         }
     }
     }
@@ -427,14 +465,13 @@ extension PeripheralManager {
     func orangeWritePwd() {
     func orangeWritePwd() {
         perform { [self] (manager) in
         perform { [self] (manager) in
             do {
             do {
-                guard let characteristic = peripheral.getOrangeCharacteristic(.orange) else {
-                    throw PeripheralManagerError.unknownCharacteristic
+                guard let characteristic = peripheral.getOrangeCharacteristic(.orangeRX) else {
+                    throw PeripheralManagerError.unknownCharacteristic(OrangeServiceCharacteristicUUID.orangeRX.cbUUID)
                 }
                 }
                 let value = Data([0xAA])
                 let value = Data([0xAA])
-                add(log: "write: \(value.hexadecimalString)")
                 try writeValue(value, for: characteristic, type: .withResponse, timeout: PeripheralManager.expectedMaxBLELatency)
                 try writeValue(value, for: characteristic, type: .withResponse, timeout: PeripheralManager.expectedMaxBLELatency)
             } catch (_) {
             } catch (_) {
-                add(log: "orangeWritePwd failed")
+                log.debug("orangeWritePwd failed")
             }
             }
         }
         }
     }
     }
@@ -442,14 +479,14 @@ extension PeripheralManager {
     func orangeReadSet() {
     func orangeReadSet() {
         perform { [self] (manager) in
         perform { [self] (manager) in
             do {
             do {
-                guard let characteristic = peripheral.getOrangeCharacteristic(.orange) else {
-                    throw PeripheralManagerError.unknownCharacteristic
+                guard let characteristic = peripheral.getOrangeCharacteristic(.orangeRX) else {
+                    throw PeripheralManagerError.unknownCharacteristic(OrangeServiceCharacteristicUUID.orangeRX.cbUUID)
                 }
                 }
-                let value = Data([0xdd, 0x01])
-                add(log: "write: \(value.hexadecimalString)")
+                let value = Data([OrangeLinkRequestType.cfgHeader.rawValue, 0x01])
+                log.debug("orangeReadSet write: %@", value.hexadecimalString)
                 try writeValue(value, for: characteristic, type: .withResponse, timeout: PeripheralManager.expectedMaxBLELatency)
                 try writeValue(value, for: characteristic, type: .withResponse, timeout: PeripheralManager.expectedMaxBLELatency)
             } catch (_) {
             } catch (_) {
-                add(log: "orangeReadSet failed")
+                log.debug("orangeReadSet failed")
             }
             }
         }
         }
     }
     }
@@ -457,14 +494,13 @@ extension PeripheralManager {
     func orangeReadVDC() {
     func orangeReadVDC() {
         perform { [self] (manager) in
         perform { [self] (manager) in
             do {
             do {
-                guard let characteristic = peripheral.getOrangeCharacteristic(.orange) else {
-                    throw PeripheralManagerError.unknownCharacteristic
+                guard let characteristic = peripheral.getOrangeCharacteristic(.orangeRX) else {
+                    throw PeripheralManagerError.unknownCharacteristic(OrangeServiceCharacteristicUUID.orangeRX.cbUUID)
                 }
                 }
-                let value = Data([0xdd, 0x03])
-                add(log: "write: \(value.hexadecimalString)")
+                let value = Data([OrangeLinkRequestType.cfgHeader.rawValue, 0x03])
                 try writeValue(value, for: characteristic, type: .withResponse, timeout: PeripheralManager.expectedMaxBLELatency)
                 try writeValue(value, for: characteristic, type: .withResponse, timeout: PeripheralManager.expectedMaxBLELatency)
             } catch (_) {
             } catch (_) {
-                add(log: "orangeReadSet failed")
+                log.debug("orangeReadVDC failed")
             }
             }
         }
         }
     }
     }
@@ -472,14 +508,13 @@ extension PeripheralManager {
     func orangeClose() {
     func orangeClose() {
         perform { [self] (manager) in
         perform { [self] (manager) in
             do {
             do {
-                guard let characteristic = peripheral.getOrangeCharacteristic(.orange) else {
-                    throw PeripheralManagerError.unknownCharacteristic
+                guard let characteristic = peripheral.getOrangeCharacteristic(.orangeRX) else {
+                    throw PeripheralManagerError.unknownCharacteristic(OrangeServiceCharacteristicUUID.orangeRX.cbUUID)
                 }
                 }
                 let value = Data([0xcc])
                 let value = Data([0xcc])
-                add(log: "write: \(value.hexadecimalString)")
                 try writeValue(value, for: characteristic, type: .withResponse, timeout: PeripheralManager.expectedMaxBLELatency)
                 try writeValue(value, for: characteristic, type: .withResponse, timeout: PeripheralManager.expectedMaxBLELatency)
             } catch (_) {
             } catch (_) {
-                add(log: "orangeClose failed")
+                log.debug("orangeClose failed")
             }
             }
         }
         }
     }
     }
@@ -530,7 +565,7 @@ extension PeripheralManager {
                         return false
                         return false
                     default:
                     default:
                         guard let response = R(data: value) else {
                         guard let response = R(data: value) else {
-                            log.error("Unable to parse response.")
+                            log.error("Unable to parse response: %{public}@", value.hexadecimalString)
                             // We don't recognize the contents. Keep listening.
                             // We don't recognize the contents. Keep listening.
                             return false
                             return false
                         }
                         }
@@ -543,7 +578,15 @@ extension PeripheralManager {
                 peripheral.writeValue(data, for: characteristic, type: type)
                 peripheral.writeValue(data, for: characteristic, type: type)
             }
             }
         } catch let error as PeripheralManagerError {
         } catch let error as PeripheralManagerError {
-            throw RileyLinkDeviceError.peripheralManagerError(error)
+            // If the write succeeded, but we get no response, BLE comms are working but RL command channel is hung
+            if case .timeout(let unmetConditions) = error,
+               let firstUnmetCondition = unmetConditions.first,
+               case .valueUpdate = firstUnmetCondition
+            {
+                throw RileyLinkDeviceError.commandsBlocked
+            } else {
+                throw RileyLinkDeviceError.peripheralManagerError(error)
+            }
         }
         }
 
 
         guard let response = capturedResponse else {
         guard let response = capturedResponse else {

+ 19 - 49
Dependencies/rileylink_ios/RileyLinkBLEKit/PeripheralManager.swift

@@ -52,8 +52,6 @@ class PeripheralManager: NSObject {
     // Confined to `queue`
     // Confined to `queue`
     private var needsConfiguration = true
     private var needsConfiguration = true
     
     
-    var logString = ""
-
     weak var delegate: PeripheralManagerDelegate? {
     weak var delegate: PeripheralManagerDelegate? {
         didSet {
         didSet {
             queue.sync {
             queue.sync {
@@ -62,8 +60,6 @@ class PeripheralManager: NSObject {
         }
         }
     }
     }
     
     
-    var setDatas: [UInt8] = [0xdd, 0x02, 0x00, 0x00]
-
     // Called from RileyLinkDeviceManager.managerQueue
     // Called from RileyLinkDeviceManager.managerQueue
     init(peripheral: CBPeripheral, configuration: Configuration, centralManager: CBCentralManager, queue: DispatchQueue) {
     init(peripheral: CBPeripheral, configuration: Configuration, centralManager: CBCentralManager, queue: DispatchQueue) {
         self.peripheral = peripheral
         self.peripheral = peripheral
@@ -97,7 +93,7 @@ extension PeripheralManager {
     }
     }
 }
 }
 
 
-protocol PeripheralManagerDelegate: class {
+protocol PeripheralManagerDelegate: AnyObject {
     func peripheralManager(_ manager: PeripheralManager, didUpdateValueFor characteristic: CBCharacteristic)
     func peripheralManager(_ manager: PeripheralManager, didUpdateValueFor characteristic: CBCharacteristic)
     
     
     func peripheralManager(_ manager: PeripheralManager, didUpdateNotificationStateFor characteristic: CBCharacteristic)
     func peripheralManager(_ manager: PeripheralManager, didUpdateNotificationStateFor characteristic: CBCharacteristic)
@@ -114,17 +110,13 @@ protocol PeripheralManagerDelegate: class {
 extension PeripheralManager {
 extension PeripheralManager {
     func configureAndRun(_ block: @escaping (_ manager: PeripheralManager) -> Void) -> (() -> Void) {
     func configureAndRun(_ block: @escaping (_ manager: PeripheralManager) -> Void) -> (() -> Void) {
         return {
         return {
-            // TODO: Accessing self might be a race on initialization
-            if !self.needsConfiguration && self.peripheral.services == nil {
-                self.log.error("Configured peripheral has no services. Reconfiguring…")
-            }
-            
             if self.needsConfiguration || self.peripheral.services == nil {
             if self.needsConfiguration || self.peripheral.services == nil {
+                self.log.default("Configuring peripheral %{public}@, needsConfiguration=%{public}@, has services = %{public}@", self.peripheral, String(describing: self.needsConfiguration), String(describing:  self.peripheral.services != nil))
                 do {
                 do {
                     try self.applyConfiguration()
                     try self.applyConfiguration()
-                    self.log.default("Peripheral configuration completed")
+                    self.log.default("Peripheral configuration completed: %{public}@", self.peripheral)
                 } catch let error {
                 } catch let error {
-                    self.log.error("Error applying peripheral configuration: %@", String(describing: error))
+                    self.log.error("Error applying peripheral configuration: %{public}@", String(describing: error))
                     // Will retry
                     // Will retry
                 }
                 }
 
 
@@ -137,7 +129,7 @@ extension PeripheralManager {
                         self.log.error("No delegate set for configuration")
                         self.log.error("No delegate set for configuration")
                     }
                     }
                 } catch let error {
                 } catch let error {
-                    self.log.error("Error applying delegate configuration: %@", String(describing: error))
+                    self.log.error("Error applying delegate configuration: %{public}@", String(describing: error))
                     // Will retry
                     // Will retry
                 }
                 }
             }
             }
@@ -151,8 +143,10 @@ extension PeripheralManager {
     }
     }
 
 
     private func assertConfiguration() {
     private func assertConfiguration() {
-        perform { (_) in
-            // Intentionally empty to trigger configuration if necessary
+        if peripheral.state == .connected {
+            perform { (_) in
+                // Intentionally empty to trigger configuration if necessary
+            }
         }
         }
     }
     }
 
 
@@ -161,24 +155,23 @@ extension PeripheralManager {
 
 
         for service in peripheral.services ?? [] {
         for service in peripheral.services ?? [] {
             guard let characteristics = configuration.serviceCharacteristics[service.uuid] else {
             guard let characteristics = configuration.serviceCharacteristics[service.uuid] else {
-                // Not all services may have characteristics
+                // Not all services have characteristics
                 continue
                 continue
             }
             }
 
 
             try discoverCharacteristics(characteristics, for: service, timeout: discoveryTimeout)
             try discoverCharacteristics(characteristics, for: service, timeout: discoveryTimeout)
         }
         }
 
 
+        // Subscribe to notifying characteristics
         for (serviceUUID, characteristicUUIDs) in configuration.notifyingCharacteristics {
         for (serviceUUID, characteristicUUIDs) in configuration.notifyingCharacteristics {
             guard let service = peripheral.services?.itemWithUUID(serviceUUID) else {
             guard let service = peripheral.services?.itemWithUUID(serviceUUID) else {
-                throw PeripheralManagerError.unknownCharacteristic
+                // Not all RL's have OrangeLink service
+                continue
             }
             }
 
 
-            add(log: "serviceUUID: \(serviceUUID.uuidString)")
-            
             for characteristicUUID in characteristicUUIDs {
             for characteristicUUID in characteristicUUIDs {
-                add(log: "characteristicUUID: \(characteristicUUID.uuidString)")
                 guard let characteristic = service.characteristics?.itemWithUUID(characteristicUUID) else {
                 guard let characteristic = service.characteristics?.itemWithUUID(characteristicUUID) else {
-                    throw PeripheralManagerError.unknownCharacteristic
+                    throw PeripheralManagerError.unknownCharacteristic(characteristicUUID)
                 }
                 }
 
 
                 guard !characteristic.isNotifying else {
                 guard !characteristic.isNotifying else {
@@ -209,7 +202,7 @@ extension PeripheralManager {
         }
         }
 
 
         guard commandConditions.isEmpty else {
         guard commandConditions.isEmpty else {
-            throw PeripheralManagerError.notReady
+            throw PeripheralManagerError.busy
         }
         }
 
 
         // Run
         // Run
@@ -229,7 +222,7 @@ extension PeripheralManager {
         }
         }
 
 
         guard signaled else {
         guard signaled else {
-            throw PeripheralManagerError.timeout
+            throw PeripheralManagerError.timeout(commandConditions)
         }
         }
 
 
         if let error = commandError {
         if let error = commandError {
@@ -275,7 +268,6 @@ extension PeripheralManager {
 
 
     /// - Throws: PeripheralManagerError
     /// - Throws: PeripheralManagerError
     func setNotifyValue(_ enabled: Bool, for characteristic: CBCharacteristic, timeout: TimeInterval) throws {
     func setNotifyValue(_ enabled: Bool, for characteristic: CBCharacteristic, timeout: TimeInterval) throws {
-        add(log: "setNotifyValue: \(characteristic.uuid.uuidString)")
         try runCommand(timeout: timeout) {
         try runCommand(timeout: timeout) {
             addCondition(.notificationStateUpdate(characteristic: characteristic, enabled: enabled))
             addCondition(.notificationStateUpdate(characteristic: characteristic, enabled: enabled))
 
 
@@ -294,18 +286,6 @@ extension PeripheralManager {
         return characteristic.value
         return characteristic.value
     }
     }
 
 
-    /// - Throws: PeripheralManagerError
-    func wait(for characteristic: CBCharacteristic, timeout: TimeInterval) throws -> Data {
-        try runCommand(timeout: timeout) {
-            addCondition(.valueUpdate(characteristic: characteristic, matching: nil))
-        }
-
-        guard let value = characteristic.value else {
-            throw PeripheralManagerError.timeout
-        }
-
-        return value
-    }
 
 
     /// - Throws: PeripheralManagerError
     /// - Throws: PeripheralManagerError
     func writeValue(_ value: Data, for characteristic: CBCharacteristic, type: CBCharacteristicWriteType, timeout: TimeInterval) throws {
     func writeValue(_ value: Data, for characteristic: CBCharacteristic, type: CBCharacteristicWriteType, timeout: TimeInterval) throws {
@@ -389,7 +369,7 @@ extension PeripheralManager: CBPeripheralDelegate {
 
 
     func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
     func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
         commandLock.lock()
         commandLock.lock()
-
+        
         if let index = commandConditions.firstIndex(where: { (condition) -> Bool in
         if let index = commandConditions.firstIndex(where: { (condition) -> Bool in
             if case .write(characteristic: characteristic) = condition {
             if case .write(characteristic: characteristic) = condition {
                 return true
                 return true
@@ -410,7 +390,7 @@ extension PeripheralManager: CBPeripheralDelegate {
 
 
     func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
     func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
         commandLock.lock()
         commandLock.lock()
-
+        
         var notifyDelegate = false
         var notifyDelegate = false
 
 
         if let index = commandConditions.firstIndex(where: { (condition) -> Bool in
         if let index = commandConditions.firstIndex(where: { (condition) -> Bool in
@@ -470,22 +450,12 @@ extension PeripheralManager: CBCentralManagerDelegate {
     }
     }
 }
 }
 
 
-
 extension PeripheralManager {
 extension PeripheralManager {
     
     
-    func add(log: String) {
-        print("LLLLLL: \(log)")
-        logString += "\(Date())\n\(log)\n"
-        if logString.count > 10000 {
-            logString.removeFirst(1000)
-        }
-    }
-    
     public override var debugDescription: String {
     public override var debugDescription: String {
         var items = [
         var items = [
             "## PeripheralManager",
             "## PeripheralManager",
-            "peripheral: \(peripheral)",
-            "log: \(logString)"
+            "peripheral: \(peripheral)"
         ]
         ]
         queue.sync {
         queue.sync {
             items.append("needsConfiguration: \(needsConfiguration)")
             items.append("needsConfiguration: \(needsConfiguration)")

+ 16 - 7
Dependencies/rileylink_ios/RileyLinkBLEKit/PeripheralManagerError.swift

@@ -8,11 +8,14 @@
 import CoreBluetooth
 import CoreBluetooth
 
 
 
 
-public enum PeripheralManagerError: Error {
+enum PeripheralManagerError: Error {
     case cbPeripheralError(Error)
     case cbPeripheralError(Error)
     case notReady
     case notReady
-    case timeout
-    case unknownCharacteristic
+    case busy
+    case timeout([PeripheralManager.CommandCondition])
+    case emptyValue
+    case unknownCharacteristic(CBUUID)
+    case unknownService(CBUUID)
 }
 }
 
 
 
 
@@ -22,11 +25,17 @@ extension PeripheralManagerError: LocalizedError {
         case .cbPeripheralError(let error):
         case .cbPeripheralError(let error):
             return error.localizedDescription
             return error.localizedDescription
         case .notReady:
         case .notReady:
-            return LocalizedString("RileyLink is not connected", comment: "Not ready error description")
+            return LocalizedString("RileyLink is not connected", comment: "PeripheralManagerError.notReady error description")
+        case .busy:
+            return LocalizedString("RileyLink is busy", comment: "PeripheralManagerError.busy error description")
         case .timeout:
         case .timeout:
-            return LocalizedString("RileyLink did not respond in time", comment: "Timeout error description")
-        case .unknownCharacteristic:
-            return LocalizedString("Unknown characteristic", comment: "Error description")
+            return LocalizedString("RileyLink did not respond in time", comment: "PeripheralManagerError.timeout error description")
+        case .emptyValue:
+            return LocalizedString("Characteristic value was empty", comment: "PeripheralManagerError.emptyValue error description")
+        case .unknownCharacteristic(let cbuuid):
+            return String(format: LocalizedString("Unknown characteristic: %@", comment: "PeripheralManagerError.unknownCharacteristic error description"), cbuuid.uuidString)
+        case .unknownService(let cbuuid):
+            return String(format: LocalizedString("Unknown service: %@", comment: "PeripheralManagerError.unknownCharacteristic error description"), cbuuid.uuidString)
         }
         }
     }
     }
 
 

+ 199 - 141
Dependencies/rileylink_ios/RileyLinkBLEKit/RileyLinkDevice.swift

@@ -8,6 +8,18 @@
 import CoreBluetooth
 import CoreBluetooth
 import os.log
 import os.log
 
 
+public enum RileyLinkHardwareType {
+    case riley
+    case orange
+    case ema
+
+    var monitorsBattery: Bool {
+        if self == .riley {
+            return false
+        }
+        return true
+    }
+}
 
 
 /// TODO: Should we be tracking the most recent "pump" RSSI?
 /// TODO: Should we be tracking the most recent "pump" RSSI?
 public class RileyLinkDevice {
 public class RileyLinkDevice {
@@ -21,6 +33,27 @@ public class RileyLinkDevice {
     // Confined to `manager.queue`
     // Confined to `manager.queue`
     private var radioFirmwareVersion: RadioFirmwareVersion?
     private var radioFirmwareVersion: RadioFirmwareVersion?
 
 
+    public var rlFirmwareDescription: String {
+        let versions = [radioFirmwareVersion, bleFirmwareVersion].compactMap { (version: CustomStringConvertible?) -> String? in
+            if let version = version {
+                return String(describing: version)
+            } else {
+                return nil
+            }
+        }
+
+        return versions.joined(separator: " / ")
+    }
+
+    private var version: String {
+        switch hardwareType {
+        case .riley, .ema, .none:
+            return rlFirmwareDescription
+        case .orange:
+            return orangeLinkFirmwareHardwareVersion
+        }
+    }
+
     // Confined to `lock`
     // Confined to `lock`
     private var idleListeningState: IdleListeningState = .disabled
     private var idleListeningState: IdleListeningState = .disabled
 
 
@@ -33,17 +66,48 @@ public class RileyLinkDevice {
 
 
     // Confined to `lock`
     // Confined to `lock`
     private var isTimerTickEnabled = true
     private var isTimerTickEnabled = true
-    
-    // Confined to `lock`
-    private var logs = ""
 
 
     /// Serializes access to device state
     /// Serializes access to device state
     private var lock = os_unfair_lock()
     private var lock = os_unfair_lock()
-    
-    private var fw_hw = "FW/HW"
-    public var ledOn: Bool = false
-    public var vibrationOn: Bool = false
-    public var voltage = ""
+
+    private var orangeLinkFirmwareHardwareVersion = "v1.x"
+    private var orangeLinkHardwareVersionMajorMinor: [Int]?
+    private var ledOn: Bool = false
+    private var vibrationOn: Bool = false
+    private var voltage: Float?
+    private var batteryLevel: Int?
+    private var hasPiezo: Bool {
+        if let olHW = orangeLinkHardwareVersionMajorMinor, olHW[0] == 1, olHW[1] >= 1 {
+            return true
+        } else if let olHW = orangeLinkHardwareVersionMajorMinor, olHW[0] == 2, olHW[1] == 6 {
+            return true
+       }
+        return false
+    }
+
+    public var hasOrangeLinkService: Bool {
+        return self.manager.peripheral.services?.itemWithUUID(RileyLinkServiceUUID.orange.cbUUID) != nil
+    }
+
+    public var hardwareType: RileyLinkHardwareType? {
+        guard let services = self.manager.peripheral.services else {
+            return nil
+        }
+
+        guard let bleComponents = self.bleFirmwareVersion else {
+            return nil
+        }
+
+        if services.itemWithUUID(RileyLinkServiceUUID.secureDFU.cbUUID) != nil {
+            return .orange
+        } else if bleComponents.components[0] == 3 {
+            // this returns true for riley with ema firmware, but that is OK
+            return .ema
+        } else {
+            // as long as riley ble remains at 2.x with ema at 3.x this will work
+            return .riley
+        }
+      }
 
 
     /// The queue used to serialize sessions and observe when they've drained
     /// The queue used to serialize sessions and observe when they've drained
     private let sessionQueue: OperationQueue = {
     private let sessionQueue: OperationQueue = {
@@ -101,43 +165,63 @@ extension RileyLinkDevice {
         manager.setCustomName(name)
         manager.setCustomName(name)
     }
     }
     
     
-    public func getBatterylevel() -> String {
-        do {
-            return try manager.readBatteryLevel(timeout: 1)
-        } catch {}
-        return ""
+    public func updateBatteryLevel() {
+        manager.readBatteryLevel { value in
+            if let batteryLevel = value {
+                self.batteryLevel = batteryLevel
+                NotificationCenter.default.post(
+                    name: .DeviceBatteryLevelUpdated,
+                    object: self,
+                    userInfo: [RileyLinkDevice.batteryLevelKey: batteryLevel]
+                )
+                NotificationCenter.default.post(name: .DeviceStatusUpdated, object: self)
+            }
+        }
     }
     }
-    
-    public func orangeAction(mode: Int) {
-        add(log: "orangeAction: \(mode)")
-        manager.orangeAction(mode: RileyLinkOrangeMode(rawValue: UInt8(mode))!)
+
+    public func orangeAction(_ command: OrangeLinkCommand) {
+        log.debug("orangeAction: %@", "\(command)")
+        manager.orangeAction(command)
     }
     }
-    
-    public func orangeSetAction(index: Int, open: Bool) {
-        add(log: "orangeSetAction: \(index), \(open)")
-        manager.setAction(index: index, open: open)
+
+    public func setOrangeConfig(_ config: OrangeLinkConfigurationSetting, isOn: Bool) {
+        log.debug("setOrangeConfig: %@, %@", "\(String(describing: config))", "\(isOn)")
+        manager.setOrangeConfig(config, isOn: isOn)
     }
     }
-    
+
     public func orangeWritePwd() {
     public func orangeWritePwd() {
-        add(log: "orangeWritePwd")
+        log.debug("orangeWritePwd")
         manager.orangeWritePwd()
         manager.orangeWritePwd()
     }
     }
-    
+
     public func orangeClose() {
     public func orangeClose() {
-        add(log: "orangeClose")
+        log.debug("orangeClose")
         manager.orangeClose()
         manager.orangeClose()
     }
     }
-    
+
     public func orangeReadSet() {
     public func orangeReadSet() {
-        add(log: "orangeReadSet")
+        log.debug("orangeReadSet")
         manager.orangeReadSet()
         manager.orangeReadSet()
     }
     }
-    
+
     public func orangeReadVDC() {
     public func orangeReadVDC() {
-        add(log: "orangeReadVDC")
+        log.debug("orangeReadVDC")
         manager.orangeReadVDC()
         manager.orangeReadVDC()
     }
     }
-    
+
+    public func findDevice() {
+        log.debug("findDevice")
+        manager.findDevice()
+    }
+
+    public func setDiagnosticeLEDModeForBLEChip(_ mode: RileyLinkLEDMode) {
+        manager.setLEDMode(mode: mode)
+    }
+
+    public func readDiagnosticLEDModeForBLEChip(completion: @escaping (RileyLinkLEDMode?) -> Void) {
+        manager.readDiagnosticLEDMode(completion: completion)
+    }
+
     public func enableBLELEDs() {
     public func enableBLELEDs() {
         manager.setLEDMode(mode: .on)
         manager.setLEDMode(mode: .on)
     }
     }
@@ -176,15 +260,13 @@ extension RileyLinkDevice {
 
 
         public let name: String?
         public let name: String?
 
 
-        public let bleFirmwareVersion: BLEFirmwareVersion?
+        public let version: String
 
 
-        public let radioFirmwareVersion: RadioFirmwareVersion?
-        
-        public let fw_hw: String?
-        
-        public var ledOn: Bool = false
-        public var vibrationOn: Bool = false
-        public var voltage = ""
+        public let ledOn: Bool
+        public let vibrationOn: Bool
+        public let voltage: Float?
+        public let battery: Int?
+        public let hasPiezo: Bool
     }
     }
 
 
     public func getStatus(_ completion: @escaping (_ status: Status) -> Void) {
     public func getStatus(_ completion: @escaping (_ status: Status) -> Void) {
@@ -196,12 +278,12 @@ extension RileyLinkDevice {
             completion(Status(
             completion(Status(
                 lastIdle: lastIdle,
                 lastIdle: lastIdle,
                 name: self.name,
                 name: self.name,
-                bleFirmwareVersion: self.bleFirmwareVersion,
-                radioFirmwareVersion: self.radioFirmwareVersion,
-                fw_hw: self.fw_hw,
+                version: self.version,
                 ledOn: self.ledOn,
                 ledOn: self.ledOn,
                 vibrationOn: self.vibrationOn,
                 vibrationOn: self.vibrationOn,
-                voltage: self.voltage
+                voltage: self.voltage,
+                battery: self.batteryLevel,
+                hasPiezo: self.hasPiezo
             ))
             ))
         }
         }
     }
     }
@@ -209,8 +291,12 @@ extension RileyLinkDevice {
 
 
 
 
 // MARK: - Command session management
 // MARK: - Command session management
+// CommandSessions are a way to serialize access to the RileyLink command/response facility.
+// All commands that send data out on the RL data characteristic need to be in a command session.
+// Accessing other characteristics on the RileyLink can be done without a command session.
 extension RileyLinkDevice {
 extension RileyLinkDevice {
     public func runSession(withName name: String, _ block: @escaping (_ session: CommandSession) -> Void) {
     public func runSession(withName name: String, _ block: @escaping (_ session: CommandSession) -> Void) {
+        self.log.default("Scheduling session %{public}@", name)
         sessionQueue.addOperation(manager.configureAndRun({ [weak self] (manager) in
         sessionQueue.addOperation(manager.configureAndRun({ [weak self] (manager) in
             self?.log.default("======================== %{public}@ ===========================", name)
             self?.log.default("======================== %{public}@ ===========================", name)
             let bleFirmwareVersion = self?.bleFirmwareVersion
             let bleFirmwareVersion = self?.bleFirmwareVersion
@@ -274,7 +360,6 @@ extension RileyLinkDevice {
 
 
         self.isIdleListeningPending = true
         self.isIdleListeningPending = true
         os_unfair_lock_unlock(&lock)
         os_unfair_lock_unlock(&lock)
-        self.log.debug("Enqueuing idle listening")
 
 
         self.manager.startIdleListening(idleTimeout: timeout, channel: channel) { (error) in
         self.manager.startIdleListening(idleTimeout: timeout, channel: channel) { (error) in
             os_unfair_lock_lock(&self.lock)
             os_unfair_lock_lock(&self.lock)
@@ -285,6 +370,7 @@ extension RileyLinkDevice {
                 os_unfair_lock_unlock(&self.lock)
                 os_unfair_lock_unlock(&self.lock)
             } else {
             } else {
                 self.lastIdle = Date()
                 self.lastIdle = Date()
+                self.log.debug("Started idle listening")
                 os_unfair_lock_unlock(&self.lock)
                 os_unfair_lock_unlock(&self.lock)
                 NotificationCenter.default.post(name: .DeviceDidStartIdle, object: self)
                 NotificationCenter.default.post(name: .DeviceDidStartIdle, object: self)
             }
             }
@@ -326,23 +412,24 @@ extension RileyLinkDevice {
     }
     }
 
 
     func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
     func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
-        log.debug("didConnect %@", peripheral)
+        log.default("didConnect %{public}@", peripheral)
         if case .connected = peripheral.state {
         if case .connected = peripheral.state {
             assertIdleListening(forceRestart: false)
             assertIdleListening(forceRestart: false)
             assertTimerTick()
             assertTimerTick()
         }
         }
 
 
         manager.centralManager(central, didConnect: peripheral)
         manager.centralManager(central, didConnect: peripheral)
+
         NotificationCenter.default.post(name: .DeviceConnectionStateDidChange, object: self)
         NotificationCenter.default.post(name: .DeviceConnectionStateDidChange, object: self)
     }
     }
 
 
     func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
     func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
-        log.debug("didDisconnectPeripheral %@", peripheral)
+        log.default("didDisconnectPeripheral %{public}@", peripheral)
         NotificationCenter.default.post(name: .DeviceConnectionStateDidChange, object: self)
         NotificationCenter.default.post(name: .DeviceConnectionStateDidChange, object: self)
     }
     }
 
 
     func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
     func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
-        log.debug("didFailToConnect %@", peripheral)
+        log.default("didFailToConnect %{public}@", peripheral)
         NotificationCenter.default.post(name: .DeviceConnectionStateDidChange, object: self)
         NotificationCenter.default.post(name: .DeviceConnectionStateDidChange, object: self)
     }
     }
 }
 }
@@ -350,24 +437,42 @@ extension RileyLinkDevice {
 
 
 extension RileyLinkDevice: PeripheralManagerDelegate {
 extension RileyLinkDevice: PeripheralManagerDelegate {
     func peripheralManager(_ manager: PeripheralManager, didUpdateNotificationStateFor characteristic: CBCharacteristic) {
     func peripheralManager(_ manager: PeripheralManager, didUpdateNotificationStateFor characteristic: CBCharacteristic) {
-        add(log: "didUpdate: \(characteristic.uuid.uuidString)")
-//        switch OrangeServiceCharacteristicUUID(rawValue: characteristic.uuid.uuidString) {
-//        case .orange, .orangeNotif:
-//            manager.writePsw = true
-//            orangeWritePwd()
-//        default:
-//            break
-//        }
         log.debug("Did didUpdateNotificationStateFor %@", characteristic)
         log.debug("Did didUpdateNotificationStateFor %@", characteristic)
     }
     }
-    
-    // This is called from the central's queue
+
+    // If PeripheralManager receives a response on the data queue, without an outstanding request,
+    // it will pass the update to this method, which is called on the central's queue.
+    // This is how idle listen responses are handled
     func peripheralManager(_ manager: PeripheralManager, didUpdateValueFor characteristic: CBCharacteristic) {
     func peripheralManager(_ manager: PeripheralManager, didUpdateValueFor characteristic: CBCharacteristic) {
-        log.debug("Did UpdateValueFor %@", characteristic)
-        add(log: "Did UpdateValueFor: \(characteristic.uuid.uuidString), value: \(characteristic.value?.hexadecimalString ?? "")")
-        switch MainServiceCharacteristicUUID(rawValue: characteristic.uuid.uuidString) {
-        case .data?:
-            guard let value = characteristic.value, value.count > 0 else {
+        let characteristicService: CBService? = characteristic.service
+        guard let cbService = characteristicService, let service = RileyLinkServiceUUID(rawValue: cbService.uuid.uuidString) else {
+            log.debug("Update from characteristic on unknown service: %@", String(describing: characteristic.service))
+            return
+        }
+
+        switch service {
+        case .main:
+            guard let mainCharacteristic = MainServiceCharacteristicUUID(rawValue: characteristic.uuid.uuidString) else {
+                log.debug("Update from unknown characteristic %@ on main service.", characteristic.uuid.uuidString)
+                return
+            }
+            handleCharacteristicUpdate(mainCharacteristic, value: characteristic.value)
+
+        case .orange:
+            guard let orangeCharacteristic = OrangeServiceCharacteristicUUID(rawValue: characteristic.uuid.uuidString) else {
+                log.debug("Update from unknown characteristic %@ on orange service.", characteristic.uuid.uuidString)
+                return
+            }
+            handleCharacteristicUpdate(orangeCharacteristic, value: characteristic.value)
+        default:
+            return
+        }
+    }
+
+    private func handleCharacteristicUpdate(_ characteristic: MainServiceCharacteristicUUID, value: Data?) {
+        switch characteristic {
+        case .data:
+            guard let value = value, value.count > 0 else {
                 return
                 return
             }
             }
 
 
@@ -386,7 +491,10 @@ extension RileyLinkDevice: PeripheralManagerDelegate {
 
 
                     if let response = response {
                     if let response = response {
                         switch response.code {
                         switch response.code {
-                        case .rxTimeout, .commandInterrupted, .zeroData, .invalidParam, .unknownCommand:
+                        case .commandInterrupted:
+                            self.log.debug("Received commandInterrupted during idle; assuming device is still listening.")
+                            return
+                        case .rxTimeout, .zeroData, .invalidParam, .unknownCommand:
                             self.log.debug("Idle error received: %@", String(describing: response.code))
                             self.log.debug("Idle error received: %@", String(describing: response.code))
                         case .success:
                         case .success:
                             if let packet = response.packet {
                             if let packet = response.packet {
@@ -404,84 +512,42 @@ extension RileyLinkDevice: PeripheralManagerDelegate {
                 } else {
                 } else {
                     self.log.error("Skipping parsing characteristic value update due to missing BLE firmware version")
                     self.log.error("Skipping parsing characteristic value update due to missing BLE firmware version")
                 }
                 }
-
-                self.resetBatteryAlert()
-                self.orangeReadVDC()
                 self.assertIdleListening(forceRestart: true)
                 self.assertIdleListening(forceRestart: true)
             }
             }
-        case .responseCount?:
+        case .responseCount:
             // PeripheralManager.Configuration.valueUpdateMacros is responsible for handling this response.
             // PeripheralManager.Configuration.valueUpdateMacros is responsible for handling this response.
-            self.resetBatteryAlert()
-            self.orangeReadVDC()
             break
             break
-        case .timerTick?:
+        case .timerTick:
             NotificationCenter.default.post(name: .DeviceTimerDidTick, object: self)
             NotificationCenter.default.post(name: .DeviceTimerDidTick, object: self)
-            self.resetBatteryAlert()
-            self.orangeReadVDC()
             assertIdleListening(forceRestart: false)
             assertIdleListening(forceRestart: false)
-        case .customName?, .firmwareVersion?, .ledMode?, .none:
+        case .customName, .firmwareVersion, .ledMode:
             break
             break
         }
         }
-        
-        switch OrangeServiceCharacteristicUUID(rawValue: characteristic.uuid.uuidString) {
-        case .orange, .orangeNotif:
-            guard let data = characteristic.value, !data.isEmpty else { return }
+    }
+
+    private func handleCharacteristicUpdate(_ characteristic: OrangeServiceCharacteristicUUID, value: Data?) {
+        switch characteristic {
+        case .orangeRX, .orangeTX:
+            guard let data = value, !data.isEmpty else { return }
             if data.first == 0xbb {
             if data.first == 0xbb {
-                guard let data = characteristic.value, data.count > 6 else { return }
+                guard data.count > 6 else { return }
                 if data[1] == 0x09, data[2] == 0xaa {
                 if data[1] == 0x09, data[2] == 0xaa {
-                    fw_hw = "FW\(data[3]).\(data[4])/HW\(data[5]).\(data[6])"
-                    NotificationCenter.default.post(name: .DeviceFW_HWChange, object: self)
+                    orangeLinkFirmwareHardwareVersion = "FW\(data[3]).\(data[4])/HW\(data[5]).\(data[6])"
+                    orangeLinkHardwareVersionMajorMinor = [Int(data[5]), Int(data[6])]
+                    NotificationCenter.default.post(name: .DeviceStatusUpdated, object: self)
                 }
                 }
-            } else if data.first == 0xdd {
-                guard let data = characteristic.value, data.count > 2 else { return }
+            } else if data.first == OrangeLinkRequestType.cfgHeader.rawValue {
+                guard data.count > 2 else { return }
                 if data[1] == 0x01 {
                 if data[1] == 0x01 {
-                    guard let data = characteristic.value, data.count > 5 else { return }
+                    guard data.count > 5 else { return }
                     ledOn = (data[3] != 0)
                     ledOn = (data[3] != 0)
                     vibrationOn = (data[4] != 0)
                     vibrationOn = (data[4] != 0)
-                    NotificationCenter.default.post(name: .DeviceFW_HWChange, object: self)
+                    NotificationCenter.default.post(name: .DeviceStatusUpdated, object: self)
                 } else if data[1] == 0x03 {
                 } else if data[1] == 0x03 {
-                    guard var data = characteristic.value, data.count > 4 else { return }
-                    data = Data(data[3...4])
-                    let int = UInt16(bigEndian: data.withUnsafeBytes { $0.load(as: UInt16.self) })
-                    voltage = String(format: "%.1f%", Float(int) / 1000)
-                    NotificationCenter.default.post(name: .DeviceFW_HWChange, object: self)
-                    
-                    guard Date() > Date(timeIntervalSince1970: UserDefaults.standard.double(forKey: "voltage_date")).addingTimeInterval(60 * 60),
-                          UserDefaults.standard.double(forKey: "voltage_alert_value") != 0 else { return }
-                    
-                    UserDefaults.standard.setValue(Date().timeIntervalSince1970, forKey: "voltage_date")
-                    DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
-                        let value = UserDefaults.standard.double(forKey: "voltage_alert_value")
-                        if (Double(self.voltage) ?? 100) <= value {
-                            let content = UNMutableNotificationContent()
-                            content.title = "Low Voltage"
-                            content.subtitle = self.voltage
-                            let request = UNNotificationRequest.init(identifier: "Orange Low Voltage", content: content, trigger: nil)
-                            UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
-                        }
-                    }
-                }
-            }
-        default:
-            break
-        }
-    }
-    
-    func resetBatteryAlert() {
-        guard Date() > Date(timeIntervalSince1970: UserDefaults.standard.double(forKey: "battery_date")).addingTimeInterval(60 * 60) else { return }
-        if UserDefaults.standard.integer(forKey: "battery_alert_value") != 0 {
-            manager.queue.async {
-                UserDefaults.standard.setValue(Date().timeIntervalSince1970, forKey: "battery_date")
-                let batteryLevel = self.getBatterylevel()
-                DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
-                    let value = UserDefaults.standard.integer(forKey: "battery_alert_value")
-                    if (Int(batteryLevel) ?? 100) <= value {
-                        let content = UNMutableNotificationContent()
-                        content.title = "Low Battery"
-                        content.subtitle = batteryLevel
-                        let request = UNNotificationRequest.init(identifier: "Orange Low Battery", content: content, trigger: nil)
-                        UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
-                    }
+                    guard data.count > 4 else { return }
+                    let int = UInt16(bigEndian: Data(data[3...4]).withUnsafeBytes { $0.load(as: UInt16.self) })
+                    voltage = Float(int) / 1000
+                    NotificationCenter.default.post(name: .DeviceStatusUpdated, object: self)
                 }
                 }
             }
             }
         }
         }
@@ -511,23 +577,13 @@ extension RileyLinkDevice: PeripheralManagerDelegate {
 
 
         let radioVersionString = try manager.readRadioFirmwareVersion(timeout: 1, responseType: bleFirmwareVersion?.responseType ?? .buffered)
         let radioVersionString = try manager.readRadioFirmwareVersion(timeout: 1, responseType: bleFirmwareVersion?.responseType ?? .buffered)
         radioFirmwareVersion = RadioFirmwareVersion(versionString: radioVersionString)
         radioFirmwareVersion = RadioFirmwareVersion(versionString: radioVersionString)
-        
+
         try manager.setOrangeNotifyOn()
         try manager.setOrangeNotifyOn()
     }
     }
 }
 }
 
 
 
 
 extension RileyLinkDevice: CustomDebugStringConvertible {
 extension RileyLinkDevice: CustomDebugStringConvertible {
-    
-    public func add(log: String) {
-        os_unfair_lock_lock(&lock)
-        if self.logs.count > 10000 {
-            self.logs.removeLast(1000)
-        }
-        self.logs.append("\(Date())\n\(log)\n")
-        os_unfair_lock_unlock(&lock)
-    }
-    
     public var debugDescription: String {
     public var debugDescription: String {
         os_unfair_lock_lock(&lock)
         os_unfair_lock_lock(&lock)
         let lastIdle = self.lastIdle
         let lastIdle = self.lastIdle
@@ -545,9 +601,7 @@ extension RileyLinkDevice: CustomDebugStringConvertible {
             "* radioFirmware: \(String(describing: radioFirmwareVersion))",
             "* radioFirmware: \(String(describing: radioFirmwareVersion))",
             "* bleFirmware: \(String(describing: bleFirmwareVersion))",
             "* bleFirmware: \(String(describing: bleFirmwareVersion))",
             "* peripheralManager: \(manager)",
             "* peripheralManager: \(manager)",
-            "* sessionQueue.operationCount: \(sessionQueue.operationCount)",
-            "* logs: \(logs)",
-            "* manager logs: \(manager.logString)"
+            "* sessionQueue.operationCount: \(sessionQueue.operationCount)"
         ].joined(separator: "\n")
         ].joined(separator: "\n")
     }
     }
 }
 }
@@ -557,6 +611,8 @@ extension RileyLinkDevice {
     public static let notificationPacketKey = "com.rileylink.RileyLinkBLEKit.RileyLinkDevice.NotificationPacket"
     public static let notificationPacketKey = "com.rileylink.RileyLinkBLEKit.RileyLinkDevice.NotificationPacket"
 
 
     public static let notificationRSSIKey = "com.rileylink.RileyLinkBLEKit.RileyLinkDevice.NotificationRSSI"
     public static let notificationRSSIKey = "com.rileylink.RileyLinkBLEKit.RileyLinkDevice.NotificationRSSI"
+
+    public static let batteryLevelKey = "com.rileylink.RileyLinkBLEKit.RileyLinkDevice.BatteryLevel"
 }
 }
 
 
 
 
@@ -572,6 +628,8 @@ extension Notification.Name {
     public static let DeviceRSSIDidChange = Notification.Name(rawValue: "com.rileylink.RileyLinkBLEKit.RSSIDidChange")
     public static let DeviceRSSIDidChange = Notification.Name(rawValue: "com.rileylink.RileyLinkBLEKit.RSSIDidChange")
 
 
     public static let DeviceTimerDidTick = Notification.Name(rawValue: "com.rileylink.RileyLinkBLEKit.TimerTickDidChange")
     public static let DeviceTimerDidTick = Notification.Name(rawValue: "com.rileylink.RileyLinkBLEKit.TimerTickDidChange")
-    
-    public static let DeviceFW_HWChange = Notification.Name(rawValue: "com.rileylink.RileyLinkBLEKit.DeviceFW_HWChange")
+
+    public static let DeviceStatusUpdated = Notification.Name(rawValue: "com.rileylink.RileyLinkBLEKit.DeviceStatusUpdated")
+
+    public static let DeviceBatteryLevelUpdated = Notification.Name(rawValue: "com.rileylink.RileyLinkBLEKit.BatteryLevelUpdated")
 }
 }

+ 6 - 1
Dependencies/rileylink_ios/RileyLinkBLEKit/RileyLinkDeviceError.swift

@@ -7,11 +7,12 @@
 
 
 
 
 public enum RileyLinkDeviceError: Error {
 public enum RileyLinkDeviceError: Error {
-    case peripheralManagerError(PeripheralManagerError)
+    case peripheralManagerError(LocalizedError)
     case invalidInput(String)
     case invalidInput(String)
     case writeSizeLimitExceeded(maxLength: Int)
     case writeSizeLimitExceeded(maxLength: Int)
     case invalidResponse(Data)
     case invalidResponse(Data)
     case responseTimeout
     case responseTimeout
+    case commandsBlocked
     case unsupportedCommand(String)
     case unsupportedCommand(String)
 }
 }
 
 
@@ -29,6 +30,8 @@ extension RileyLinkDeviceError: LocalizedError {
             return String(format: LocalizedString("Data exceeded maximum size of %@ bytes", comment: "Write size limit exceeded error description (1: size limit)"), NumberFormatter.localizedString(from: NSNumber(value: maxLength), number: .none))
             return String(format: LocalizedString("Data exceeded maximum size of %@ bytes", comment: "Write size limit exceeded error description (1: size limit)"), NumberFormatter.localizedString(from: NSNumber(value: maxLength), number: .none))
         case .responseTimeout:
         case .responseTimeout:
             return LocalizedString("Pump did not respond in time", comment: "Response timeout error description")
             return LocalizedString("Pump did not respond in time", comment: "Response timeout error description")
+        case .commandsBlocked:
+            return LocalizedString("RileyLink command did not respond", comment: "commandsBlocked error description")
         case .unsupportedCommand(let command):
         case .unsupportedCommand(let command):
             return String(format: LocalizedString("RileyLink firmware does not support the %@ command", comment: "Unsupported command error description"), String(describing: command))
             return String(format: LocalizedString("RileyLink firmware does not support the %@ command", comment: "Unsupported command error description"), String(describing: command))
         }
         }
@@ -47,6 +50,8 @@ extension RileyLinkDeviceError: LocalizedError {
         switch self {
         switch self {
         case .peripheralManagerError(let error):
         case .peripheralManagerError(let error):
             return error.recoverySuggestion
             return error.recoverySuggestion
+        case .commandsBlocked:
+            return LocalizedString("RileyLink may need to be turned off and back on.", comment: "commandsBlocked recovery suggestion")
         default:
         default:
             return nil
             return nil
         }
         }

+ 3 - 7
Dependencies/rileylink_ios/RileyLinkKit/PumpOpsSession.swift

@@ -11,7 +11,7 @@ import LoopKit
 import RileyLinkBLEKit
 import RileyLinkBLEKit
 
 
 
 
-protocol PumpOpsSessionDelegate: class {
+protocol PumpOpsSessionDelegate: AnyObject {
     func pumpOpsSession(_ session: PumpOpsSession, didChange state: PumpState)
     func pumpOpsSession(_ session: PumpOpsSession, didChange state: PumpState)
     func pumpOpsSessionDidChangeRadioConfig(_ session: PumpOpsSession)
     func pumpOpsSessionDidChangeRadioConfig(_ session: PumpOpsSession)
 }
 }
@@ -827,12 +827,8 @@ extension PumpOpsSession {
     }
     }
 
 
     /// - Throws: PumpOpsError.deviceError
     /// - Throws: PumpOpsError.deviceError
-    public func enableCCLEDs() throws {
-        do {
-            try session.enableCCLEDs()
-        } catch let error as LocalizedError {
-            throw PumpOpsError.deviceError(error)
-        }
+    func setCCLEDMode(_ mode: RileyLinkLEDMode) throws {
+        throw PumpOpsError.noResponse(during: "Tests")
     }
     }
 
 
     /// - Throws:
     /// - Throws:

+ 0 - 16
Dependencies/rileylink_ios/RileyLinkKit/RileyLinkDevice.swift

@@ -7,22 +7,6 @@
 
 
 import RileyLinkBLEKit
 import RileyLinkBLEKit
 
 
-
-extension RileyLinkDevice.Status {
-    public var firmwareDescription: String {
-        let versions = [radioFirmwareVersion, bleFirmwareVersion].compactMap { (version: CustomStringConvertible?) -> String? in
-            if let version = version {
-                return String(describing: version)
-            } else {
-                return nil
-            }
-        }
-
-        return versions.joined(separator: " / ")
-    }
-}
-
-
 extension Notification.Name {
 extension Notification.Name {
     public static let DeviceRadioConfigDidChange = Notification.Name(rawValue: "com.rileylink.RileyLinkKit.DeviceRadioConfigDidChange")
     public static let DeviceRadioConfigDidChange = Notification.Name(rawValue: "com.rileylink.RileyLinkKit.DeviceRadioConfigDidChange")
 
 

+ 2 - 0
Dependencies/rileylink_ios/RileyLinkKit/RileyLinkPumpManager.swift

@@ -43,6 +43,8 @@ open class RileyLinkPumpManager {
 
 
     open func deviceTimerDidTick(_ device: RileyLinkDevice) { }
     open func deviceTimerDidTick(_ device: RileyLinkDevice) { }
 
 
+    open func device(_ device: RileyLinkDevice, didUpdateBattery level: Int) { }
+
     // MARK: - CustomDebugStringConvertible
     // MARK: - CustomDebugStringConvertible
     
     
     open var debugDescription: String {
     open var debugDescription: String {

+ 3 - 39
Dependencies/rileylink_ios/RileyLinkKitUI/Base.lproj/Localizable.strings

@@ -25,6 +25,9 @@
 /* The title of the cell showing firmware version */
 /* The title of the cell showing firmware version */
 "Firmware" = "Firmware";
 "Firmware" = "Firmware";
 
 
+/* The title of the cell showing current rileylink frequency */
+"Frequency" = "Frequency";
+
 /* The title of the cell showing device name */
 /* The title of the cell showing device name */
 "Name" = "Name";
 "Name" = "Name";
 
 
@@ -36,42 +39,3 @@
 
 
 /* The title of the cell showing uptime */
 /* The title of the cell showing uptime */
 "Uptime" = "Uptime";
 "Uptime" = "Uptime";
-
-/* The title of the cell showing current rileylink frequency */
-"Frequency" = "Frequency";
-
-/* The title of the cell showing battery level */
-"Battery level" = "Battery level";
-
-/* The title of the cell showing ORL */
-"ORL" = "ORL";
-
-/* The title of the cell showing Voltage */
-"Voltage" = "Voltage";
-
-/* The title of the cell showing voltage level */
-"Low Battery Alert" = "Low Battery Alert";
-
-/* The title of the cell showing Lighten Yellow LED */
-"Lighten Yellow LED" = "Lighten Yellow LED";
-
-/* The title of the cell showing Lighten Red LED */
-"Lighten Red LED" = "Lighten Red LED";
-
-/* The title of the cell showing Test Vibrator */
-"Test Vibrator" = "Test Vibrator";
-
-/* he title of the cell showing Connection LED */
-"Enable Connection State LED" = "Enable Connection State LED";
-
-/* he title of the cell showing Stop Vibrator */
-"Enable Connection State Vibrator" = "Enable Connection State Vibrator";
-
-/* The title of the section describing commands */
-"Test Commands" = "Test Commands";
-
-/* The title of the section describing commands */
-"Configure Commands" = "Configure Commands";
-
-/* The title of the section describing commands */
-"Alert" = "Alert";

+ 62 - 0
Dependencies/rileylink_ios/RileyLinkKitUI/CommandResponseViewController.swift

@@ -0,0 +1,62 @@
+//
+//  CommandResponseViewController.swift
+//  RileyLinkKitUI
+//
+//  Created by Pete Schwamb on 7/19/21.
+//  Copyright © 2021 Pete Schwamb. All rights reserved.
+//
+
+import Foundation
+import LoopKitUI
+import RileyLinkBLEKit
+
+extension CommandResponseViewController {
+    typealias T = CommandResponseViewController
+    
+    static func getStatistics(device: RileyLinkDevice) -> T {
+        return T { (completionHandler) -> String in
+            device.runSession(withName: "Get Statistics") { session in
+                let response: String
+
+                do {
+                    let stats = try session.getRileyLinkStatistics()
+                    response = String(describing: stats)
+                } catch let error {
+                    response = String(describing: error)
+                }
+
+                DispatchQueue.main.async {
+                    completionHandler(response)
+                }
+            }
+            
+            return LocalizedString("Get Statistics…", comment: "Progress message for getting statistics.")
+        }
+    }
+    
+    static func setDiagnosticLEDMode(device: RileyLinkDevice, mode: RileyLinkLEDMode) -> T {
+        return T { (completionHandler) -> String in
+            device.setDiagnosticeLEDModeForBLEChip(mode)
+            device.runSession(withName: "Update diagnostic LED mode") { session in
+                let response: String
+                do {
+                    try session.setCCLEDMode(mode)
+                    switch mode {
+                    case .on:
+                        response = "Diagnostic mode enabled"
+                    default:
+                        response = "Diagnostic mode disabled"
+                    }
+                } catch let error {
+                    response = String(describing: error)
+                }
+
+                DispatchQueue.main.async {
+                    completionHandler(response)
+                }
+            }
+
+            return LocalizedString("Updating diagnostic LEDs mode", comment: "Progress message for changing diagnostic LED mode")
+        }
+    }
+}

File diff suppressed because it is too large
+ 429 - 308
Dependencies/rileylink_ios/RileyLinkKitUI/RileyLinkDeviceTableViewController.swift


+ 3 - 39
Dependencies/rileylink_ios/RileyLinkKitUI/sv.lproj/Localizable.strings

@@ -25,6 +25,9 @@
 /* The title of the cell showing firmware version */
 /* The title of the cell showing firmware version */
 "Firmware" = "Firmware";
 "Firmware" = "Firmware";
 
 
+/* The title of the cell showing current rileylink frequency */
+"Frequency" = "Frekvens";
+
 /* The title of the cell showing device name */
 /* The title of the cell showing device name */
 "Name" = "Namn";
 "Name" = "Namn";
 
 
@@ -36,42 +39,3 @@
 
 
 /* The title of the cell showing uptime */
 /* The title of the cell showing uptime */
 "Uptime" = "Körs sedan";
 "Uptime" = "Körs sedan";
-
-/* The title of the cell showing current rileylink frequency */
-"Frequency" = "Frekvens";
-
-/* The title of the cell showing battery level */
-"Battery level" = "Batterinivå";
-
-/* The title of the cell showing ORL */
-"ORL" = "ORL";
-
-/* The title of the cell showing Voltage */
-"Voltage" = "Spänning";
-
-/* The title of the cell showing voltage level */
-"Low Battery Alert" = "Varning för låg batterinivå";
-
-/* The title of the cell showing Lighten Yellow LED */
-"Lighten Yellow LED" = "Ljusare gul LED";
-
-/* The title of the cell showing Lighten Red LED */
-"Lighten Red LED" = "Ljusare röd LED";
-
-/* The title of the cell showing Test Vibrator */
-"Test Vibrator" = "Testa vibrator";
-
-/* he title of the cell showing Connection LED */
-"Enable Connection State LED" = "Visa LED för anslutningsstatus";
-
-/* he title of the cell showing Stop Vibrator */
-"Enable Connection State Vibrator" = "Vibrera för anslutningsstatus";
-
-/* The title of the section describing commands */
-"Test Commands" = "Testkommandon";
-
-/* The title of the section describing commands */
-"Configure Commands" = "Inställningar för OrangeLink";
-
-/* The title of the section describing commands */
-"Alert" = "Varningar";