Jelajahi Sumber

Merge branch 'avouspierre/dash_dev' into Crowdin

Jon B.M 3 tahun lalu
induk
melakukan
aab94a963f
100 mengubah file dengan 1734 tambahan dan 766 penghapusan
  1. 20 0
      Dependencies/LibreTransmitter/Sources/LibreTransmitter/ConcreteGlucoseDisplayable.swift
  2. 12 0
      Dependencies/LoopKit/LoopKit.xcodeproj/project.pbxproj
  3. 8 3
      Dependencies/LoopKit/LoopKit/DeviceManager/PumpManager.swift
  4. 3 3
      Dependencies/LoopKit/LoopKit/Insulin/ExponentialInsulinModelPreset.swift
  5. 3 1
      Dependencies/LoopKit/LoopKit/InsulinKit/CachedInsulinDeliveryObject+CoreDataClass.swift
  6. 4 4
      Dependencies/LoopKit/LoopKit/InsulinKit/DoseEntry.swift
  7. 9 4
      Dependencies/LoopKit/LoopKit/InsulinKit/DoseStore.swift
  8. 22 25
      Dependencies/LoopKit/LoopKit/InsulinKit/InsulinMath.swift
  9. 1 1
      Dependencies/LoopKit/LoopKit/QuantityFormatter.swift
  10. 1 2
      Dependencies/LoopKit/LoopKit/TherapySettings.swift
  11. 63 6
      Dependencies/LoopKit/LoopKitTests/DoseStoreTests.swift
  12. 282 0
      Dependencies/LoopKit/LoopKitTests/Fixtures/InsulinKit/reservoir_iob_test.json
  13. 2 2
      Dependencies/LoopKit/LoopKitTests/HKUnitTests.swift
  14. 13 12
      Dependencies/LoopKit/LoopKitUI/CarbKit/FoodEmojiDataSource.swift
  15. 5 0
      Dependencies/LoopKit/LoopKitUI/View Controllers/EmojiInputController.swift
  16. 3 0
      Dependencies/LoopKit/LoopKitUI/ViewModels/TherapySettingsViewModel.swift
  17. 5 1
      Dependencies/LoopKit/LoopKitUI/Views/Information Screens/InformationView.swift
  18. 16 2
      Dependencies/LoopKit/LoopKitUI/Views/ScheduleEditor.swift
  19. 8 1
      Dependencies/LoopKit/LoopKitUI/Views/Settings Editors/CorrectionRangeOverridesEditor.swift
  20. 8 2
      Dependencies/LoopKit/LoopKitUI/Views/Settings Editors/TherapySettingsView.swift
  21. 0 64
      Dependencies/LoopKit/LoopkitPatch.txt
  22. 1 5
      Dependencies/LoopKit/MockKit/MockCGMManager.swift
  23. 1 1
      Dependencies/LoopKit/MockKit/MockPumpManager.swift
  24. 5 1
      Dependencies/LoopKit/MockKitUI/Views/DeliveryUncertaintyRecoveryView.swift
  25. 5 5
      Dependencies/OmniBLE/OmniBLE/Bluetooth/Pair/LTKExchanger.swift
  26. 2 2
      Dependencies/OmniBLE/OmniBLE/Bluetooth/PeripheralManager+OmniBLE.swift
  27. 3 3
      Dependencies/OmniBLE/OmniBLE/Bluetooth/Session/SessionEstablisher.swift
  28. 14 2
      Dependencies/OmniBLE/OmniBLE/OmnipodCommon/UnfinalizedDose.swift
  29. 3 3
      Dependencies/OmniBLE/OmniBLE/PumpManager/MessageTransport.swift
  30. 2 8
      Dependencies/OmniBLE/OmniBLE/PumpManager/OmniBLEPumpManager.swift
  31. 1 1
      Dependencies/OmniBLE/OmniBLE/PumpManager/PodState.swift
  32. 1 1
      Dependencies/OmniBLE/OmniBLE/PumpManagerUI/ViewControllers/DashUICoordinator.swift
  33. 1 1
      Dependencies/OmniBLE/OmniBLE/PumpManagerUI/Views/DeliveryUncertaintyRecoveryView.swift
  34. 1 0
      Dependencies/OmniBLE/OmniBLE/PumpManagerUI/Views/ExpirationReminderSetupView.swift
  35. 7 5
      Dependencies/OmniBLE/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift
  36. 19 8
      Dependencies/rileylink_ios/MinimedKit/Messages/CarelinkMessageBody.swift
  37. 16 3
      Dependencies/rileylink_ios/MinimedKit/Messages/ChangeTempBasalCarelinkMessageBody.swift
  38. 5 13
      Dependencies/rileylink_ios/MinimedKit/Messages/DeviceLinkMessageBody.swift
  39. 5 2
      Dependencies/rileylink_ios/MinimedKit/Messages/FindDeviceMessageBody.swift
  40. 9 8
      Dependencies/rileylink_ios/MinimedKit/Messages/GetBatteryCarelinkMessageBody.swift
  41. 9 5
      Dependencies/rileylink_ios/MinimedKit/Messages/GetPumpModelCarelinkMessageBody.swift
  42. 4 3
      Dependencies/rileylink_ios/MinimedKit/Messages/MessageBody.swift
  43. 2 4
      Dependencies/rileylink_ios/MinimedKit/Messages/MessageType.swift
  44. 4 0
      Dependencies/rileylink_ios/MinimedKit/Messages/MeterMessage.swift
  45. 5 0
      Dependencies/rileylink_ios/MinimedKit/Messages/MySentryAckMessageBody.swift
  46. 5 1
      Dependencies/rileylink_ios/MinimedKit/Messages/MySentryAlertClearedMessageBody.swift
  47. 5 1
      Dependencies/rileylink_ios/MinimedKit/Messages/MySentryAlertMessageBody.swift
  48. 5 1
      Dependencies/rileylink_ios/MinimedKit/Messages/MySentryPumpStatusMessageBody.swift
  49. 11 6
      Dependencies/rileylink_ios/MinimedKit/Messages/PowerOnCarelinkMessageBody.swift
  50. 6 1
      Dependencies/rileylink_ios/MinimedKit/Messages/PumpAckMessageBody.swift
  51. 5 1
      Dependencies/rileylink_ios/MinimedKit/Messages/PumpErrorMessageBody.swift
  52. 2 2
      Dependencies/rileylink_ios/MinimedKit/Messages/PumpMessage.swift
  53. 8 6
      Dependencies/rileylink_ios/MinimedKit/Messages/ReadPumpStatusMessageBody.swift
  54. 6 0
      Dependencies/rileylink_ios/MinimedKit/Messages/ReadRemainingInsulinMessageBody.swift
  55. 8 11
      Dependencies/rileylink_ios/MinimedKit/Messages/ReadSettingsCarelinkMessageBody.swift
  56. 4 0
      Dependencies/rileylink_ios/MinimedKit/Messages/ReadTempBasalCarelinkMessageBody.swift
  57. 4 0
      Dependencies/rileylink_ios/MinimedKit/Messages/ReadTimeCarelinkMessageBody.swift
  58. 15 7
      Dependencies/rileylink_ios/MinimedKit/Messages/SuspendResumeMessageBody.swift
  59. 5 1
      Dependencies/rileylink_ios/MinimedKit/Messages/UnknownMessageBody.swift
  60. 27 0
      Dependencies/rileylink_ios/MinimedKit/PumpEvents/PumpAlarmPumpEvent.swift
  61. 0 11
      Dependencies/rileylink_ios/MinimedKit/PumpManager/CommandSession.swift
  62. 42 2
      Dependencies/rileylink_ios/MinimedKit/PumpManager/DoseStore.swift
  63. 88 56
      Dependencies/rileylink_ios/MinimedKit/PumpManager/MinimedPumpManager.swift
  64. 5 0
      Dependencies/rileylink_ios/MinimedKit/PumpManager/MinimedPumpManagerError.swift
  65. 14 12
      Dependencies/rileylink_ios/MinimedKit/PumpManager/MinimedPumpManagerState.swift
  66. 166 0
      Dependencies/rileylink_ios/MinimedKit/PumpManager/MinimedPumpMessageSender.swift
  67. 11 98
      Dependencies/rileylink_ios/MinimedKit/PumpManager/PumpMessageSender.swift
  68. 34 29
      Dependencies/rileylink_ios/MinimedKit/PumpManager/PumpOps.swift
  69. 2 31
      Dependencies/rileylink_ios/MinimedKit/PumpManager/PumpOpsError.swift
  70. 1 1
      Dependencies/rileylink_ios/MinimedKit/PumpManager/RileyLinkDevice.swift
  71. 14 32
      Dependencies/rileylink_ios/MinimedKit/PumpManager/UnfinalizedDose.swift
  72. 34 100
      Dependencies/rileylink_ios/MinimedKitTests/MinimedPumpManagerTests.swift
  73. 62 0
      Dependencies/rileylink_ios/MinimedKitTests/Mocks/MockPumpManagerDelegate.swift
  74. 35 0
      Dependencies/rileylink_ios/MinimedKitTests/Mocks/MockPumpOps.swift
  75. 75 0
      Dependencies/rileylink_ios/MinimedKitTests/Mocks/MockRileyLinkDevice.swift
  76. 55 0
      Dependencies/rileylink_ios/MinimedKitTests/Mocks/MockRileyLinkProvider.swift
  77. 10 10
      Dependencies/rileylink_ios/MinimedKitTests/PumpOpsSynchronousBuildFromFramesTests.swift
  78. 86 11
      Dependencies/rileylink_ios/MinimedKitTests/PumpOpsSynchronousTests.swift
  79. 134 0
      Dependencies/rileylink_ios/MinimedKitTests/ReconciliationTests.swift
  80. 5 0
      Dependencies/rileylink_ios/MinimedKitTests/TimestampedHistoryEventTests.swift
  81. 1 1
      Dependencies/rileylink_ios/MinimedKitUI/CommandResponseViewController.swift
  82. 3 2
      Dependencies/rileylink_ios/MinimedKitUI/MinimedPumpManager+UI.swift
  83. 9 6
      Dependencies/rileylink_ios/MinimedKitUI/MinimedPumpSettingsViewController.swift
  84. 14 8
      Dependencies/rileylink_ios/MinimedKitUI/Setup/MinimedPumpIDSetupViewController.swift
  85. 3 1
      Dependencies/rileylink_ios/MinimedKitUI/Setup/MinimedPumpManagerSetupViewController.swift
  86. 1 1
      Dependencies/rileylink_ios/MinimedKitUI/Setup/MinimedPumpSentrySetupViewController.swift
  87. 3 3
      Dependencies/rileylink_ios/OmniKit/OmnipodCommon/MessageBlocks/ErrorResponse.swift
  88. 8 8
      Dependencies/rileylink_ios/OmniKit/OmnipodCommon/MessageBlocks/VersionResponse.swift
  89. 14 2
      Dependencies/rileylink_ios/OmniKit/OmnipodCommon/UnfinalizedDose.swift
  90. 34 37
      Dependencies/rileylink_ios/OmniKit/PumpManager/OmnipodPumpManager.swift
  91. 6 6
      Dependencies/rileylink_ios/OmniKit/PumpManager/OmnipodPumpManagerState.swift
  92. 7 7
      Dependencies/rileylink_ios/OmniKit/PumpManager/PodComms.swift
  93. 3 3
      Dependencies/rileylink_ios/OmniKit/PumpManager/PodState.swift
  94. 10 10
      Dependencies/rileylink_ios/OmniKitTests/MessageTests.swift
  95. 1 1
      Dependencies/rileylink_ios/OmniKitTests/PodCommsSessionTests.swift
  96. 4 4
      Dependencies/rileylink_ios/OmniKitTests/PodStateTests.swift
  97. 7 7
      Dependencies/rileylink_ios/OmniKitUI/ViewControllers/OmnipodUICoordinator.swift
  98. 1 1
      Dependencies/rileylink_ios/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift
  99. 3 10
      Dependencies/rileylink_ios/OmniKitUI/ViewModels/RileyLinkListDataSource.swift
  100. 0 0
      Dependencies/rileylink_ios/OmniKitUI/Views/DeliveryUncertaintyRecoveryView.swift

+ 20 - 0
Dependencies/LibreTransmitter/Sources/LibreTransmitter/ConcreteGlucoseDisplayable.swift

@@ -95,6 +95,26 @@ public enum GlucoseTrend: Int, CaseIterable {
             return LocalizedString("Falling very fast", comment: "Glucose trend down-down-down")
         }
     }
+    
+    public var direction: String {
+        switch self {
+        case .upUpUp:
+            return "DoubleUp"
+        case .upUp:
+            return "SingleUp"
+        case .up:
+            return "FortyFiveUp"
+        case .flat:
+            return "Flat"
+        case .down:
+            return "FortyFiveDown"
+        case .downDown:
+            return "SingleDown"
+        case .downDownDown:
+            return "DoubleDown"
+        }
+    }
+
 }
 
 public protocol GlucoseDisplayable {

+ 12 - 0
Dependencies/LoopKit/LoopKit.xcodeproj/project.pbxproj

@@ -788,6 +788,7 @@
 		C1F8403923DB84B700673141 /* DeviceLogEntry+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F8403723DB84B700673141 /* DeviceLogEntry+CoreDataClass.swift */; };
 		C1F8403A23DB84B700673141 /* DeviceLogEntry+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F8403823DB84B700673141 /* DeviceLogEntry+CoreDataProperties.swift */; };
 		C1F8B1E2223C3CC000DD66CF /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4303C4901E2D664200ADEDC8 /* TimeZone.swift */; };
+		C1FAC06328C7B0A100754AE2 /* reservoir_iob_test.json in Resources */ = {isa = PBXBuildFile; fileRef = C1FAC06228C7B0A100754AE2 /* reservoir_iob_test.json */; };
 		C1FAEC1D264AD6B400A3250B /* DeviceStatusBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FAEC1C264AD6B400A3250B /* DeviceStatusBadge.swift */; };
 		C1FAEC1F264AE12700A3250B /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B47ECF8725DC20810024A54D /* UIImage.swift */; };
 		C1FAEC21264AEEA300A3250B /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FAEC20264AEEA300A3250B /* UIImage.swift */; };
@@ -1648,6 +1649,7 @@
 		C1E84B8425C62FB100623C08 /* Modelv1v4.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = Modelv1v4.xcmappingmodel; sourceTree = "<group>"; };
 		C1F8403723DB84B700673141 /* DeviceLogEntry+CoreDataClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DeviceLogEntry+CoreDataClass.swift"; sourceTree = "<group>"; };
 		C1F8403823DB84B700673141 /* DeviceLogEntry+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DeviceLogEntry+CoreDataProperties.swift"; sourceTree = "<group>"; };
+		C1FAC06228C7B0A100754AE2 /* reservoir_iob_test.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = reservoir_iob_test.json; sourceTree = "<group>"; };
 		C1FAEC1C264AD6B400A3250B /* DeviceStatusBadge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceStatusBadge.swift; sourceTree = "<group>"; };
 		C1FAEC20264AEEA300A3250B /* UIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImage.swift; sourceTree = "<group>"; };
 		E9077D2624ACD59F0066A88D /* InformationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InformationView.swift; sourceTree = "<group>"; };
@@ -2284,6 +2286,7 @@
 		43D8FDC11C728FDF0073BE78 = {
 			isa = PBXGroup;
 			children = (
+				E9D95F9C24C8BC880079F47D /* Scripts */,
 				1F5DAB1B2118C91C00048054 /* Common */,
 				430059211CCDC7A200C861EA /* Extensions */,
 				43D8FDCD1C728FDF0073BE78 /* LoopKit */,
@@ -2621,6 +2624,7 @@
 				434FB6471D70096A007B9C70 /* reservoir_history_with_continuity_holes.json */,
 				43D8FED11C7294B80073BE78 /* reservoir_history_with_rewind_and_prime_input.json */,
 				43D8FED21C7294B80073BE78 /* reservoir_history_with_rewind_and_prime_output.json */,
+				C1FAC06228C7B0A100754AE2 /* reservoir_iob_test.json */,
 				43B99B031C74538D00D050F5 /* short_basal_dose.json */,
 				4378B6441ED55F8C000AE785 /* suspend_dose_reconciled_normalized_iob.json */,
 				4378B6451ED55F8C000AE785 /* suspend_dose_reconciled_normalized.json */,
@@ -2922,6 +2926,13 @@
 			path = "Settings Editors";
 			sourceTree = "<group>";
 		};
+		E9D95F9C24C8BC880079F47D /* Scripts */ = {
+			isa = PBXGroup;
+			children = (
+			);
+			path = Scripts;
+			sourceTree = "<group>";
+		};
 /* End PBXGroup section */
 
 /* Begin PBXHeadersBuildPhase section */
@@ -3395,6 +3406,7 @@
 				4322B799202FA3CC0002837D /* grouped_by_overlapping_absorption_times_output.json in Resources */,
 				435D292D205F48750026F401 /* counteraction_effect_falling_glucose_insulin.json in Resources */,
 				437874D3202FDD2D00A3D8B9 /* short_basal_dose.json in Resources */,
+				C1FAC06328C7B0A100754AE2 /* reservoir_iob_test.json in Resources */,
 				437874C6202FDD2D00A3D8B9 /* iob_from_doses_output.json in Resources */,
 				437874D6202FDD2D00A3D8B9 /* suspend_dose_reconciled.json in Resources */,
 				4343951F205EED1F0056DC37 /* counteraction_effect_falling_glucose_output.json in Resources */,

+ 8 - 3
Dependencies/LoopKit/LoopKit/DeviceManager/PumpManager.swift

@@ -54,8 +54,13 @@ public protocol PumpManagerDelegate: DeviceManagerDelegate, PumpManagerStatusObs
     /// Reports an error that should be surfaced to the user
     func pumpManager(_ pumpManager: PumpManager, didError error: PumpManagerError)
 
-    /// This should be called any time the PumpManager synchronizes with the pump, even if there are no new events in the log.
-    func pumpManager(_ pumpManager: PumpManager, hasNewPumpEvents events: [NewPumpEvent], lastSync: Date?, completion: @escaping (_ error: Error?) -> Void)
+    /// This should be called any time the PumpManager synchronizes with the pump, even if there are no new doses in the log, as changes to lastReconciliation
+    /// indicate we can trust insulin delivery status up until that point, even if there are no new doses.
+    /// For pumps whose only source of dosing adjustments is Loop, lastReconciliation should be reflective of the last time we received telemetry from the pump.
+    /// For pumps with a user interface and dosing history capabilities, lastReconciliation should be reflective of the last time we reconciled fully with pump history, and know
+    /// that we have accounted for any external doses. It is possible for the pump to report reservoir data beyond the date of lastReconciliation, and Loop can use it for
+    /// calculating IOB.
+    func pumpManager(_ pumpManager: PumpManager, hasNewPumpEvents events: [NewPumpEvent], lastReconciliation: Date?, completion: @escaping (_ error: Error?) -> Void)
 
     func pumpManager(_ pumpManager: PumpManager, didReadReservoirValue units: Double, at date: Date, completion: @escaping (_ result: Result<(newValue: ReservoirValue, lastValue: ReservoirValue?, areStoredValuesContinuous: Bool), Error>) -> Void)
 
@@ -129,7 +134,7 @@ public protocol PumpManager: DeviceManager {
     /// The maximum reservoir volume of the pump
     var pumpReservoirCapacity: Double { get }
 
-    /// The time of the last sync with the pump's event history, or last status check if pump does not provide history.
+    /// The time of the last sync with the pump's event history, or reservoir,  or last status check if pump does not provide history.
     var lastSync: Date? { get }
     
     /// The most-recent status

+ 3 - 3
Dependencies/LoopKit/LoopKit/Insulin/ExponentialInsulinModelPreset.swift

@@ -25,7 +25,7 @@ extension ExponentialInsulinModelPreset {
         case .fiasp:
             return .minutes(360)
         case .lyumjev:
-            return .minutes(300) // 360
+            return .minutes(360)
         case .afrezza:
             return .minutes(300)
         }
@@ -40,7 +40,7 @@ extension ExponentialInsulinModelPreset {
         case .fiasp:
             return .minutes(55)
         case .lyumjev:
-            return .minutes(60) //55
+            return .minutes(55)
         case.afrezza:
             return .minutes(29)
         }
@@ -55,7 +55,7 @@ extension ExponentialInsulinModelPreset {
         case .fiasp:
             return .minutes(10)
         case .lyumjev:
-            return .minutes(5) //10
+            return .minutes(10)
         case.afrezza:
             return .minutes(10)
         }

+ 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

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

@@ -584,7 +584,9 @@ extension DoseStore {
                 throw DoseStoreError.configurationError
             }
 
-            let doses = try self.getReservoirObjects(since: start).reversed().doseEntries
+            // Attempt to get the reading before "start", so we can build those doses that have an end date after "start", but a start date before "start"
+            // Any extra doses will be filtered out below, via filterDateRange
+            let doses = try self.getReservoirObjects(since: start.addingTimeInterval(-.minutes(10))).reversed().doseEntries
 
             let normalizedDoses = doses.annotated(with: basalProfile)
             self.recentReservoirNormalizedDoseEntriesCache = normalizedDoses
@@ -1214,8 +1216,9 @@ extension DoseStore {
                         if self.areReservoirValuesValid, let reservoirEndDate = self.lastStoredReservoirValue?.startDate, reservoirEndDate > self.lastPumpEventsReconciliation ?? .distantPast {
                             let reservoirDoses = try self.getNormalizedReservoirDoseEntries(start: filteredStart, end: end)
                             let endOfReservoirData = self.lastStoredReservoirValue?.endDate ?? .distantPast
+                            let startOfReservoirData = reservoirDoses.first?.startDate ?? filteredStart
                             let mutableDoses = try self.getNormalizedMutablePumpEventDoseEntries(start: endOfReservoirData)
-                            doses = insulinDeliveryDoses + reservoirDoses.map({ $0.trimmed(from: filteredStart) }) + mutableDoses
+                            doses = insulinDeliveryDoses.map({ $0.trimmed(to: startOfReservoirData) }) + reservoirDoses + mutableDoses.map({ $0.trimmed(from: endOfReservoirData) })
                         } else {
                             // Includes mutable doses.
                             doses = insulinDeliveryDoses.appendedUnion(with: try self.getNormalizedPumpEventDoseEntries(start: filteredStart, end: end))
@@ -1385,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")
 
@@ -1400,7 +1405,7 @@ extension DoseStore {
                     }
                 }
 
-                self.getPumpEventValues(since: Date.distantPast) { (result) in
+                self.getPumpEventValues(since: historyStart) { (result) in
                     report.append("")
                     report.append("### getPumpEventValues")
 

+ 22 - 25
Dependencies/LoopKit/LoopKit/InsulinKit/InsulinMath.swift

@@ -40,18 +40,10 @@ 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 modifiednetBasalUnits * model.percentEffectRemaining(at: time)
+            return netBasalUnits * model.percentEffectRemaining(at: time)
         } else {
-            return modifiednetBasalUnits * continuousDeliveryInsulinOnBoard(at: date, model: model, delta: delta)
+            return netBasalUnits * continuousDeliveryInsulinOnBoard(at: date, model: model, delta: delta)
         }
     }
 
@@ -85,21 +77,11 @@ extension DoseEntry {
         }
 
         // Consider doses within the delta time window as momentary
-        //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)
-            }
+        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)
+        }
     }
 
     func trimmed(from start: Date? = nil, to end: Date? = nil, syncIdentifier: String? = nil) -> DoseEntry {
@@ -400,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

+ 1 - 1
Dependencies/LoopKit/LoopKit/QuantityFormatter.swift

@@ -179,7 +179,7 @@ public extension HKUnit {
     var pickerFractionDigits: Int {
         switch self {
         case .internationalUnit(), .internationalUnitsPerHour:
-            return 2
+            return 3
         case HKUnit.gram().unitDivided(by: .internationalUnit()):
             return 1
         case .millimolesPerLiter,

+ 1 - 2
Dependencies/LoopKit/LoopKit/TherapySettings.swift

@@ -40,8 +40,7 @@ public struct TherapySettings: Equatable {
             suspendThreshold != nil &&
             insulinSensitivitySchedule != nil &&
             carbRatioSchedule != nil &&
-            basalRateSchedule != nil &&
-            defaultRapidActingModel != nil
+            basalRateSchedule != nil
     }
     
     public init(

+ 63 - 6
Dependencies/LoopKit/LoopKitTests/DoseStoreTests.swift

@@ -12,11 +12,19 @@ import HealthKit
 
 class DoseStoreTests: PersistenceControllerTestCase {
 
-    func testEmptyDoseStoreReturnsZeroInsulinOnBoard() {
-        // 1. Create a DoseStore
-        let healthStore = HKHealthStoreMock()
+    func loadReservoirFixture(_ resourceName: String) -> [NewReservoirValue] {
 
-        let doseStore = DoseStore(
+        let fixture: [JSONDictionary] = loadFixture(resourceName)
+        let dateFormatter = ISO8601DateFormatter.localTimeDate(timeZone: .utcTimeZone)
+
+        return fixture.map {
+            return NewReservoirValue(startDate: dateFormatter.date(from: $0["date"] as! String)!, unitVolume: $0["amount"] as! Double)
+        }
+    }
+
+    func defaultStore(testingDate: Date? = nil) -> DoseStore {
+        let healthStore = HKHealthStoreMock()
+        return DoseStore(
             healthStore: healthStore,
             cacheStore: cacheStore,
             observationEnabled: false,
@@ -25,9 +33,20 @@ class DoseStoreTests: PersistenceControllerTestCase {
             basalProfile: BasalRateSchedule(rawValue: ["timeZone": -28800, "items": [["value": 0.75, "startTime": 0.0], ["value": 0.8, "startTime": 10800.0], ["value": 0.85, "startTime": 32400.0], ["value": 1.0, "startTime": 68400.0]]]),
             insulinSensitivitySchedule: InsulinSensitivitySchedule(rawValue: ["unit": "mg/dL", "timeZone": -28800, "items": [["value": 40.0, "startTime": 0.0], ["value": 35.0, "startTime": 21600.0], ["value": 40.0, "startTime": 57600.0]]]),
             syncVersion: 1,
-            provenanceIdentifier: Bundle.main.bundleIdentifier!
+            provenanceIdentifier: Bundle.main.bundleIdentifier!,
+            test_currentDate: testingDate
         )
-        
+    }
+
+    let testingDateFormatter = DateFormatter.descriptionFormatter
+
+    func testingDate(_ input: String) -> Date {
+        return testingDateFormatter.date(from: input)!
+    }
+
+    func testEmptyDoseStoreReturnsZeroInsulinOnBoard() {
+        let doseStore = defaultStore()
+
         let queryFinishedExpectation = expectation(description: "query finished")
         
         doseStore.insulinOnBoard(at: Date()) { (result) in
@@ -41,6 +60,44 @@ class DoseStoreTests: PersistenceControllerTestCase {
         }
         waitForExpectations(timeout: 3)
     }
+
+    func testGetNormalizedDoseEntriesUsingReservoir() {
+        let now = testingDate("2022-09-05 02:04:00 +0000")
+        let doseStore = defaultStore(testingDate: now)
+
+        let reservoirReadings = loadReservoirFixture("reservoir_iob_test")
+
+        let storageExpectations = expectation(description: "reservoir store finished")
+        storageExpectations.expectedFulfillmentCount = reservoirReadings.count + 1
+        for reading in reservoirReadings.reversed() {
+            doseStore.addReservoirValue(reading.unitVolume, at: reading.startDate) { _, _, _, _ in storageExpectations.fulfill() }
+        }
+
+        let bolusStart = testingDate("2022-09-05 01:49:47 +0000")
+        let bolusEnd = testingDate("2022-09-05 01:51:19 +0000")
+        let bolus = DoseEntry(type: .bolus, startDate: bolusStart, endDate: bolusEnd, value: 2.3, unit: .units, isMutable: true)
+        let pumpEvent = NewPumpEvent(date: bolus.startDate, dose: bolus, raw: Data(hexadecimalString: "0000")!, title: "Bolus 2.3U")
+
+        doseStore.addPumpEvents([pumpEvent], lastReconciliation: testingDate("2022-09-05 01:50:18 +0000")) { error in
+            storageExpectations.fulfill()
+        }
+        
+        waitForExpectations(timeout: 2)
+
+        let queryFinishedExpectation = expectation(description: "query finished")
+
+        doseStore.insulinOnBoard(at: now) { (result) in
+            switch result {
+            case .failure(let error):
+                XCTFail("Unexpected error: \(error)")
+            case .success(let value):
+                XCTAssertEqual(1.85, value.value, accuracy: 0.01)
+            }
+            queryFinishedExpectation.fulfill()
+        }
+        waitForExpectations(timeout: 3)
+    }
+
     
     func testPumpEventTypeDoseMigration() {
         cacheStore.managedObjectContext.performAndWait {

+ 282 - 0
Dependencies/LoopKit/LoopKitTests/Fixtures/InsulinKit/reservoir_iob_test.json

@@ -0,0 +1,282 @@
+[
+{
+   "date": "2022-09-05T02:00:22",
+   "amount": 41.1,
+   "unit": "U"
+},
+{
+   "date": "2022-09-05T01:55:16",
+   "amount": 41.1,
+   "unit": "U"
+},
+{
+   "date": "2022-09-05T01:50:03",
+   "amount": 43.1,
+   "unit": "U"
+},
+{
+   "date": "2022-09-05T01:45:17",
+   "amount": 43.5,
+   "unit": "U"
+},
+{
+   "date": "2022-09-05T01:36:13",
+   "amount": 43.5,
+   "unit": "U"
+},
+{
+   "date": "2022-09-05T01:25:25",
+   "amount": 43.5,
+   "unit": "U"
+},
+{
+   "date": "2022-09-05T01:20:15",
+   "amount": 43.5,
+   "unit": "U"
+},
+{
+   "date": "2022-09-05T01:15:15",
+   "amount": 43.5,
+   "unit": "U"
+},
+{
+   "date": "2022-09-05T01:05:31",
+   "amount": 43.6,
+   "unit": "U"
+},
+{
+   "date": "2022-09-05T01:05:24",
+   "amount": 43.6,
+   "unit": "U"
+},
+{
+   "date": "2022-09-05T01:00:24",
+   "amount": 43.6,
+   "unit": "U"
+},
+{
+   "date": "2022-09-05T00:55:19",
+   "amount": 43.6,
+   "unit": "U"
+},
+{
+   "date": "2022-09-05T00:55:15",
+   "amount": 43.6,
+   "unit": "U"
+},
+{
+   "date": "2022-09-05T00:50:20",
+   "amount": 43.6,
+   "unit": "U"
+},
+{
+   "date": "2022-09-05T00:50:16",
+   "amount": 43.6,
+   "unit": "U"
+},
+{
+   "date": "2022-09-05T00:45:22",
+   "amount": 43.6,
+   "unit": "U"
+},
+{
+   "date": "2022-09-05T00:40:18",
+   "amount": 43.6,
+   "unit": "U"
+},
+{
+   "date": "2022-09-05T00:35:22",
+   "amount": 43.6,
+   "unit": "U"
+},
+{
+   "date": "2022-09-05T00:30:19",
+   "amount": 43.6,
+   "unit": "U"
+},
+{
+   "date": "2022-09-05T00:25:15",
+   "amount": 43.6,
+   "unit": "U"
+},
+{
+   "date": "2022-09-05T00:20:13",
+   "amount": 43.6,
+   "unit": "U"
+},
+{
+   "date": "2022-09-05T00:15:27",
+   "amount": 43.6,
+   "unit": "U"
+},
+{
+   "date": "2022-09-05T00:15:24",
+   "amount": 43.6,
+   "unit": "U"
+},
+{
+   "date": "2022-09-05T00:10:21",
+   "amount": 43.6,
+   "unit": "U"
+},
+{
+   "date": "2022-09-05T00:10:17",
+   "amount": 43.6,
+   "unit": "U"
+},
+{
+   "date": "2022-09-05T00:05:15",
+   "amount": 43.6,
+   "unit": "U"
+},
+{
+   "date": "2022-09-05T00:00:59",
+   "amount": 43.6,
+   "unit": "U"
+},
+{
+   "date": "2022-09-04T23:54:49",
+   "amount": 43.6,
+   "unit": "U"
+},
+{
+   "date": "2022-09-04T23:50:15",
+   "amount": 44.1,
+   "unit": "U"
+},
+{
+   "date": "2022-09-04T23:45:16",
+   "amount": 44.2,
+   "unit": "U"
+},
+{
+   "date": "2022-09-04T23:40:18",
+   "amount": 44.2,
+   "unit": "U"
+},
+{
+   "date": "2022-09-04T23:35:15",
+   "amount": 44.3,
+   "unit": "U"
+},
+{
+   "date": "2022-09-04T23:30:27",
+   "amount": 44.5,
+   "unit": "U"
+},
+{
+   "date": "2022-09-04T23:30:24",
+   "amount": 44.5,
+   "unit": "U"
+},
+{
+   "date": "2022-09-04T23:25:19",
+   "amount": 44.5,
+   "unit": "U"
+},
+{
+   "date": "2022-09-04T23:25:15",
+   "amount": 44.5,
+   "unit": "U"
+},
+{
+   "date": "2022-09-04T23:20:03",
+   "amount": 44.9,
+   "unit": "U"
+},
+{
+   "date": "2022-09-04T23:15:17",
+   "amount": 47.9,
+   "unit": "U"
+},
+{
+   "date": "2022-09-04T23:10:17",
+   "amount": 47.9,
+   "unit": "U"
+},
+{
+   "date": "2022-09-04T23:05:15",
+   "amount": 47.9,
+   "unit": "U"
+},
+{
+   "date": "2022-09-04T23:00:15",
+   "amount": 47.9,
+   "unit": "U"
+},
+{
+   "date": "2022-09-04T22:55:17",
+   "amount": 48.0,
+   "unit": "U"
+},
+{
+   "date": "2022-09-04T22:55:13",
+   "amount": 48.0,
+   "unit": "U"
+},
+{
+   "date": "2022-09-04T22:50:15",
+   "amount": 48.0,
+   "unit": "U"
+},
+{
+   "date": "2022-09-04T22:45:19",
+   "amount": 48.1,
+   "unit": "U"
+},
+{
+   "date": "2022-09-04T22:45:15",
+   "amount": 48.1,
+   "unit": "U"
+},
+{
+   "date": "2022-09-04T22:40:02",
+   "amount": 48.9,
+   "unit": "U"
+},
+{
+   "date": "2022-09-04T22:35:15",
+   "amount": 49.1,
+   "unit": "U"
+},
+{
+   "date": "2022-09-04T22:30:15",
+   "amount": 49.1,
+   "unit": "U"
+},
+{
+   "date": "2022-09-04T22:25:13",
+   "amount": 49.1,
+   "unit": "U"
+},
+{
+   "date": "2022-09-04T22:20:19",
+   "amount": 49.1,
+   "unit": "U"
+},
+{
+   "date": "2022-09-04T22:15:16",
+   "amount": 49.1,
+   "unit": "U"
+},
+{
+   "date": "2022-09-04T22:10:15",
+   "amount": 49.1,
+   "unit": "U"
+},
+{
+   "date": "2022-09-04T22:05:25",
+   "amount": 49.1,
+   "unit": "U"
+},
+{
+   "date": "2022-09-04T22:05:22",
+   "amount": 49.1,
+   "unit": "U"
+},
+{
+   "date": "2022-09-04T22:00:04",
+   "amount": 49.1,
+   "unit": "U"
+}
+]

+ 2 - 2
Dependencies/LoopKit/LoopKitTests/HKUnitTests.swift

@@ -51,8 +51,8 @@ class HKUnitTests: XCTestCase {
     }
 
     func testPickerFractionDigits() throws {
-        XCTAssertEqual(HKUnit.internationalUnit().pickerFractionDigits, 2)
-        XCTAssertEqual(HKUnit.internationalUnit().unitDivided(by: .hour()).pickerFractionDigits, 2)
+        XCTAssertEqual(HKUnit.internationalUnit().pickerFractionDigits, 3)
+        XCTAssertEqual(HKUnit.internationalUnit().unitDivided(by: .hour()).pickerFractionDigits, 3)
 
         XCTAssertEqual(HKUnit.millimolesPerLiter.pickerFractionDigits, 1)
         XCTAssertEqual(HKUnit.millimolesPerLiter.unitDivided(by: .internationalUnit()).pickerFractionDigits, 1)

+ 13 - 12
Dependencies/LoopKit/LoopKitUI/CarbKit/FoodEmojiDataSource.swift

@@ -13,14 +13,11 @@ public func CarbAbsorptionInputController() -> EmojiInputController {
 private class FoodEmojiDataSource: EmojiDataSource {
     private static let fast: [String] = {
         var fast = [
-            "🍭", "🍇", "🍈", "🍉", "🍊", "🍋", "🍌", "🍍",
+            "🍭", "🍬", "🍯",
+            "🍇", "🍈", "🍉", "🍊", "🍋", "🍌", "🍍",
             "🍎", "🍏", "🍐", "🍑", "🍒", "🍓", "🥝",
-            "🍅", "🥔", "🥕", "🌽", "🌶", "🥒", "🥗", "🍄",
-            "🍞", "🥐", "🥖", "🥞", "🍿", "🍘", "🍙",
-            "🍚", "🍢", "🍣", "🍡", "🍦", "🍧", "🍨",
-            "🍩", "🍪", "🎂", "🍰", "🍫", "🍬", "🍮",
-            "🍯", "🍼", "🥛", "☕️", "🍵",
-            "🥥", "🥦", "🥨", "🥠", "🥧",
+            "🌽", "🍿", "🍘", "🍡", "🍦", "🍧", "🎂", "🥠",
+            "☕️",
         ]
 
         return fast
@@ -28,10 +25,14 @@ private class FoodEmojiDataSource: EmojiDataSource {
 
     private static let medium: [String] = {
         var medium = [
-            "🌮", "🍆", "🍟", "🍳", "🍲", "🍱", "🍛",
-            "🍜", "🍠", "🍤", "🍥", "🍹",
-            "🥪", "🥫", "🥟", "🥡",
-        ]
+            "🌮", "🍟", "🍳", "🍲", "🍱", "🍛",
+            "🍜", "🍠", "🍤", "🍥",
+            "🥪", "🥫", "🥟", "🥡", "🍢", "🍣",
+            "🍅", "🥔", "🥕", "🌶", "🥒", "🥗", "🍄", "🥦",
+            "🍆", "🥥", "🍞", "🥐", "🥖", "🥨", "🥞", "🍙", "🍚",
+            "🍼", "🥛", "🍮", "🥧",
+            "🍨", "🍩", "🍪", "🍰", "🍫",
+       ]
 
         return medium
     }()
@@ -48,7 +49,7 @@ private class FoodEmojiDataSource: EmojiDataSource {
     private static let other: [String] = {
         var other = [
             "🍶", "🍾", "🍷", "🍸", "🍺", "🍻", "🥂", "🥃",
-            "🥣", "🥤", "🥢",
+            "🍹", "🥣", "🥤", "🥢", "🍵",
             "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣",
             "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟"
         ]

+ 5 - 0
Dependencies/LoopKit/LoopKitUI/View Controllers/EmojiInputController.swift

@@ -39,6 +39,11 @@ public class EmojiInputController: UIInputViewController, UICollectionViewDataSo
         }
 
         setupSectionIndex()
+
+        // Scroll to medium absorption
+        DispatchQueue.main.async {
+            self.collectionView.scrollToItem(at: IndexPath(row: 0, section: 1), at: .left, animated: false)
+        }
     }
 
     private func setupSectionIndex() {

+ 3 - 0
Dependencies/LoopKit/LoopKitUI/ViewModels/TherapySettingsViewModel.swift

@@ -23,6 +23,7 @@ public class TherapySettingsViewModel: ObservableObject {
     @Published public var therapySettings: TherapySettings
     private let initialTherapySettings: TherapySettings
     let sensitivityOverridesEnabled: Bool
+    let adultChildInsulinModelSelectionEnabled: Bool
     public var prescription: Prescription?
 
     private weak var delegate: TherapySettingsViewModelDelegate?
@@ -30,11 +31,13 @@ public class TherapySettingsViewModel: ObservableObject {
     public init(therapySettings: TherapySettings,
                 pumpSupportedIncrements: (() -> PumpSupportedIncrements?)? = nil,
                 sensitivityOverridesEnabled: Bool = false,
+                adultChildInsulinModelSelectionEnabled: Bool = false,
                 prescription: Prescription? = nil,
                 delegate: TherapySettingsViewModelDelegate? = nil) {
         self.therapySettings = therapySettings
         self.initialTherapySettings = therapySettings
         self.sensitivityOverridesEnabled = sensitivityOverridesEnabled
+        self.adultChildInsulinModelSelectionEnabled = adultChildInsulinModelSelectionEnabled
         self.prescription = prescription
         self.delegate = delegate
     }

+ 5 - 1
Dependencies/LoopKit/LoopKitUI/Views/Information Screens/InformationView.swift

@@ -80,7 +80,11 @@ struct InformationView<InformationalContent: View> : View {
             informationalContent
             Spacer()
         }
-        .navigationBarItems(trailing: cancelButton)
+        .toolbar {
+            ToolbarItem(placement: .navigationBarTrailing) {
+                cancelButton
+            }
+        }
         .navigationBarTitle(title, displayMode: .large)
     }
 

+ 16 - 2
Dependencies/LoopKit/LoopKitUI/Views/ScheduleEditor.swift

@@ -145,11 +145,25 @@ struct ScheduleEditor<Value: Equatable, ValueContent: View, ValuePicker: View, A
         case .acceptanceFlow:
             page
                 .navigationBarBackButtonHidden(true)
-                .navigationBarItems(leading: backButton, trailing: trailingNavigationItems)
+                .toolbar {
+                    ToolbarItem(placement: .navigationBarLeading) {
+                        backButton
+                    }
+                    ToolbarItem(placement: .navigationBarTrailing) {
+                        trailingNavigationItems
+                    }
+                }
         case .settings:
             page
                 .navigationBarBackButtonHidden(shouldAddCancelButton)
-                .navigationBarItems(leading: leadingNavigationBarItem, trailing: trailingNavigationItems)
+                .toolbar {
+                    ToolbarItem(placement: .navigationBarLeading) {
+                        leadingNavigationBarItem
+                    }
+                    ToolbarItem(placement: .navigationBarTrailing) {
+                        trailingNavigationItems
+                    }
+                }
                 .navigationBarTitle("", displayMode: .inline)
         }
     }

+ 8 - 1
Dependencies/LoopKit/LoopKitUI/Views/Settings Editors/CorrectionRangeOverridesEditor.swift

@@ -85,7 +85,14 @@ public struct CorrectionRangeOverridesEditor: View {
     private var contentWithCancel: some View {
         content
             .navigationBarBackButtonHidden(shouldAddCancelButton)
-            .navigationBarItems(leading: leadingNavigationBarItem, trailing: deleteButton)
+            .toolbar {
+                ToolbarItem(placement: .navigationBarLeading) {
+                    leadingNavigationBarItem
+                }
+                ToolbarItem(placement: .navigationBarTrailing) {
+                    deleteButton
+                }
+            }
     }
 
     private var deleteButton: some View {

+ 8 - 2
Dependencies/LoopKit/LoopKitUI/Views/Settings Editors/TherapySettingsView.swift

@@ -96,7 +96,9 @@ public struct TherapySettingsView: View {
         cards.append(carbRatioSection)
         cards.append(basalRatesSection)
         cards.append(deliveryLimitsSection)
-        cards.append(insulinModelSection)
+        if viewModel.adultChildInsulinModelSelectionEnabled {
+            cards.append(insulinModelSection)
+        }
         cards.append(insulinSensitivitiesSection)
 
         return CardStack(cards: cards)
@@ -114,7 +116,11 @@ public struct TherapySettingsView: View {
                 Color(.systemGroupedBackground)
                     .edgesIgnoringSafeArea(.all)
                 content
-                    .navigationBarItems(trailing: dismissButton)
+                    .toolbar {
+                        ToolbarItem(placement: .navigationBarTrailing) {
+                            dismissButton
+                        }
+                    }
                     .navigationBarTitle(therapySettingsTitle, displayMode: .large)
             }
         }

+ 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
-

+ 1 - 5
Dependencies/LoopKit/MockKit/MockCGMManager.swift

@@ -480,10 +480,6 @@ public final class MockCGMManager: TestingCGMManager {
     
     public static var healthKitStorageDelay: TimeInterval = 0
     
-    private func logDeviceCommunication(_ message: String, type: DeviceLogEntryType = .send) {
-        self.delegate.delegate?.deviceManager(self, logEventForDeviceIdentifier: "MockId", type: type, message: message, completion: nil)
-    }
-
     private func logDeviceComms(_ type: DeviceLogEntryType, message: String) {
         self.delegate.delegate?.deviceManager(self, logEventForDeviceIdentifier: "mockcgm", type: type, message: message, completion: nil)
     }
@@ -542,7 +538,7 @@ public final class MockCGMManager: TestingCGMManager {
 
     public func backfillData(datingBack duration: TimeInterval) {
         let now = Date()
-        self.logDeviceCommunication("backfillData(\(duration))")
+        self.logDeviceComms(.send, message: "backfillData(\(duration))")
         dataSource.backfillData(from: DateInterval(start: now.addingTimeInterval(-duration), end: now)) { result in
             switch result {
             case .error(let error):

+ 1 - 1
Dependencies/LoopKit/MockKit/MockPumpManager.swift

@@ -401,7 +401,7 @@ public final class MockPumpManager: TestingPumpManager {
         state.finalizeFinishedDoses()
         let pendingPumpEvents = state.pumpEventsToStore
         delegate.notify { (delegate) in
-            delegate?.pumpManager(self, hasNewPumpEvents: pendingPumpEvents, lastSync: self.lastSync) { error in
+            delegate?.pumpManager(self, hasNewPumpEvents: pendingPumpEvents, lastReconciliation: self.lastSync) { error in
                 if error == nil {
                     self.state.additionalPumpEvents = []
                 }

+ 5 - 1
Dependencies/LoopKit/MockKitUI/Views/DeliveryUncertaintyRecoveryView.swift

@@ -32,7 +32,11 @@ struct DeliveryUncertaintyRecoveryView: View, HorizontalSizeClassOverride {
             }
             .environment(\.horizontalSizeClass, horizontalOverride)
             .navigationBarTitle(Text("Comms Recovery"), displayMode: .large)
-            .navigationBarItems(leading: backButton)
+            .toolbar {
+                ToolbarItem(placement: .navigationBarLeading) {
+                    backButton
+                }
+            }
         }
     }
     

+ 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,

+ 14 - 2
Dependencies/OmniBLE/OmniBLE/OmnipodCommon/UnfinalizedDose.swift

@@ -199,6 +199,19 @@ 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")
+        case .resume:
+            return NSLocalizedString("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")
+        case .tempBasal:
+            return NSLocalizedString("Temp Basal", comment: "Pump Event title for UnfinalizedDose with doseType of .tempBasal")
+        }
+    }
+
     // RawRepresentable
     public init?(rawValue: RawValue) {
         guard
@@ -278,9 +291,8 @@ private extension TimeInterval {
 
 extension NewPumpEvent {
     init(_ dose: UnfinalizedDose) {
-        let title = String(describing: dose)
         let entry = DoseEntry(dose)
-        self.init(date: dose.startTime, dose: entry, raw: dose.uniqueKey, title: title)
+        self.init(date: dose.startTime, dose: entry, raw: dose.uniqueKey, title: dose.eventTitle)
     }
 }
 

+ 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)")
         }

+ 2 - 8
Dependencies/OmniBLE/OmniBLE/PumpManager/OmniBLEPumpManager.swift

@@ -80,13 +80,7 @@ extension OmniBLEPumpManagerError: LocalizedError {
 
 public class OmniBLEPumpManager: DeviceManager {
 
-    //public let managerIdentifier: String = "Omnipod-Dash" // use a single token to make parsing log files easier
-    
-    public static let managerIdentifier = "Omnipod-Dash"
-    
-    public var managerIdentifier: String {
-        return OmniBLEPumpManager.managerIdentifier
-    }
+    public let managerIdentifier: String = "Omnipod-Dash" // use a single token to make parsing log files easier
 
     public let localizedTitle = LocalizedString("Omnipod DASH", comment: "Generic title of the OmniBLE pump manager")
 
@@ -2064,7 +2058,7 @@ extension OmniBLEPumpManager: PumpManager {
             }
 
 
-            delegate.pumpManager(self, hasNewPumpEvents: doses.map { NewPumpEvent($0) }, lastSync: lastSync, completion: { (error) in
+            delegate.pumpManager(self, hasNewPumpEvents: doses.map { NewPumpEvent($0) }, lastReconciliation: lastSync, completion: { (error) in
                 if let error = error {
                     self.log.error("Error storing pod events: %@", String(describing: error))
                 } else {

+ 1 - 1
Dependencies/OmniBLE/OmniBLE/PumpManager/PodState.swift

@@ -203,7 +203,7 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl
     public mutating func updateFromStatusResponse(_ response: StatusResponse) {
         let now = updatePodTimes(timeActive: response.timeActive)
         updateDeliveryStatus(deliveryStatus: response.deliveryStatus, podProgressStatus: response.podProgressStatus, bolusNotDelivered: response.bolusNotDelivered)
-        
+
         let setupUnits = setupUnitsDelivered ?? Pod.primeUnits + Pod.cannulaInsertionUnits + Pod.cannulaInsertionUnitsExtra
 
         // Calculated new delivered value which will be a negative value until setup has completed OR after a pod reset fault

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

@@ -169,7 +169,7 @@ class DashUICoordinator: UINavigationController, PumpManagerOnboarding, Completi
             viewModel.navigateTo = { [weak self] (screen) in
                 self?.navigateTo(screen)
             }
-            let view = OmniBLESettingsView(viewModel: viewModel)
+            let view = OmniBLESettingsView(viewModel: viewModel, supportedInsulinTypes: allowedInsulinTypes)
             return hostingController(rootView: view)
         case .pairPod:
             pumpManagerOnboardingDelegate?.pumpManagerOnboarding(didCreatePumpManager: pumpManager)

+ 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()
                 }
             }

+ 1 - 0
Dependencies/OmniBLE/OmniBLE/PumpManagerUI/Views/ExpirationReminderSetupView.swift

@@ -39,6 +39,7 @@ struct ExpirationReminderSetupView: View {
             .padding()
         }
         .navigationBarTitle("Expiration Reminder", displayMode: .automatic)
+        .navigationBarHidden(false)
         .toolbar {
             ToolbarItem(placement: .navigationBarTrailing) {
                 Button(LocalizedString("Cancel", comment: "Cancel button title"), action: {

+ 7 - 5
Dependencies/OmniBLE/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift

@@ -17,15 +17,17 @@ struct OmniBLESettingsView: View  {
     
     @State private var showingDeleteConfirmation = false
     
-    @State private var showSuspendOptions = false;
+    @State private var showSuspendOptions = false
 
-    @State private var showManualTempBasalOptions = false;
+    @State private var showManualTempBasalOptions = false
 
-    @State private var showSyncTimeOptions = false;
+    @State private var showSyncTimeOptions = false
 
-    @State private var sendingTestBeepsCommand = false;
+    @State private var sendingTestBeepsCommand = false
 
     @State private var cancelingTempBasal = false
+
+    var supportedInsulinTypes: [InsulinType]
     
     @Environment(\.guidanceColors) var guidanceColors
     @Environment(\.insulinTintColor) var insulinTintColor
@@ -407,7 +409,7 @@ struct OmniBLESettingsView: View  {
                             .foregroundColor(.secondary)
                     }
                 }
-                NavigationLink(destination: InsulinTypeSetting(initialValue: viewModel.insulinType, supportedInsulinTypes: InsulinType.allCases, allowUnsetInsulinType: false, didChange: viewModel.didChangeInsulinType)) {
+                NavigationLink(destination: InsulinTypeSetting(initialValue: viewModel.insulinType, supportedInsulinTypes: supportedInsulinTypes, allowUnsetInsulinType: false, didChange: viewModel.didChangeInsulinType)) {
                     HStack {
                         FrameworkLocalText("Insulin Type", comment: "Text for confidence reminders navigation link").foregroundColor(Color.primary)
                         if let currentTitle = viewModel.insulinType?.brandName {

+ 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))"
     }
+
 }

File diff ditekan karena terlalu besar
+ 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))
         }
 

+ 88 - 56
Dependencies/rileylink_ios/MinimedKit/PumpManager/MinimedPumpManager.swift

@@ -23,7 +23,7 @@ public class MinimedPumpManager: RileyLinkPumpManager {
         return MinimedPumpManager.managerIdentifier
     }
     
-    public init(state: MinimedPumpManagerState, rileyLinkDeviceProvider: RileyLinkDeviceProvider, rileyLinkConnectionManager: RileyLinkConnectionManager? = nil, pumpOps: PumpOps? = nil) {
+    public init(state: MinimedPumpManagerState, rileyLinkDeviceProvider: RileyLinkDeviceProvider, pumpOps: PumpOps? = nil) {
         self.lockedState = Locked(state)
 
         self.hkDevice = HKDevice(
@@ -37,27 +37,28 @@ public class MinimedPumpManager: RileyLinkPumpManager {
             udiDeviceIdentifier: nil
         )
         
-        super.init(rileyLinkDeviceProvider: rileyLinkDeviceProvider, rileyLinkConnectionManager: rileyLinkConnectionManager)
+        super.init(rileyLinkDeviceProvider: rileyLinkDeviceProvider)
 
         // Pump communication
         let idleListeningEnabled = state.pumpModel.hasMySentry && state.useMySentry
-        self.pumpOps = pumpOps ?? PumpOps(pumpSettings: state.pumpSettings, pumpState: state.pumpState, delegate: self)
+
+        self.pumpOps = pumpOps ?? MinimedPumpOps(pumpSettings: state.pumpSettings, pumpState: state.pumpState, delegate: self)
 
         self.rileyLinkDeviceProvider.idleListeningState = idleListeningEnabled ? MinimedPumpManagerState.idleListeningEnabledDefaults : .disabled
     }
 
     public required convenience init?(rawState: PumpManager.RawStateValue) {
         guard let state = MinimedPumpManagerState(rawValue: rawState),
-            let connectionManagerState = state.rileyLinkConnectionManagerState else
+            let connectionManagerState = state.rileyLinkConnectionState else
         {
             return nil
         }
+
+        let deviceProvider = RileyLinkBluetoothDeviceProvider(autoConnectIDs: connectionManagerState.autoConnectIDs)
+
+        self.init(state: state, rileyLinkDeviceProvider: deviceProvider)
         
-        let rileyLinkConnectionManager = RileyLinkConnectionManager(state: connectionManagerState)
-        
-        self.init(state: state, rileyLinkDeviceProvider: rileyLinkConnectionManager.deviceProvider, rileyLinkConnectionManager: rileyLinkConnectionManager)
-        
-        rileyLinkConnectionManager.delegate = self
+        deviceProvider.delegate = self
     }
 
     public private(set) var pumpOps: PumpOps!
@@ -175,6 +176,11 @@ public class MinimedPumpManager: RileyLinkPumpManager {
         }
     }
 
+    private func logDeviceCommunication(_ message: String, type: DeviceLogEntryType = .send) {
+        // Not dispatching here; if delegate queue is blocked, timestamps will be delayed
+        self.pumpDelegate.delegate?.deviceManager(self, logEventForDeviceIdentifier: state.pumpID, type: type, message: message, completion: nil)
+    }
+
     private let cgmDelegate = WeakSynchronizedDelegate<CGMManagerDelegate>()
     private let pumpDelegate = WeakSynchronizedDelegate<PumpManagerDelegate>()
 
@@ -186,13 +192,13 @@ public class MinimedPumpManager: RileyLinkPumpManager {
 
     // MARK: - RileyLink Updates
 
-    override public var rileyLinkConnectionManagerState: RileyLinkConnectionManagerState? {
+    override public var rileyLinkConnectionManagerState: RileyLinkConnectionState? {
         get {
-            return state.rileyLinkConnectionManagerState
+            return state.rileyLinkConnectionState
         }
         set {
             setState { (state) in
-                state.rileyLinkConnectionManagerState = newValue
+                state.rileyLinkConnectionState = newValue
             }
         }
     }
@@ -208,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)
         }
     }
 
@@ -452,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)
         }
@@ -480,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)
     }
     
@@ -534,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)
     }
 
@@ -545,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)
 
@@ -559,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)
     }
 
@@ -690,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)
                 }
@@ -718,7 +727,7 @@ extension MinimedPumpManager {
     ///   - error: An error describing why the fetch and/or store failed
     private func fetchPumpHistory(_ completion: @escaping (_ error: Error?) -> Void) {
         guard let insulinType = insulinType else {
-            completion(PumpManagerError.configuration(nil))
+            completion(PumpManagerError.configuration(MinimedPumpManagerError.insulinTypeNotConfigured))
             return
         }
         
@@ -737,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
@@ -757,9 +767,11 @@ extension MinimedPumpManager {
                             preconditionFailure("pumpManagerDelegate cannot be nil")
                         }
                         
-                        let pendingEvents = (self.state.pendingDoses + [self.state.unfinalizedBolus, self.state.unfinalizedTempBasal]).compactMap({ $0?.newPumpEvent })
+                        let pendingEvents = (self.state.pendingDoses + [self.state.unfinalizedBolus, self.state.unfinalizedTempBasal]).compactMap({ $0?.newPumpEvent() })
 
-                        delegate.pumpManager(self, hasNewPumpEvents: remainingHistoryEvents + pendingEvents, lastSync: self.lastSync, completion: { (error) in
+                        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 {
                                 self.recents.lastAddedPumpEvents = Date()
@@ -791,9 +803,9 @@ extension MinimedPumpManager {
         }
     }
 
-    private func storePendingPumpEvents(_ completion: @escaping (_ error: MinimedPumpManagerError?) -> Void) {
+    private func storePendingPumpEvents(forceFinalization: Bool = false, _ completion: @escaping (_ error: MinimedPumpManagerError?) -> Void) {
         // Must be called from the sessionQueue
-        let events = (self.state.pendingDoses + [self.state.unfinalizedBolus, self.state.unfinalizedTempBasal]).compactMap({ $0?.newPumpEvent })
+        let events = (self.state.pendingDoses + [self.state.unfinalizedBolus, self.state.unfinalizedTempBasal]).compactMap({ $0?.newPumpEvent(forceFinalization: forceFinalization) })
                 
         log.debug("Storing pending pump events: %{public}@", String(describing: events))
 
@@ -802,7 +814,7 @@ extension MinimedPumpManager {
                 preconditionFailure("pumpManagerDelegate cannot be nil")
             }
 
-            delegate.pumpManager(self, hasNewPumpEvents: events, lastSync: self.lastSync, completion: { (error) in
+            delegate.pumpManager(self, hasNewPumpEvents: events, lastReconciliation: self.state.lastReconciliation, completion: { (error) in
                 // Called on an unknown queue by the delegate
                 if let error = error {
                     self.log.error("Pump event storage failed: %{public}@", String(describing: error))
@@ -1068,7 +1080,7 @@ extension MinimedPumpManager: PumpManager {
 
     public func suspendDelivery(completion: @escaping (Error?) -> Void) {
         guard let insulinType = insulinType else {
-            completion(PumpManagerError.configuration(nil))
+            completion(PumpManagerError.configuration(MinimedPumpManagerError.insulinTypeNotConfigured))
             return
         }
         
@@ -1077,7 +1089,7 @@ extension MinimedPumpManager: PumpManager {
 
     public func resumeDelivery(completion: @escaping (Error?) -> Void) {
         guard let insulinType = insulinType else {
-            completion(PumpManagerError.configuration(nil))
+            completion(PumpManagerError.configuration(MinimedPumpManagerError.insulinTypeNotConfigured))
             return
         }
         
@@ -1174,12 +1186,12 @@ extension MinimedPumpManager: PumpManager {
         }
         
         guard let insulinType = insulinType else {
-            completion(.configuration(nil))
+            completion(.configuration(MinimedPumpManagerError.insulinTypeNotConfigured))
             return
         }
 
 
-        pumpOps.runSession(withName: "Bolus", using: rileyLinkDeviceProvider.firstConnectedDevice) { (session) in
+        pumpOps.runSession(withName: "Bolus", usingSelector: rileyLinkDeviceProvider.firstConnectedDevice) { (session) in
 
             guard let session = session else {
                 completion(.connection(MinimedPumpManagerError.noRileyLink))
@@ -1273,7 +1285,7 @@ extension MinimedPumpManager: PumpManager {
     public func cancelBolus(completion: @escaping (PumpManagerResult<DoseEntry?>) -> Void) {
         
         guard let insulinType = insulinType else {
-            completion(.failure(.configuration(nil)))
+            completion(.failure(.configuration(MinimedPumpManagerError.insulinTypeNotConfigured)))
             return
         }
 
@@ -1290,11 +1302,11 @@ extension MinimedPumpManager: PumpManager {
     
     public func enactTempBasal(unitsPerHour: Double, for duration: TimeInterval, completion: @escaping (PumpManagerError?) -> Void) {
         guard let insulinType = insulinType else {
-            completion(.configuration(nil))
+            completion(.configuration(MinimedPumpManagerError.insulinTypeNotConfigured))
             return
         }
 
-        pumpOps.runSession(withName: "Set Temp Basal", using: rileyLinkDeviceProvider.firstConnectedDevice) { (session) in
+        pumpOps.runSession(withName: "Set Temp Basal", usingSelector: rileyLinkDeviceProvider.firstConnectedDevice) { (session) in
             guard let session = session else {
                 completion(.connection(MinimedPumpManagerError.noRileyLink))
                 return
@@ -1305,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
                 
@@ -1319,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)
                     }
@@ -1346,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 {
@@ -1400,7 +1412,7 @@ extension MinimedPumpManager: PumpManager {
     public func setMaximumTempBasalRate(_ rate: Double) { }
 
     public func syncBasalRateSchedule(items scheduleItems: [RepeatingScheduleValue<Double>], completion: @escaping (Result<BasalRateSchedule, Error>) -> Void) {
-        pumpOps.runSession(withName: "Save Basal Profile", using: rileyLinkDeviceProvider.firstConnectedDevice) { (session) in
+        pumpOps.runSession(withName: "Save Basal Profile", usingSelector: rileyLinkDeviceProvider.firstConnectedDevice) { (session) in
             guard let session = session else {
                 completion(.failure(PumpManagerError.connection(MinimedPumpManagerError.noRileyLink)))
                 return
@@ -1419,7 +1431,7 @@ extension MinimedPumpManager: PumpManager {
     }
 
     public func syncDeliveryLimits(limits deliveryLimits: DeliveryLimits, completion: @escaping (Result<DeliveryLimits, Error>) -> Void) {
-        pumpOps.runSession(withName: "Save Settings", using: rileyLinkDeviceProvider.firstConnectedDevice) { (session) in
+        pumpOps.runSession(withName: "Save Settings", usingSelector: rileyLinkDeviceProvider.firstConnectedDevice) { (session) in
             guard let session = session else {
                 completion(.failure(PumpManagerError.connection(MinimedPumpManagerError.noRileyLink)))
                 return
@@ -1444,9 +1456,29 @@ extension MinimedPumpManager: PumpManager {
             }
         }
     }
+
+    public func deletePump(completion: @escaping () -> Void) {
+        storePendingPumpEvents(forceFinalization: true) { error in
+            self.notifyDelegateOfDeletion {
+                completion()
+            }
+        }
+    }
 }
 
 extension MinimedPumpManager: PumpOpsDelegate {
+    public func willSend(_ message: String) {
+        logDeviceCommunication(message, type: .send)
+    }
+
+    public func didReceive(_ message: String) {
+        logDeviceCommunication(message, type: .receive)
+    }
+
+    public func didError(_ message: String) {
+        logDeviceCommunication(message, type: .error)
+    }
+
     public func pumpOps(_ pumpOps: PumpOps, didChange state: PumpState) {
         setState { (pumpManagerState) in
             pumpManagerState.pumpState = state

+ 5 - 0
Dependencies/rileylink_ios/MinimedKit/PumpManager/MinimedPumpManagerError.swift

@@ -11,6 +11,7 @@ public enum MinimedPumpManagerError: Error {
     case noRileyLink
     case bolusInProgress
     case pumpSuspended
+    case insulinTypeNotConfigured
     case noDate  // TODO: This is less of an error and more of a precondition/assertion state
     case tuneFailed(LocalizedError)
     case commsError(LocalizedError)
@@ -27,6 +28,8 @@ extension MinimedPumpManagerError: LocalizedError {
             return LocalizedString("Bolus in Progress", comment: "Error description when failure due to bolus in progress")
         case .pumpSuspended:
             return LocalizedString("Pump is Suspended", comment: "Error description when failure due to pump suspended")
+        case .insulinTypeNotConfigured:
+            return LocalizedString("Insulin Type is not configured", comment: "Error description for MinimedPumpManagerError.insulinTypeNotConfigured")
         case .noDate:
             return nil
         case .tuneFailed(let error):
@@ -51,6 +54,8 @@ extension MinimedPumpManagerError: LocalizedError {
         switch self {
         case .noRileyLink:
             return LocalizedString("Make sure your RileyLink is nearby and powered on", comment: "Recovery suggestion")
+        case .insulinTypeNotConfigured:
+            return LocalizedString("Go to pump settings and select insulin type", comment: "Recovery suggestion for MinimedPumpManagerError.insulinTypeNotConfigured")
         case .tuneFailed(let error):
             return error.recoverySuggestion
         default:

+ 14 - 12
Dependencies/rileylink_ios/MinimedKit/PumpManager/MinimedPumpManagerState.swift

@@ -98,7 +98,7 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable {
         }
     }
 
-    public var rileyLinkConnectionManagerState: RileyLinkConnectionManagerState?
+    public var rileyLinkConnectionState: RileyLinkConnectionState?
 
     public var timeZone: TimeZone
 
@@ -120,7 +120,7 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable {
     
     public var lastRileyLinkBatteryAlertDate: Date = .distantPast
     
-    public init(isOnboarded: Bool, useMySentry: Bool, pumpColor: PumpColor, pumpID: String, pumpModel: PumpModel, pumpFirmwareVersion: String, pumpRegion: PumpRegion, rileyLinkConnectionManagerState: RileyLinkConnectionManagerState?, timeZone: TimeZone, suspendState: SuspendState, insulinType: InsulinType)
+    public init(isOnboarded: Bool, useMySentry: Bool, pumpColor: PumpColor, pumpID: String, pumpModel: PumpModel, pumpFirmwareVersion: String, pumpRegion: PumpRegion, rileyLinkConnectionState: RileyLinkConnectionState?, timeZone: TimeZone, suspendState: SuspendState, insulinType: InsulinType, lastTuned: Date?, lastValidFrequency: Measurement<UnitFrequency>?)
     {
         self.isOnboarded = isOnboarded
         self.useMySentry = useMySentry
@@ -129,10 +129,12 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable {
         self.pumpModel = pumpModel
         self.pumpFirmwareVersion = pumpFirmwareVersion
         self.pumpRegion = pumpRegion
-        self.rileyLinkConnectionManagerState = rileyLinkConnectionManagerState
+        self.rileyLinkConnectionState = rileyLinkConnectionState
         self.timeZone = timeZone
         self.suspendState = suspendState
         self.insulinType = insulinType
+        self.lastTuned = lastTuned
+        self.lastValidFrequency = lastValidFrequency
     }
 
     public init?(rawValue: RawValue) {
@@ -168,11 +170,11 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable {
             if let oldRileyLinkPumpManagerStateRaw = rawValue["rileyLinkPumpManagerState"] as? [String : Any],
                 let connectedPeripheralIDs = oldRileyLinkPumpManagerStateRaw["connectedPeripheralIDs"] as? [String]
             {
-                self.rileyLinkConnectionManagerState = RileyLinkConnectionManagerState(autoConnectIDs: Set(connectedPeripheralIDs))
+                self.rileyLinkConnectionState = RileyLinkConnectionState(autoConnectIDs: Set(connectedPeripheralIDs))
             }
         } else {
-            if let rawState = rawValue["rileyLinkConnectionManagerState"] as? RileyLinkConnectionManagerState.RawValue {
-                self.rileyLinkConnectionManagerState = RileyLinkConnectionManagerState(rawValue: rawState)
+            if let rawState = rawValue["rileyLinkConnectionManagerState"] as? RileyLinkConnectionState.RawValue {
+                self.rileyLinkConnectionState = RileyLinkConnectionState(rawValue: rawState)
             }
         }
         
@@ -237,7 +239,7 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable {
         }
         reconciliationMappings = recentlyReconciledEvents
         
-        lastReconciliation = rawValue["lastSync"] as? Date
+        lastReconciliation = rawValue["lastReconciliation"] as? Date
         
         if let rawInsulinType = rawValue["insulinType"] as? InsulinType.RawValue {
             insulinType = InsulinType(rawValue: rawInsulinType)
@@ -269,10 +271,10 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable {
         value["batteryPercentage"] = batteryPercentage
         value["lastReservoirReading"] = lastReservoirReading?.rawValue
         value["lastValidFrequency"] = lastValidFrequency?.converted(to: .megahertz).value
-        value["rileyLinkConnectionManagerState"] = rileyLinkConnectionManagerState?.rawValue
+        value["rileyLinkConnectionManagerState"] = rileyLinkConnectionState?.rawValue
         value["unfinalizedBolus"] = unfinalizedBolus?.rawValue
         value["unfinalizedTempBasal"] = unfinalizedTempBasal?.rawValue
-        value["lastSync"] = lastReconciliation
+        value["lastReconciliation"] = lastReconciliation
         value["insulinType"] = insulinType?.rawValue
         value["rileyLinkBatteryAlertLevel"] = rileyLinkBatteryAlertLevel
         value["lastRileyLinkBatteryAlertDate"] = lastRileyLinkBatteryAlertDate
@@ -283,7 +285,7 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable {
 
 
 extension MinimedPumpManagerState {
-    static let idleListeningEnabledDefaults: RileyLinkDevice.IdleListeningState = .enabled(timeout: .minutes(4), channel: 0)
+    static let idleListeningEnabledDefaults: RileyLinkBluetoothDevice.IdleListeningState = .enabled(timeout: .minutes(4), channel: 0)
 }
 
 
@@ -310,11 +312,11 @@ extension MinimedPumpManagerState: CustomDebugStringConvertible {
             "pendingDoses: \(pendingDoses)",
             "timeZone: \(timeZone)",
             "recentlyReconciledEvents: \(reconciliationMappings.values.map { "\($0.eventRaw.hexadecimalString) -> \($0.uuid)" })",
-            "lastSync: \(String(describing: lastReconciliation))",
+            "lastReconciliation: \(String(describing: lastReconciliation))",
             "insulinType: \(String(describing: insulinType))",
             "rileyLinkBatteryAlertLevel: \(String(describing: rileyLinkBatteryAlertLevel))",
             "lastRileyLinkBatteryAlertDate \(String(describing: lastRileyLinkBatteryAlertDate))",
-            String(reflecting: rileyLinkConnectionManagerState),
+            String(reflecting: rileyLinkConnectionState),
         ].joined(separator: "\n")
     }
 }

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

@@ -0,0 +1,166 @@
+//
+//  MinimedPumpMessageSender.swift
+//  MinimedKit
+//
+//  Created by Pete Schwamb on 9/3/22.
+//  Copyright © 2022 Pete Schwamb. All rights reserved.
+//
+
+import Foundation
+import RileyLinkBLEKit
+import os.log
+
+
+public protocol CommsLogger: AnyObject {
+    // Comms logging
+    func willSend(_ message: String)
+    func didReceive(_ message: String)
+    func didError(_ message: String)
+}
+
+private let log = OSLog(category: "MinimedPumpMessageSender")
+
+struct MinimedPumpMessageSender: PumpMessageSender {
+
+    static let standardPumpResponseWindow: TimeInterval = .milliseconds(200)
+
+    var commandSession: CommandSession
+    weak var commsLogger: CommsLogger?
+
+    func resetRadioConfig() throws {
+        try commandSession.resetRadioConfig()
+    }
+
+    func updateRegister(_ address: RileyLinkBLEKit.CC111XRegister, value: UInt8) throws {
+        try commandSession.updateRegister(address, value: value)
+    }
+
+    func setBaseFrequency(_ frequency: Measurement<UnitFrequency>) throws {
+        try commandSession.setBaseFrequency(frequency)
+    }
+
+    func listen(onChannel channel: Int, timeout: TimeInterval) throws -> RileyLinkBLEKit.RFPacket? {
+        return try commandSession.listen(onChannel: channel, timeout: timeout)
+    }
+
+    func getRileyLinkStatistics() throws -> RileyLinkBLEKit.RileyLinkStatistics {
+        return try commandSession.getRileyLinkStatistics()
+    }
+
+    /// - Throws: PumpOpsError.deviceError
+    func send(_ msg: PumpMessage) throws {
+        do {
+            try commandSession.send(MinimedPacket(outgoingData: msg.txData).encodedData(), onChannel: 0, timeout: 0)
+        } catch let error as LocalizedError {
+            throw PumpOpsError.deviceError(error)
+        }
+    }
+
+    /// Sends a message to the pump, expecting a PumpMessage with specific response body type
+    ///
+    /// - Parameters:
+    ///   - message: The message to send
+    ///   - responseType: The expected response message type
+    ///   - repeatCount: The number of times to repeat the message before listening begins
+    ///   - timeout: The length of time to listen for a pump response
+    ///   - retryCount: The number of times to repeat the send & listen sequence
+    /// - Returns: The expected response message body
+    /// - Throws:
+    ///     - PumpOpsError.couldNotDecode
+    ///     - PumpOpsError.crosstalk
+    ///     - PumpOpsError.deviceError
+    ///     - PumpOpsError.noResponse
+    ///     - PumpOpsError.pumpError
+    ///     - PumpOpsError.unexpectedResponse
+    ///     - PumpOpsError.unknownResponse
+    func getResponse<T: MessageBody>(to message: PumpMessage, responseType: MessageType, repeatCount: Int, timeout: TimeInterval, retryCount: Int) throws -> T {
+
+        commsLogger?.willSend(String(describing: message))
+
+        do {
+            let response = try sendAndListen(message, repeatCount: repeatCount, timeout: timeout, retryCount: retryCount)
+
+            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)
+                    case .unknown(let code):
+                        throw PumpOpsError.unknownPumpErrorCode(code)
+                    }
+                } else {
+                    throw PumpOpsError.unexpectedResponse(response, from: message)
+                }
+            }
+            commsLogger?.didReceive(String(describing: response))
+            usleep(200000) // 0.2s
+            return body
+        } catch {
+            commsLogger?.didError(error.localizedDescription)
+            throw error
+        }
+    }
+
+    /// Sends a message to the pump, listening for a any known PumpMessage in reply
+    ///
+    /// - Parameters:
+    ///   - message: The message to send
+    ///   - repeatCount: The number of times to repeat the message before listening begins
+    ///   - timeout: The length of time to listen for a pump response
+    ///   - retryCount: The number of times to repeat the send & listen sequence
+    /// - Returns: The message reply
+    /// - Throws: An error describing a failure in the sending or receiving of a message:
+    ///     - PumpOpsError.couldNotDecode
+    ///     - PumpOpsError.crosstalk
+    ///     - PumpOpsError.deviceError
+    ///     - PumpOpsError.noResponse
+    ///     - PumpOpsError.unknownResponse
+    func sendAndListen(_ message: PumpMessage, repeatCount: Int, timeout: TimeInterval, retryCount: Int) throws -> PumpMessage {
+        let rfPacket = try sendAndListenForPacket(message, repeatCount: repeatCount, timeout: timeout, retryCount: retryCount)
+
+        guard let packet = MinimedPacket(encodedData: rfPacket.data) else {
+            throw PumpOpsError.couldNotDecode(rx: rfPacket.data, during: message)
+        }
+
+        guard let response = PumpMessage(rxData: packet.data) else {
+            // Unknown packet type or message type
+            throw PumpOpsError.unknownResponse(rx: packet.data, during: message)
+        }
+
+        guard response.address == message.address else {
+            throw PumpOpsError.crosstalk(response, during: message)
+        }
+
+        return response
+    }
+
+    // Send a PumpMessage, and listens for a packet; used by callers who need to see RSSI
+    /// - Throws:
+    ///     - PumpOpsError.noResponse
+    ///     - PumpOpsError.deviceError
+    func sendAndListenForPacket(_ message: PumpMessage, repeatCount: Int, timeout: TimeInterval, retryCount: Int) throws -> RFPacket {
+        let packet: RFPacket?
+
+        do {
+            packet = try commandSession.sendAndListen(MinimedPacket(outgoingData: message.txData).encodedData(), repeatCount: repeatCount, timeout: timeout, retryCount: retryCount)
+        } catch let error as LocalizedError {
+            throw PumpOpsError.deviceError(error)
+        }
+
+        guard let rfPacket = packet else {
+            throw PumpOpsError.noResponse(during: message)
+        }
+
+        return rfPacket
+    }
+
+    /// - Throws: PumpOpsError.deviceError
+    func listenForPacket(onChannel channel: Int, timeout: TimeInterval) throws -> RFPacket? {
+        do {
+            return try listen(onChannel: channel, timeout: timeout)
+        } catch let error as LocalizedError {
+            throw PumpOpsError.deviceError(error)
+        }
+    }
+}

+ 11 - 98
Dependencies/rileylink_ios/MinimedKit/PumpManager/PumpMessageSender.swift

@@ -8,14 +8,8 @@
 
 import Foundation
 import RileyLinkBLEKit
-import os.log
 
-private let standardPumpResponseWindow: TimeInterval = .milliseconds(200)
-
-private let log = OSLog(category: "PumpMessageSender")
-
-
-protocol PumpMessageSender {
+public protocol PumpMessageSender {
     /// - Throws: LocalizedError
     func resetRadioConfig() throws
 
@@ -25,41 +19,16 @@ protocol PumpMessageSender {
     /// - Throws: LocalizedError
     func setBaseFrequency(_ frequency: Measurement<UnitFrequency>) throws
 
-    /// Sends data to the pump, listening for a reply
-    ///
-    /// - Parameters:
-    ///   - data: The data to send
-    ///   - repeatCount: The number of times to repeat the message before listening begins
-    ///   - timeout: The length of time to listen for a response before timing out
-    ///   - retryCount: The number of times to repeat the send & listen sequence
-    /// - Returns: The packet reply
-    /// - Throws: LocalizedError
-    func sendAndListen(_ data: Data, repeatCount: Int, timeout: TimeInterval, retryCount: Int) throws -> RFPacket
-
     /// - Throws: LocalizedError
     func listen(onChannel channel: Int, timeout: TimeInterval) throws -> RFPacket?
 
     /// - Throws: LocalizedError
-    func send(_ data: Data, onChannel channel: Int, timeout: TimeInterval) throws
-    
-    /// - Throws: LocalizedError
-    func setCCLEDMode(_ mode: RileyLinkLEDMode) throws
-    
+    func send(_ msg: PumpMessage) throws
+
     /// - Throws: LocalizedError
     func getRileyLinkStatistics() throws -> RileyLinkStatistics
-}
 
-extension PumpMessageSender {
-    /// - Throws: PumpOpsError.deviceError
-    func send(_ msg: PumpMessage) throws {
-        do {
-            try send(MinimedPacket(outgoingData: msg.txData).encodedData(), onChannel: 0, timeout: 0)
-        } catch let error as LocalizedError {
-            throw PumpOpsError.deviceError(error)
-        }
-    }
-
-    /// Sends a message to the pump, expecting a specific response body
+    /// Sends a message to the pump, expecting a PumpMessage with specific response body type
     ///
     /// - Parameters:
     ///   - message: The message to send
@@ -76,30 +45,9 @@ extension PumpMessageSender {
     ///     - PumpOpsError.pumpError
     ///     - PumpOpsError.unexpectedResponse
     ///     - PumpOpsError.unknownResponse
-    func getResponse<T: MessageBody>(to message: PumpMessage, responseType: MessageType = .pumpAck, repeatCount: Int = 0, timeout: TimeInterval = standardPumpResponseWindow, retryCount: Int = 3) throws -> T {
-        
-        log.debug("getResponse() Sending: %{public}@, %d, %f, %d)", String(describing: message), repeatCount, timeout, retryCount)
-        
-        let response = try sendAndListen(message, repeatCount: repeatCount, timeout: timeout, retryCount: retryCount)
-
-        guard response.messageType == responseType, let body = response.messageBody as? T else {
-            if let body = response.messageBody as? PumpErrorMessageBody {
-                switch body.errorCode {
-                case .known(let code):
-                    throw PumpOpsError.pumpError(code)
-                case .unknown(let code):
-                    throw PumpOpsError.unknownPumpErrorCode(code)
-                }
-            } else {
-                log.debug("getResponse() Received unexpected response: %{public}@", String(describing: response))
-                throw PumpOpsError.unexpectedResponse(response, from: message)
-            }
-        }
-        log.debug("getResponse() Received: %{public}@", String(describing: response))
-        return body
-    }
+    func getResponse<T: MessageBody>(to message: PumpMessage, responseType: MessageType, repeatCount: Int, timeout: TimeInterval, retryCount: Int) throws -> T
 
-    /// Sends a message to the pump, listening for a message in reply
+    /// Sends a message to the pump, listening for a any known PumpMessage in reply
     ///
     /// - Parameters:
     ///   - message: The message to send
@@ -113,50 +61,15 @@ extension PumpMessageSender {
     ///     - PumpOpsError.deviceError
     ///     - PumpOpsError.noResponse
     ///     - PumpOpsError.unknownResponse
-    func sendAndListen(_ message: PumpMessage, repeatCount: Int = 0, timeout: TimeInterval = standardPumpResponseWindow, retryCount: Int = 3) throws -> PumpMessage {
-        let rfPacket = try sendAndListenForPacket(message, repeatCount: repeatCount, timeout: timeout, retryCount: retryCount)
-
-        guard let packet = MinimedPacket(encodedData: rfPacket.data) else {
-            throw PumpOpsError.couldNotDecode(rx: rfPacket.data, during: message)
-        }
-
-        guard let response = PumpMessage(rxData: packet.data) else {
-            // Unknown packet type or message type
-            throw PumpOpsError.unknownResponse(rx: packet.data, during: message)
-        }
-
-        guard response.address == message.address else {
-            throw PumpOpsError.crosstalk(response, during: message)
-        }
-
-        return response
-    }
+    func sendAndListen(_ message: PumpMessage, repeatCount: Int, timeout: TimeInterval, retryCount: Int) throws -> PumpMessage
 
+    // Send a PumpMessage, and listens for a packet; used by callers who need to see RSSI
     /// - Throws:
     ///     - PumpOpsError.noResponse
     ///     - PumpOpsError.deviceError
-    func sendAndListenForPacket(_ message: PumpMessage, repeatCount: Int = 0, timeout: TimeInterval = standardPumpResponseWindow, retryCount: Int = 3) throws -> RFPacket {
-        let packet: RFPacket?
-
-        do {
-            packet = try sendAndListen(MinimedPacket(outgoingData: message.txData).encodedData(), repeatCount: repeatCount, timeout: timeout, retryCount: retryCount)
-        } catch let error as LocalizedError {
-            throw PumpOpsError.deviceError(error)
-        }
-
-        guard let rfPacket = packet else {
-            throw PumpOpsError.noResponse(during: message)
-        }
-
-        return rfPacket
-    }
+    func sendAndListenForPacket(_ message: PumpMessage, repeatCount: Int, timeout: TimeInterval, retryCount: Int) throws -> RFPacket
 
     /// - Throws: PumpOpsError.deviceError
-    func listenForPacket(onChannel channel: Int, timeout: TimeInterval) throws -> RFPacket? {
-        do {
-            return try listen(onChannel: channel, timeout: timeout)
-        } catch let error as LocalizedError {
-            throw PumpOpsError.deviceError(error)
-        }
-    }
+    func listenForPacket(onChannel channel: Int, timeout: TimeInterval) throws -> RFPacket?
 }
+

+ 34 - 29
Dependencies/rileylink_ios/MinimedKit/PumpManager/PumpOps.swift

@@ -13,20 +13,35 @@ import os.log
 import LoopKit
 
 
-public protocol PumpOpsDelegate: AnyObject {
-    // TODO: Audit clients of this as its called on the session queue
+public protocol PumpOpsDelegate: AnyObject, CommsLogger {
     func pumpOps(_ pumpOps: PumpOps, didChange state: PumpState)
 }
 
+public protocol PumpOps {
+    func runSession(withName name: String, using device: RileyLinkDevice, _ block: @escaping (_ session: PumpOpsSession) -> Void)
+}
+
+extension PumpOps {
+    public func runSession(withName name: String, usingSelector deviceSelector: @escaping (_ completion: @escaping (_ device: RileyLinkDevice?) -> Void) -> Void, _ block: @escaping (_ session: PumpOpsSession?) -> Void) {
+        deviceSelector { (device) in
+            guard let device = device else {
+                block(nil)
+                return
+            }
+
+            self.runSession(withName: name, using: device, block)
+        }
+    }
+}
 
-public class PumpOps {
+public class MinimedPumpOps: PumpOps {
     private let log = OSLog(category: "PumpOps")
 
-    public let pumpSettings: PumpSettings
+    private let pumpSettings: PumpSettings
 
-    public let pumpState: Locked<PumpState>
+    private let pumpState: Locked<PumpState>
 
-    private let configuredDevices: Locked<Set<RileyLinkDevice>> = Locked(Set())
+    private let configuredDevices: Locked<Set<UUID>> = Locked(Set())
 
     // Isolated to RileyLinkDeviceManager.sessionQueue
     private var sessionDevice: RileyLinkDevice?
@@ -46,20 +61,10 @@ public class PumpOps {
         }
     }
 
-    public func runSession(withName name: String, using deviceSelector: @escaping (_ completion: @escaping (_ device: RileyLinkDevice?) -> Void) -> Void, _ block: @escaping (_ session: PumpOpsSession?) -> Void) {
-        deviceSelector { (device) in
-            guard let device = device else {
-                block(nil)
-                return
-            }
-
-            self.runSession(withName: name, using: device, block)
-        }
-    }
-
     public func runSession(withName name: String, using device: RileyLinkDevice, _ block: @escaping (_ session: PumpOpsSession) -> Void) {
         device.runSession(withName: name) { (commandSession) in
-            let session = PumpOpsSession(settings: self.pumpSettings, pumpState: self.pumpState.value, session: commandSession, delegate: self)
+            let minimedPumpMessageSender = MinimedPumpMessageSender(commandSession: commandSession, commsLogger: self.delegate)
+            let session = PumpOpsSession(settings: self.pumpSettings, pumpState: self.pumpState.value, messageSender: minimedPumpMessageSender, delegate: self)
             self.sessionDevice = device
             if !commandSession.firmwareVersion.isUnknown {
                 self.configureDevice(device, with: session)
@@ -74,7 +79,7 @@ public class PumpOps {
 
     // Must be called from within the RileyLinkDevice sessionQueue
     private func configureDevice(_ device: RileyLinkDevice, with session: PumpOpsSession) {
-        guard !self.configuredDevices.value.contains(device) else {
+        guard !self.configuredDevices.value.contains(device.peripheralIdentifier) else {
             return
         }
 
@@ -92,7 +97,7 @@ public class PumpOps {
         NotificationCenter.default.addObserver(self, selector: #selector(deviceRadioConfigDidChange(_:)), name: .DeviceRadioConfigDidChange, object: device)
         NotificationCenter.default.addObserver(self, selector: #selector(deviceRadioConfigDidChange(_:)), name: .DeviceConnectionStateDidChange, object: device)
         _ = configuredDevices.mutate { (value) in
-            value.insert(device)
+            value.insert(device.peripheralIdentifier)
         }
     }
 
@@ -105,45 +110,45 @@ public class PumpOps {
         NotificationCenter.default.removeObserver(self, name: .DeviceConnectionStateDidChange, object: device)
 
         _ = configuredDevices.mutate { (value) in
-            value.remove(device)
+            value.remove(device.peripheralIdentifier)
         }
     }
 }
 
 // Delivered on RileyLinkDeviceManager.sessionQueue
-extension PumpOps: PumpOpsSessionDelegate {
-    func pumpOpsSessionDidChangeRadioConfig(_ session: PumpOpsSession) {
+extension MinimedPumpOps: PumpOpsSessionDelegate {
+    public func pumpOpsSessionDidChangeRadioConfig(_ session: PumpOpsSession) {
         if let sessionDevice = self.sessionDevice {
-            self.configuredDevices.value = [sessionDevice]
+            self.configuredDevices.value = [sessionDevice.peripheralIdentifier]
         }
     }
     
-    func pumpOpsSession(_ session: PumpOpsSession, didChange state: PumpState) {
+    public func pumpOpsSession(_ session: PumpOpsSession, didChange state: PumpState) {
         self.pumpState.value = state
         delegate?.pumpOps(self, didChange: state)
         NotificationCenter.default.post(
             name: .PumpOpsStateDidChange,
             object: self,
-            userInfo: [PumpOps.notificationPumpStateKey: pumpState]
+            userInfo: [MinimedPumpOps.notificationPumpStateKey: pumpState]
         )
     }
 }
 
 
-extension PumpOps: CustomDebugStringConvertible {
+extension MinimedPumpOps: CustomDebugStringConvertible {
     public var debugDescription: String {
         return [
             "### PumpOps",
             "pumpSettings: \(String(reflecting: pumpSettings))",
             "pumpState: \(String(reflecting: pumpState.value))",
-            "configuredDevices: \(configuredDevices.value.map({ $0.peripheralIdentifier.uuidString }))",
+            "configuredDevices: \(configuredDevices.value.map({ $0.uuidString }))",
         ].joined(separator: "\n")
     }
 }
 
 
 /// Provide a notification contract that clients can use to inform RileyLink UI of changes to PumpOps.PumpState
-extension PumpOps {
+extension MinimedPumpOps {
     public static let notificationPumpStateKey = "com.rileylink.RileyLinkKit.PumpOps.PumpState"
 }
 

+ 2 - 31
Dependencies/rileylink_ios/MinimedKit/PumpManager/PumpOpsError.swift

@@ -38,35 +38,6 @@ extension PumpOpsError: LocalizedError {
     public var errorDescription: String? {
         switch self {
         case .bolusInProgress:
-            return nil
-        case .couldNotDecode:
-            return LocalizedString("Decoding Error", comment: "Error description")
-        case .crosstalk:
-            return nil
-        case .deviceError:
-            return LocalizedString("Device Error", comment: "Error description")
-        case .noResponse:
-            return nil
-        case .pumpError:
-            return LocalizedString("Pump Error", comment: "Error description")
-        case .pumpSuspended:
-            return nil
-        case .rfCommsFailure:
-            return nil
-        case .unexpectedResponse:
-            return nil
-        case .unknownPumpErrorCode:
-            return nil
-        case .unknownPumpModel:
-            return nil
-        case .unknownResponse:
-            return nil
-        }
-    }
-
-    public var failureReason: String? {
-        switch self {
-        case .bolusInProgress:
             return LocalizedString("A bolus is already in progress", comment: "Communications error for a bolus currently running")
         case .couldNotDecode(rx: let data, during: let during):
             return String(format: LocalizedString("Invalid response during %1$@: %2$@", comment: "Format string for failure reason. (1: The operation being performed) (2: The response data)"), String(describing: during), data.hexadecimalString)
@@ -78,8 +49,8 @@ extension PumpOpsError: LocalizedError {
             return LocalizedString("Pump is suspended", comment: "")
         case .rfCommsFailure(let msg):
             return msg
-        case .unexpectedResponse:
-            return LocalizedString("Pump responded unexpectedly", comment: "")
+        case .unexpectedResponse(let response, _):
+            return String(format: LocalizedString("Unexpected response %1$@", comment: "Format string for an unexpectedResponse. (2: The response)"), String(describing: response))
         case .unknownPumpErrorCode(let code):
             return String(format: LocalizedString("Unknown pump error code: %1$@", comment: "The format string description of an unknown pump error code. (1: The specific error code raw value)"), String(describing: code))
         case .unknownPumpModel(let model):

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

@@ -9,7 +9,7 @@ import HealthKit
 import RileyLinkBLEKit
 
 
-extension RileyLinkDevice.Status {
+extension RileyLinkDeviceStatus {
     func device(pumpID: String, pumpModel: PumpModel) -> HKDevice {
         return HKDevice(
             name: name,

+ 14 - 32
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")
         }
     }
 
@@ -258,8 +258,8 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti
 // MARK: - UnfinalizedDose
 
 extension UnfinalizedDose {
-    var newPumpEvent: NewPumpEvent {
-        return NewPumpEvent(self)
+    func newPumpEvent(forceFinalization: Bool = false) -> NewPumpEvent {
+        return NewPumpEvent(self, forceFinalization: forceFinalization)
     }
 }
 
@@ -268,46 +268,28 @@ extension UnfinalizedDose {
 
 
 extension NewPumpEvent {
-    init(_ dose: UnfinalizedDose) {
-        let entry = DoseEntry(dose)
+    init(_ dose: UnfinalizedDose, forceFinalization: Bool = false) {
+        let entry = DoseEntry(dose, forceFinalization: forceFinalization)
         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
 
 extension DoseEntry {
-    init (_ dose: UnfinalizedDose) {
+    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)
+            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)
+            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 {

+ 34 - 100
Dependencies/rileylink_ios/MinimedKitTests/MinimedPumpManagerTests.swift

@@ -7,112 +7,46 @@
 //
 
 import XCTest
+import RileyLinkBLEKit
 @testable import MinimedKit
 import LoopKit
 
 class MinimedPumpManagerTests: XCTestCase {
 
-    func testPendingDoseUpdatesWithActualDeliveryFromHistoryDose() {
-        
-        let bolusTime = Date().addingTimeInterval(-TimeInterval(minutes: 5));
-        
-        let bolusEventTime = bolusTime.addingTimeInterval(2)
-
-        let cancelTime = bolusEventTime.addingTimeInterval(TimeInterval(minutes: 1))
-
-        let unfinalizedBolus = UnfinalizedDose(bolusAmount: 5.4, startTime: bolusTime, duration: TimeInterval(200), insulinType: .novolog, automatic: false, isReconciledWithHistory: false)
-        
-        // 5.4 bolus interrupted at 1.0 units
-        let eventDose = DoseEntry(type: .bolus, startDate: bolusEventTime, endDate: cancelTime, value: unfinalizedBolus.units, unit: .units, deliveredUnits: 1.0)
-        
-        let bolusEvent = NewPumpEvent(
-            date: bolusEventTime,
-            dose: eventDose,
-            raw: Data(hexadecimalString: "abcdef")!,
-            title: "Test Bolus",
-            type: .bolus)
-        
-        let result = MinimedPumpManager.reconcilePendingDosesWith([bolusEvent], reconciliationMappings: [:], pendingDoses: [unfinalizedBolus])
-        
-        // Should mark pending bolus as reconciled
-        XCTAssertEqual(1, result.pendingDoses.count)
-        let pendingBolus = result.pendingDoses.first!
-        XCTAssertEqual(true, pendingBolus.isReconciledWithHistory)
-        
-        // Pending bolus should be updated with actual delivery amount
-        XCTAssertEqual(1.0, pendingBolus.units)
-        XCTAssertEqual(5.4, pendingBolus.programmedUnits)
-        XCTAssertEqual(TimeInterval(minutes: 1), pendingBolus.duration)
-        XCTAssertEqual(true, pendingBolus.isFinished)
-    }
-    
-    func testReconciledDosesShouldOnlyAppearInReturnedPendingDoses() {
-        
-        let bolusTime = Date().addingTimeInterval(-TimeInterval(minutes: 5));
-
-        // Shows up in history 2 seconds later
-        let bolusEventTime = bolusTime.addingTimeInterval(2)
-        
-        let bolusAmount = 1.5
-        
-        let bolusDuration = PumpModel.model523.bolusDeliveryTime(units: bolusAmount)
-
-        let unfinalizedBolus = UnfinalizedDose(bolusAmount: bolusAmount, startTime: bolusTime, duration: bolusDuration, insulinType: .novolog, automatic: false, isReconciledWithHistory: false)
-        
-        let eventDose = DoseEntry(type: .bolus, startDate: bolusEventTime, endDate: bolusEventTime.addingTimeInterval(bolusDuration), value: bolusAmount, unit: .units, deliveredUnits: bolusAmount)
-        
-        let bolusEvent = NewPumpEvent(
-            date: bolusEventTime,
-            dose: eventDose,
-            raw: Data(hexadecimalString: "abcdef")!,
-            title: "Test Bolus",
-            type: .bolus)
-        
-        let result = MinimedPumpManager.reconcilePendingDosesWith([bolusEvent], reconciliationMappings: [:], pendingDoses: [unfinalizedBolus])
-        
-        // Should mark pending bolus as reconciled
-        XCTAssertEqual(1, result.pendingDoses.count)
-        let pendingBolus = result.pendingDoses.first!
-        XCTAssertEqual(true, pendingBolus.isReconciledWithHistory)
-        
-        XCTAssertEqual(1, result.reconciliationMappings.count)
-        XCTAssertEqual(unfinalizedBolus.uuid, result.reconciliationMappings[bolusEvent.raw]?.uuid)
-        XCTAssertEqual(unfinalizedBolus.startTime, result.reconciliationMappings[bolusEvent.raw]?.startTime)
-
-        // Bolus should not be returned as history event
-        XCTAssert(result.remainingEvents.isEmpty)
+    var rlProvider: MockRileyLinkProvider!
+    var mockPumpManagerDelegate: MockPumpManagerDelegate!
+    var pumpManager: MinimedPumpManager!
+
+    override func setUpWithError() throws {
+        let device = MockRileyLinkDevice()
+        rlProvider = MockRileyLinkProvider(devices: [device])
+        let rlManagerState = RileyLinkConnectionState(autoConnectIDs: [])
+        let state = MinimedPumpManagerState(
+            isOnboarded: true,
+            useMySentry: true,
+            pumpColor: .blue,
+            pumpID: "123456",
+            pumpModel: .model523,
+            pumpFirmwareVersion: "VER 2.4A1.1",
+            pumpRegion: .northAmerica,
+            rileyLinkConnectionState: rlManagerState,
+            timeZone: .currentFixed,
+            suspendState: .resumed(Date()),
+            insulinType: .novolog,
+            lastTuned: nil,
+            lastValidFrequency: nil)
+        let pumpOps = MockPumpOps(pumpState: state.pumpState, pumpSettings: state.pumpSettings)
+        pumpManager = MinimedPumpManager(state: state, rileyLinkDeviceProvider: rlProvider, pumpOps: pumpOps)
+        mockPumpManagerDelegate = MockPumpManagerDelegate()
+        pumpManager.pumpManagerDelegate = mockPumpManagerDelegate
     }
-    
-    func testReconciledDosesShouldNotAppearInReturnedPumpEvents() {
-        
-        let bolusTime = Date().addingTimeInterval(-TimeInterval(minutes: 5));
 
-        // Shows up in history 2 seconds later
-        let bolusEventTime = bolusTime.addingTimeInterval(2)
-        
-        let bolusAmount = 1.5
-        
-        let bolusDuration = PumpModel.model523.bolusDeliveryTime(units: bolusAmount)
-
-        let eventDose = DoseEntry(type: .bolus, startDate: bolusEventTime, endDate: bolusEventTime.addingTimeInterval(bolusDuration), value: bolusAmount, unit: .units, deliveredUnits: bolusAmount)
-        
-        let bolusEvent = NewPumpEvent(
-            date: bolusEventTime,
-            dose: eventDose,
-            raw: Data(hexadecimalString: "abcdef")!,
-            title: "Test Bolus",
-            type: .bolus)
-        
-        
-        
-        let reconciliationMappings: [Data:ReconciledDoseMapping] = [
-            bolusEvent.raw : ReconciledDoseMapping(startTime: bolusTime, uuid: UUID(), eventRaw: bolusEvent.raw)
-        ]
-        
-        let result = MinimedPumpManager.reconcilePendingDosesWith([bolusEvent], reconciliationMappings: reconciliationMappings, pendingDoses: [])
-        
-        // Bolus should not be returned as history event
-        XCTAssert(result.remainingEvents.isEmpty)
+    func testBolusWithInvalidResponse() {
+        let exp = expectation(description: "enactBolus callback")
+        pumpManager.enactBolus(units: 2.3, activationType: .manualNoRecommendation) { error in
+            XCTAssertNotNil(error)
+            exp.fulfill()
+        }
+        waitForExpectations(timeout: 2)
     }
-
 }

+ 62 - 0
Dependencies/rileylink_ios/MinimedKitTests/Mocks/MockPumpManagerDelegate.swift

@@ -0,0 +1,62 @@
+//
+//  MockPumpManagerDelegate.swift
+//  MinimedKitTests
+//
+//  Created by Pete Schwamb on 9/5/22.
+//  Copyright © 2022 Pete Schwamb. All rights reserved.
+//
+
+import Foundation
+import LoopKit
+
+class MockPumpManagerDelegate: PumpManagerDelegate {
+
+    func pumpManagerBLEHeartbeatDidFire(_ pumpManager: PumpManager) {}
+
+    func pumpManagerMustProvideBLEHeartbeat(_ pumpManager: PumpManager) -> Bool {
+        return false
+    }
+
+    func pumpManagerWillDeactivate(_ pumpManager: PumpManager) {}
+
+    func pumpManagerPumpWasReplaced(_ pumpManager: PumpManager) {}
+
+    func pumpManager(_ pumpManager: PumpManager, didUpdatePumpRecordsBasalProfileStartEvents pumpRecordsBasalProfileStartEvents: Bool) {}
+
+    func pumpManager(_ pumpManager: PumpManager, didError error: PumpManagerError) {}
+
+    var reportedPumpEvents: [(events: [NewPumpEvent], lastReconciliation: Date?)] = []
+    
+    func pumpManager(_ pumpManager: PumpManager, hasNewPumpEvents events: [NewPumpEvent], lastReconciliation: Date?, completion: @escaping (Error?) -> Void) {
+        reportedPumpEvents.append((events: events, lastReconciliation: lastReconciliation))
+    }
+
+    func pumpManager(_ pumpManager: PumpManager, didReadReservoirValue units: Double, at date: Date, completion: @escaping (Result<(newValue: ReservoirValue, lastValue: ReservoirValue?, areStoredValuesContinuous: Bool), Error>) -> Void) {}
+
+    func pumpManager(_ pumpManager: PumpManager, didAdjustPumpClockBy adjustment: TimeInterval) {}
+
+    func pumpManagerDidUpdateState(_ pumpManager: PumpManager) {}
+
+    func startDateToFilterNewPumpEvents(for manager: PumpManager) -> Date {
+        return Date()
+    }
+
+    var detectedSystemTimeOffset: TimeInterval = 0
+
+    func deviceManager(_ manager: DeviceManager, logEventForDeviceIdentifier deviceIdentifier: String?, type: DeviceLogEntryType, message: String, completion: ((Error?) -> Void)?) {}
+
+    func pumpManager(_ pumpManager: PumpManager, didUpdate status: PumpManagerStatus, oldStatus: PumpManagerStatus) {}
+
+    func issueAlert(_ alert: Alert) {}
+
+    func retractAlert(identifier: Alert.Identifier) {}
+
+    func doesIssuedAlertExist(identifier: Alert.Identifier, completion: @escaping (Result<Bool, Error>) -> Void) {}
+
+    func lookupAllUnretracted(managerIdentifier: String, completion: @escaping (Result<[PersistedAlert], Error>) -> Void) {}
+
+    func lookupAllUnacknowledgedUnretracted(managerIdentifier: String, completion: @escaping (Result<[PersistedAlert], Error>) -> Void) {}
+
+    func recordRetractedAlert(_ alert: Alert, at date: Date) {}
+
+}

+ 35 - 0
Dependencies/rileylink_ios/MinimedKitTests/Mocks/MockPumpOps.swift

@@ -0,0 +1,35 @@
+//
+//  MockPumpOps.swift
+//  MinimedKitTests
+//
+//  Created by Pete Schwamb on 9/5/22.
+//  Copyright © 2022 Pete Schwamb. All rights reserved.
+//
+
+import Foundation
+import MinimedKit
+import RileyLinkBLEKit
+
+class MockPumpOps: PumpOps, PumpOpsSessionDelegate {
+
+    var pumpState: PumpState
+
+    var pumpSettings: PumpSettings
+
+    func pumpOpsSession(_ session: MinimedKit.PumpOpsSession, didChange state: MinimedKit.PumpState) {
+        pumpState = state
+    }
+
+    func pumpOpsSessionDidChangeRadioConfig(_ session: MinimedKit.PumpOpsSession) { }
+
+    public func runSession(withName name: String, using device: RileyLinkDevice, _ block: @escaping (_ session: PumpOpsSession) -> Void) {
+        let minimedPumpMessageSender = MockPumpMessageSender()
+        let session = PumpOpsSession(settings: self.pumpSettings, pumpState: self.pumpState, messageSender: minimedPumpMessageSender, delegate: self)
+        block(session)
+    }
+
+    init(pumpState: PumpState, pumpSettings: PumpSettings) {
+        self.pumpState = pumpState
+        self.pumpSettings = pumpSettings
+    }
+}

+ 75 - 0
Dependencies/rileylink_ios/MinimedKitTests/Mocks/MockRileyLinkDevice.swift

@@ -0,0 +1,75 @@
+//
+//  MockRileyLinkDevice.swift
+//  MinimedKitTests
+//
+//  Created by Pete Schwamb on 9/5/22.
+//  Copyright © 2022 Pete Schwamb. All rights reserved.
+//
+
+import Foundation
+import RileyLinkBLEKit
+import CoreBluetooth
+
+class MockRileyLinkDevice: RileyLinkDevice {
+    var isConnected: Bool = true
+
+    var rlFirmwareDescription: String = "Mock"
+
+    var hasOrangeLinkService: Bool = false
+
+    var hardwareType: RileyLinkHardwareType? = .riley
+
+    var rssi: Int? = nil
+
+    var name: String? = "Mock"
+
+    var deviceURI: String =  "rileylink://Mock"
+
+    var peripheralIdentifier: UUID = UUID()
+
+    var peripheralState: CBPeripheralState = .connected
+
+    func readRSSI() {}
+
+    func setCustomName(_ name: String) {}
+
+    func updateBatteryLevel() {}
+
+    func orangeAction(_ command: OrangeLinkCommand) {}
+
+    func setOrangeConfig(_ config: OrangeLinkConfigurationSetting, isOn: Bool) {}
+
+    func orangeWritePwd() {}
+
+    func orangeClose() {}
+
+    func orangeReadSet() {}
+
+    func orangeReadVDC() {}
+
+    func findDevice() {}
+
+    func setDiagnosticeLEDModeForBLEChip(_ mode: RileyLinkLEDMode) {}
+
+    func readDiagnosticLEDModeForBLEChip(completion: @escaping (RileyLinkLEDMode?) -> Void) {}
+
+    func assertOnSessionQueue() {}
+
+    func sessionQueueAsyncAfter(deadline: DispatchTime, execute: @escaping () -> Void) {}
+
+    func runSession(withName name: String, _ block: @escaping (CommandSession) -> Void) {
+        assertionFailure("MockRileyLinkDevice.runSession should not be called during testing.  Use MockPumpOps for communication stubs.")
+    }
+
+    func getStatus(_ completion: @escaping (RileyLinkDeviceStatus) -> Void) {
+        completion(RileyLinkDeviceStatus(
+            lastIdle: Date(),
+            name: name,
+            version: rlFirmwareDescription,
+            ledOn: false,
+            vibrationOn: false,
+            voltage: 3.0,
+            battery: nil,
+            hasPiezo: false))
+    }
+}

+ 55 - 0
Dependencies/rileylink_ios/MinimedKitTests/Mocks/MockRileyLinkProvider.swift

@@ -0,0 +1,55 @@
+//
+//  MockRileyLinkProvider.swift
+//  MinimedKitTests
+//
+//  Created by Pete Schwamb on 9/5/22.
+//  Copyright © 2022 Pete Schwamb. All rights reserved.
+//
+
+import Foundation
+import RileyLinkBLEKit
+
+class MockRileyLinkProvider: RileyLinkDeviceProvider {
+
+    init(devices: [RileyLinkDevice]) {
+        self.devices = devices
+    }
+
+    var devices: [RileyLinkDevice]
+
+    var delegate: RileyLinkDeviceProviderDelegate?
+
+    var idleListeningState: RileyLinkBluetoothDevice.IdleListeningState = .disabled
+
+    var idleListeningEnabled: Bool = false
+
+    var timerTickEnabled: Bool = false
+
+    var connectingCount: Int = 0
+
+    func deprioritize(_ device: RileyLinkDevice, completion: (() -> Void)?) {
+    }
+
+    func assertIdleListening(forcingRestart: Bool) {
+    }
+
+    func getDevices(_ completion: @escaping ([RileyLinkDevice]) -> Void) {
+        completion(devices)
+    }
+
+    func connect(_ device: RileyLinkDevice) {
+    }
+
+    func disconnect(_ device: RileyLinkDevice) {
+    }
+
+    func setScanningEnabled(_ enabled: Bool) {
+    }
+
+    func shouldConnect(to deviceID: String) -> Bool {
+        return false
+    }
+
+    var debugDescription: String = "MockRileyLinkProvider"
+
+}

+ 10 - 10
Dependencies/rileylink_ios/MinimedKitTests/PumpOpsSynchronousBuildFromFramesTests.swift

@@ -20,7 +20,7 @@ class PumpOpsSynchronousBuildFromFramesTests: XCTestCase {
     var pumpID: String!
     var pumpRegion: PumpRegion!
     var pumpModel: PumpModel!
-    var messageSenderStub: PumpMessageSenderStub!
+    var mockPumpMessageSender: MockPumpMessageSender!
     var timeZone: TimeZone!
     
     override func setUp() {
@@ -30,7 +30,7 @@ class PumpOpsSynchronousBuildFromFramesTests: XCTestCase {
         pumpRegion = .worldWide
         pumpModel = PumpModel.model523
 
-        messageSenderStub = PumpMessageSenderStub()
+        mockPumpMessageSender = MockPumpMessageSender()
         timeZone = TimeZone(secondsFromGMT: 0)
         
         loadSUT()
@@ -42,11 +42,11 @@ class PumpOpsSynchronousBuildFromFramesTests: XCTestCase {
         pumpState.pumpModel = pumpModel
         pumpState.awakeUntil = Date(timeIntervalSinceNow: 100) // pump is awake
         
-        sut = PumpOpsSession(settings: pumpSettings, pumpState: pumpState, session: messageSenderStub, delegate: messageSenderStub)
+        sut = PumpOpsSession(settings: pumpSettings, pumpState: pumpState, messageSender: mockPumpMessageSender, delegate: mockPumpMessageSender)
     }
     
     func testErrorIsntThrown() {
-        messageSenderStub.responses = buildResponsesDictionary()
+        mockPumpMessageSender.responses = buildResponsesDictionary()
         
         assertNoThrow(try _ = sut.getHistoryEvents(since: Date()))
     }
@@ -58,7 +58,7 @@ class PumpOpsSynchronousBuildFromFramesTests: XCTestCase {
         pumpAckArray.insert(message, at: 0)
         responseDictionary[.getHistoryPage]! = pumpAckArray
         
-        messageSenderStub.responses = responseDictionary
+        mockPumpMessageSender.responses = responseDictionary
         
         // Didn't receive a .pumpAck short reponse so throw an error
         assertThrows(try _ = sut.getHistoryEvents(since: Date()))
@@ -71,14 +71,14 @@ class PumpOpsSynchronousBuildFromFramesTests: XCTestCase {
         pumpAckArray.insert(message, at: 1)
         responseDictionary[.getHistoryPage]! = pumpAckArray
         
-        messageSenderStub.responses = responseDictionary
+        mockPumpMessageSender.responses = responseDictionary
         
         // Didn't receive a .getHistoryPage as 2nd response so throw an error
         assertThrows(try _ = sut.getHistoryEvents(since: Date()))
     }
     
     func test332EventsReturnedUntilOutOrder() {
-        messageSenderStub.responses = buildResponsesDictionary()
+        mockPumpMessageSender.responses = buildResponsesDictionary()
         
         let date = Date(timeIntervalSince1970: 0)
         do {
@@ -91,7 +91,7 @@ class PumpOpsSynchronousBuildFromFramesTests: XCTestCase {
     }
     
     func testEventsReturnedAfterTime() {
-        messageSenderStub.responses = buildResponsesDictionary()
+        mockPumpMessageSender.responses = buildResponsesDictionary()
         timeZone = TimeZone.current
         
         loadSUT()
@@ -109,7 +109,7 @@ class PumpOpsSynchronousBuildFromFramesTests: XCTestCase {
     }
     
     func testGMTEventsAreTheSame() {
-        messageSenderStub.responses = buildResponsesDictionary()
+        mockPumpMessageSender.responses = buildResponsesDictionary()
         timeZone = TimeZone(secondsFromGMT:0)
         
         loadSUT()
@@ -126,7 +126,7 @@ class PumpOpsSynchronousBuildFromFramesTests: XCTestCase {
     }
     
     func testEventsReturnedAreAscendingOrder() {
-        messageSenderStub.responses = buildResponsesDictionary()
+        mockPumpMessageSender.responses = buildResponsesDictionary()
         
         //02/11/2017 @ 12:00am (UTC)
         let date = DateComponents(calendar: Calendar.current, timeZone: pumpState.timeZone, year: 2017, month: 2, day: 11, hour: 0, minute: 0, second: 0).date!

+ 86 - 11
Dependencies/rileylink_ios/MinimedKitTests/PumpOpsSynchronousTests.swift

@@ -20,7 +20,7 @@ class PumpOpsSynchronousTests: XCTestCase {
     var pumpID: String!
     var pumpRegion: PumpRegion!
     var pumpModel: PumpModel!
-    var messageSenderStub: PumpMessageSenderStub!
+    var mockMessageSender: MockPumpMessageSender!
 
     let dateComponents2007 = DateComponents(calendar: Calendar.current, year: 2007, month: 1, day: 1)
     let dateComponents2017 = DateComponents(calendar: Calendar.current, year: 2017, month: 1, day: 1)
@@ -46,7 +46,7 @@ class PumpOpsSynchronousTests: XCTestCase {
         pumpRegion = .worldWide
         pumpModel = PumpModel.model523
 
-        messageSenderStub = PumpMessageSenderStub()
+        mockMessageSender = MockPumpMessageSender()
         
         setUpSUT()
     }
@@ -58,7 +58,7 @@ class PumpOpsSynchronousTests: XCTestCase {
         pumpState.pumpModel = pumpModel
         pumpState.awakeUntil = Date(timeIntervalSinceNow: 100) // pump is awake
         
-        sut = PumpOpsSession(settings: pumpSettings, pumpState: pumpState, session: messageSenderStub, delegate: messageSenderStub)
+        sut = PumpOpsSession(settings: pumpSettings, pumpState: pumpState, messageSender: mockMessageSender, delegate: mockMessageSender)
     }
     
     /// Duplicates logic in setUp with a new PumpModel
@@ -312,7 +312,13 @@ func randomDataString(length:Int) -> String {
     return s
 }
 
-class PumpMessageSenderStub: PumpMessageSender {
+class MockPumpMessageSender: PumpMessageSender {
+
+    func listenForPacket(onChannel channel: Int, timeout: TimeInterval) throws -> RileyLinkBLEKit.RFPacket? {
+        // do nothing
+        return nil
+    }
+
     func getRileyLinkStatistics() throws -> RileyLinkStatistics {
         throw PumpOpsError.noResponse(during: "Tests")
     }
@@ -344,7 +350,8 @@ class PumpMessageSenderStub: PumpMessageSender {
 
             response = responseArray[numberOfResponsesReceived]
         } else {
-            response = PumpMessage(rxData: Data())!
+            let packet = MinimedPacket(encodedData: Data(hexadecimalString: "a969a39966b1566555b235")!)!
+            response = PumpMessage(rxData: packet.data)!
         }
 
         var encoded = MinimedPacket(outgoingData: response.txData).encodedData()
@@ -357,11 +364,83 @@ class PumpMessageSenderStub: PumpMessageSender {
         return rfPacket
     }
 
+    func getResponse<T: MessageBody>(to message: PumpMessage, responseType: MessageType, repeatCount: Int, timeout: TimeInterval, retryCount: Int) throws -> T {
+
+        let response = try sendAndListen(message, repeatCount: repeatCount, timeout: timeout, retryCount: retryCount)
+
+        guard response.messageType == responseType, let body = response.messageBody as? T else {
+            if let body = response.messageBody as? PumpErrorMessageBody {
+                switch body.errorCode {
+                case .known(let code):
+                    throw PumpOpsError.pumpError(code)
+                case .unknown(let code):
+                    throw PumpOpsError.unknownPumpErrorCode(code)
+                }
+            } else {
+                throw PumpOpsError.unexpectedResponse(response, from: message)
+            }
+        }
+        return body
+    }
+
+    /// Sends a message to the pump, listening for a any known PumpMessage in reply
+    ///
+    /// - Parameters:
+    ///   - message: The message to send
+    ///   - repeatCount: The number of times to repeat the message before listening begins
+    ///   - timeout: The length of time to listen for a pump response
+    ///   - retryCount: The number of times to repeat the send & listen sequence
+    /// - Returns: The message reply
+    /// - Throws: An error describing a failure in the sending or receiving of a message:
+    ///     - PumpOpsError.couldNotDecode
+    ///     - PumpOpsError.crosstalk
+    ///     - PumpOpsError.deviceError
+    ///     - PumpOpsError.noResponse
+    ///     - PumpOpsError.unknownResponse
+    func sendAndListen(_ message: PumpMessage, repeatCount: Int, timeout: TimeInterval, retryCount: Int) throws -> PumpMessage {
+        let rfPacket = try sendAndListenForPacket(message, repeatCount: repeatCount, timeout: timeout, retryCount: retryCount)
+
+        guard let packet = MinimedPacket(encodedData: rfPacket.data) else {
+            throw PumpOpsError.couldNotDecode(rx: rfPacket.data, during: message)
+        }
+
+        guard let response = PumpMessage(rxData: packet.data) else {
+            // Unknown packet type or message type
+            throw PumpOpsError.unknownResponse(rx: packet.data, during: message)
+        }
+
+        guard response.address == message.address else {
+            throw PumpOpsError.crosstalk(response, during: message)
+        }
+
+        return response
+    }
+
+    // Send a PumpMessage, and listens for a packet; used by callers who need to see RSSI
+    /// - Throws:
+    ///     - PumpOpsError.noResponse
+    ///     - PumpOpsError.deviceError
+    func sendAndListenForPacket(_ message: PumpMessage, repeatCount: Int, timeout: TimeInterval, retryCount: Int) throws -> RFPacket {
+        let packet: RFPacket?
+
+        do {
+            packet = try sendAndListen(MinimedPacket(outgoingData: message.txData).encodedData(), repeatCount: repeatCount, timeout: timeout, retryCount: retryCount)
+        } catch let error as LocalizedError {
+            throw PumpOpsError.deviceError(error)
+        }
+
+        guard let rfPacket = packet else {
+            throw PumpOpsError.noResponse(during: message)
+        }
+
+        return rfPacket
+    }
+
     func listen(onChannel channel: Int, timeout: TimeInterval) throws -> RFPacket? {
         throw PumpOpsError.noResponse(during: "Tests")
     }
 
-    func send(_ data: Data, onChannel channel: Int, timeout: TimeInterval) throws {
+    func send(_ msg: MinimedKit.PumpMessage) throws {
         // Do nothing
     }
 
@@ -377,17 +456,13 @@ class PumpMessageSenderStub: PumpMessageSender {
         throw PumpOpsError.noResponse(during: "Tests")
     }
     
-    func setCCLEDMode(_ mode: RileyLinkLEDMode) throws {
-        throw PumpOpsError.noResponse(during: "Tests")
-    }
-    
     var responses = [MessageType: [PumpMessage]]()
     
     // internal tracking of how many times a response type has been received
     private var responsesHaveOccured = [MessageType: Int]()
 }
 
-extension PumpMessageSenderStub: PumpOpsSessionDelegate {
+extension MockPumpMessageSender: PumpOpsSessionDelegate {
     func pumpOpsSession(_ session: PumpOpsSession, didChange state: PumpState) {
 
     }

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

@@ -0,0 +1,134 @@
+//
+//  ReconciliationTests.swift
+//  MinimedKitTests
+//
+//  Created by Pete Schwamb on 9/5/22.
+//  Copyright © 2022 Pete Schwamb. All rights reserved.
+//
+
+import XCTest
+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));
+
+        let bolusEventTime = bolusTime.addingTimeInterval(2)
+
+        let cancelTime = bolusEventTime.addingTimeInterval(TimeInterval(minutes: 1))
+
+        let unfinalizedBolus = UnfinalizedDose(bolusAmount: 5.4, startTime: bolusTime, duration: TimeInterval(200), insulinType: .novolog, automatic: false, isReconciledWithHistory: false)
+
+        // 5.4 bolus interrupted at 1.0 units
+        let eventDose = DoseEntry(type: .bolus, startDate: bolusEventTime, endDate: cancelTime, value: unfinalizedBolus.units, unit: .units, deliveredUnits: 1.0)
+
+        let bolusEvent = NewPumpEvent(
+            date: bolusEventTime,
+            dose: eventDose,
+            raw: Data(hexadecimalString: "abcdef")!,
+            title: "Test Bolus",
+            type: .bolus)
+
+        let result = MinimedPumpManager.reconcilePendingDosesWith([bolusEvent], reconciliationMappings: [:], pendingDoses: [unfinalizedBolus])
+
+        // Should mark pending bolus as reconciled
+        XCTAssertEqual(1, result.pendingDoses.count)
+        let pendingBolus = result.pendingDoses.first!
+        XCTAssertEqual(true, pendingBolus.isReconciledWithHistory)
+
+        // Pending bolus should be updated with actual delivery amount
+        XCTAssertEqual(1.0, pendingBolus.units)
+        XCTAssertEqual(5.4, pendingBolus.programmedUnits)
+        XCTAssertEqual(TimeInterval(minutes: 1), pendingBolus.duration)
+        XCTAssertEqual(true, pendingBolus.isFinished)
+    }
+
+    func testReconciledDosesShouldOnlyAppearInReturnedPendingDoses() {
+
+        let bolusTime = Date().addingTimeInterval(-TimeInterval(minutes: 5));
+
+        // Shows up in history 2 seconds later
+        let bolusEventTime = bolusTime.addingTimeInterval(2)
+
+        let bolusAmount = 1.5
+
+        let bolusDuration = PumpModel.model523.bolusDeliveryTime(units: bolusAmount)
+
+        let unfinalizedBolus = UnfinalizedDose(bolusAmount: bolusAmount, startTime: bolusTime, duration: bolusDuration, insulinType: .novolog, automatic: false, isReconciledWithHistory: false)
+
+        let eventDose = DoseEntry(type: .bolus, startDate: bolusEventTime, endDate: bolusEventTime.addingTimeInterval(bolusDuration), value: bolusAmount, unit: .units, deliveredUnits: bolusAmount)
+
+        let bolusEvent = NewPumpEvent(
+            date: bolusEventTime,
+            dose: eventDose,
+            raw: Data(hexadecimalString: "abcdef")!,
+            title: "Test Bolus",
+            type: .bolus)
+
+        let result = MinimedPumpManager.reconcilePendingDosesWith([bolusEvent], reconciliationMappings: [:], pendingDoses: [unfinalizedBolus])
+
+        // Should mark pending bolus as reconciled
+        XCTAssertEqual(1, result.pendingDoses.count)
+        let pendingBolus = result.pendingDoses.first!
+        XCTAssertEqual(true, pendingBolus.isReconciledWithHistory)
+
+        XCTAssertEqual(1, result.reconciliationMappings.count)
+        XCTAssertEqual(unfinalizedBolus.uuid, result.reconciliationMappings[bolusEvent.raw]?.uuid)
+        XCTAssertEqual(unfinalizedBolus.startTime, result.reconciliationMappings[bolusEvent.raw]?.startTime)
+
+        // Bolus should not be returned as history event
+        XCTAssert(result.remainingEvents.isEmpty)
+    }
+
+    func testReconciledDosesShouldNotAppearInReturnedPumpEvents() {
+
+        let bolusTime = Date().addingTimeInterval(-TimeInterval(minutes: 5));
+
+        // Shows up in history 2 seconds later
+        let bolusEventTime = bolusTime.addingTimeInterval(2)
+
+        let bolusAmount = 1.5
+
+        let bolusDuration = PumpModel.model523.bolusDeliveryTime(units: bolusAmount)
+
+        let eventDose = DoseEntry(type: .bolus, startDate: bolusEventTime, endDate: bolusEventTime.addingTimeInterval(bolusDuration), value: bolusAmount, unit: .units, deliveredUnits: bolusAmount)
+
+        let bolusEvent = NewPumpEvent(
+            date: bolusEventTime,
+            dose: eventDose,
+            raw: Data(hexadecimalString: "abcdef")!,
+            title: "Test Bolus",
+            type: .bolus)
+
+
+
+        let reconciliationMappings: [Data:ReconciledDoseMapping] = [
+            bolusEvent.raw : ReconciledDoseMapping(startTime: bolusTime, uuid: UUID(), eventRaw: bolusEvent.raw)
+        ]
+
+        let result = MinimedPumpManager.reconcilePendingDosesWith([bolusEvent], reconciliationMappings: reconciliationMappings, pendingDoses: [])
+
+        // Bolus should not be returned as history event
+        XCTAssert(result.remainingEvents.isEmpty)
+    }
+}

+ 5 - 0
Dependencies/rileylink_ios/MinimedKitTests/TimestampedHistoryEventTests.swift

@@ -69,6 +69,11 @@ class TimestampedHistoryEventTests: XCTestCase {
 
     }
 
+    func testBolusOnX22() {
+        let bolus = BolusNormalPumpEvent(availableData: Data(hexadecimalString: "567901e443494eda97dbfd38150216f3")!, pumpModel: .model522)!
+        XCTAssertEqual(bolus.wasRemotelyTriggered, true)
+    }
+
     func testSquareWaveIsMutableOnX23() {
         let squareBolus = BolusNormalPumpEvent(availableData: Data(hexadecimalString: "010080008000240209a24a1510")!, pumpModel: .model523)!
         let squareBolusTimestamp = squareBolus.timestamp.date!

+ 1 - 1
Dependencies/rileylink_ios/MinimedKitUI/CommandResponseViewController.swift

@@ -40,7 +40,7 @@ extension CommandResponseViewController {
 
     static func changeTime(ops: PumpOps?, rileyLinkDeviceProvider: RileyLinkDeviceProvider) -> T {
         return T { (completionHandler) -> String in
-            ops?.runSession(withName: "Set time", using: rileyLinkDeviceProvider.firstConnectedDevice) { (session) in
+            ops?.runSession(withName: "Set time", usingSelector: rileyLinkDeviceProvider.firstConnectedDevice) { (session) in
                 let response: String
                 do {
                     guard let session = session else {

+ 3 - 2
Dependencies/rileylink_ios/MinimedKitUI/MinimedPumpManager+UI.swift

@@ -21,6 +21,7 @@ extension MinimedPumpManager: PumpManagerUI {
 
     static public func setupViewController(initialSettings settings: PumpManagerSetupSettings, bluetoothProvider: BluetoothProvider, colorPalette: LoopUIColorPalette, allowDebugFeatures: Bool, allowedInsulinTypes: [InsulinType]) -> SetupUIResult<PumpManagerViewController, PumpManagerUI> {
         let navVC = MinimedPumpManagerSetupViewController.instantiateFromStoryboard()
+        navVC.supportedInsulinTypes = allowedInsulinTypes
         let didConfirm: (InsulinType) -> Void = { [weak navVC] (confirmedType) in
             if let navVC = navVC {
                 navVC.insulinType = confirmedType
@@ -45,14 +46,14 @@ extension MinimedPumpManager: PumpManagerUI {
     }
 
     public func settingsViewController(bluetoothProvider: BluetoothProvider, colorPalette: LoopUIColorPalette, allowDebugFeatures: Bool, allowedInsulinTypes: [InsulinType]) -> PumpManagerViewController {
-        let settings = MinimedPumpSettingsViewController(pumpManager: self)
+        let settings = MinimedPumpSettingsViewController(pumpManager: self, supportedInsulinTypes: allowedInsulinTypes)
         let nav = PumpManagerSettingsNavigationViewController(rootViewController: settings)
         return nav
     }
     
     public func deliveryUncertaintyRecoveryViewController(colorPalette: LoopUIColorPalette, allowDebugFeatures: Bool) -> (UIViewController & CompletionNotifying) {
         // Return settings for now. No uncertainty handling atm.
-        let settings = MinimedPumpSettingsViewController(pumpManager: self)
+        let settings = MinimedPumpSettingsViewController(pumpManager: self, supportedInsulinTypes: [])
         let nav = SettingsNavigationViewController(rootViewController: settings)
         return nav
     }

+ 9 - 6
Dependencies/rileylink_ios/MinimedKitUI/MinimedPumpSettingsViewController.swift

@@ -15,6 +15,8 @@ import LoopKit
 class MinimedPumpSettingsViewController: RileyLinkSettingsViewController {
 
     let pumpManager: MinimedPumpManager
+
+    let supportedInsulinTypes: [InsulinType]
     
     private var ops: PumpOps {
         return pumpManager.pumpOps
@@ -63,8 +65,9 @@ class MinimedPumpSettingsViewController: RileyLinkSettingsViewController {
     }
 
     
-    init(pumpManager: MinimedPumpManager) {
+    init(pumpManager: MinimedPumpManager, supportedInsulinTypes: [InsulinType]) {
         self.pumpManager = pumpManager
+        self.supportedInsulinTypes = supportedInsulinTypes
         super.init(rileyLinkPumpManager: pumpManager, devicesSectionIndex: Section.rileyLinks.rawValue, style: .grouped)
     }
 
@@ -96,7 +99,7 @@ class MinimedPumpSettingsViewController: RileyLinkSettingsViewController {
         let mainQueue = OperationQueue.main
 
         center.addObserver(forName: .PumpOpsStateDidChange, object: pumpManager.pumpOps, queue: mainQueue) { [weak self] (note) in
-            if let state = note.userInfo?[PumpOps.notificationPumpStateKey] as? PumpState {
+            if let state = note.userInfo?[MinimedPumpOps.notificationPumpStateKey] as? PumpState {
                 self?.pumpState = state
             }
         }
@@ -106,7 +109,7 @@ class MinimedPumpSettingsViewController: RileyLinkSettingsViewController {
         let button = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneTapped(_:)))
         self.navigationItem.setRightBarButton(button, animated: false)
         
-        self.pumpState = pumpManager.pumpOps.pumpState.value
+        self.pumpState = pumpManager.state.pumpState
     }
 
     @objc func doneTapped(_ sender: Any) {
@@ -373,7 +376,7 @@ class MinimedPumpSettingsViewController: RileyLinkSettingsViewController {
 
                 show(vc, sender: sender)
             case .insulinType:
-                let view = InsulinTypeSetting(initialValue: pumpManager.insulinType ?? .novolog, supportedInsulinTypes: InsulinType.allCases, allowUnsetInsulinType: false) { (newType) in
+                let view = InsulinTypeSetting(initialValue: pumpManager.insulinType ?? .novolog, supportedInsulinTypes: supportedInsulinTypes, allowUnsetInsulinType: false) { (newType) in
                     self.pumpManager.insulinType = newType
                 }
                 let vc = DismissibleHostingController(rootView: view)
@@ -397,8 +400,8 @@ class MinimedPumpSettingsViewController: RileyLinkSettingsViewController {
             let vc = RileyLinkDeviceTableViewController(
                 device: device,
                 batteryAlertLevel: pumpManager.rileyLinkBatteryAlertLevel,
-                batteryAlertLevelChanged: { value in
-                    self.pumpManager.rileyLinkBatteryAlertLevel = value
+                batteryAlertLevelChanged: { [weak self] value in
+                    self?.pumpManager.rileyLinkBatteryAlertLevel = value
                 }
             )
 

+ 14 - 8
Dependencies/rileylink_ios/MinimedKitUI/Setup/MinimedPumpIDSetupViewController.swift

@@ -90,16 +90,18 @@ class MinimedPumpIDSetupViewController: SetupTableViewController {
             
             return MinimedPumpManagerState(
                 isOnboarded: false,
-                useMySentry: true,
+                useMySentry: pumpState?.useMySentry ?? true,
                 pumpColor: pumpColor,
                 pumpID: pumpID,
                 pumpModel: pumpModel,
                 pumpFirmwareVersion: pumpFirmwareVersion,
                 pumpRegion: pumpRegion,
-                rileyLinkConnectionManagerState: rileyLinkPumpManager.rileyLinkConnectionManagerState,
+                rileyLinkConnectionState: rileyLinkPumpManager.rileyLinkConnectionManagerState,
                 timeZone: timeZone,
                 suspendState: .resumed(Date()),
-                insulinType: insulinType
+                insulinType: insulinType,
+                lastTuned: pumpState?.lastTuned,
+                lastValidFrequency: pumpState?.lastValidFrequency
             )
         }
     }
@@ -111,9 +113,7 @@ class MinimedPumpIDSetupViewController: SetupTableViewController {
 
         return MinimedPumpManager(
             state: pumpManagerState,
-            rileyLinkDeviceProvider: rileyLinkPumpManager.rileyLinkDeviceProvider,
-            rileyLinkConnectionManager: rileyLinkPumpManager.rileyLinkConnectionManager,
-            pumpOps: self.pumpOps)
+            rileyLinkDeviceProvider: rileyLinkPumpManager.rileyLinkDeviceProvider)
     }
 
     // MARK: -
@@ -247,9 +247,9 @@ class MinimedPumpIDSetupViewController: SetupTableViewController {
     private func setupPump(with settings: PumpSettings) {
         continueState = .reading
 
-        let pumpOps = PumpOps(pumpSettings: settings, pumpState: pumpState, delegate: self)
+        let pumpOps = MinimedPumpOps(pumpSettings: settings, pumpState: pumpState, delegate: self)
         self.pumpOps = pumpOps
-        pumpOps.runSession(withName: "Pump ID Setup", using: rileyLinkPumpManager.rileyLinkDeviceProvider.firstConnectedDevice, { (session) in
+        pumpOps.runSession(withName: "Pump ID Setup", usingSelector: rileyLinkPumpManager.rileyLinkDeviceProvider.firstConnectedDevice, { (session) in
             guard let session = session else {
                 DispatchQueue.main.async {
                     self.lastError = PumpManagerError.connection(MinimedPumpManagerError.noRileyLink)
@@ -436,6 +436,12 @@ extension MinimedPumpIDSetupViewController: UITextFieldDelegate {
 
 
 extension MinimedPumpIDSetupViewController: PumpOpsDelegate {
+    // TODO: create PumpManager and report it to Loop before pump setup
+    // No pumpManager available yet, so no device logs.
+    func willSend(_ message: String) {}
+    func didReceive(_ message: String) {}
+    func didError(_ message: String) {}
+
     func pumpOps(_ pumpOps: PumpOps, didChange state: PumpState) {
         DispatchQueue.main.async {
             self.pumpState = state

+ 3 - 1
Dependencies/rileylink_ios/MinimedKitUI/Setup/MinimedPumpManagerSetupViewController.swift

@@ -40,6 +40,8 @@ public class MinimedPumpManagerSetupViewController: RileyLinkManagerSetupViewCon
     
     internal var insulinType: InsulinType?
 
+    internal var supportedInsulinTypes: [InsulinType]?
+
     /*
      1. RileyLink
      - RileyLinkPumpManagerState
@@ -119,7 +121,7 @@ public class MinimedPumpManagerSetupViewController: RileyLinkManagerSetupViewCon
 
             pumpManagerOnboardingDelegate?.pumpManagerOnboarding(didOnboardPumpManager: pumpManager)
 
-            let settingsViewController = MinimedPumpSettingsViewController(pumpManager: pumpManager)
+            let settingsViewController = MinimedPumpSettingsViewController(pumpManager: pumpManager, supportedInsulinTypes: supportedInsulinTypes!)
             setViewControllers([settingsViewController], animated: true)
         }
     }

+ 1 - 1
Dependencies/rileylink_ios/MinimedKitUI/Setup/MinimedPumpSentrySetupViewController.swift

@@ -89,7 +89,7 @@ class MinimedPumpSentrySetupViewController: SetupTableViewController {
 
         continueState = .listening
 
-        pumpManager.pumpOps.runSession(withName: "MySentry Pairing", using: pumpManager.rileyLinkDeviceProvider.firstConnectedDevice) { (session) in
+        pumpManager.pumpOps.runSession(withName: "MySentry Pairing", usingSelector: pumpManager.rileyLinkDeviceProvider.firstConnectedDevice) { (session) in
             guard let session = session else {
                 DispatchQueue.main.async {
                     self.continueState = .notStarted

+ 3 - 3
Dependencies/rileylink_ios/OmniKit/OmnipodCommon/MessageBlocks/ErrorResponse.swift

@@ -8,10 +8,10 @@
 
 import Foundation
 
-fileprivate let errorResponseCode_badNonce: UInt8 = 0x14
+fileprivate let errorResponseCode_badNonce: UInt8 = 0x14 // only returned on Eros
 
 public enum ErrorResponseType {
-    case badNonce(nonceResyncKey: UInt16)
+    case badNonce(nonceResyncKey: UInt16) // only returned on Eros
     case nonretryableError(code: UInt8, faultEventCode: FaultEventCode, podProgress: PodProgressStatus)
 }
 
@@ -27,7 +27,7 @@ public struct ErrorResponse : MessageBlock {
         let errorCode = encodedData[2]
         switch (errorCode) {
         case errorResponseCode_badNonce:
-            // For this error code only the 2 next bytes are the encoded nonce resync key.
+            // For this error code only the 2 next bytes are the encoded nonce resync key (only returned on Eros)
             let nonceResyncKey: UInt16 = encodedData[3...].toBigEndian(UInt16.self)
             errorResponseType = .badNonce(nonceResyncKey: nonceResyncKey)
             break

File diff ditekan karena terlalu besar
+ 8 - 8
Dependencies/rileylink_ios/OmniKit/OmnipodCommon/MessageBlocks/VersionResponse.swift


+ 14 - 2
Dependencies/rileylink_ios/OmniKit/OmnipodCommon/UnfinalizedDose.swift

@@ -199,6 +199,19 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti
         }
     }
 
+    public var eventTitle: String {
+        switch doseType {
+        case .bolus:
+            return LocalizedString("Bolus", comment: "Pump Event title for UnfinalizedDose with doseType of .bolus")
+        case .resume:
+            return LocalizedString("Resume", comment: "Pump Event title for UnfinalizedDose with doseType of .resume")
+        case .suspend:
+            return LocalizedString("Suspend", comment: "Pump Event title for UnfinalizedDose with doseType of .suspend")
+        case .tempBasal:
+            return LocalizedString("Temp Basal", comment: "Pump Event title for UnfinalizedDose with doseType of .tempBasal")
+        }
+    }
+
     // RawRepresentable
     public init?(rawValue: RawValue) {
         guard
@@ -278,9 +291,8 @@ private extension TimeInterval {
 
 extension NewPumpEvent {
     init(_ dose: UnfinalizedDose) {
-        let title = String(describing: dose)
         let entry = DoseEntry(dose)
-        self.init(date: dose.startTime, dose: entry, raw: dose.uniqueKey, title: title)
+        self.init(date: dose.startTime, dose: entry, raw: dose.uniqueKey, title: dose.eventTitle)
     }
 }
 

+ 34 - 37
Dependencies/rileylink_ios/OmniKit/PumpManager/OmnipodPumpManager.swift

@@ -92,21 +92,15 @@ extension OmnipodPumpManagerError: LocalizedError {
 
 public class OmnipodPumpManager: RileyLinkPumpManager {
     
-    //public let managerIdentifier: String = "Omnipod"
-    
-    public static let managerIdentifier = "Omnipod"
-    
-    public var managerIdentifier: String {
-        return OmnipodPumpManager.managerIdentifier
-    }
+    public let managerIdentifier: String = "Omnipod"
     
     public let localizedTitle = LocalizedString("Omnipod", comment: "Generic title of the omnipod pump manager")
     
-    public init(state: OmnipodPumpManagerState, rileyLinkDeviceProvider: RileyLinkDeviceProvider, rileyLinkConnectionManager: RileyLinkConnectionManager? = nil, dateGenerator: @escaping () -> Date = Date.init) {
+    public init(state: OmnipodPumpManagerState, rileyLinkDeviceProvider: RileyLinkDeviceProvider, dateGenerator: @escaping () -> Date = Date.init) {
         self.lockedState = Locked(state)
         self.lockedPodComms = Locked(PodComms(podState: state.podState))
         self.dateGenerator = dateGenerator
-        super.init(rileyLinkDeviceProvider: rileyLinkDeviceProvider, rileyLinkConnectionManager: rileyLinkConnectionManager)
+        super.init(rileyLinkDeviceProvider: rileyLinkDeviceProvider)
 
         self.podComms.delegate = self
         self.podComms.messageLogger = self
@@ -119,11 +113,11 @@ public class OmnipodPumpManager: RileyLinkPumpManager {
             return nil
         }
 
-        let rileyLinkConnectionManager = RileyLinkConnectionManager(state: connectionManagerState)
+        let deviceProvider = RileyLinkBluetoothDeviceProvider(autoConnectIDs: connectionManagerState.autoConnectIDs)
 
-        self.init(state: state, rileyLinkDeviceProvider: rileyLinkConnectionManager.deviceProvider, rileyLinkConnectionManager: rileyLinkConnectionManager)
+        self.init(state: state, rileyLinkDeviceProvider: deviceProvider)
 
-        rileyLinkConnectionManager.delegate = self
+        deviceProvider.delegate = self
     }
 
     private var podComms: PodComms {
@@ -175,7 +169,10 @@ public class OmnipodPumpManager: RileyLinkPumpManager {
             }
 
             if oldValue.podState?.lastInsulinMeasurements?.reservoirLevel != newValue.podState?.lastInsulinMeasurements?.reservoirLevel {
-                if let lastInsulinMeasurements = newValue.podState?.lastInsulinMeasurements, let reservoirLevel = lastInsulinMeasurements.reservoirLevel {
+                if let lastInsulinMeasurements = newValue.podState?.lastInsulinMeasurements,
+                   let reservoirLevel = lastInsulinMeasurements.reservoirLevel,
+                   reservoirLevel != Pod.reservoirLevelAboveThresholdMagicNumber
+                {
                     self.pumpDelegate.notify({ (delegate) in
                         self.log.info("DU: updating reservoir level %{public}@", String(describing: reservoirLevel))
                         delegate?.pumpManager(self, didReadReservoirValue: reservoirLevel, at: lastInsulinMeasurements.validTime) { _ in }
@@ -233,7 +230,7 @@ public class OmnipodPumpManager: RileyLinkPumpManager {
     
     // MARK: - RileyLink Updates
 
-    override public var rileyLinkConnectionManagerState: RileyLinkConnectionManagerState? {
+    override public var rileyLinkConnectionManagerState: RileyLinkConnectionState? {
         get {
             return state.rileyLinkConnectionManagerState
         }
@@ -321,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)
         }
@@ -329,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):
@@ -361,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)
             }
@@ -696,7 +693,7 @@ extension OmnipodPumpManager {
     #if targetEnvironment(simulator)
     private func jumpStartPod(address: UInt32, lot: UInt32, tid: UInt32, fault: DetailedStatus? = nil, startDate: Date? = nil, mockFault: Bool) {
         let start = startDate ?? Date()
-        var podState = PodState(address: address, piVersion: "jumpstarted", pmVersion: "jumpstarted", lot: lot, tid: tid, insulinType: .novolog)
+        var podState = PodState(address: address, pmVersion: "jumpstarted", piVersion: "jumpstarted", lot: lot, tid: tid, insulinType: .novolog)
         podState.setupProgress = .podPaired
         podState.activatedAt = start
         podState.expiresAt = start + .hours(72)
@@ -744,22 +741,22 @@ extension OmnipodPumpManager {
         let deviceSelector = self.rileyLinkDeviceProvider.firstConnectedDevice
         let primeSession = { (result: PodComms.SessionRunResult) in
             switch result {
-            case .success(let session):
+            case .success(let messageSender):
                 // We're on the session queue
-                session.assertOnSessionQueue()
+                messageSender.assertOnSessionQueue()
 
                 self.log.default("Beginning pod prime")
 
                 // Clean up any previously un-stored doses if needed
                 let unstoredDoses = self.state.unstoredDoses
-                if self.store(doses: unstoredDoses, in: session) {
+                if self.store(doses: unstoredDoses, in: messageSender) {
                     self.setState({ (state) in
                         state.unstoredDoses.removeAll()
                     })
                 }
 
                 do {
-                    let primeFinishedAt = try session.prime()
+                    let primeFinishedAt = try messageSender.prime()
                     completion(.success(primeFinishedAt))
                 } catch let error {
                     completion(.failure(PumpManagerError.communication(error as? LocalizedError)))
@@ -867,14 +864,14 @@ extension OmnipodPumpManager {
         let rileyLinkSelector = self.rileyLinkDeviceProvider.firstConnectedDevice
         self.podComms.runSession(withName:  "Insert cannula", using: rileyLinkSelector) { (result) in
             switch result {
-            case .success(let session):
+            case .success(let messageSender):
                 do {
                     if self.state.podState?.setupProgress.needsInitialBasalSchedule == true {
                         let scheduleOffset = timeZone.scheduleOffset(forDate: Date())
-                        try session.programInitialBasalSchedule(self.state.basalSchedule, scheduleOffset: scheduleOffset)
+                        try messageSender.programInitialBasalSchedule(self.state.basalSchedule, scheduleOffset: scheduleOffset)
 
-                        session.dosesForStorage() { (doses) -> Bool in
-                            return self.store(doses: doses, in: session)
+                        messageSender.dosesForStorage() { (doses) -> Bool in
+                            return self.store(doses: doses, in: messageSender)
                         }
                     }
 
@@ -886,7 +883,7 @@ extension OmnipodPumpManager {
                         .lowReservoir(self.state.lowReservoirReminderValue)
                     ]
 
-                    let finishWait = try session.insertCannula(optionalAlerts: alerts)
+                    let finishWait = try messageSender.insertCannula(optionalAlerts: alerts)
                     completion(.success(finishWait))
                 } catch let error {
                     completion(.failure(.communication(error)))
@@ -905,9 +902,9 @@ extension OmnipodPumpManager {
         let deviceSelector = self.rileyLinkDeviceProvider.firstConnectedDevice
         self.podComms.runSession(withName: "Check cannula insertion finished", using: deviceSelector) { (result) in
             switch result {
-            case .success(let session):
+            case .success(let messageSender):
                 do {
-                    try session.checkInsertionCompleted()
+                    try messageSender.checkInsertionCompleted()
                     completion(nil)
                 } catch let error {
                     self.log.error("Failed to fetch pod status: %{public}@", String(describing: error))
@@ -1059,9 +1056,9 @@ extension OmnipodPumpManager {
         let rileyLinkSelector = self.rileyLinkDeviceProvider.firstConnectedDevice
         self.podComms.runSession(withName: "Deactivate pod", using: rileyLinkSelector) { (result) in
             switch result {
-            case .success(let session):
+            case .success(let messageSender):
                 do {
-                    try session.deactivatePod()
+                    try messageSender.deactivatePod()
                     completion(nil)
                 } catch let error {
                     completion(OmnipodPumpManagerError.communication(error))
@@ -2056,7 +2053,7 @@ extension OmnipodPumpManager: PumpManager {
                 preconditionFailure("pumpManagerDelegate cannot be nil")
             }
 
-            delegate.pumpManager(self, hasNewPumpEvents: doses.map { NewPumpEvent($0) }, lastSync: lastSync, completion: { (error) in
+            delegate.pumpManager(self, hasNewPumpEvents: doses.map { NewPumpEvent($0) }, lastReconciliation: lastSync, completion: { (error) in
                 if let error = error {
                     self.log.error("Error storing pod events: %@", String(describing: error))
                 } else {

+ 6 - 6
Dependencies/rileylink_ios/OmniKit/PumpManager/OmnipodPumpManagerState.swift

@@ -31,7 +31,7 @@ public struct OmnipodPumpManagerState: RawRepresentable, Equatable {
     
     public var basalSchedule: BasalSchedule
     
-    public var rileyLinkConnectionManagerState: RileyLinkConnectionManagerState?
+    public var rileyLinkConnectionManagerState: RileyLinkConnectionState?
 
     public var unstoredDoses: [UnfinalizedDose]
 
@@ -93,7 +93,7 @@ public struct OmnipodPumpManagerState: RawRepresentable, Equatable {
 
     // MARK: -
 
-    public init(isOnboarded: Bool, podState: PodState?, timeZone: TimeZone, basalSchedule: BasalSchedule, rileyLinkConnectionManagerState: RileyLinkConnectionManagerState?, insulinType: InsulinType?, maximumTempBasalRate: Double) {
+    public init(isOnboarded: Bool, podState: PodState?, timeZone: TimeZone, basalSchedule: BasalSchedule, rileyLinkConnectionManagerState: RileyLinkConnectionState?, insulinType: InsulinType?, maximumTempBasalRate: Double) {
         self.isOnboarded = isOnboarded
         self.podState = podState
         self.timeZone = timeZone
@@ -154,11 +154,11 @@ public struct OmnipodPumpManagerState: RawRepresentable, Equatable {
             timeZone = TimeZone.currentFixed
         }
         
-        let rileyLinkConnectionManagerState: RileyLinkConnectionManagerState?
-        if let rileyLinkConnectionManagerStateRaw = rawValue["rileyLinkConnectionManagerState"] as? RileyLinkConnectionManagerState.RawValue {
-            rileyLinkConnectionManagerState = RileyLinkConnectionManagerState(rawValue: rileyLinkConnectionManagerStateRaw)
+        let rileyLinkConnectionManagerState: RileyLinkConnectionState?
+        if let rileyLinkConnectionManagerStateRaw = rawValue["rileyLinkConnectionManagerState"] as? RileyLinkConnectionState.RawValue {
+            rileyLinkConnectionManagerState = RileyLinkConnectionState(rawValue: rileyLinkConnectionManagerStateRaw)
         } else {
-            rileyLinkConnectionManagerState = RileyLinkConnectionManagerState(autoConnectIDs: [])
+            rileyLinkConnectionManagerState = RileyLinkConnectionState(autoConnectIDs: [])
         }
         
         var insulinType: InsulinType?

+ 7 - 7
Dependencies/rileylink_ios/OmniKit/PumpManager/PodComms.swift

@@ -19,7 +19,7 @@ protocol PodCommsDelegate: AnyObject {
 
 class PodComms: CustomDebugStringConvertible {
     
-    private let configuredDevices: Locked<Set<RileyLinkDevice>> = Locked(Set())
+    private let configuredDevices: Locked<Set<UUID>> = Locked(Set())
 
     weak var delegate: PodCommsDelegate?
     
@@ -189,8 +189,8 @@ class PodComms: CustomDebugStringConvertible {
                 log.default("Creating PodState for address %{public}@ [lot %u tid %u], packet #%d, message #%d", String(format: "%04X", config.address), config.lot, config.tid, transport.packetNumber, transport.messageNumber)
                 self.podState = PodState(
                     address: config.address,
-                    piVersion: String(describing: config.piVersion),
-                    pmVersion: String(describing: config.pmVersion),
+                    pmVersion: String(describing: config.firmwareVersion),
+                    piVersion: String(describing: config.iFirmwareVersion),
                     lot: config.lot,
                     tid: config.tid,
                     packetNumber: transport.packetNumber,
@@ -412,7 +412,7 @@ class PodComms: CustomDebugStringConvertible {
     private func configureDevice(_ device: RileyLinkDevice, with session: CommandSession) {
         session.assertOnSessionQueue()
 
-        guard !self.configuredDevices.value.contains(device) else {
+        guard !self.configuredDevices.value.contains(device.peripheralIdentifier) else {
             return
         }
         
@@ -431,7 +431,7 @@ class PodComms: CustomDebugStringConvertible {
         
         log.debug("added device %{public}@ to configuredDevices", device.name ?? "unknown")
         _ = configuredDevices.mutate { (value) in
-            value.insert(device)
+            value.insert(device.peripheralIdentifier)
         }
     }
     
@@ -445,7 +445,7 @@ class PodComms: CustomDebugStringConvertible {
         NotificationCenter.default.removeObserver(self, name: .DeviceConnectionStateDidChange, object: device)
 
         _ = configuredDevices.mutate { (value) in
-            value.remove(device)
+            value.remove(device.peripheralIdentifier)
         }
     }
     
@@ -455,7 +455,7 @@ class PodComms: CustomDebugStringConvertible {
         return [
             "## PodComms",
             "podState: \(String(reflecting: podState))",
-            "configuredDevices: \(configuredDevices.value.map { $0.peripheralIdentifier.uuidString })",
+            "configuredDevices: \(configuredDevices.value.map { $0.uuidString })",
             "delegate: \(String(describing: delegate != nil))",
             ""
         ].joined(separator: "\n")

+ 3 - 3
Dependencies/rileylink_ios/OmniKit/PumpManager/PodState.swift

@@ -60,8 +60,8 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl
 
     public var setupUnitsDelivered: Double?
 
-    public let piVersion: String
     public let pmVersion: String
+    public let piVersion: String
     public let lot: UInt32
     public let tid: UInt32
     var activeAlertSlots: AlertSet
@@ -114,11 +114,11 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl
         return false
     }
 
-    public init(address: UInt32, piVersion: String, pmVersion: String, lot: UInt32, tid: UInt32, packetNumber: Int = 0, messageNumber: Int = 0, insulinType: InsulinType) {
+    public init(address: UInt32, pmVersion: String, piVersion: String, lot: UInt32, tid: UInt32, packetNumber: Int = 0, messageNumber: Int = 0, insulinType: InsulinType) {
         self.address = address
         self.nonceState = NonceState(lot: lot, tid: tid)
-        self.piVersion = piVersion
         self.pmVersion = pmVersion
+        self.piVersion = piVersion
         self.lot = lot
         self.tid = tid
         self.lastInsulinMeasurements = nil

+ 10 - 10
Dependencies/rileylink_ios/OmniKitTests/MessageTests.swift

@@ -78,8 +78,8 @@ class MessageTests: XCTestCase {
         do {
             let config = try VersionResponse(encodedData: Data(hexadecimalString: "011502070002070002020000a64000097c279c1f08ced2")!)
             XCTAssertEqual(23, config.data.count)
-            XCTAssertEqual("2.7.0", String(describing: config.piVersion))
-            XCTAssertEqual("2.7.0", String(describing: config.pmVersion))
+            XCTAssertEqual("2.7.0", String(describing: config.firmwareVersion))
+            XCTAssertEqual("2.7.0", String(describing: config.iFirmwareVersion))
             XCTAssertEqual(42560, config.lot)
             XCTAssertEqual(621607, config.tid)
             XCTAssertEqual(0x1f08ced2, config.address)
@@ -103,8 +103,8 @@ class MessageTests: XCTestCase {
             let message = try Message(encodedData: Data(hexadecimalString: "ffffffff041d011b13881008340a5002070002070002030000a62b000447941f00ee878352")!)
             let config = message.messageBlocks[0] as! VersionResponse
             XCTAssertEqual(29, config.data.count)
-            XCTAssertEqual("2.7.0", String(describing: config.piVersion))
-            XCTAssertEqual("2.7.0", String(describing: config.pmVersion))
+            XCTAssertEqual("2.7.0", String(describing: config.firmwareVersion))
+            XCTAssertEqual("2.7.0", String(describing: config.iFirmwareVersion))
             XCTAssertEqual(42539, config.lot)
             XCTAssertEqual(280468, config.tid)
             XCTAssertEqual(0x1f00ee87, config.address)
@@ -127,8 +127,8 @@ class MessageTests: XCTestCase {
         do {
             let config = try VersionResponse(encodedData: Data(hexadecimalString: "0115031b0008080004020812a011000c175700ffffffff")!)
             XCTAssertEqual(23, config.data.count)
-            XCTAssertEqual("3.27.0", String(describing: config.pmVersion))
-            XCTAssertEqual("8.8.0", String(describing: config.piVersion))
+            XCTAssertEqual("3.27.0", String(describing: config.firmwareVersion))
+            XCTAssertEqual("8.8.0", String(describing: config.iFirmwareVersion))
             XCTAssertEqual(135438353, config.lot)
             XCTAssertEqual(792407, config.tid)
             XCTAssertEqual(0xFFFFFFFF, config.address)
@@ -152,8 +152,8 @@ class MessageTests: XCTestCase {
             let message = try Message(encodedData: Data(hexadecimalString: "ffffffff0c1d011b13881008340a50031b0008080004030812a011000c175717244389816c")!)
             let config = message.messageBlocks[0] as! VersionResponse
             XCTAssertEqual(29, config.data.count)
-            XCTAssertEqual("3.27.0", String(describing: config.pmVersion))
-            XCTAssertEqual("8.8.0", String(describing: config.piVersion))
+            XCTAssertEqual("3.27.0", String(describing: config.firmwareVersion))
+            XCTAssertEqual("8.8.0", String(describing: config.iFirmwareVersion))
             XCTAssertEqual(135438353, config.lot)
             XCTAssertEqual(792407, config.tid)
             XCTAssertEqual(0x17244389, config.address)
@@ -176,8 +176,8 @@ class MessageTests: XCTestCase {
         do {
             let message = try Message(encodedData: Data(hexadecimalString: "ffffffff04170115020700020700020e0000a5ad00053030971f08686301fd")!)
             let config = message.messageBlocks[0] as! VersionResponse
-            XCTAssertEqual("2.7.0", String(describing: config.piVersion))
-            XCTAssertEqual("2.7.0", String(describing: config.pmVersion))
+            XCTAssertEqual("2.7.0", String(describing: config.firmwareVersion))
+            XCTAssertEqual("2.7.0", String(describing: config.iFirmwareVersion))
             XCTAssertEqual(0x0000a5ad, config.lot)
             XCTAssertEqual(0x00053030, config.tid)
             XCTAssertEqual(0x1f086863, config.address)

+ 1 - 1
Dependencies/rileylink_ios/OmniKitTests/PodCommsSessionTests.swift

@@ -20,7 +20,7 @@ class PodCommsSessionTests: XCTestCase {
 
 
     override func setUp() {
-        podState = PodState(address: address, piVersion: "2.7.0", pmVersion: "2.7.0", lot: 43620, tid: 560313, insulinType: .novolog)
+        podState = PodState(address: address, pmVersion: "2.7.0", piVersion: "2.7.0", lot: 43620, tid: 560313, insulinType: .novolog)
         mockTransport = MockMessageTransport(address: podState.address, messageNumber: 5)
     }
 

+ 4 - 4
Dependencies/rileylink_ios/OmniKitTests/PodStateTests.swift

@@ -12,7 +12,7 @@ import XCTest
 class PodStateTests: XCTestCase {
 
     func testNonceValues() {
-        var podState = PodState(address: 0x1f000000, piVersion: "1.1.0", pmVersion: "1.1.0", lot: 42560, tid: 661771, insulinType: .novolog)
+        var podState = PodState(address: 0x1f000000, pmVersion: "1.1.0", piVersion: "1.1.0", lot: 42560, tid: 661771, insulinType: .novolog)
         
         XCTAssertEqual(podState.currentNonce, 0x8c61ee59)
         podState.advanceToNextNonce()
@@ -26,12 +26,12 @@ class PodStateTests: XCTestCase {
     func testResyncNonce() {
         do {
             let config = try VersionResponse(encodedData: Data(hexadecimalString: "011502070002070002020000a62b0002249da11f00ee860318")!)
-            var podState = PodState(address: 0x1f00ee86, piVersion: "1.1.0", pmVersion: "1.1.0", lot: config.lot, tid: config.tid, insulinType: .novolog)
+            var podState = PodState(address: config.address, pmVersion: config.firmwareVersion.description, piVersion: config.iFirmwareVersion.description, lot: config.lot, tid: config.tid, insulinType: .novolog)
 
             XCTAssertEqual(42539, config.lot)
-            XCTAssertEqual(140445,  config.tid)
+            XCTAssertEqual(140445, config.tid)
             
-            XCTAssertEqual(0x8fd39264,  podState.currentNonce)
+            XCTAssertEqual(0x8fd39264, podState.currentNonce)
 
             // ID1:1f00ee86 PTYPE:PDM SEQ:26 ID2:1f00ee86 B9:24 BLEN:6 BODY:1c042e07c7c703c1 CRC:f4
             let sentPacket = try Packet(encodedData: Data(hexadecimalString: "1f00ee86ba1f00ee8624061c042e07c7c703c1f4")!)

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

@@ -189,15 +189,15 @@ class OmnipodUICoordinator: UINavigationController, PumpManagerOnboarding, Compl
                     let vc = RileyLinkDeviceTableViewController(
                         device: device,
                         batteryAlertLevel: self.pumpManager.rileyLinkBatteryAlertLevel,
-                        batteryAlertLevelChanged: { value in
-                            self.pumpManager.rileyLinkBatteryAlertLevel = value
+                        batteryAlertLevelChanged: { [weak self] value in
+                            self?.pumpManager.rileyLinkBatteryAlertLevel = value
                         }
                     )
                     self.show(vc, sender: self)
                 }
             }
 
-            let view = OmnipodSettingsView(viewModel: viewModel, rileyLinkListDataSource: rileyLinkListDataSource, handleRileyLinkSelection: handleRileyLinkSelection)
+            let view = OmnipodSettingsView(viewModel: viewModel, rileyLinkListDataSource: rileyLinkListDataSource, handleRileyLinkSelection: handleRileyLinkSelection, supportedInsulinTypes: allowedInsulinTypes)
             return hostingController(rootView: view)
         case .pairPod:
             pumpManagerOnboardingDelegate?.pumpManagerOnboarding(didCreatePumpManager: pumpManager)
@@ -314,8 +314,8 @@ class OmnipodUICoordinator: UINavigationController, PumpManagerOnboarding, Compl
                         let vc = RileyLinkDeviceTableViewController(
                             device: device,
                             batteryAlertLevel: self.pumpManager.rileyLinkBatteryAlertLevel,
-                            batteryAlertLevelChanged: { value in
-                                self.pumpManager.rileyLinkBatteryAlertLevel = value
+                            batteryAlertLevelChanged: { [weak self] value in
+                                self?.pumpManager.rileyLinkBatteryAlertLevel = value
                             }
                         )
                         self.show(vc, sender: self)
@@ -371,7 +371,7 @@ class OmnipodUICoordinator: UINavigationController, PumpManagerOnboarding, Compl
         if pumpManager == nil, let pumpManagerSettings = pumpManagerSettings {
             let basalSchedule = pumpManagerSettings.basalSchedule
 
-            let rileyLinkConnectionManager = RileyLinkConnectionManager(autoConnectIDs: [])
+            let deviceProvider = RileyLinkBluetoothDeviceProvider(autoConnectIDs: [])
 
             let pumpManagerState = OmnipodPumpManagerState(
                 isOnboarded: false,
@@ -382,7 +382,7 @@ class OmnipodUICoordinator: UINavigationController, PumpManagerOnboarding, Compl
                 insulinType: nil,
                 maximumTempBasalRate: pumpManagerSettings.maxBasalRateUnitsPerHour)
 
-            self.pumpManager = OmnipodPumpManager(state: pumpManagerState, rileyLinkDeviceProvider: rileyLinkConnectionManager.deviceProvider, rileyLinkConnectionManager: rileyLinkConnectionManager)
+            self.pumpManager = OmnipodPumpManager(state: pumpManagerState, rileyLinkDeviceProvider: deviceProvider)
         } else {
             guard let pumpManager = pumpManager else {
                 fatalError("Unable to create Omnipod PumpManager")

+ 1 - 1
Dependencies/rileylink_ios/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift

@@ -253,7 +253,7 @@ class OmnipodSettingsViewModel: ObservableObject {
     }
 
     func updateConnectionStatus() {
-        pumpManager.rileyLinkConnectionManager?.deviceProvider.getDevices { (devices) in
+        pumpManager.rileyLinkDeviceProvider.getDevices { (devices) in
             DispatchQueue.main.async { [weak self] in
                 self?.rileylinkConnected = devices.firstConnected != nil
             }

+ 3 - 10
Dependencies/rileylink_ios/OmniKitUI/ViewModels/RileyLinkListDataSource.swift

@@ -34,7 +34,7 @@ class RileyLinkListDataSource: ObservableObject {
     func autoconnectBinding(for device: RileyLinkDevice) -> Binding<Bool> {
         return Binding(
             get: { [weak self] in
-                if let connectionManager = self?.rileyLinkPumpManager.rileyLinkConnectionManager {
+                if let connectionManager = self?.rileyLinkPumpManager.rileyLinkDeviceProvider {
                     return connectionManager.shouldConnect(to: device.peripheralIdentifier.uuidString)
                 } else {
                     return false
@@ -59,7 +59,7 @@ class RileyLinkListDataSource: ObservableObject {
 
     public var isScanningEnabled: Bool = false {
         didSet {
-            rileyLinkPumpManager.rileyLinkConnectionManager?.setScanningEnabled(isScanningEnabled)
+            rileyLinkPumpManager.rileyLinkDeviceProvider.setScanningEnabled(isScanningEnabled)
 
             if isScanningEnabled {
                 rssiFetchTimer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(updateRSSI), userInfo: nil, repeats: true)
@@ -75,11 +75,7 @@ class RileyLinkListDataSource: ObservableObject {
         return true
         #else
 
-        guard let connectionManager = rileyLinkPumpManager.rileyLinkConnectionManager else {
-            return false
-        }
-
-        return connectionManager.connectingCount > 0
+        return rileyLinkPumpManager.rileyLinkDeviceProvider.connectingCount > 0
         #endif
     }
 
@@ -96,6 +92,3 @@ class RileyLinkListDataSource: ObservableObject {
         }
     }
 }
-
-
-extension RileyLinkDevice: Identifiable {}

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


Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini