Procházet zdrojové kódy

update loop framework - version 20220918

(cherry picked from commit b5f6ba7c21353bb796bb8dd7a7824fa724af7526)
avouspierre před 3 roky
rodič
revize
be0a6516d0
51 změnil soubory, kde provedl 415 přidání a 797 odebrání
  1. 3 1
      Dependencies/LoopKit/LoopKit/InsulinKit/CachedInsulinDeliveryObject+CoreDataClass.swift
  2. 4 4
      Dependencies/LoopKit/LoopKit/InsulinKit/DoseEntry.swift
  3. 4 2
      Dependencies/LoopKit/LoopKit/InsulinKit/DoseStore.swift
  4. 15 0
      Dependencies/LoopKit/LoopKit/InsulinKit/InsulinMath.swift
  5. 0 64
      Dependencies/LoopKit/LoopkitPatch.txt
  6. 5 5
      Dependencies/OmniBLE/OmniBLE/Bluetooth/Pair/LTKExchanger.swift
  7. 2 2
      Dependencies/OmniBLE/OmniBLE/Bluetooth/PeripheralManager+OmniBLE.swift
  8. 3 3
      Dependencies/OmniBLE/OmniBLE/Bluetooth/Session/SessionEstablisher.swift
  9. 3 3
      Dependencies/OmniBLE/OmniBLE/PumpManager/MessageTransport.swift
  10. 1 1
      Dependencies/OmniBLE/OmniBLE/PumpManagerUI/Views/DeliveryUncertaintyRecoveryView.swift
  11. 19 8
      Dependencies/rileylink_ios/MinimedKit/Messages/CarelinkMessageBody.swift
  12. 16 3
      Dependencies/rileylink_ios/MinimedKit/Messages/ChangeTempBasalCarelinkMessageBody.swift
  13. 5 13
      Dependencies/rileylink_ios/MinimedKit/Messages/DeviceLinkMessageBody.swift
  14. 5 2
      Dependencies/rileylink_ios/MinimedKit/Messages/FindDeviceMessageBody.swift
  15. 9 8
      Dependencies/rileylink_ios/MinimedKit/Messages/GetBatteryCarelinkMessageBody.swift
  16. 9 5
      Dependencies/rileylink_ios/MinimedKit/Messages/GetPumpModelCarelinkMessageBody.swift
  17. 4 3
      Dependencies/rileylink_ios/MinimedKit/Messages/MessageBody.swift
  18. 2 4
      Dependencies/rileylink_ios/MinimedKit/Messages/MessageType.swift
  19. 4 0
      Dependencies/rileylink_ios/MinimedKit/Messages/MeterMessage.swift
  20. 5 0
      Dependencies/rileylink_ios/MinimedKit/Messages/MySentryAckMessageBody.swift
  21. 5 1
      Dependencies/rileylink_ios/MinimedKit/Messages/MySentryAlertClearedMessageBody.swift
  22. 5 1
      Dependencies/rileylink_ios/MinimedKit/Messages/MySentryAlertMessageBody.swift
  23. 5 1
      Dependencies/rileylink_ios/MinimedKit/Messages/MySentryPumpStatusMessageBody.swift
  24. 11 6
      Dependencies/rileylink_ios/MinimedKit/Messages/PowerOnCarelinkMessageBody.swift
  25. 6 1
      Dependencies/rileylink_ios/MinimedKit/Messages/PumpAckMessageBody.swift
  26. 5 1
      Dependencies/rileylink_ios/MinimedKit/Messages/PumpErrorMessageBody.swift
  27. 2 2
      Dependencies/rileylink_ios/MinimedKit/Messages/PumpMessage.swift
  28. 8 6
      Dependencies/rileylink_ios/MinimedKit/Messages/ReadPumpStatusMessageBody.swift
  29. 6 0
      Dependencies/rileylink_ios/MinimedKit/Messages/ReadRemainingInsulinMessageBody.swift
  30. 8 11
      Dependencies/rileylink_ios/MinimedKit/Messages/ReadSettingsCarelinkMessageBody.swift
  31. 4 0
      Dependencies/rileylink_ios/MinimedKit/Messages/ReadTempBasalCarelinkMessageBody.swift
  32. 4 0
      Dependencies/rileylink_ios/MinimedKit/Messages/ReadTimeCarelinkMessageBody.swift
  33. 15 7
      Dependencies/rileylink_ios/MinimedKit/Messages/SuspendResumeMessageBody.swift
  34. 5 1
      Dependencies/rileylink_ios/MinimedKit/Messages/UnknownMessageBody.swift
  35. 27 0
      Dependencies/rileylink_ios/MinimedKit/PumpEvents/PumpAlarmPumpEvent.swift
  36. 0 11
      Dependencies/rileylink_ios/MinimedKit/PumpManager/CommandSession.swift
  37. 42 2
      Dependencies/rileylink_ios/MinimedKit/PumpManager/DoseStore.swift
  38. 35 29
      Dependencies/rileylink_ios/MinimedKit/PumpManager/MinimedPumpManager.swift
  39. 2 0
      Dependencies/rileylink_ios/MinimedKit/PumpManager/MinimedPumpMessageSender.swift
  40. 9 27
      Dependencies/rileylink_ios/MinimedKit/PumpManager/UnfinalizedDose.swift
  41. 16 0
      Dependencies/rileylink_ios/MinimedKitTests/ReconciliationTests.swift
  42. 4 4
      Dependencies/rileylink_ios/OmniKit/OmnipodCommon/UnfinalizedDose.swift
  43. 8 8
      Dependencies/rileylink_ios/OmniKit/PumpManager/OmnipodPumpManager.swift
  44. 1 1
      Dependencies/rileylink_ios/OmniKitUI/Views/DeliveryUncertaintyRecoveryView.swift
  45. 2 2
      Dependencies/rileylink_ios/OmniKitUI/Views/ManualTempBasalEntryView.swift
  46. 4 4
      Dependencies/rileylink_ios/RileyLink/View Controllers/MainViewController.swift
  47. 0 99
      Dependencies/rileylink_ios/RileyLinkBLEKit/RileyLinkConnectionManager.swift
  48. 0 39
      Dependencies/rileylink_ios/RileyLinkBLEKit/RileyLinkConnectionManagerState.swift
  49. 0 338
      Dependencies/rileylink_ios/RileyLinkBLEKit/RileyLinkDeviceManager.swift
  50. 48 54
      Dependencies/rileylink_ios/RileyLinkKit/PumpOpsSession.swift
  51. 10 10
      Dependencies/rileylink_ios/RileyLinkKitUI/RileyLinkDeviceTableViewController.swift

+ 3 - 1
Dependencies/LoopKit/LoopKit/InsulinKit/CachedInsulinDeliveryObject+CoreDataClass.swift

@@ -235,7 +235,9 @@ extension CachedInsulinDeliveryObject {
         // override enactments/cancels.
         //assert(entry.startDate == startDate)
         assert(entry.syncIdentifier == syncIdentifier)
-        assert(isMutable)
+        if !isMutable {
+            assertionFailure("Attempt to update un-mutable dose: \(self) with \(entry)")
+        }
 
         self.startDate = entry.startDate
         self.endDate = entry.endDate

+ 4 - 4
Dependencies/LoopKit/LoopKit/InsulinKit/DoseEntry.swift

@@ -28,12 +28,12 @@ public struct DoseEntry: TimelineValue, Equatable {
     /// The scheduled basal rate during this dose entry
     public internal(set) var scheduledBasalRate: HKQuantity?
 
-    public init(suspendDate: Date, automatic: Bool? = nil, wasProgrammedByPumpUI: Bool = false) {
-        self.init(type: .suspend, startDate: suspendDate, value: 0, unit: .units, automatic: automatic, wasProgrammedByPumpUI: wasProgrammedByPumpUI)
+    public init(suspendDate: Date, automatic: Bool? = nil, isMutable: Bool = false, wasProgrammedByPumpUI: Bool = false) {
+        self.init(type: .suspend, startDate: suspendDate, value: 0, unit: .units, automatic: automatic, isMutable: isMutable, wasProgrammedByPumpUI: wasProgrammedByPumpUI)
     }
 
-    public init(resumeDate: Date, insulinType: InsulinType? = nil, automatic: Bool? = nil, wasProgrammedByPumpUI: Bool = false) {
-        self.init(type: .resume, startDate: resumeDate, value: 0, unit: .units, insulinType: insulinType, automatic: automatic, wasProgrammedByPumpUI: wasProgrammedByPumpUI)
+    public init(resumeDate: Date, insulinType: InsulinType? = nil, automatic: Bool? = nil, isMutable: Bool = false, wasProgrammedByPumpUI: Bool = false) {
+        self.init(type: .resume, startDate: resumeDate, value: 0, unit: .units, insulinType: insulinType, automatic: automatic, isMutable: isMutable, wasProgrammedByPumpUI: wasProgrammedByPumpUI)
     }
 
     // If the insulin model field is nil, it's assumed that the model is the type of insulin the pump dispenses

+ 4 - 2
Dependencies/LoopKit/LoopKit/InsulinKit/DoseStore.swift

@@ -1388,7 +1388,9 @@ extension DoseStore {
                 report.append("* insulinOnBoard: \(String(describing: value))")
             }
 
-            self.getReservoirValues(since: Date.distantPast) { (result) in
+            let historyStart = Date().addingTimeInterval(-.hours(24))
+
+            self.getReservoirValues(since: historyStart) { (result) in
                 report.append("")
                 report.append("### getReservoirValues")
 
@@ -1403,7 +1405,7 @@ extension DoseStore {
                     }
                 }
 
-                self.getPumpEventValues(since: Date.distantPast) { (result) in
+                self.getPumpEventValues(since: historyStart) { (result) in
                     report.append("")
                     report.append("### getPumpEventValues")
 

+ 15 - 0
Dependencies/LoopKit/LoopKit/InsulinKit/InsulinMath.swift

@@ -382,6 +382,21 @@ extension Collection where Element == DoseEntry {
                     if endDate > last.startDate {
                         reconciled.append(last.trimmed(from: nil, to: endDate, syncIdentifier: last.syncIdentifier))
                     }
+                } else if let suspend = lastSuspend {
+                    // Handle missing resume. Basal following suspend, with no resume.
+                    reconciled.append(DoseEntry(
+                        type: suspend.type,
+                        startDate: suspend.startDate,
+                        endDate: dose.startDate,
+                        value: suspend.value,
+                        unit: suspend.unit,
+                        description: suspend.description ?? dose.description,
+                        syncIdentifier: suspend.syncIdentifier,
+                        insulinType: suspend.insulinType,
+                        isMutable: suspend.isMutable,
+                        wasProgrammedByPumpUI: suspend.wasProgrammedByPumpUI
+                    ))
+                    lastSuspend = nil
                 }
 
                 lastBasal = dose

+ 0 - 64
Dependencies/LoopKit/LoopkitPatch.txt

@@ -1,65 +0,0 @@
-From 3af74b1dbc7e888de53b7dbc5a20e5ec91641e0f Mon Sep 17 00:00:00 2001
-From: Jon Fawcett <jonfawcett10@gmail.com>
-Date: Wed, 2 Mar 2022 20:59:01 -0500
-Subject: [PATCH] Add alternate application factor, strategy switching,
- negative iob factor
-
----
- LoopKit/InsulinKit/InsulinMath.swift | 32 ++++++++++++++++++++++------
- 1 file changed, 25 insertions(+), 7 deletions(-)
-
-diff --git a/LoopKit/InsulinKit/InsulinMath.swift b/LoopKit/InsulinKit/InsulinMath.swift
-index e2d0f74..b57dcd3 100644
---- a/LoopKit/InsulinKit/InsulinMath.swift
-+++ b/LoopKit/InsulinKit/InsulinMath.swift
-@@ -40,10 +40,18 @@ extension DoseEntry {
-         }
- 
-         // Consider doses within the delta time window as momentary
-+        //ken changes
-+        //implement user set negative basal multiplier
-+        let negativeBasalMultiplier = UserDefaults.standard.double(forKey: "negativeBasalMultiplier")
-+        var modifiednetBasalUnits = netBasalUnits
-+        if netBasalUnits < 0.0 {
-+            modifiednetBasalUnits = netBasalUnits * negativeBasalMultiplier
-+        }
-+        //this used netBasalUnits as multiplier originally
-         if endDate.timeIntervalSince(startDate) <= 1.05 * delta {
--            return netBasalUnits * model.percentEffectRemaining(at: time)
-+            return modifiednetBasalUnits * model.percentEffectRemaining(at: time)
-         } else {
--            return netBasalUnits * continuousDeliveryInsulinOnBoard(at: date, model: model, delta: delta)
-+            return modifiednetBasalUnits * continuousDeliveryInsulinOnBoard(at: date, model: model, delta: delta)
-         }
-     }
- 
-@@ -77,11 +85,21 @@ extension DoseEntry {
-         }
- 
-         // Consider doses within the delta time window as momentary
--        if endDate.timeIntervalSince(startDate) <= 1.05 * delta {
--            return netBasalUnits * -insulinSensitivity * (1.0 - model.percentEffectRemaining(at: time))
--        } else {
--            return netBasalUnits * -insulinSensitivity * continuousDeliveryGlucoseEffect(at: date, model: model, delta: delta)
--        }
-+        //ken changes
-+            //if net basal is negative use a mulitplier (0-1)
-+            //modified in user settings
-+            
-+            let negativeBasalMultiplier = UserDefaults.standard.double(forKey: "negativeBasalMultiplier")
-+            var modifiednetBasalUnits = netBasalUnits
-+            if netBasalUnits < 0.0 {
-+                modifiednetBasalUnits = netBasalUnits * negativeBasalMultiplier
-+            }
-+            //originally used netBasalUnits
-+            if endDate.timeIntervalSince(startDate) <= 1.05 * delta {
-+                return modifiednetBasalUnits * -insulinSensitivity * (1.0 - model.percentEffectRemaining(at: time))
-+            } else {
-+                return modifiednetBasalUnits * -insulinSensitivity * continuousDeliveryGlucoseEffect(at: date, model: model, delta: delta)
-+            }
-     }
- 
-     func trimmed(from start: Date? = nil, to end: Date? = nil, syncIdentifier: String? = nil) -> DoseEntry {
-2.29.2
-

+ 5 - 5
Dependencies/OmniBLE/OmniBLE/Bluetooth/Pair/LTKExchanger.swift

@@ -56,7 +56,7 @@ class LTKExchanger {
         try throwOnSendError(sps1.message, LTKExchanger.SPS1)
 
         log.debug("Reading sps1")
-        let podSps1 = try manager.readMessage()
+        let podSps1 = try manager.readMessagePacket()
         guard let _ = podSps1 else {
             throw PodProtocolError.pairingException("Could not read SPS1")
         }
@@ -74,7 +74,7 @@ class LTKExchanger {
         )
         try throwOnSendError(sps2.message, LTKExchanger.SPS2)
 
-        let podSps2 = try manager.readMessage()
+        let podSps2 = try manager.readMessagePacket()
         guard let _ = podSps2 else {
             throw PodProtocolError.pairingException("Could not read SPS2")
         }
@@ -90,12 +90,12 @@ class LTKExchanger {
             keys: [LTKExchanger.SP0GP0],
             payloads: [Data()]
         )
-        let result = manager.sendPacket(sp0gp0.message)
+        let result = manager.sendMessagePacket(sp0gp0.message)
         guard case .sentWithAcknowledgment = result else {
             throw PodProtocolError.pairingException("Error sending SP0GP0: \(result)")
         }
 
-        let p0 = try manager.readMessage()
+        let p0 = try manager.readMessagePacket()
         guard let _ = p0 else {
             throw PodProtocolError.pairingException("Could not read P0")
         }
@@ -113,7 +113,7 @@ class LTKExchanger {
     }
 
     private func throwOnSendError(_ msg: MessagePacket, _ msgType: String) throws {
-        let result = manager.sendPacket(msg)
+        let result = manager.sendMessagePacket(msg)
         guard case .sentWithAcknowledgment = result else {
             throw PodProtocolError.pairingException("Send failure: \(result)")
         }

+ 2 - 2
Dependencies/OmniBLE/OmniBLE/Bluetooth/PeripheralManager+OmniBLE.swift

@@ -40,7 +40,7 @@ extension PeripheralManager {
         try setNotifyValue(true, for: dataChar, timeout: .seconds(2))
     }
         
-    func sendPacket(_ message: MessagePacket, _ forEncryption: Bool = false) -> SendMessageResult {
+    func sendMessagePacket(_ message: MessagePacket, _ forEncryption: Bool = false) -> SendMessageResult {
         dispatchPrecondition(condition: .onQueue(queue))
         
         var didSend = false
@@ -75,7 +75,7 @@ extension PeripheralManager {
     }
     
     /// - Throws: PeripheralManagerError
-    func readMessage() throws -> MessagePacket? {
+    func readMessagePacket() throws -> MessagePacket? {
         dispatchPrecondition(condition: .onQueue(queue))
 
         var packet: MessagePacket?

+ 3 - 3
Dependencies/OmniBLE/OmniBLE/Bluetooth/Session/SessionEstablisher.swift

@@ -54,11 +54,11 @@ class SessionEstablisher {
     func negotiateSessionKeys() throws -> SessionResult {
         msgSeq += 1
         let challenge = try eapAkaChallenge()
-        let sendResult = manager.sendPacket(challenge)
+        let sendResult = manager.sendMessagePacket(challenge)
         guard case .sentWithAcknowledgment = sendResult else {
             throw SessionEstablishmentException.CommunicationError("Could not send the EAP AKA challenge: $sendResult")
         }
-        guard let challengeResponse = try manager.readMessage() else {
+        guard let challengeResponse = try manager.readMessagePacket() else {
             throw SessionEstablishmentException.CommunicationError("Could not establish session")
         }
 
@@ -72,7 +72,7 @@ class SessionEstablisher {
 
         msgSeq += 1
         let success = eapSuccess()
-        let _ = manager.sendPacket(success)
+        let _ = manager.sendMessagePacket(success)
 
         return .SessionKeys(SessionKeys(
             ck: milenage.ck,

+ 3 - 3
Dependencies/OmniBLE/OmniBLE/PumpManager/MessageTransport.swift

@@ -213,7 +213,7 @@ class PodMessageTransport: MessageTransport {
 
         let sendMessage = try getCmdMessage(cmd: message)
 
-        let writeResult = manager.sendPacket(sendMessage)
+        let writeResult = manager.sendMessagePacket(sendMessage)
         switch writeResult {
         case .sentWithAcknowledgment:
             break;
@@ -264,7 +264,7 @@ class PodMessageTransport: MessageTransport {
     func readAndAckResponse() throws -> Message {
         guard let enDecrypt = self.enDecrypt else { throw PodCommsError.podNotConnected }
 
-        let readResponse = try manager.readMessage()
+        let readResponse = try manager.readMessagePacket()
         guard let readMessage = readResponse else {
             throw PodProtocolError.messageIOException("Could not read response")
         }
@@ -280,7 +280,7 @@ class PodMessageTransport: MessageTransport {
         incrementNonceSeq()
         let ack = try getAck(response: decrypted)
         log.debug("Sending ACK: %@ in packet $ack", ack.payload.hexadecimalString)
-        let ackResult = manager.sendPacket(ack)
+        let ackResult = manager.sendMessagePacket(ack)
         guard case .sentWithAcknowledgment = ackResult else {
             throw PodProtocolError.messageIOException("Could not write $msgType: \(ackResult)")
         }

+ 1 - 1
Dependencies/OmniBLE/OmniBLE/PumpManagerUI/Views/DeliveryUncertaintyRecoveryView.swift

@@ -29,7 +29,7 @@ struct DeliveryUncertaintyRecoveryView: View {
                     self.model.podDeactivationChosen()
                 }) {
                     Text(LocalizedString("Deactivate Pod", comment: "Button title to deactive pod on uncertain program"))
-                    .actionButtonStyle()
+                    .actionButtonStyle(.destructive)
                     .padding()
                 }
             }

+ 19 - 8
Dependencies/rileylink_ios/MinimedKit/Messages/CarelinkMessageBody.swift

@@ -8,25 +8,31 @@
 
 import Foundation
 
+extension Data {
+    func paddedTo(length: Int) -> Data {
+        var data = self
+        data.append(contentsOf: [UInt8](repeating: 0, count: length - data.count))
+        return data
+    }
+}
 
-public class CarelinkLongMessageBody: MessageBody {
+public class CarelinkLongMessageBody: DecodableMessageBody {
     public static var length: Int = 65
 
     let rxData: Data
 
     public required init?(rxData: Data) {
-        var data: Data = rxData
-
-        if data.count < type(of: self).length {
-            data.append(contentsOf: [UInt8](repeating: 0, count: type(of: self).length - data.count))
-        }
-
-        self.rxData = data
+        self.rxData = rxData.paddedTo(length: type(of: self).length)
     }
 
     public var txData: Data {
         return rxData
     }
+
+    public var description: String {
+        return "CarelinkLongMessage(\(rxData.hexadecimalString))"
+    }
+
 }
 
 
@@ -50,4 +56,9 @@ public class CarelinkShortMessageBody: MessageBody {
     public var txData: Data {
         return data
     }
+
+    public var description: String {
+        return "CarelinkShortMessage(\(data.hexadecimalString))"
+    }
+
 }

+ 16 - 3
Dependencies/rileylink_ios/MinimedKit/Messages/ChangeTempBasalCarelinkMessageBody.swift

@@ -9,9 +9,18 @@
 import Foundation
 
 
-public class ChangeTempBasalCarelinkMessageBody: CarelinkLongMessageBody {
+public class ChangeTempBasalCarelinkMessageBody: MessageBody {
+    public static var length: Int = 65
 
-    public convenience init(unitsPerHour: Double, duration: TimeInterval) {
+    public var txData: Data
+
+    let unitsPerHour: Double
+    let duration: TimeInterval
+
+    public init(unitsPerHour: Double, duration: TimeInterval) {
+
+        self.unitsPerHour = unitsPerHour
+        self.duration = duration
 
         let length = 3
         let strokesPerUnit: Double = 40
@@ -20,7 +29,11 @@ public class ChangeTempBasalCarelinkMessageBody: CarelinkLongMessageBody {
 
         let data = Data(hexadecimalString: String(format: "%02x%04x%02x", length, strokes, timeSegments))!
 
-        self.init(rxData: data)!
+        self.txData = data.paddedTo(length: type(of: self).length)
+    }
+
+    public var description: String {
+        return "ChangeTempBasal(rate:\(unitsPerHour) U/hr duration:\(duration)"
     }
 
 }

+ 5 - 13
Dependencies/rileylink_ios/MinimedKit/Messages/DeviceLinkMessageBody.swift

@@ -8,17 +8,17 @@
 
 import Foundation
 
-public struct DeviceLinkMessageBody: MessageBody {
+public struct DeviceLinkMessageBody: DecodableMessageBody {
     
     public static let length = 5
     
     public let deviceAddress: Data
     public let sequence: UInt8
-    let rxData: Data
+    public var txData: Data
     
     
     public init?(rxData: Data) {
-        self.rxData = rxData
+        self.txData = rxData
         
         if rxData.count == type(of: self).length {
             self.deviceAddress = rxData.subdata(in: 1..<4)
@@ -28,15 +28,7 @@ public struct DeviceLinkMessageBody: MessageBody {
         }
     }
     
-    public var txData: Data {
-        return rxData
+    public var description: String {
+        return "DeviceLink(\(deviceAddress.hexadecimalString), \(sequence))"
     }
-    
-    public var dictionaryRepresentation: [String: Any] {
-        return [
-            "sequence": Int(sequence),
-            "deviceAddress": deviceAddress.hexadecimalString,
-        ]
-    }
-    
 }

+ 5 - 2
Dependencies/rileylink_ios/MinimedKit/Messages/FindDeviceMessageBody.swift

@@ -8,7 +8,7 @@
 
 import Foundation
 
-public struct FindDeviceMessageBody: MessageBody {
+public struct FindDeviceMessageBody: DecodableMessageBody {
     
     public static let length = 5
     
@@ -38,5 +38,8 @@ public struct FindDeviceMessageBody: MessageBody {
             "deviceAddress": deviceAddress.hexadecimalString,
         ]
     }
-    
+
+    public var description: String {
+        return "FindDevice(\(deviceAddress.hexadecimalString), \(sequence))"
+    }
 }

+ 9 - 8
Dependencies/rileylink_ios/MinimedKit/Messages/GetBatteryCarelinkMessageBody.swift

@@ -25,25 +25,26 @@ public enum BatteryStatus: Equatable {
     }
 }
 
-public class GetBatteryCarelinkMessageBody: CarelinkLongMessageBody {
+public class GetBatteryCarelinkMessageBody: DecodableMessageBody {
+    public static var length: Int = 65
+
+    public var txData: Data
+
     public let status: BatteryStatus
     public let volts: Double
     
     public required init?(rxData: Data) {
         guard rxData.count == type(of: self).length else {
-            volts = 0
-            status = .unknown(rawVal: 0)
-            super.init(rxData: rxData)
             return nil
         }
         
         volts = Double(Int(rxData[2]) << 8 + Int(rxData[3])) / 100.0
         status = BatteryStatus(statusByte: rxData[1])
-        
-        super.init(rxData: rxData)
+        txData = rxData
     }
 
-    public required init?(rxData: NSData) {
-        fatalError("init(rxData:) has not been implemented")
+    public var description: String {
+        return "GetBattery(status:\(status), volts:\(volts))"
     }
+
 }

+ 9 - 5
Dependencies/rileylink_ios/MinimedKit/Messages/GetPumpModelCarelinkMessageBody.swift

@@ -8,21 +8,25 @@
 
 import Foundation
 
-public class GetPumpModelCarelinkMessageBody: CarelinkLongMessageBody {
+public class GetPumpModelCarelinkMessageBody: DecodableMessageBody {
+    public var txData: Data
+
+    public static var length: Int = 65
+
     public let model: String
     
     public required init?(rxData: Data) {
         guard rxData.count == type(of: self).length,
             let mdl = String(data: rxData.subdata(in: 2..<5), encoding: String.Encoding.ascii) else {
                 model = ""
-                super.init(rxData: rxData)
                 return nil
         }
         model = mdl
-        super.init(rxData: rxData)
+        txData = rxData
     }
 
-    public required init?(rxData: NSData) {
-        fatalError("init(rxData:) has not been implemented")
+    public var description: String {
+        return "GetPumpModel(\(model))"
     }
+
 }

+ 4 - 3
Dependencies/rileylink_ios/MinimedKit/Messages/MessageBody.swift

@@ -9,18 +9,19 @@
 import Foundation
 
 
-public protocol MessageBody {
+public protocol MessageBody: CustomStringConvertible {
     static var length: Int {
         get
     }
 
-    init?(rxData: Data)
-
     var txData: Data {
         get
     }
 }
 
+public protocol DecodableMessageBody: MessageBody {
+    init?(rxData: Data)
+}
 
 public protocol DictionaryRepresentable {
     var dictionaryRepresentation: [String: Any] {

+ 2 - 4
Dependencies/rileylink_ios/MinimedKit/Messages/MessageType.swift

@@ -89,8 +89,8 @@ public enum MessageType: UInt8 {
     case readCaptureEventEnabled      = 0xf1  // Body[1] encodes the bool state 0101000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
     case changeCaptureEventEnable     = 0xf2
     case readOtherDevicesStatus       = 0xf3
-    
-    var bodyType: MessageBody.Type {
+
+    var decodeType: DecodableMessageBody.Type {
         switch self {
         case .alert:
             return MySentryAlertMessageBody.self
@@ -142,8 +142,6 @@ public enum MessageType: UInt8 {
             return ReadOtherDevicesStatusMessageBody.self
         case .readRemoteControlIDs:
             return ReadRemoteControlIDsMessageBody.self
-        case .suspendResume:
-            return SuspendResumeMessageBody.self
         case .readFirmwareVersion:
             return GetPumpFirmwareVersionMessageBody.self
         default:

+ 4 - 0
Dependencies/rileylink_ios/MinimedKit/Messages/MeterMessage.swift

@@ -41,4 +41,8 @@ public struct MeterMessage: MessageBody, DictionaryRepresentable {
             "ackFlag": ackFlag,
         ]
     }
+
+    public var description: String {
+        return "Meter(\(glucose), \(ackFlag))"
+    }
 }

+ 5 - 0
Dependencies/rileylink_ios/MinimedKit/Messages/MySentryAckMessageBody.swift

@@ -48,4 +48,9 @@ public struct MySentryAckMessageBody: MessageBody {
 
         return Data(buffer)
     }
+
+    public var description: String {
+        return "MySentryAck(\(sequence), \(mySentryID.hexadecimalString), \(responseMessageTypes))"
+    }
+
 }

+ 5 - 1
Dependencies/rileylink_ios/MinimedKit/Messages/MySentryAlertClearedMessageBody.swift

@@ -18,7 +18,7 @@ See: [MinimedRF Class](https://github.com/ps2/minimed_rf/blob/master/lib/minimed
 a2 594040 02 80 52 14
 ```
 */
-public struct MySentryAlertClearedMessageBody: MessageBody, DictionaryRepresentable {
+public struct MySentryAlertClearedMessageBody: DecodableMessageBody, DictionaryRepresentable {
     public static let length = 2
 
     public let alertType: MySentryAlertType?
@@ -45,4 +45,8 @@ public struct MySentryAlertClearedMessageBody: MessageBody, DictionaryRepresenta
             "cleared": true
         ]
     }
+
+    public var description: String {
+        return "MySentryAlertCleared(\(String(describing: alertType)))"
+    }
 }

+ 5 - 1
Dependencies/rileylink_ios/MinimedKit/Messages/MySentryAlertMessageBody.swift

@@ -18,7 +18,7 @@ See: [MinimedRF Class](https://github.com/ps2/minimed_rf/blob/master/lib/minimed
 a2 594040 01 7c 65 0727070f0906 0175 4c
 ```
 */
-public struct MySentryAlertMessageBody: MessageBody, DictionaryRepresentable {
+public struct MySentryAlertMessageBody: DecodableMessageBody, DictionaryRepresentable {
     public static let length = 10
 
     public let alertType: MySentryAlertType?
@@ -54,6 +54,10 @@ public struct MySentryAlertMessageBody: MessageBody, DictionaryRepresentable {
             "alertType": (alertType != nil ? String(describing: alertType!) : rxData.subdata(in: 1..<2).hexadecimalString),
             "byte89": rxData.subdata(in: 8..<10).hexadecimalString
         ]
+    }
 
+    public var description: String {
+        return "MySentryAlert(\(String(describing: alertType)), \(alertDate))"
     }
+
 }

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 5 - 1
Dependencies/rileylink_ios/MinimedKit/Messages/MySentryPumpStatusMessageBody.swift


+ 11 - 6
Dependencies/rileylink_ios/MinimedKit/Messages/PowerOnCarelinkMessageBody.swift

@@ -9,16 +9,21 @@
 import Foundation
 
 
-public class PowerOnCarelinkMessageBody: CarelinkLongMessageBody {
+public struct PowerOnCarelinkMessageBody: MessageBody {
+    public static var length: Int = 65
 
-    public convenience init(duration: TimeInterval) {
+    public var txData: Data
+    let duration: TimeInterval
+
+    public init(duration: TimeInterval) {
+        self.duration = duration
         let numArgs = 2
         let on = 1
         let durationMinutes: Int = Int(ceil(duration / 60.0))
+        self.txData = Data(hexadecimalString: String(format: "%02x%02x%02x", numArgs, on, durationMinutes))!.paddedTo(length: PowerOnCarelinkMessageBody.length)
+    }
 
-        let data = Data(hexadecimalString: String(format: "%02x%02x%02x", numArgs, on, durationMinutes))!
-
-        self.init(rxData: data)!
+    public var description: String {
+        return "PowerOn(duration:\(duration))"
     }
-  
 }

+ 6 - 1
Dependencies/rileylink_ios/MinimedKit/Messages/PumpAckMessageBody.swift

@@ -8,7 +8,7 @@
 
 import Foundation
 
-public class PumpAckMessageBody: MessageBody {
+public class PumpAckMessageBody: DecodableMessageBody {
     public static let length = 1
     
     let rxData: Data
@@ -20,4 +20,9 @@ public class PumpAckMessageBody: MessageBody {
     public var txData: Data {
         return rxData
     }
+
+    public var description: String {
+        return "PumpAck(\(rxData.hexadecimalString))"
+    }
+
 }

+ 5 - 1
Dependencies/rileylink_ios/MinimedKit/Messages/PumpErrorMessageBody.swift

@@ -38,7 +38,7 @@ public enum PumpErrorCode: UInt8, CustomStringConvertible {
     }
 }
 
-public class PumpErrorMessageBody: MessageBody {
+public class PumpErrorMessageBody: DecodableMessageBody {
     public static let length = 1
     
     let rxData: Data
@@ -57,4 +57,8 @@ public class PumpErrorMessageBody: MessageBody {
     public var txData: Data {
         return rxData
     }
+
+    public var description: String {
+        return "PumpError(\(errorCode))"
+    }
 }

+ 2 - 2
Dependencies/rileylink_ios/MinimedKit/Messages/PumpMessage.swift

@@ -25,7 +25,7 @@ public struct PumpMessage : CustomStringConvertible {
         guard rxData.count >= 6,
             let packetType = PacketType(rawValue: rxData[0]), packetType != .meter,
             let messageType = MessageType(rawValue: rxData[4]),
-            let messageBody = messageType.bodyType.init(rxData: rxData.subdata(in: 5..<rxData.count))
+            let messageBody = messageType.decodeType.init(rxData: rxData.subdata(in: 5..<rxData.count))
         else {
             return nil
         }
@@ -48,7 +48,7 @@ public struct PumpMessage : CustomStringConvertible {
     }
     
     public var description: String {
-        return String(format: "PumpMessage(%1$@, %2$@, %3$@, %4$@)", String(describing: packetType), String(describing: messageType), address.hexadecimalString, self.messageBody.txData.hexadecimalString)
+        return "PumpMessage(\(packetType), \(messageType), \(address.hexadecimalString), \(messageBody))"
     }
 
 }

+ 8 - 6
Dependencies/rileylink_ios/MinimedKit/Messages/ReadPumpStatusMessageBody.swift

@@ -8,7 +8,11 @@
 
 import Foundation
 
-public class ReadPumpStatusMessageBody: CarelinkLongMessageBody {
+public class ReadPumpStatusMessageBody: DecodableMessageBody {
+
+    public static var length: Int = 65
+
+    public var txData: Data
 
     public let bolusing: Bool
     public let suspended: Bool
@@ -20,12 +24,10 @@ public class ReadPumpStatusMessageBody: CarelinkLongMessageBody {
 
         bolusing = rxData[2] > 0
         suspended = rxData[3] > 0
-
-        super.init(rxData: rxData)
+        self.txData = rxData
     }
 
-    public required init?(rxData: NSData) {
-        fatalError("init(rxData:) has not been implemented")
+    public var description: String {
+        return "ReadPumpStatus(bolusing:\(bolusing), suspended:\(suspended))"
     }
-    
 }

+ 6 - 0
Dependencies/rileylink_ios/MinimedKit/Messages/ReadRemainingInsulinMessageBody.swift

@@ -30,5 +30,11 @@ public class ReadRemainingInsulinMessageBody: CarelinkLongMessageBody {
         }
 
         super.init(rxData: rxData)
+
+    }
+
+    override public var description: String {
+        return "ReadRemainingInsulin(x23:\(getUnitsRemaining(insulinBitPackingScale: PumpModel.model523.insulinBitPackingScale)), x22:\(getUnitsRemaining(insulinBitPackingScale: PumpModel.model522.insulinBitPackingScale)))"
     }
+
 }

+ 8 - 11
Dependencies/rileylink_ios/MinimedKit/Messages/ReadSettingsCarelinkMessageBody.swift

@@ -49,7 +49,11 @@ public enum BasalProfile {
  a7 594040 c0 19 00 01 00 01 01 00 96 008c 00 00 00 00000064010400 14 00 1901 01 01 00 00 000000000000 000000 00000000000000000000000000000000000000000000000000000000 e9
  ```
  */
-public class ReadSettingsCarelinkMessageBody: CarelinkLongMessageBody {
+public class ReadSettingsCarelinkMessageBody: DecodableMessageBody {
+    public static var length: Int = 65
+
+    public var txData: Data
+
     private static let maxBolusMultiplier: Double = 10
     private static let maxBasalMultiplier: Double = 40
 
@@ -85,18 +89,11 @@ public class ReadSettingsCarelinkMessageBody: CarelinkLongMessageBody {
 
         let rawInsulinActionCurveHours: UInt8 = rxData[18]
         insulinActionCurveHours = Int(rawInsulinActionCurveHours)
-
-        super.init(rxData: rxData)
+        txData = rxData
     }
 
-    public required init?(rxData: NSData) {
-        fatalError("init(rxData:) has not been implemented")
+    public var description: String {
+        return "ReadSettings(maxBasal:\(maxBasal), maxBolus:\(maxBolus), insulinActionCurveHours:\(insulinActionCurveHours), selectedBasalProfile:\(selectedBasalProfile))"
     }
-}
-
 
-extension ReadSettingsCarelinkMessageBody: DictionaryRepresentable {
-    public var dictionaryRepresentation: [String: Any] {
-        return [:]
-    }
 }

+ 4 - 0
Dependencies/rileylink_ios/MinimedKit/Messages/ReadTempBasalCarelinkMessageBody.swift

@@ -51,4 +51,8 @@ public class ReadTempBasalCarelinkMessageBody: CarelinkLongMessageBody {
     public required init?(rxData: NSData) {
         fatalError("init(rxData:) has not been implemented")
     }
+
+    override public var description: String {
+        return String(format: "ReadTempBasal(time_remaining:%1$@s, rate:%2$@, rate_type:%3$@)", String(describing: timeRemaining), String(describing: rate), String(describing: rateType))
+    }
 }

+ 4 - 0
Dependencies/rileylink_ios/MinimedKit/Messages/ReadTimeCarelinkMessageBody.swift

@@ -31,4 +31,8 @@ public class ReadTimeCarelinkMessageBody: CarelinkLongMessageBody {
 
         super.init(rxData: rxData)
     }
+
+    override public var description: String {
+        return "ReadTime(\(dateComponents))"
+    }
 }

+ 15 - 7
Dependencies/rileylink_ios/MinimedKit/Messages/SuspendResumeMessageBody.swift

@@ -8,18 +8,26 @@
 
 import Foundation
 
-public class SuspendResumeMessageBody: CarelinkLongMessageBody {
-    
+public class SuspendResumeMessageBody: MessageBody {
+    public static var length: Int = 65
+
+    public var txData: Data
+
     public enum SuspendResumeState: UInt8 {
         case suspend = 0x01
         case resume = 0x00
     }
-    
-    public convenience init(state: SuspendResumeState) {
+
+    let state: SuspendResumeState
+
+    public init(state: SuspendResumeState) {
+        self.state = state
         let numArgs = 1
         let data = Data(hexadecimalString: String(format: "%02x%02x", numArgs, state.rawValue))!
-        
-        self.init(rxData: data)!
+        self.txData = data.paddedTo(length: type(of: self).length)
+    }
+
+    public var description: String {
+        return "SuspendResume(type:\(state)"
     }
-    
 }

+ 5 - 1
Dependencies/rileylink_ios/MinimedKit/Messages/UnknownMessageBody.swift

@@ -9,7 +9,7 @@
 import Foundation
 
 
-public struct UnknownMessageBody: MessageBody, DictionaryRepresentable {
+public struct UnknownMessageBody: DecodableMessageBody, DictionaryRepresentable {
     public static var length = 0
 
     let rxData: Data
@@ -25,4 +25,8 @@ public struct UnknownMessageBody: MessageBody, DictionaryRepresentable {
     public var dictionaryRepresentation: [String: Any] {
         return ["rawData": rxData]
     }
+
+    public var description: String {
+        return "UnknownMessage(\(rxData.hexadecimalString))"
+    }
 }

+ 27 - 0
Dependencies/rileylink_ios/MinimedKit/PumpEvents/PumpAlarmPumpEvent.swift

@@ -46,6 +46,33 @@ public enum PumpAlarmType {
     }
 }
 
+extension PumpAlarmType {
+    var localizedString: String {
+        switch self {
+        case .autoOff:
+            return LocalizedString("Auto-Off Alarm", comment: "Title for PumpAlarmType.autoOff")
+        case .batteryOutLimitExceeded:
+            return LocalizedString("Battery Out Limit", comment: "Title for PumpAlarmType.batteryOutLimitExceeded")
+        case .noDelivery:
+            return LocalizedString("No Delivery Alarm", comment: "Title for PumpAlarmType.noDelivery")
+        case .batteryDepleted:
+            return LocalizedString("Battery Depleted", comment: "Title for PumpAlarmType.batteryDepleted")
+        case .deviceReset:
+            return LocalizedString("Device Reset", comment: "Title for deviceReset")
+        case .deviceResetBatteryIssue17:
+            return LocalizedString("BatteryIssue17", comment: "Title for PumpAlarmType.deviceResetBatteryIssue17")
+        case .deviceResetBatteryIssue21:
+            return LocalizedString("BatteryIssue21", comment: "Title for PumpAlarmType.deviceResetBatteryIssue21")
+        case .reprogramError:
+            return LocalizedString("Reprogram Error", comment: "Title for PumpAlarmType.reprogramError")
+        case .emptyReservoir:
+            return LocalizedString("Empty Reservoir", comment: "Title for PumpAlarmType.emptyReservoir")
+        case .unknownType:
+            return LocalizedString("Unknown Alarm", comment: "Title for PumpAlarmType.unknownType")
+        }
+    }
+}
+
 public struct PumpAlarmPumpEvent: TimestampedPumpEvent {
     public let length: Int
     public let rawData: Data

+ 0 - 11
Dependencies/rileylink_ios/MinimedKit/PumpManager/CommandSession.swift

@@ -1,11 +0,0 @@
-//
-//  CommandSession.swift
-//  RileyLinkKit
-//
-//  Copyright © 2017 Pete Schwamb. All rights reserved.
-//
-
-import RileyLinkBLEKit
-
-
-extension CommandSession: PumpMessageSender { }

+ 42 - 2
Dependencies/rileylink_ios/MinimedKit/PumpManager/DoseStore.swift

@@ -25,8 +25,11 @@ extension Collection where Element == TimestampedHistoryEvent {
             var dose: DoseEntry?
             var eventType: LoopKit.PumpEventType?
 
+            title = String(describing: type(of: event.pumpEvent))
+
             switch event.pumpEvent {
             case let bolus as BolusNormalPumpEvent:
+                title = LocalizedString("Bolus", comment: "Event title for bolus")
                 let bolusEndDate: Date
                 if let lastSuspend = lastSuspend, bolus.programmed != bolus.amount, lastSuspend.startDate > event.date {
                     bolusEndDate = lastSuspend.startDate
@@ -41,29 +44,39 @@ extension Collection where Element == TimestampedHistoryEvent {
                 }
                 dose = DoseEntry(type: .bolus, startDate: event.date, endDate: bolusEndDate, value: bolus.programmed, unit: .units, deliveredUnits: bolus.amount, automatic: automatic, isMutable: event.isMutable(atDate: now, forPump: model), wasProgrammedByPumpUI: !bolus.wasRemotelyTriggered)
             case let suspendEvent as SuspendPumpEvent:
+                title = LocalizedString("Suspend", comment: "Event title for suspend")
                 dose = DoseEntry(suspendDate: event.date, wasProgrammedByPumpUI: !suspendEvent.wasRemotelyTriggered)
                 lastSuspend = dose
             case let resumeEvent as ResumePumpEvent:
+                title = LocalizedString("Resume", comment: "Event title for resume")
                 dose = DoseEntry(resumeDate: event.date, wasProgrammedByPumpUI: !resumeEvent.wasRemotelyTriggered)
             case let temp as TempBasalPumpEvent:
                 if case .Absolute = temp.rateType {
                     lastTempBasal = DoseEntry(type: .tempBasal, startDate: event.date, value: temp.rate, unit: .unitsPerHour, isMutable: event.isMutable(atDate: now, forPump: model), wasProgrammedByPumpUI: !temp.wasRemotelyTriggered)
                     continue
+                } else {
+                    title = LocalizedString("Percent Temp Basal", comment: "Event title for percent based temp basal")
                 }
             case let tempDuration as TempBasalDurationPumpEvent:
                 if let lastTemp = lastTempBasal, lastTemp.startDate == event.date {
+                    if tempDuration.duration == 0 {
+                        title = LocalizedString("Cancel Temp Basal", comment: "Event title for temp basal cancel")
+                    } else {
+                        title = LocalizedString("Temp Basal", comment: "Event title for temporary basal rate start")
+                    }
                     dose = DoseEntry(
                         type: .tempBasal,
                         startDate: event.date,
                         endDate: event.date.addingTimeInterval(TimeInterval(minutes: Double(tempDuration.duration))),
                         value: lastTemp.unitsPerHour,
                         unit: .unitsPerHour,
-                        automatic: !lastTemp.wasProgrammedByPumpUI,
+                        automatic: false, // If this was automatic dose, it should be set as such during reconciliation
                         isMutable: event.isMutable(atDate: now, forPump: model),
                         wasProgrammedByPumpUI: lastTemp.wasProgrammedByPumpUI
                     )
                 }
             case let basal as BasalProfileStartPumpEvent:
+                title = LocalizedString("Scheduled Basal", comment: "Event title for starting scheduled basal")
                 dose = DoseEntry(
                     type: .basal,
                     startDate: event.date,
@@ -74,6 +87,7 @@ extension Collection where Element == TimestampedHistoryEvent {
                     isMutable: event.isMutable(atDate: now, forPump: model)
                 )
             case is RewindPumpEvent:
+                title = LocalizedString("Rewind", comment: "Event title for rewind")
                 eventType = .rewind
 
                 /* 
@@ -86,6 +100,7 @@ extension Collection where Element == TimestampedHistoryEvent {
                 dose = DoseEntry(suspendDate: event.date)
                 isRewound = true
             case is PrimePumpEvent:
+                title = LocalizedString("Prime", comment: "Event title for rewind")
                 eventType = .prime
 
                 if isRewound {
@@ -93,6 +108,7 @@ extension Collection where Element == TimestampedHistoryEvent {
                     dose = DoseEntry(resumeDate: event.date)
                 }
             case let alarm as PumpAlarmPumpEvent:
+                title = alarm.alarmType.localizedString
                 eventType = .alarm
 
                 if case .noDelivery = alarm.alarmType {
@@ -100,17 +116,41 @@ extension Collection where Element == TimestampedHistoryEvent {
                 }
                 break
             case let alarm as ClearAlarmPumpEvent:
+                title = "Clear Alarm"
                 eventType = .alarmClear
 
                 if case .noDelivery = alarm.alarmType {
                     dose = DoseEntry(resumeDate: event.date)
                 }
                 break
+            case is JournalEntryMealMarkerPumpEvent:
+                title = "Meal"
+                break
+            case is JournalEntryPumpLowBatteryPumpEvent:
+                title = "Low Battery"
+                break
+            case is JournalEntryPumpLowReservoirPumpEvent:
+                title = "Low Reservoir"
+                break
+            case is ChangeBasalProfilePumpEvent:
+                title = "Change Basal Schedule"
+                break
+            case is ChangeBasalProfilePatternPumpEvent:
+                title = "Change Basal Profile Schedule"
+                break
+            case is SelectBasalProfilePumpEvent:
+                title = "Select Profile"
+                break
+            case is ChangeTimePumpEvent:
+                title = "Change Time"
+                break
+            case is NewTimePumpEvent:
+                title = "New Time"
+                break
             default:
                 break
             }
 
-            title = String(describing: type(of: event.pumpEvent))
             events.append(NewPumpEvent(date: event.date, dose: dose, raw: event.pumpEvent.rawData, title: title, type: eventType))
         }
 

+ 35 - 29
Dependencies/rileylink_ios/MinimedKit/PumpManager/MinimedPumpManager.swift

@@ -214,15 +214,18 @@ public class MinimedPumpManager: RileyLinkPumpManager {
             return
         }
 
-        log.debug("MinimedPacket received: %{public}@", String(describing: message))
+        logDeviceCommunication("MySentry \(String(describing: message))", type: .receive)
 
         switch message.messageBody {
         case let body as MySentryPumpStatusMessageBody:
             self.updatePumpStatus(body, from: device)
-        case is MySentryAlertMessageBody, is MySentryAlertClearedMessageBody:
+        case let body as MySentryAlertMessageBody:
+            self.log.default("MySentry Alert: %{public}@", String(describing: body))
+        case let body as MySentryAlertClearedMessageBody:
+            self.log.default("MySentry Alert Cleared: %{public}@", String(describing: body))
             break
-        case let body:
-            self.log.error("Unknown MySentry Message: %d: %{public}@", message.messageType.rawValue, body.txData.hexadecimalString)
+        default:
+            self.log.error("Unknown MySentry Message: %d: %{public}@", message.messageType.rawValue, message.txData.hexadecimalString)
         }
     }
 
@@ -458,14 +461,14 @@ extension MinimedPumpManager {
 
         if case .suspended = state.suspendState {
             return PumpStatusHighlight(
-                localizedMessage: NSLocalizedString("Insulin Suspended", comment: "Status highlight that insulin delivery was suspended."),
+                localizedMessage: LocalizedString("Insulin Suspended", comment: "Status highlight that insulin delivery was suspended."),
                 imageName: "pause.circle.fill",
                 state: .warning)
         }
         
         if date.timeIntervalSince(lastSync(for: state, recents: recents) ?? .distantPast) > .minutes(12) {
             return PumpStatusHighlight(
-                localizedMessage: NSLocalizedString("Signal Loss", comment: "Status highlight when communications with the pod haven't happened recently."),
+                localizedMessage: LocalizedString("Signal Loss", comment: "Status highlight when communications with the pod haven't happened recently."),
                 imageName: "exclamationmark.circle.fill",
                 state: .critical)
         }
@@ -486,9 +489,9 @@ extension MinimedPumpManager {
     }
 
     private var pumpBatteryLowAlert: Alert {
-        let title = NSLocalizedString("Pump Battery Low", comment: "The notification title for a low pump battery")
-        let body = NSLocalizedString("Change the pump battery immediately", comment: "The notification alert describing a low pump battery")
-        let content = Alert.Content(title: title, body: body, acknowledgeActionButtonLabel: NSLocalizedString("Dismiss", comment: "Default alert dismissal"))
+        let title = LocalizedString("Pump Battery Low", comment: "The notification title for a low pump battery")
+        let body = LocalizedString("Change the pump battery immediately", comment: "The notification alert describing a low pump battery")
+        let content = Alert.Content(title: title, body: body, acknowledgeActionButtonLabel: LocalizedString("Dismiss", comment: "Default alert dismissal"))
         return Alert(identifier: Self.pumpBatteryLowAlertIdentifier, foregroundContent: content, backgroundContent: content, trigger: .immediate)
     }
     
@@ -540,9 +543,9 @@ extension MinimedPumpManager {
     }
 
     private var pumpReservoirEmptyAlert: Alert {
-        let title = NSLocalizedString("Pump Reservoir Empty", comment: "The notification title for an empty pump reservoir")
-        let body = NSLocalizedString("Change the pump reservoir now", comment: "The notification alert describing an empty pump reservoir")
-        let content = Alert.Content(title: title, body: body, acknowledgeActionButtonLabel: NSLocalizedString("Ok", comment: "Default alert dismissal"))
+        let title = LocalizedString("Pump Reservoir Empty", comment: "The notification title for an empty pump reservoir")
+        let body = LocalizedString("Change the pump reservoir now", comment: "The notification alert describing an empty pump reservoir")
+        let content = Alert.Content(title: title, body: body, acknowledgeActionButtonLabel: LocalizedString("Ok", comment: "Default alert dismissal"))
         return Alert(identifier: Self.pumpReservoirEmptyAlertIdentifier, foregroundContent: content, backgroundContent: content, trigger: .immediate)
     }
 
@@ -551,7 +554,7 @@ extension MinimedPumpManager {
     }
 
     private func pumpReservoirLowAlertForAmount(_ units: Double, andTimeRemaining remaining: TimeInterval?) -> Alert {
-        let title = NSLocalizedString("Pump Reservoir Low", comment: "The notification title for a low pump reservoir")
+        let title = LocalizedString("Pump Reservoir Low", comment: "The notification title for a low pump reservoir")
 
         let unitsString = NumberFormatter.localizedString(from: NSNumber(value: units), number: .decimal)
 
@@ -565,12 +568,12 @@ extension MinimedPumpManager {
         let body: String
 
         if let remaining = remaining, let timeString = intervalFormatter.string(from: remaining) {
-            body = String(format: NSLocalizedString("%1$@ U left: %2$@", comment: "Low reservoir alert with time remaining format string. (1: Number of units remaining)(2: approximate time remaining)"), unitsString, timeString)
+            body = String(format: LocalizedString("%1$@ U left: %2$@", comment: "Low reservoir alert with time remaining format string. (1: Number of units remaining)(2: approximate time remaining)"), unitsString, timeString)
         } else {
-            body = String(format: NSLocalizedString("%1$@ U left", comment: "Low reservoir alert format string. (1: Number of units remaining)"), unitsString)
+            body = String(format: LocalizedString("%1$@ U left", comment: "Low reservoir alert format string. (1: Number of units remaining)"), unitsString)
         }
 
-        let content = Alert.Content(title: title, body: body, acknowledgeActionButtonLabel: NSLocalizedString("Ok", comment: "Default alert dismissal"))
+        let content = Alert.Content(title: title, body: body, acknowledgeActionButtonLabel: LocalizedString("Ok", comment: "Default alert dismissal"))
         return Alert(identifier: Self.pumpReservoirLowAlertIdentifier, foregroundContent: content, backgroundContent: content, trigger: .immediate)
     }
 
@@ -696,19 +699,19 @@ extension MinimedPumpManager {
             }
             
             if var runningTempBasal = state.unfinalizedTempBasal {
-                // Look for following temp basal cancel event
+                // Look for following temp basal cancel event in pump history
                 if let tempBasalCancellation = result.remainingEvents.first(where: { (event) -> Bool in
                     if let dose = event.dose,
-                        dose.type == .tempBasal,
-                        dose.startDate > runningTempBasal.startTime,
-                        dose.startDate < runningTempBasal.finishTime,
-                        dose.unitsPerHour == 0
+                       dose.type == .tempBasal,
+                       dose.startDate > runningTempBasal.startTime,
+                       dose.startDate < runningTempBasal.finishTime,
+                       dose.startDate.timeIntervalSince(dose.endDate) == 0
                     {
                         return true
                     }
                     return false
                 }) {
-                    runningTempBasal.finishTime = tempBasalCancellation.date
+                    runningTempBasal.cancel(at: tempBasalCancellation.date, pumpModel: state.pumpModel)
                     state.unfinalizedTempBasal = runningTempBasal
                     state.suspendState = .resumed(tempBasalCancellation.date)
                 }
@@ -743,6 +746,7 @@ extension MinimedPumpManager {
                     }
 
                     // Include events up to a minute before startDate, since pump event time and pending event time might be off
+                    self.log.default("Fetching history since %{public}@", String(describing: startDate.addingTimeInterval(.minutes(-1))))
                     let (historyEvents, model) = try session.getHistoryEvents(since: startDate.addingTimeInterval(.minutes(-1)))
                     
                     // Reconcile history with pending doses
@@ -765,6 +769,8 @@ extension MinimedPumpManager {
                         
                         let pendingEvents = (self.state.pendingDoses + [self.state.unfinalizedBolus, self.state.unfinalizedTempBasal]).compactMap({ $0?.newPumpEvent() })
 
+                        self.log.default("Reporting new pump events: %{public}@", String(describing: remainingHistoryEvents + pendingEvents))
+
                         delegate.pumpManager(self, hasNewPumpEvents: remainingHistoryEvents + pendingEvents, lastReconciliation: self.state.lastReconciliation, completion: { (error) in
                             // Called on an unknown queue by the delegate
                             if error == nil {
@@ -1311,12 +1317,10 @@ extension MinimedPumpManager: PumpManager {
             let result = session.setTempBasal(unitsPerHour, duration: duration)
             
             switch result {
-            case .success(let response):
+            case .success:
                 let now = Date()
-                let endDate = now.addingTimeInterval(response.timeRemaining)
-                let startDate = endDate.addingTimeInterval(-duration)
 
-                let dose = UnfinalizedDose(tempBasalRate: unitsPerHour, startTime: startDate, duration: duration, insulinType: insulinType)
+                let dose = UnfinalizedDose(tempBasalRate: unitsPerHour, startTime: now, duration: duration, insulinType: insulinType, automatic: true)
                 
                 self.recents.tempBasalEngageState = .stable
                 
@@ -1325,14 +1329,14 @@ extension MinimedPumpManager: PumpManager {
                 // If we were successful, then we know we aren't suspended
                 self.setState({ (state) in
                     if case .suspended = state.suspendState {
-                        state.suspendState = .resumed(startDate)
+                        state.suspendState = .resumed(now)
                     } else if isResumingScheduledBasal {
-                        state.suspendState = .resumed(startDate)
+                        state.suspendState = .resumed(now)
                     }
                     
                     let pumpModel = state.pumpModel
                     
-                    state.unfinalizedTempBasal?.cancel(at: startDate, pumpModel: pumpModel)
+                    state.unfinalizedTempBasal?.cancel(at: now, pumpModel: pumpModel)
                     if let previousTempBasal = state.unfinalizedTempBasal {
                         state.pendingDoses.append(previousTempBasal)
                     }
@@ -1352,6 +1356,8 @@ extension MinimedPumpManager: PumpManager {
             case .failure(let error):
                 completion(.communication(error))
 
+                self.logDeviceCommunication("Set temp basal failed: \(error.localizedDescription)", type: .error)
+
                 // If we got a command-refused error, we might be suspended or bolusing, so update the state accordingly
                 if case .arguments(.pumpError(.commandRefused)) = error {
                     do {

+ 2 - 0
Dependencies/rileylink_ios/MinimedKit/PumpManager/MinimedPumpMessageSender.swift

@@ -82,6 +82,7 @@ struct MinimedPumpMessageSender: PumpMessageSender {
 
             guard response.messageType == responseType, let body = response.messageBody as? T else {
                 if let body = response.messageBody as? PumpErrorMessageBody {
+                    commsLogger?.didReceive(String(describing: response))
                     switch body.errorCode {
                     case .known(let code):
                         throw PumpOpsError.pumpError(code)
@@ -93,6 +94,7 @@ struct MinimedPumpMessageSender: PumpMessageSender {
                 }
             }
             commsLogger?.didReceive(String(describing: response))
+            usleep(200000) // 0.2s
             return body
         } catch {
             commsLogger?.didError(error.localizedDescription)

+ 9 - 27
Dependencies/rileylink_ios/MinimedKit/PumpManager/UnfinalizedDose.swift

@@ -145,13 +145,13 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti
     public var eventTitle: String {
         switch doseType {
         case .bolus:
-            return NSLocalizedString("Bolus", comment: "Pump Event title for UnfinalizedDose with doseType of .bolus")
+            return LocalizedString("Bolus", comment: "Pump Event title for UnfinalizedDose with doseType of .bolus")
         case .resume:
-            return NSLocalizedString("Resume", comment: "Pump Event title for UnfinalizedDose with doseType of .resume")
+            return LocalizedString("Resume", comment: "Pump Event title for UnfinalizedDose with doseType of .resume")
         case .suspend:
-            return NSLocalizedString("Suspend", comment: "Pump Event title for UnfinalizedDose with doseType of .suspend")
+            return LocalizedString("Suspend", comment: "Pump Event title for UnfinalizedDose with doseType of .suspend")
         case .tempBasal:
-            return NSLocalizedString("Temp Basal", comment: "Pump Event title for UnfinalizedDose with doseType of .tempBasal")
+            return LocalizedString("Temp Basal", comment: "Pump Event title for UnfinalizedDose with doseType of .tempBasal")
         }
     }
 
@@ -273,11 +273,6 @@ extension NewPumpEvent {
         let raw = dose.uuid.asRaw
         self.init(date: dose.startTime, dose: entry, raw: raw, title: dose.eventTitle)
     }
-    
-    func replacingAttributes(raw newRaw: Data, date newDate: Date) -> NewPumpEvent {
-        let newDose = dose?.replacingAttributes(startDate: newDate)
-        return NewPumpEvent(date: newDate, dose: newDose, raw: newRaw, title: title, type: type)
-    }
 }
 
 // MARK: - DoseEntry
@@ -286,28 +281,15 @@ extension DoseEntry {
     init (_ dose: UnfinalizedDose, forceFinalization: Bool = false) {
         switch dose.doseType {
         case .bolus:
-            self = DoseEntry(type: .bolus, startDate: dose.startTime, endDate: dose.finishTime, value: dose.programmedUnits ?? dose.units, unit: .units, deliveredUnits: dose.finalizedUnits, insulinType: dose.insulinType, automatic: dose.automatic, isMutable: (!dose.isFinished || !dose.isReconciledWithHistory) && !forceFinalization)
+            self = DoseEntry(type: .bolus, startDate: dose.startTime, endDate: dose.finishTime, value: dose.programmedUnits ?? dose.units, unit: .units, deliveredUnits: dose.finalizedUnits, insulinType: dose.insulinType, automatic: dose.automatic, isMutable: !dose.isReconciledWithHistory && !forceFinalization)
         case .tempBasal:
-            self = DoseEntry(type: .tempBasal, startDate: dose.startTime, endDate: dose.finishTime, value: dose.programmedTempRate ?? dose.rate, unit: .unitsPerHour, deliveredUnits: dose.finalizedUnits, insulinType: dose.insulinType, isMutable: (!dose.isFinished || !dose.isReconciledWithHistory) && !forceFinalization)
+            self = DoseEntry(type: .tempBasal, startDate: dose.startTime, endDate: dose.finishTime, value: dose.programmedTempRate ?? dose.rate, unit: .unitsPerHour, deliveredUnits: dose.finalizedUnits, insulinType: dose.insulinType, automatic: dose.automatic, isMutable: !dose.isReconciledWithHistory && !forceFinalization)
         case .suspend:
-            self = DoseEntry(suspendDate: dose.startTime)
+            self = DoseEntry(suspendDate: dose.startTime, isMutable: !dose.isReconciledWithHistory)
         case .resume:
-            self = DoseEntry(resumeDate: dose.startTime, insulinType: dose.insulinType)
-        }
-    }
-    
-    func replacingAttributes(startDate newStartDate: Date) -> DoseEntry {
-        let value: Double
-        switch unit {
-        case .units:
-            value = programmedUnits
-        case .unitsPerHour:
-            value = unitsPerHour
+            self = DoseEntry(resumeDate: dose.startTime, insulinType: dose.insulinType, isMutable: !dose.isReconciledWithHistory)
         }
-        let duration = endDate.timeIntervalSince(startDate)
-        let newEndDate = newStartDate.addingTimeInterval(duration)
-        return DoseEntry(type: type, startDate: newStartDate, endDate: newEndDate, value: value, unit: unit, deliveredUnits: deliveredUnits, description: description, syncIdentifier: syncIdentifier, insulinType: insulinType, isMutable: isMutable, wasProgrammedByPumpUI: wasProgrammedByPumpUI)
-    }
+    }    
 }
 
 extension Collection where Element == NewPumpEvent {

+ 16 - 0
Dependencies/rileylink_ios/MinimedKitTests/ReconciliationTests.swift

@@ -11,8 +11,24 @@ import RileyLinkBLEKit
 @testable import MinimedKit
 import LoopKit
 
+extension DateFormatter {
+    static var descriptionFormatter: DateFormatter {
+        let formatter = self.init()
+        formatter.dateFormat = "yyyy-MM-dd HH:mm:ssZZZZZ"
+
+        return formatter
+    }
+}
+
+
 final class ReconciliationTests: XCTestCase {
 
+    let testingDateFormatter = DateFormatter.descriptionFormatter
+
+    func testingDate(_ input: String) -> Date {
+        return testingDateFormatter.date(from: input)!
+    }
+
     func testPendingDoseUpdatesWithActualDeliveryFromHistoryDose() {
 
         let bolusTime = Date().addingTimeInterval(-TimeInterval(minutes: 5));

+ 4 - 4
Dependencies/rileylink_ios/OmniKit/OmnipodCommon/UnfinalizedDose.swift

@@ -202,13 +202,13 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti
     public var eventTitle: String {
         switch doseType {
         case .bolus:
-            return NSLocalizedString("Bolus", comment: "Pump Event title for UnfinalizedDose with doseType of .bolus")
+            return LocalizedString("Bolus", comment: "Pump Event title for UnfinalizedDose with doseType of .bolus")
         case .resume:
-            return NSLocalizedString("Resume", comment: "Pump Event title for UnfinalizedDose with doseType of .resume")
+            return LocalizedString("Resume", comment: "Pump Event title for UnfinalizedDose with doseType of .resume")
         case .suspend:
-            return NSLocalizedString("Suspend", comment: "Pump Event title for UnfinalizedDose with doseType of .suspend")
+            return LocalizedString("Suspend", comment: "Pump Event title for UnfinalizedDose with doseType of .suspend")
         case .tempBasal:
-            return NSLocalizedString("Temp Basal", comment: "Pump Event title for UnfinalizedDose with doseType of .tempBasal")
+            return LocalizedString("Temp Basal", comment: "Pump Event title for UnfinalizedDose with doseType of .tempBasal")
         }
     }
 

+ 8 - 8
Dependencies/rileylink_ios/OmniKit/PumpManager/OmnipodPumpManager.swift

@@ -318,7 +318,7 @@ extension OmnipodPumpManager {
     
     public func buildPumpStatusHighlight(for state: OmnipodPumpManagerState, andDate date: Date = Date()) -> PumpStatusHighlight? {
         if state.podState?.needsCommsRecovery == true {
-            return PumpStatusHighlight(localizedMessage: NSLocalizedString("Comms Issue", comment: "Status highlight that delivery is uncertain."),
+            return PumpStatusHighlight(localizedMessage: LocalizedString("Comms Issue", comment: "Status highlight that delivery is uncertain."),
                                                          imageName: "exclamationmark.circle.fill",
                                                          state: .critical)
         }
@@ -326,17 +326,17 @@ extension OmnipodPumpManager {
         switch podCommState(for: state) {
         case .activating:
             return PumpStatusHighlight(
-                localizedMessage: NSLocalizedString("Finish Pairing", comment: "Status highlight that when pod is activating."),
+                localizedMessage: LocalizedString("Finish Pairing", comment: "Status highlight that when pod is activating."),
                 imageName: "exclamationmark.circle.fill",
                 state: .warning)
         case .deactivating:
             return PumpStatusHighlight(
-                localizedMessage: NSLocalizedString("Finish Deactivation", comment: "Status highlight that when pod is deactivating."),
+                localizedMessage: LocalizedString("Finish Deactivation", comment: "Status highlight that when pod is deactivating."),
                 imageName: "exclamationmark.circle.fill",
                 state: .warning)
         case .noPod:
             return PumpStatusHighlight(
-                localizedMessage: NSLocalizedString("No Pod", comment: "Status highlight that when no pod is paired."),
+                localizedMessage: LocalizedString("No Pod", comment: "Status highlight that when no pod is paired."),
                 imageName: "exclamationmark.circle.fill",
                 state: .warning)
         case .fault(let detail):
@@ -358,22 +358,22 @@ extension OmnipodPumpManager {
         case .active:
             if let reservoirPercent = state.reservoirLevel?.percentage, reservoirPercent == 0 {
                 return PumpStatusHighlight(
-                    localizedMessage: NSLocalizedString("No Insulin", comment: "Status highlight that a pump is out of insulin."),
+                    localizedMessage: LocalizedString("No Insulin", comment: "Status highlight that a pump is out of insulin."),
                     imageName: "exclamationmark.circle.fill",
                     state: .critical)
             } else if state.podState?.isSuspended == true {
                 return PumpStatusHighlight(
-                    localizedMessage: NSLocalizedString("Insulin Suspended", comment: "Status highlight that insulin delivery was suspended."),
+                    localizedMessage: LocalizedString("Insulin Suspended", comment: "Status highlight that insulin delivery was suspended."),
                     imageName: "pause.circle.fill",
                     state: .warning)
             } else if date.timeIntervalSince(state.lastPumpDataReportDate ?? .distantPast) > .minutes(12) {
                 return PumpStatusHighlight(
-                    localizedMessage: NSLocalizedString("Signal Loss", comment: "Status highlight when communications with the pod haven't happened recently."),
+                    localizedMessage: LocalizedString("Signal Loss", comment: "Status highlight when communications with the pod haven't happened recently."),
                     imageName: "exclamationmark.circle.fill",
                     state: .critical)
             } else if isRunningManualTempBasal(for: state) {
                 return PumpStatusHighlight(
-                    localizedMessage: NSLocalizedString("Manual Basal", comment: "Status highlight when manual temp basal is running."),
+                    localizedMessage: LocalizedString("Manual Basal", comment: "Status highlight when manual temp basal is running."),
                     imageName: "exclamationmark.circle.fill",
                     state: .warning)
             }

+ 1 - 1
Dependencies/rileylink_ios/OmniKitUI/Views/DeliveryUncertaintyRecoveryView.swift

@@ -68,7 +68,7 @@ struct DeliveryUncertaintyRecoveryView: View {
                     self.model.podDeactivationChosen()
                 }) {
                     Text(LocalizedString("Deactivate Pod", comment: "Button title to deactive pod on uncertain program"))
-                    .actionButtonStyle()
+                    .actionButtonStyle(.destructive)
                     .padding()
                 }
             }

+ 2 - 2
Dependencies/rileylink_ios/OmniKitUI/Views/ManualTempBasalEntryView.swift

@@ -122,7 +122,7 @@ struct ManualTempBasalEntryView: View {
                 .buttonStyle(ActionButtonStyle(.primary))
                 .padding()
             }
-            .navigationTitle(NSLocalizedString("Temporary Basal", comment: "Navigation Title for ManualTempBasalEntryView"))
+            .navigationTitle(LocalizedString("Temporary Basal", comment: "Navigation Title for ManualTempBasalEntryView"))
             .navigationBarItems(trailing: cancelButton)
             .alert(isPresented: $showingErrorAlert, content: { errorAlert })
             .disabled(enacting)
@@ -153,7 +153,7 @@ struct ManualTempBasalEntryView: View {
 
 
     var cancelButton: some View {
-        Button(NSLocalizedString("Cancel", comment: "Cancel button text in navigation bar on insert cannula screen")) {
+        Button(LocalizedString("Cancel", comment: "Cancel button text in navigation bar on insert cannula screen")) {
             didCancel?()
         }
         .accessibility(identifier: "button_cancel")

+ 4 - 4
Dependencies/rileylink_ios/RileyLink/View Controllers/MainViewController.swift

@@ -32,7 +32,7 @@ class MainViewController: RileyLinkSettingsViewController {
 
         super.init(rileyLinkPumpManager: rileyLinkPumpManager, devicesSectionIndex: Section.rileyLinks.rawValue, style: .grouped)
         
-        self.title = NSLocalizedString("RileyLink Testing", comment: "Title for RileyLink Testing main view controller")
+        self.title = LocalizedString("RileyLink Testing", comment: "Title for RileyLink Testing main view controller")
     }
     
     required init?(coder aDecoder: NSCoder) {
@@ -136,13 +136,13 @@ class MainViewController: RileyLinkSettingsViewController {
                     let textButtonCell = cell as? TextButtonTableViewCell
                     textButtonCell?.isEnabled = shouldAllowAddingPump
                     textButtonCell?.isUserInteractionEnabled = shouldAllowAddingPump
-                    cell.textLabel?.text = NSLocalizedString("Add Minimed Pump", comment: "Title text for button to set up a new minimed pump")
+                    cell.textLabel?.text = LocalizedString("Add Minimed Pump", comment: "Title text for button to set up a new minimed pump")
                 case .setupOmnipod:
                     cell = tableView.dequeueReusableCell(withIdentifier: TextButtonTableViewCell.className, for: indexPath)
                     let textButtonCell = cell as? TextButtonTableViewCell
                     textButtonCell?.isEnabled = shouldAllowAddingPump
                     textButtonCell?.isUserInteractionEnabled = shouldAllowAddingPump
-                    cell.textLabel?.text = NSLocalizedString("Setup Omnipod", comment: "Title text for button to set up omnipod")
+                    cell.textLabel?.text = LocalizedString("Setup Omnipod", comment: "Title text for button to set up omnipod")
                 }
             }
         }
@@ -154,7 +154,7 @@ class MainViewController: RileyLinkSettingsViewController {
         case .rileyLinks:
             return super.tableView(tableView, titleForHeaderInSection: section)
         case .pump:
-            return NSLocalizedString("Pumps", comment: "Title text for section listing configured pumps")
+            return LocalizedString("Pumps", comment: "Title text for section listing configured pumps")
         }
     }
     

+ 0 - 99
Dependencies/rileylink_ios/RileyLinkBLEKit/RileyLinkConnectionManager.swift

@@ -1,99 +0,0 @@
-//
-//  RileyLinkConnectionManager.swift
-//  RileyLinkBLEKit
-//
-//  Created by Pete Schwamb on 8/16/18.
-//  Copyright © 2018 Pete Schwamb. All rights reserved.
-//
-
-import Foundation
-
-public protocol RileyLinkConnectionManagerDelegate : AnyObject {
-    func rileyLinkConnectionManager(_ rileyLinkConnectionManager: RileyLinkConnectionManager, didChange state: RileyLinkConnectionManagerState)
-}
-
-public class RileyLinkConnectionManager {
-    
-    public typealias RawStateValue = [String : Any]
-
-    /// The current, serializable state of the manager
-    public var rawState: RawStateValue {
-        return state.rawValue
-    }
-    
-    public private(set) var state: RileyLinkConnectionManagerState {
-        didSet {
-            delegate?.rileyLinkConnectionManager(self, didChange: state)
-        }
-    }
-
-    public var deviceProvider: RileyLinkDeviceProvider {
-        return rileyLinkDeviceManager
-    }
-    
-    public weak var delegate: RileyLinkConnectionManagerDelegate?
-    
-    private let rileyLinkDeviceManager: RileyLinkDeviceManager
-    
-    private var autoConnectIDs: Set<String> {
-        get {
-            return state.autoConnectIDs
-        }
-        set {
-            state.autoConnectIDs = newValue
-        }
-    }
-    
-    public init(state: RileyLinkConnectionManagerState) {
-        self.rileyLinkDeviceManager = RileyLinkDeviceManager(autoConnectIDs: state.autoConnectIDs)
-        self.state = state
-    }
-    
-    public init(autoConnectIDs: Set<String>) {
-        self.rileyLinkDeviceManager = RileyLinkDeviceManager(autoConnectIDs: autoConnectIDs)
-        self.state = RileyLinkConnectionManagerState(autoConnectIDs: autoConnectIDs)
-    }
-    
-    public convenience init?(rawValue: RawStateValue) {
-        if let state = RileyLinkConnectionManagerState(rawValue: rawValue) {
-            self.init(state: state)
-        } else {
-            return nil
-        }
-    }
-    
-    public var connectingCount: Int {
-        return self.autoConnectIDs.count
-    }
-    
-    public func shouldConnect(to deviceID: String) -> Bool {
-        return self.autoConnectIDs.contains(deviceID)
-    }
-    
-    public func connect(_ device: RileyLinkDevice) {
-        autoConnectIDs.insert(device.peripheralIdentifier.uuidString)
-        rileyLinkDeviceManager.connect(device)
-    }
-    
-    public func disconnect(_ device: RileyLinkDevice) {
-        autoConnectIDs.remove(device.peripheralIdentifier.uuidString)
-        rileyLinkDeviceManager.disconnect(device)
-    }
-
-    public func setScanningEnabled(_ enabled: Bool) {
-        rileyLinkDeviceManager.setScanningEnabled(enabled)
-    }
-}
-
-public protocol RileyLinkDeviceProvider: AnyObject {
-    func getDevices(_ completion: @escaping (_ devices: [RileyLinkDevice]) -> Void)
-    var idleListeningEnabled: Bool { get }
-    var timerTickEnabled: Bool { get set }
-    func deprioritize(_ device: RileyLinkDevice, completion: (() -> Void)?)
-    func assertIdleListening(forcingRestart: Bool)
-    var idleListeningState: RileyLinkDevice.IdleListeningState { get set }
-
-    var debugDescription: String { get }
-}
-
-extension RileyLinkDeviceManager: RileyLinkDeviceProvider {}

+ 0 - 39
Dependencies/rileylink_ios/RileyLinkBLEKit/RileyLinkConnectionManagerState.swift

@@ -1,39 +0,0 @@
-//
-//  RileyLinkConnectionManagerState.swift
-//  RileyLinkBLEKit
-//
-//  Created by Pete Schwamb on 8/21/18.
-//  Copyright © 2018 Pete Schwamb. All rights reserved.
-//
-
-import Foundation
-
-public struct RileyLinkConnectionManagerState: RawRepresentable, Equatable {
-    
-    public typealias RawValue = RileyLinkConnectionManager.RawStateValue
-    
-    public var autoConnectIDs: Set<String>
-
-    public init(autoConnectIDs: Set<String>) {
-        self.autoConnectIDs = autoConnectIDs
-    }
-    
-    public init?(rawValue: RileyLinkConnectionManager.RawStateValue) {
-        guard
-            let autoConnectIDs = rawValue["autoConnectIDs"] as? [String]
-            else {
-                return nil
-        }
-        
-        self.init(autoConnectIDs: Set(autoConnectIDs))
-    }
-    
-    public var rawValue: RawValue {
-        return [
-            "autoConnectIDs": Array(autoConnectIDs),
-        ]
-    }
-
-    
-    
-}

+ 0 - 338
Dependencies/rileylink_ios/RileyLinkBLEKit/RileyLinkDeviceManager.swift

@@ -1,338 +0,0 @@
-//
-//  RileyLinkDeviceManager.swift
-//  RileyLinkBLEKit
-//
-//  Copyright © 2017 Pete Schwamb. All rights reserved.
-//
-
-import CoreBluetooth
-import os.log
-import LoopKit
-
-
-public class RileyLinkDeviceManager: NSObject {
-    private let log = OSLog(category: "RileyLinkDeviceManager")
-
-    // Isolated to centralQueue
-    private var central: CBCentralManager!
-
-    private let centralQueue = DispatchQueue(label: "com.rileylink.RileyLinkBLEKit.BluetoothManager.centralQueue", qos: .unspecified)
-
-    internal let sessionQueue = DispatchQueue(label: "com.rileylink.RileyLinkBLEKit.RileyLinkDeviceManager.sessionQueue", qos: .unspecified)
-
-    // Isolated to centralQueue
-    private var devices: [RileyLinkDevice] = [] {
-        didSet {
-            NotificationCenter.default.post(name: .ManagerDevicesDidChange, object: self)
-        }
-    }
-
-    // Isolated to centralQueue
-    private var autoConnectIDs: Set<String>
-
-    // Isolated to centralQueue
-    private var isScanningEnabled = false
-
-    public init(autoConnectIDs: Set<String>) {
-        self.autoConnectIDs = autoConnectIDs
-
-        super.init()
-
-        centralQueue.sync {
-            central = CBCentralManager(
-                delegate: self,
-                queue: centralQueue,
-                options: [
-                    CBCentralManagerOptionRestoreIdentifierKey: "com.rileylink.CentralManager"
-                ]
-            )
-        }
-    }
-
-    // MARK: - Configuration
-
-    public var idleListeningEnabled: Bool {
-        if case .disabled = idleListeningState {
-            return false
-        } else {
-            return true
-        }
-    }
-
-    public var idleListeningState: RileyLinkDevice.IdleListeningState {
-        get {
-            return lockedIdleListeningState.value
-        }
-        set {
-            lockedIdleListeningState.value = newValue
-            centralQueue.async {
-                for device in self.devices {
-                    device.setIdleListeningState(newValue)
-                }
-            }
-        }
-    }
-    private let lockedIdleListeningState = Locked(RileyLinkDevice.IdleListeningState.disabled)
-
-    public var timerTickEnabled: Bool {
-        get {
-            return lockedTimerTickEnabled.value
-        }
-        set {
-            lockedTimerTickEnabled.value = newValue
-            centralQueue.async {
-                for device in self.devices {
-                    if device.peripheralState == .connected {
-                        device.setTimerTickEnabled(newValue)
-                    }
-                }
-            }
-        }
-    }
-    private let lockedTimerTickEnabled = Locked(true)
-}
-
-
-// MARK: - Connecting
-extension RileyLinkDeviceManager {
-    public func getAutoConnectIDs(_ completion: @escaping (_ autoConnectIDs: Set<String>) -> Void) {
-        centralQueue.async {
-            completion(self.autoConnectIDs)
-        }
-    }
-    
-    public func connect(_ device: RileyLinkDevice) {
-        centralQueue.async {
-            self.autoConnectIDs.insert(device.manager.peripheral.identifier.uuidString)
-
-            guard let peripheral = self.reloadPeripheral(for: device) else {
-                return
-            }
-
-            self.central.connectIfNecessary(peripheral)
-        }
-    }
-
-    public func disconnect(_ device: RileyLinkDevice) {
-        centralQueue.async {
-            self.autoConnectIDs.remove(device.manager.peripheral.identifier.uuidString)
-
-            guard let peripheral = self.reloadPeripheral(for: device) else {
-                return
-            }
-
-            self.central.cancelPeripheralConnectionIfNecessary(peripheral)
-        }
-    }
-
-    /// Asks the central manager for its peripheral instance for a given device.
-    /// It seems to be possible that this reference changes across a bluetooth reset, and not updating the reference can result in API MISUSE warnings
-    ///
-    /// - Parameter device: The device to reload
-    /// - Returns: The peripheral instance returned by the central manager
-    private func reloadPeripheral(for device: RileyLinkDevice) -> CBPeripheral? {
-        dispatchPrecondition(condition: .onQueue(centralQueue))
-
-        guard let peripheral = central.retrievePeripherals(withIdentifiers: [device.manager.peripheral.identifier]).first else {
-            return nil
-        }
-
-        device.manager.peripheral = peripheral
-        return peripheral
-    }
-
-    private var hasDiscoveredAllAutoConnectDevices: Bool {
-        dispatchPrecondition(condition: .onQueue(centralQueue))
-
-        return autoConnectIDs.isSubset(of: devices.map { $0.manager.peripheral.identifier.uuidString })
-    }
-
-    private func autoConnectDevices() {
-        dispatchPrecondition(condition: .onQueue(centralQueue))
-
-        for device in devices where autoConnectIDs.contains(device.manager.peripheral.identifier.uuidString) {
-            log.info("Attempting reconnect to %@", device.manager.peripheral)
-            connect(device)
-        }
-    }
-
-    private func addPeripheral(_ peripheral: CBPeripheral, rssi: Int? = nil) {
-        dispatchPrecondition(condition: .onQueue(centralQueue))
-
-        var device: RileyLinkDevice! = devices.first(where: { $0.manager.peripheral.identifier == peripheral.identifier })
-
-        if let device = device {
-            device.manager.peripheral = peripheral
-        } else {
-            device = RileyLinkDevice(peripheralManager: PeripheralManager(peripheral: peripheral, configuration: .rileyLink, centralManager: central, queue: sessionQueue), rssi: rssi)
-            if peripheral.state == .connected {
-                device.setTimerTickEnabled(timerTickEnabled)
-                device.setIdleListeningState(idleListeningState)
-            }
-
-            devices.append(device)
-
-            log.info("Created device for peripheral %@", peripheral)
-        }
-
-        if autoConnectIDs.contains(peripheral.identifier.uuidString) {
-            central.connectIfNecessary(peripheral)
-        }
-    }
-}
-
-
-extension RileyLinkDeviceManager {
-    public func getDevices(_ completion: @escaping (_ devices: [RileyLinkDevice]) -> Void) {
-        centralQueue.async {
-            completion(self.devices)
-        }
-    }
-
-    public func deprioritize(_ device: RileyLinkDevice, completion: (() -> Void)? = nil) {
-        centralQueue.async {
-            self.devices.deprioritize(device)
-            completion?()
-        }
-    }
-}
-
-extension Array where Element == RileyLinkDevice {
-    public var firstConnected: Element? {
-        return self.first { (device) -> Bool in
-            return device.manager.peripheral.state == .connected
-        }
-    }
-
-    mutating func deprioritize(_ element: Element) {
-        if let index = self.firstIndex(where: { $0 === element }) {
-            self.swapAt(index, self.count - 1)
-        }
-    }
-}
-
-
-// MARK: - Scanning
-extension RileyLinkDeviceManager {
-    public func setScanningEnabled(_ enabled: Bool) {
-        centralQueue.async {
-            self.isScanningEnabled = enabled
-
-            if case .poweredOn = self.central.state {
-                if enabled {
-                    self.central.scanForPeripherals()
-                } else if self.central.isScanning {
-                    self.central.stopScan()
-                }
-            }
-        }
-    }
-
-    public func assertIdleListening(forcingRestart: Bool) {
-        centralQueue.async {
-            for device in self.devices {
-                device.assertIdleListening(forceRestart: forcingRestart)
-            }
-        }
-    }
-}
-
-
-// MARK: - Delegate methods called on `centralQueue`
-extension RileyLinkDeviceManager: CBCentralManagerDelegate {
-    public func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
-        log.default("%@", #function)
-
-        guard let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] else {
-            return
-        }
-
-        for peripheral in peripherals {
-            addPeripheral(peripheral)
-        }
-    }
-
-    public func centralManagerDidUpdateState(_ central: CBCentralManager) {
-        log.default("%@: %@", #function, central.state.description)
-        if case .poweredOn = central.state {
-            autoConnectDevices()
-
-            if isScanningEnabled || !hasDiscoveredAllAutoConnectDevices {
-                central.scanForPeripherals()
-            } else if central.isScanning {
-                central.stopScan()
-            }
-        }
-
-        for device in devices {
-            device.centralManagerDidUpdateState(central)
-        }
-    }
-
-    public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
-        log.default("Discovered %@ at %@", peripheral, RSSI)
-
-        addPeripheral(peripheral, rssi: Int(truncating: RSSI))
-
-        // TODO: Should we keep scanning? There's no UI to remove a lost RileyLink, which could result in a battery drain due to indefinite scanning.
-        if !isScanningEnabled && central.isScanning && hasDiscoveredAllAutoConnectDevices {
-            log.default("All peripherals discovered")
-            central.stopScan()
-        }
-    }
-
-    public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
-        // Notify the device so it can begin configuration
-        for device in devices where device.manager.peripheral.identifier == peripheral.identifier {
-            device.centralManager(central, didConnect: peripheral)
-        }
-    }
-
-    public func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
-        for device in devices where device.manager.peripheral.identifier == peripheral.identifier {
-            device.centralManager(central, didDisconnectPeripheral: peripheral, error: error)
-        }
-
-        autoConnectDevices()
-    }
-
-    public func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
-        log.error("%@: %@: %@", #function, peripheral, String(describing: error))
-
-        for device in devices where device.manager.peripheral.identifier == peripheral.identifier {
-            device.centralManager(central, didFailToConnect: peripheral, error: error)
-        }
-
-        autoConnectDevices()
-    }
-}
-
-
-extension RileyLinkDeviceManager {
-    public override var debugDescription: String {
-        var report = [
-            "## RileyLinkDeviceManager",
-            "central: \(central!)",
-            "autoConnectIDs: \(autoConnectIDs)",
-            "timerTickEnabled: \(timerTickEnabled)",
-            "idleListeningState: \(idleListeningState)"
-        ]
-
-        for device in devices {
-            report.append(String(reflecting: device))
-            report.append("")
-        }
-
-        return report.joined(separator: "\n\n")
-    }
-}
-
-
-extension Notification.Name {
-    public static let ManagerDevicesDidChange = Notification.Name("com.rileylink.RileyLinkBLEKit.DevicesDidChange")
-}
-
-extension RileyLinkDeviceManager {
-    public static let autoConnectIDsStateKey = "com.rileylink.RileyLinkBLEKit.AutoConnectIDs"
-}
-

+ 48 - 54
Dependencies/rileylink_ios/RileyLinkKit/PumpOpsSession.swift

@@ -390,7 +390,7 @@ extension PumpOpsSession {
 // MARK: - Command messages
 extension PumpOpsSession {
     /// - Throws: `PumpCommandError` specifying the failure sequence
-    private func runCommandWithArguments<T: MessageBody>(_ message: PumpMessage, responseType: MessageType = .pumpAck) throws -> T {
+    private func runCommandWithArguments<T: MessageBody>(_ message: PumpMessage, responseType: MessageType = .pumpAck, retryCount: Int = 3) throws -> T {
         do {
             try wakeup()
 
@@ -401,7 +401,7 @@ extension PumpOpsSession {
         }
 
         do {
-            return try messageSender.getResponse(to: message, responseType: responseType, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow,  retryCount: 3)
+            return try messageSender.getResponse(to: message, responseType: responseType, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow,  retryCount: retryCount)
         } catch let error as PumpOpsError {
             throw PumpCommandError.arguments(error)
         }
@@ -455,61 +455,59 @@ extension PumpOpsSession {
     /// - Parameters:
     ///   - unitsPerHour: The new basal rate, in Units per hour
     ///   - duration: The duration of the rate
-    /// - Returns: A result containing the pump message body describing the new basal rate or an error
-    public func setTempBasal(_ unitsPerHour: Double, duration: TimeInterval) -> Result<ReadTempBasalCarelinkMessageBody,PumpCommandError> {
-        var lastError: PumpCommandError?
-        
+    /// - Returns:
+    ///   - .success: A bool that indicates if the dose was confirmed successful
+    ///   - .failure: An error describing why the command failed
+    public func setTempBasal(_ unitsPerHour: Double, duration: TimeInterval) -> Result<Bool,PumpCommandError> {
+
         let message = PumpMessage(settings: settings, type: .changeTempBasal, body: ChangeTempBasalCarelinkMessageBody(unitsPerHour: unitsPerHour, duration: duration))
 
-        for attempt in 1..<4 {
-            do {
-                do {
-                    try wakeup()
+        do {
+            try wakeup()
+        } catch {
+            // Certain failure, as we haven't sent actual command yet; wakeup failed
+            return .failure(.command(error as? PumpOpsError ?? PumpOpsError.rfCommsFailure(String(describing: error))))
+        }
 
-                    let shortMessage = PumpMessage(packetType: message.packetType, address: message.address.hexadecimalString, messageType: message.messageType, messageBody: CarelinkShortMessageBody())
-                    let _: PumpAckMessageBody = try messageSender.getResponse(to: shortMessage, responseType: .pumpAck, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow,  retryCount: 3)
-                } catch let error as PumpOpsError {
-                    throw PumpCommandError.command(error)
-                }
+        do {
+            let shortMessage = PumpMessage(packetType: message.packetType, address: message.address.hexadecimalString, messageType: message.messageType, messageBody: CarelinkShortMessageBody())
+            let _: PumpAckMessageBody = try messageSender.getResponse(to: shortMessage, responseType: .pumpAck, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow,  retryCount: 3)
+        } catch {
+            // Certain failure, as we haven't sent actual command yet; just preflight short command
+            return .failure(.command(error as? PumpOpsError ?? PumpOpsError.rfCommsFailure(String(describing: error))))
+        }
 
-                do {
-                    let _: PumpAckMessageBody = try messageSender.getResponse(to: message, responseType: .pumpAck, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow,  retryCount: 3)
-                } catch PumpOpsError.pumpError(let errorCode) {
-                    lastError = .arguments(.pumpError(errorCode))
-                    break  // Stop because we have a pump error response
-                } catch PumpOpsError.unknownPumpErrorCode(let errorCode) {
-                    lastError = .arguments(.unknownPumpErrorCode(errorCode))
-                    break  // Stop because we have a pump error response
-                } catch {
-                    // The pump does not ACK a successful temp basal. We'll check manually below if it was successful.
-                }
+        var uncertainFailureError: PumpCommandError?
 
-                let response: ReadTempBasalCarelinkMessageBody = try messageSender.getResponse(to: PumpMessage(settings: settings, type: .readTempBasal), responseType: .readTempBasal, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow,  retryCount: 3)
+        do {
+            let _: PumpAckMessageBody = try messageSender.getResponse(to: message, responseType: .pumpAck, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow,  retryCount: 1)
+            // Even in success case, we try to verify, below
+        } catch PumpOpsError.pumpError(let errorCode) {
+            return .failure(.arguments(.pumpError(errorCode)))
+        } catch PumpOpsError.unknownPumpErrorCode(let errorCode) {
+            return .failure(.arguments(.unknownPumpErrorCode(errorCode)))
+        } catch {
+            // Some pumps do not ACK a successful temp basal. Check manually to see if it was successful.
+            uncertainFailureError = .command(error as? PumpOpsError ?? PumpOpsError.rfCommsFailure(String(describing: error)))
+        }
 
-                if response.timeRemaining == duration && response.rateType == .absolute {
-                    return .success(response)
-                } else {
-                    return .failure(PumpCommandError.arguments(PumpOpsError.rfCommsFailure("Could not verify TempBasal on attempt \(attempt). ")))
+        do {
+            let response: ReadTempBasalCarelinkMessageBody = try messageSender.getResponse(to: PumpMessage(settings: settings, type: .readTempBasal), responseType: .readTempBasal, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow,  retryCount: 3)
+            // Duration is always whole minute values
+            if response.timeRemaining == duration && response.rateType == .absolute {
+                return .success(true)
+            } else {
+                // readTempBasal does not match what we attempted to command
+                if let failureError = uncertainFailureError {
+                    return .failure(failureError)
                 }
-            } catch let error as PumpCommandError {
-                lastError = error
-            } catch let error as PumpOpsError {
-                lastError = .command(error)
-            } catch {
-                lastError = .command(.noResponse(during: "Set temp basal"))
+                // successful readTempBasal shows no temp basal running, so we failed
+                return .failure(PumpCommandError.arguments(PumpOpsError.rfCommsFailure("Confirmed that temp basal failed, and ")))
             }
+        } catch {
+            // unsuccessful readTempBasal; assume command reached pump, but we're uncertain
+            return .success(false)
         }
-        
-        return .failure(lastError!)
-    }
-
-    public func readTempBasal() throws -> Double {
-        
-        try wakeup()
-        
-        let response: ReadTempBasalCarelinkMessageBody = try messageSender.getResponse(to: PumpMessage(settings: settings, type: .readTempBasal), responseType: .readTempBasal, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow,  retryCount: 3)
-        
-        return response.rate
     }
 
     /// Changes the pump's clock to the specified date components in the system time zone
@@ -556,7 +554,7 @@ extension PumpOpsSession {
     ///   - units: The number of units to deliver
     ///   - cancelExistingTemp: If true, additional pump commands will be issued to clear any running temp basal. Defaults to false.
     /// - Throws: SetBolusError describing the certainty of the underlying error
-    public func setNormalBolus(units: Double, cancelExistingTemp: Bool = false) throws {
+    public func setNormalBolus(units: Double) throws {
         let pumpModel: PumpModel
 
         try wakeup()
@@ -572,13 +570,9 @@ extension PumpOpsSession {
             throw PumpOpsError.pumpSuspended
         }
 
-        if cancelExistingTemp {
-            _ = setTempBasal(0, duration: 0)
-        }
-
         let message = PumpMessage(settings: settings, type: .bolus, body: BolusCarelinkMessageBody(units: units, insulinBitPackingScale: pumpModel.insulinBitPackingScale))
 
-        let _: PumpAckMessageBody = try runCommandWithArguments(message)
+        let _: PumpAckMessageBody = try runCommandWithArguments(message, retryCount: 0)
         return
     }
 

+ 10 - 10
Dependencies/rileylink_ios/RileyLinkKitUI/RileyLinkDeviceTableViewController.swift

@@ -568,17 +568,17 @@ public class RileyLinkDeviceTableViewController: UITableViewController {
                 cell.textLabel?.text = LocalizedString("Frequency", comment: "The title of the cell showing current rileylink frequency")
                 cell.setDetailFrequency(frequency, formatter: frequencyFormatter)
             case .battery:
-                cell.textLabel?.text = NSLocalizedString("Battery level", comment: "The title of the cell showing battery level")
+                cell.textLabel?.text = LocalizedString("Battery level", comment: "The title of the cell showing battery level")
                 cell.setDetailBatteryLevel(battery)
             case .voltage:
-                cell.textLabel?.text = NSLocalizedString("Voltage", comment: "The title of the cell showing ORL")
+                cell.textLabel?.text = LocalizedString("Voltage", comment: "The title of the cell showing ORL")
                 cell.setVoltage(voltage)
             }
         case .alert:
             switch AlertRow(rawValue: indexPath.row)! {
             case .battery:
                 cell.accessoryType = .disclosureIndicator
-                cell.textLabel?.text = NSLocalizedString("Low Battery Alert", comment: "The title of the cell showing battery level")
+                cell.textLabel?.text = LocalizedString("Low Battery Alert", comment: "The title of the cell showing battery level")
                 cell.setBatteryAlert(batteryAlertLevel, formatter: integerFormatter)
             }
         case .rileyLinkCommands:
@@ -598,19 +598,19 @@ public class RileyLinkDeviceTableViewController: UITableViewController {
                 switchView.isHidden = false
                 cell.accessoryType = .none
                 switchView.isOn = yellowOn
-                cell.textLabel?.text = NSLocalizedString("Lighten Yellow LED", comment: "The title of the cell showing Lighten Yellow LED")
+                cell.textLabel?.text = LocalizedString("Lighten Yellow LED", comment: "The title of the cell showing Lighten Yellow LED")
             case .red:
                 switchView.isHidden = false
                 cell.accessoryType = .none
                 switchView.isOn = redOn
-                cell.textLabel?.text = NSLocalizedString("Lighten Red LED", comment: "The title of the cell showing Lighten Red LED")
+                cell.textLabel?.text = LocalizedString("Lighten Red LED", comment: "The title of the cell showing Lighten Red LED")
             case .shake:
                 switchView.isHidden = false
                 switchView.isOn = shakeOn
                 cell.accessoryType = .none
-                cell.textLabel?.text = NSLocalizedString("Test Vibration", comment: "The title of the cell showing Test Vibration")
+                cell.textLabel?.text = LocalizedString("Test Vibration", comment: "The title of the cell showing Test Vibration")
             case .findDevice:
-                cell.textLabel?.text = NSLocalizedString("Find Device", comment: "The title of the cell for sounding device finding piezo")
+                cell.textLabel?.text = LocalizedString("Find Device", comment: "The title of the cell for sounding device finding piezo")
                 cell.detailTextLabel?.text = nil
             }
         case .configureCommand:
@@ -619,12 +619,12 @@ public class RileyLinkDeviceTableViewController: UITableViewController {
                 switchView.isHidden = false
                 switchView.isOn = ledOn
                 cell.accessoryType = .none
-                cell.textLabel?.text = NSLocalizedString("Connection LED", comment: "The title of the cell for connection LED")
+                cell.textLabel?.text = LocalizedString("Connection LED", comment: "The title of the cell for connection LED")
             case .connectionVibrate:
                 switchView.isHidden = false
                 switchView.isOn = vibrationOn
                 cell.accessoryType = .none
-                cell.textLabel?.text = NSLocalizedString("Connection Vibration", comment: "The title of the cell for connection vibration")
+                cell.textLabel?.text = LocalizedString("Connection Vibration", comment: "The title of the cell for connection vibration")
             }
         }
 
@@ -841,6 +841,6 @@ private extension UITableViewCell {
     }
     
     func setBatteryAlert(_ level: Int?, formatter: NumberFormatter) {
-        detailTextLabel?.text = formatter.percentString(from: level) ?? NSLocalizedString("Off", comment: "Detail text when battery alert disabled.")
+        detailTextLabel?.text = formatter.percentString(from: level) ?? LocalizedString("Off", comment: "Detail text when battery alert disabled.")
     }
 }