From 71e56170c04228c269217ef897a59ae15abdf63c Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Mon, 25 Sep 2023 15:53:37 -0300 Subject: [PATCH 01/26] [COASTAL-1291] plugin identifier is no longer class property (#5) --- OmniKit/PumpManager/OmnipodPumpManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift index b33524a..dc3215e 100644 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -92,7 +92,7 @@ extension OmnipodPumpManagerError: LocalizedError { public class OmnipodPumpManager: RileyLinkPumpManager { - public static let pluginIdentifier: String = "Omnipod" + public let pluginIdentifier: String = "Omnipod" public let localizedTitle = LocalizedString("Omnipod", comment: "Generic title of the omnipod pump manager") From 0a6fbb4667f1ec5e393e1a1f255a0528f60178f3 Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Mon, 25 Sep 2023 15:53:37 -0300 Subject: [PATCH 02/26] [COASTAL-1291] plugin identifier is no longer class property (#5) --- OmniKit/PumpManager/OmnipodPumpManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift index b33524a..dc3215e 100644 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -92,7 +92,7 @@ extension OmnipodPumpManagerError: LocalizedError { public class OmnipodPumpManager: RileyLinkPumpManager { - public static let pluginIdentifier: String = "Omnipod" + public let pluginIdentifier: String = "Omnipod" public let localizedTitle = LocalizedString("Omnipod", comment: "Generic title of the omnipod pump manager") From f153705536b53125877426913839e94f7e0058f2 Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Fri, 12 Jan 2024 11:10:00 -0800 Subject: [PATCH 03/26] [COASTAL-1335] Update Picker Component --- .../Views/ExpirationReminderPickerView.swift | 11 ++++++--- .../Views/ManualTempBasalEntryView.swift | 23 ++++++++++++++----- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/OmniKitUI/Views/ExpirationReminderPickerView.swift b/OmniKitUI/Views/ExpirationReminderPickerView.swift index e7a5e5b..6744535 100644 --- a/OmniKitUI/Views/ExpirationReminderPickerView.swift +++ b/OmniKitUI/Views/ExpirationReminderPickerView.swift @@ -43,9 +43,14 @@ struct ExpirationReminderPickerView: View { } } if showingHourPicker { - ResizeablePicker(selection: expirationReminderDefault, - data: Array(Self.expirationReminderHoursAllowed), - formatter: { expirationReminderHourString($0) }) + Picker(selection: expirationReminderDefault) { + ForEach(Array(Self.expirationReminderHoursAllowed), id: \.self) { value in + Text(expirationReminderHourString(value)) + } + } label: { + EmptyView() + } + .pickerStyle(.wheel) } } } diff --git a/OmniKitUI/Views/ManualTempBasalEntryView.swift b/OmniKitUI/Views/ManualTempBasalEntryView.swift index a676aaf..9dc1ef8 100644 --- a/OmniKitUI/Views/ManualTempBasalEntryView.swift +++ b/OmniKitUI/Views/ManualTempBasalEntryView.swift @@ -81,12 +81,23 @@ struct ManualTempBasalEntryView: View { Text(String(format: LocalizedString("%1$@ for %2$@", comment: "Summary string for temporary basal rate configuration page"), formatRate(rateEntered), formatDuration(durationEntered))) } HStack { - ResizeablePicker(selection: $rateEntered, - data: allowedRates, - formatter: { formatRate($0) }) - ResizeablePicker(selection: $durationEntered, - data: Pod.supportedTempBasalDurations, - formatter: { formatDuration($0) }) + Picker(selection: $rateEntered) { + ForEach(allowedRates, id: \.self) { value in + Text(formatRate(value)) + } + } label: { + EmptyView() + } + .pickerStyle(.wheel) + + Picker(selection: $durationEntered) { + ForEach(Pod.supportedTempBasalDurations, id: \.self) { value in + Text(formatDuration(value)) + } + } label: { + EmptyView() + } + .pickerStyle(.wheel) } .frame(maxHeight: 162.0) .alert(isPresented: $showingMissingConfigAlert, content: { missingConfigAlert }) From 7e78573b002600a1c0dda2753a3559cef5aed0bc Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Fri, 12 Jan 2024 11:10:00 -0800 Subject: [PATCH 04/26] [COASTAL-1335] Update Picker Component --- .../Views/ExpirationReminderPickerView.swift | 11 ++++++--- .../Views/ManualTempBasalEntryView.swift | 23 ++++++++++++++----- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/OmniKitUI/Views/ExpirationReminderPickerView.swift b/OmniKitUI/Views/ExpirationReminderPickerView.swift index e7a5e5b..6744535 100644 --- a/OmniKitUI/Views/ExpirationReminderPickerView.swift +++ b/OmniKitUI/Views/ExpirationReminderPickerView.swift @@ -43,9 +43,14 @@ struct ExpirationReminderPickerView: View { } } if showingHourPicker { - ResizeablePicker(selection: expirationReminderDefault, - data: Array(Self.expirationReminderHoursAllowed), - formatter: { expirationReminderHourString($0) }) + Picker(selection: expirationReminderDefault) { + ForEach(Array(Self.expirationReminderHoursAllowed), id: \.self) { value in + Text(expirationReminderHourString(value)) + } + } label: { + EmptyView() + } + .pickerStyle(.wheel) } } } diff --git a/OmniKitUI/Views/ManualTempBasalEntryView.swift b/OmniKitUI/Views/ManualTempBasalEntryView.swift index a676aaf..9dc1ef8 100644 --- a/OmniKitUI/Views/ManualTempBasalEntryView.swift +++ b/OmniKitUI/Views/ManualTempBasalEntryView.swift @@ -81,12 +81,23 @@ struct ManualTempBasalEntryView: View { Text(String(format: LocalizedString("%1$@ for %2$@", comment: "Summary string for temporary basal rate configuration page"), formatRate(rateEntered), formatDuration(durationEntered))) } HStack { - ResizeablePicker(selection: $rateEntered, - data: allowedRates, - formatter: { formatRate($0) }) - ResizeablePicker(selection: $durationEntered, - data: Pod.supportedTempBasalDurations, - formatter: { formatDuration($0) }) + Picker(selection: $rateEntered) { + ForEach(allowedRates, id: \.self) { value in + Text(formatRate(value)) + } + } label: { + EmptyView() + } + .pickerStyle(.wheel) + + Picker(selection: $durationEntered) { + ForEach(Pod.supportedTempBasalDurations, id: \.self) { value in + Text(formatDuration(value)) + } + } label: { + EmptyView() + } + .pickerStyle(.wheel) } .frame(maxHeight: 162.0) .alert(isPresented: $showingMissingConfigAlert, content: { missingConfigAlert }) From 12fb306be2cb76cd4986d45f29a429f17a474e54 Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Fri, 7 Jun 2024 14:10:53 -0300 Subject: [PATCH 05/26] [LOOP-4801] adding pump inoperable (#8) * adding pump inoperable * fault return pumpInoperable --- OmniKit/PumpManager/OmnipodPumpManager.swift | 2 +- OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift index 51d36c0..e69dde3 100644 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -490,7 +490,7 @@ extension OmnipodPumpManager { switch podCommState(for: state) { case .fault: - return .active(.distantPast) + return .pumpInoperable default: break } diff --git a/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift b/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift index 081fff7..37937f4 100644 --- a/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift +++ b/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift @@ -159,7 +159,7 @@ class OmnipodSettingsViewModel: ObservableObject { switch basalDeliveryState { case .active(_), .initiatingTempBasal: return true - case .tempBasal(_), .cancelingTempBasal, .suspending, .suspended(_), .resuming, .none: + default: return false } } From 5628aae267eb94be110944d97c83f95bc693fca1 Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Fri, 7 Jun 2024 14:10:53 -0300 Subject: [PATCH 06/26] [LOOP-4801] adding pump inoperable (#8) * adding pump inoperable * fault return pumpInoperable --- OmniKit/PumpManager/OmnipodPumpManager.swift | 2 +- OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift index 51d36c0..e69dde3 100644 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -490,7 +490,7 @@ extension OmnipodPumpManager { switch podCommState(for: state) { case .fault: - return .active(.distantPast) + return .pumpInoperable default: break } diff --git a/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift b/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift index 081fff7..37937f4 100644 --- a/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift +++ b/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift @@ -159,7 +159,7 @@ class OmnipodSettingsViewModel: ObservableObject { switch basalDeliveryState { case .active(_), .initiatingTempBasal: return true - case .tempBasal(_), .cancelingTempBasal, .suspending, .suspended(_), .resuming, .none: + default: return false } } From f3ac4608f95d5b69bca075eae94c54493b65d69b Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Thu, 21 Nov 2024 15:49:45 -0800 Subject: [PATCH 07/26] [LOOP-5153] Remove HealthKit dependency from LoopAlgorithm --- OmniKit/OmnipodCommon/PumpManagerAlert.swift | 6 +++--- OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift | 8 ++++---- OmniKitUI/Views/ExpirationReminderPickerView.swift | 6 +++--- OmniKitUI/Views/LowReservoirReminderEditView.swift | 8 ++++---- OmniKitUI/Views/LowReservoirReminderSetupView.swift | 8 ++++---- OmniKitUI/Views/ManualTempBasalEntryView.swift | 12 ++++++------ OmniKitUI/Views/NotificationSettingsView.swift | 6 +++--- 7 files changed, 27 insertions(+), 27 deletions(-) diff --git a/OmniKit/OmnipodCommon/PumpManagerAlert.swift b/OmniKit/OmnipodCommon/PumpManagerAlert.swift index b92fa2c..480e09c 100644 --- a/OmniKit/OmnipodCommon/PumpManagerAlert.swift +++ b/OmniKit/OmnipodCommon/PumpManagerAlert.swift @@ -7,8 +7,8 @@ // import Foundation +import LoopAlgorithm import LoopKit -import HealthKit public enum PumpManagerAlert: Hashable { case multiCommand(triggeringSlot: AlertSlot?) @@ -72,8 +72,8 @@ public enum PumpManagerAlert: Hashable { case .podExpireImminent: return LocalizedString("Change Pod now. Insulin delivery will stop in 1 hour.", comment: "Alert content body for podExpireImminent pod alert") case .lowReservoir(_, let lowReservoirReminderValue): - let quantityFormatter = QuantityFormatter(for: .internationalUnit()) - let valueString = quantityFormatter.string(from: HKQuantity(unit: .internationalUnit(), doubleValue: lowReservoirReminderValue)) ?? String(describing: lowReservoirReminderValue) + let quantityFormatter = QuantityFormatter(for: .internationalUnit) + let valueString = quantityFormatter.string(from: LoopQuantity(unit: .internationalUnit, doubleValue: lowReservoirReminderValue)) ?? String(describing: lowReservoirReminderValue) return String(format: LocalizedString("%1$@ insulin or less remaining in Pod. Change Pod soon.", comment: "Format string for alert content body for lowReservoir pod alert. (1: reminder value)"), valueString) case .suspendInProgress: return LocalizedString("Suspend In Progress Reminder", comment: "Alert content body for suspendInProgress pod alert") diff --git a/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift b/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift index 37937f4..c9611bf 100644 --- a/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift +++ b/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift @@ -9,7 +9,7 @@ import SwiftUI import LoopKit import LoopKitUI -import HealthKit +import LoopAlgorithm import OmniKit import Combine @@ -205,7 +205,7 @@ class OmnipodSettingsViewModel: ObservableObject { return nil } - let reservoirVolumeFormatter = QuantityFormatter(for: .internationalUnit()) + let reservoirVolumeFormatter = QuantityFormatter(for: .internationalUnit) var didFinish: (() -> Void)? @@ -395,13 +395,13 @@ class OmnipodSettingsViewModel: ObservableObject { func reservoirText(for level: ReservoirLevel) -> String { switch level { case .aboveThreshold: - let quantity = HKQuantity(unit: .internationalUnit(), doubleValue: Pod.maximumReservoirReading) + let quantity = LoopQuantity(unit: .internationalUnit, doubleValue: Pod.maximumReservoirReading) let thresholdString = reservoirVolumeFormatter.string(from: quantity, includeUnit: false) ?? "" let unitString = reservoirVolumeFormatter.localizedUnitStringWithPlurality(forValue: Pod.maximumReservoirReading, avoidLineBreaking: true) return String(format: LocalizedString("%1$@+ %2$@", comment: "Format string for reservoir level above max measurable threshold. (1: measurable reservoir threshold) (2: units)"), thresholdString, unitString) case .valid(let value): - let quantity = HKQuantity(unit: .internationalUnit(), doubleValue: value) + let quantity = LoopQuantity(unit: .internationalUnit, doubleValue: value) return reservoirVolumeFormatter.string(from: quantity) ?? "" } } diff --git a/OmniKitUI/Views/ExpirationReminderPickerView.swift b/OmniKitUI/Views/ExpirationReminderPickerView.swift index 6744535..b9e8486 100644 --- a/OmniKitUI/Views/ExpirationReminderPickerView.swift +++ b/OmniKitUI/Views/ExpirationReminderPickerView.swift @@ -7,9 +7,9 @@ // import SwiftUI +import LoopAlgorithm import LoopKit import LoopKitUI -import HealthKit struct ExpirationReminderPickerView: View { @@ -21,7 +21,7 @@ struct ExpirationReminderPickerView: View { @State var showingHourPicker: Bool = false - var expirationDefaultFormatter = QuantityFormatter(for: .hour()) + var expirationDefaultFormatter = QuantityFormatter(for: .hour) var expirationDefaultString: String { return expirationReminderHourString(expirationReminderDefault.wrappedValue) @@ -57,7 +57,7 @@ struct ExpirationReminderPickerView: View { private func expirationReminderHourString(_ value: Int) -> String { if value > 0 { - return expirationDefaultFormatter.string(from: HKQuantity(unit: .hour(), doubleValue: Double(value)))! + return expirationDefaultFormatter.string(from: LoopQuantity(unit: .hour, doubleValue: Double(value)))! } else { return LocalizedString("No Reminder", comment: "Value text for no expiration reminder") } diff --git a/OmniKitUI/Views/LowReservoirReminderEditView.swift b/OmniKitUI/Views/LowReservoirReminderEditView.swift index 61fddbb..a20b9eb 100644 --- a/OmniKitUI/Views/LowReservoirReminderEditView.swift +++ b/OmniKitUI/Views/LowReservoirReminderEditView.swift @@ -6,11 +6,11 @@ // Copyright © 2021 LoopKit Authors. All rights reserved. // -import SwiftUI +import LoopAlgorithm import LoopKit import LoopKitUI -import HealthKit import OmniKit +import SwiftUI struct LowReservoirReminderEditView: View { @@ -89,7 +89,7 @@ struct LowReservoirReminderEditView: View { } func formatValue(_ value: Int) -> String { - return insulinQuantityFormatter.string(from: HKQuantity(unit: .internationalUnit(), doubleValue: Double(value))) ?? "" + return insulinQuantityFormatter.string(from: LoopQuantity(unit: .internationalUnit, doubleValue: Double(value))) ?? "" } var saveButtonText: String { @@ -148,7 +148,7 @@ struct LowReservoirReminderEditView_Previews: PreviewProvider { static var previews: some View { LowReservoirReminderEditView( lowReservoirReminderValue: 20, - insulinQuantityFormatter: QuantityFormatter(for: .internationalUnit()), + insulinQuantityFormatter: QuantityFormatter(for: .internationalUnit), onSave: { (_, _) in }, onFinish: { } ) diff --git a/OmniKitUI/Views/LowReservoirReminderSetupView.swift b/OmniKitUI/Views/LowReservoirReminderSetupView.swift index 4a64990..ab5ec1b 100644 --- a/OmniKitUI/Views/LowReservoirReminderSetupView.swift +++ b/OmniKitUI/Views/LowReservoirReminderSetupView.swift @@ -6,11 +6,11 @@ // Copyright © 2021 LoopKit Authors. All rights reserved. // -import SwiftUI +import LoopAlgorithm import LoopKitUI import LoopKit -import HealthKit import OmniKit +import SwiftUI struct LowReservoirReminderSetupView: View { @@ -20,10 +20,10 @@ struct LowReservoirReminderSetupView: View { public var continueButtonTapped: (() -> Void)? public var cancelButtonTapped: (() -> Void)? - var insulinQuantityFormatter = QuantityFormatter(for: .internationalUnit()) + var insulinQuantityFormatter = QuantityFormatter(for: .internationalUnit) func formatValue(_ value: Int) -> String { - return insulinQuantityFormatter.string(from: HKQuantity(unit: .internationalUnit(), doubleValue: Double(value))) ?? "" + return insulinQuantityFormatter.string(from: LoopQuantity(unit: .internationalUnit, doubleValue: Double(value))) ?? "" } var body: some View { diff --git a/OmniKitUI/Views/ManualTempBasalEntryView.swift b/OmniKitUI/Views/ManualTempBasalEntryView.swift index 9dc1ef8..60cf043 100644 --- a/OmniKitUI/Views/ManualTempBasalEntryView.swift +++ b/OmniKitUI/Views/ManualTempBasalEntryView.swift @@ -6,11 +6,11 @@ // Copyright © 2022 LoopKit Authors. All rights reserved. // -import SwiftUI +import LoopAlgorithm import LoopKitUI import LoopKit -import HealthKit import OmniKit +import SwiftUI struct ManualTempBasalEntryView: View { @@ -51,7 +51,7 @@ struct ManualTempBasalEntryView: View { } private static let durationFormatter: QuantityFormatter = { - let quantityFormatter = QuantityFormatter(for: .hour()) + let quantityFormatter = QuantityFormatter(for: .hour) quantityFormatter.numberFormatter.minimumFractionDigits = 1 quantityFormatter.numberFormatter.maximumFractionDigits = 1 quantityFormatter.unitStyle = .long @@ -59,16 +59,16 @@ struct ManualTempBasalEntryView: View { }() private var durationUnitsLabel: some View { - Text(QuantityFormatter(for: .hour()).localizedUnitStringWithPlurality()) + Text(QuantityFormatter(for: .hour).localizedUnitStringWithPlurality()) .foregroundColor(Color(.secondaryLabel)) } func formatRate(_ rate: Double) -> String { - return ManualTempBasalEntryView.rateFormatter.string(from: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: rate)) ?? "" + return ManualTempBasalEntryView.rateFormatter.string(from: LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: rate)) ?? "" } func formatDuration(_ duration: TimeInterval) -> String { - return ManualTempBasalEntryView.durationFormatter.string(from: HKQuantity(unit: .hour(), doubleValue: duration.hours)) ?? "" + return ManualTempBasalEntryView.durationFormatter.string(from: LoopQuantity(unit: .hour, doubleValue: duration.hours)) ?? "" } var body: some View { diff --git a/OmniKitUI/Views/NotificationSettingsView.swift b/OmniKitUI/Views/NotificationSettingsView.swift index d806b67..63d9e97 100644 --- a/OmniKitUI/Views/NotificationSettingsView.swift +++ b/OmniKitUI/Views/NotificationSettingsView.swift @@ -7,9 +7,9 @@ // import SwiftUI +import LoopAlgorithm import LoopKit import LoopKitUI -import HealthKit struct NotificationSettingsView: View { @@ -29,7 +29,7 @@ struct NotificationSettingsView: View { var onSaveLowReservoirReminder: ((_ selectedValue: Int, _ completion: @escaping (_ error: Error?) -> Void) -> Void)? - var insulinQuantityFormatter = QuantityFormatter(for: .internationalUnit()) + var insulinQuantityFormatter = QuantityFormatter(for: .internationalUnit) var body: some View { RoundedCardScrollView { @@ -114,7 +114,7 @@ struct NotificationSettingsView: View { { RoundedCardValueRow( label: LocalizedString("Low Reservoir Reminder", comment: "Label for low reservoir reminder row"), - value: insulinQuantityFormatter.string(from: HKQuantity(unit: .internationalUnit(), doubleValue: Double(lowReservoirReminderValue))) ?? "", + value: insulinQuantityFormatter.string(from: LoopQuantity(unit: .internationalUnit, doubleValue: Double(lowReservoirReminderValue))) ?? "", highlightValue: false, disclosure: true) } From 159da137ea6ce6bcb0a0d88aeaedc8ec17b07976 Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Thu, 21 Nov 2024 15:49:45 -0800 Subject: [PATCH 08/26] [LOOP-5153] Remove HealthKit dependency from LoopAlgorithm --- OmniKit/OmnipodCommon/PumpManagerAlert.swift | 6 +++--- OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift | 8 ++++---- OmniKitUI/Views/ExpirationReminderPickerView.swift | 6 +++--- OmniKitUI/Views/LowReservoirReminderEditView.swift | 8 ++++---- OmniKitUI/Views/LowReservoirReminderSetupView.swift | 8 ++++---- OmniKitUI/Views/ManualTempBasalEntryView.swift | 12 ++++++------ OmniKitUI/Views/NotificationSettingsView.swift | 6 +++--- 7 files changed, 27 insertions(+), 27 deletions(-) diff --git a/OmniKit/OmnipodCommon/PumpManagerAlert.swift b/OmniKit/OmnipodCommon/PumpManagerAlert.swift index b92fa2c..480e09c 100644 --- a/OmniKit/OmnipodCommon/PumpManagerAlert.swift +++ b/OmniKit/OmnipodCommon/PumpManagerAlert.swift @@ -7,8 +7,8 @@ // import Foundation +import LoopAlgorithm import LoopKit -import HealthKit public enum PumpManagerAlert: Hashable { case multiCommand(triggeringSlot: AlertSlot?) @@ -72,8 +72,8 @@ public enum PumpManagerAlert: Hashable { case .podExpireImminent: return LocalizedString("Change Pod now. Insulin delivery will stop in 1 hour.", comment: "Alert content body for podExpireImminent pod alert") case .lowReservoir(_, let lowReservoirReminderValue): - let quantityFormatter = QuantityFormatter(for: .internationalUnit()) - let valueString = quantityFormatter.string(from: HKQuantity(unit: .internationalUnit(), doubleValue: lowReservoirReminderValue)) ?? String(describing: lowReservoirReminderValue) + let quantityFormatter = QuantityFormatter(for: .internationalUnit) + let valueString = quantityFormatter.string(from: LoopQuantity(unit: .internationalUnit, doubleValue: lowReservoirReminderValue)) ?? String(describing: lowReservoirReminderValue) return String(format: LocalizedString("%1$@ insulin or less remaining in Pod. Change Pod soon.", comment: "Format string for alert content body for lowReservoir pod alert. (1: reminder value)"), valueString) case .suspendInProgress: return LocalizedString("Suspend In Progress Reminder", comment: "Alert content body for suspendInProgress pod alert") diff --git a/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift b/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift index 37937f4..c9611bf 100644 --- a/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift +++ b/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift @@ -9,7 +9,7 @@ import SwiftUI import LoopKit import LoopKitUI -import HealthKit +import LoopAlgorithm import OmniKit import Combine @@ -205,7 +205,7 @@ class OmnipodSettingsViewModel: ObservableObject { return nil } - let reservoirVolumeFormatter = QuantityFormatter(for: .internationalUnit()) + let reservoirVolumeFormatter = QuantityFormatter(for: .internationalUnit) var didFinish: (() -> Void)? @@ -395,13 +395,13 @@ class OmnipodSettingsViewModel: ObservableObject { func reservoirText(for level: ReservoirLevel) -> String { switch level { case .aboveThreshold: - let quantity = HKQuantity(unit: .internationalUnit(), doubleValue: Pod.maximumReservoirReading) + let quantity = LoopQuantity(unit: .internationalUnit, doubleValue: Pod.maximumReservoirReading) let thresholdString = reservoirVolumeFormatter.string(from: quantity, includeUnit: false) ?? "" let unitString = reservoirVolumeFormatter.localizedUnitStringWithPlurality(forValue: Pod.maximumReservoirReading, avoidLineBreaking: true) return String(format: LocalizedString("%1$@+ %2$@", comment: "Format string for reservoir level above max measurable threshold. (1: measurable reservoir threshold) (2: units)"), thresholdString, unitString) case .valid(let value): - let quantity = HKQuantity(unit: .internationalUnit(), doubleValue: value) + let quantity = LoopQuantity(unit: .internationalUnit, doubleValue: value) return reservoirVolumeFormatter.string(from: quantity) ?? "" } } diff --git a/OmniKitUI/Views/ExpirationReminderPickerView.swift b/OmniKitUI/Views/ExpirationReminderPickerView.swift index 6744535..b9e8486 100644 --- a/OmniKitUI/Views/ExpirationReminderPickerView.swift +++ b/OmniKitUI/Views/ExpirationReminderPickerView.swift @@ -7,9 +7,9 @@ // import SwiftUI +import LoopAlgorithm import LoopKit import LoopKitUI -import HealthKit struct ExpirationReminderPickerView: View { @@ -21,7 +21,7 @@ struct ExpirationReminderPickerView: View { @State var showingHourPicker: Bool = false - var expirationDefaultFormatter = QuantityFormatter(for: .hour()) + var expirationDefaultFormatter = QuantityFormatter(for: .hour) var expirationDefaultString: String { return expirationReminderHourString(expirationReminderDefault.wrappedValue) @@ -57,7 +57,7 @@ struct ExpirationReminderPickerView: View { private func expirationReminderHourString(_ value: Int) -> String { if value > 0 { - return expirationDefaultFormatter.string(from: HKQuantity(unit: .hour(), doubleValue: Double(value)))! + return expirationDefaultFormatter.string(from: LoopQuantity(unit: .hour, doubleValue: Double(value)))! } else { return LocalizedString("No Reminder", comment: "Value text for no expiration reminder") } diff --git a/OmniKitUI/Views/LowReservoirReminderEditView.swift b/OmniKitUI/Views/LowReservoirReminderEditView.swift index 61fddbb..a20b9eb 100644 --- a/OmniKitUI/Views/LowReservoirReminderEditView.swift +++ b/OmniKitUI/Views/LowReservoirReminderEditView.swift @@ -6,11 +6,11 @@ // Copyright © 2021 LoopKit Authors. All rights reserved. // -import SwiftUI +import LoopAlgorithm import LoopKit import LoopKitUI -import HealthKit import OmniKit +import SwiftUI struct LowReservoirReminderEditView: View { @@ -89,7 +89,7 @@ struct LowReservoirReminderEditView: View { } func formatValue(_ value: Int) -> String { - return insulinQuantityFormatter.string(from: HKQuantity(unit: .internationalUnit(), doubleValue: Double(value))) ?? "" + return insulinQuantityFormatter.string(from: LoopQuantity(unit: .internationalUnit, doubleValue: Double(value))) ?? "" } var saveButtonText: String { @@ -148,7 +148,7 @@ struct LowReservoirReminderEditView_Previews: PreviewProvider { static var previews: some View { LowReservoirReminderEditView( lowReservoirReminderValue: 20, - insulinQuantityFormatter: QuantityFormatter(for: .internationalUnit()), + insulinQuantityFormatter: QuantityFormatter(for: .internationalUnit), onSave: { (_, _) in }, onFinish: { } ) diff --git a/OmniKitUI/Views/LowReservoirReminderSetupView.swift b/OmniKitUI/Views/LowReservoirReminderSetupView.swift index 4a64990..ab5ec1b 100644 --- a/OmniKitUI/Views/LowReservoirReminderSetupView.swift +++ b/OmniKitUI/Views/LowReservoirReminderSetupView.swift @@ -6,11 +6,11 @@ // Copyright © 2021 LoopKit Authors. All rights reserved. // -import SwiftUI +import LoopAlgorithm import LoopKitUI import LoopKit -import HealthKit import OmniKit +import SwiftUI struct LowReservoirReminderSetupView: View { @@ -20,10 +20,10 @@ struct LowReservoirReminderSetupView: View { public var continueButtonTapped: (() -> Void)? public var cancelButtonTapped: (() -> Void)? - var insulinQuantityFormatter = QuantityFormatter(for: .internationalUnit()) + var insulinQuantityFormatter = QuantityFormatter(for: .internationalUnit) func formatValue(_ value: Int) -> String { - return insulinQuantityFormatter.string(from: HKQuantity(unit: .internationalUnit(), doubleValue: Double(value))) ?? "" + return insulinQuantityFormatter.string(from: LoopQuantity(unit: .internationalUnit, doubleValue: Double(value))) ?? "" } var body: some View { diff --git a/OmniKitUI/Views/ManualTempBasalEntryView.swift b/OmniKitUI/Views/ManualTempBasalEntryView.swift index 9dc1ef8..60cf043 100644 --- a/OmniKitUI/Views/ManualTempBasalEntryView.swift +++ b/OmniKitUI/Views/ManualTempBasalEntryView.swift @@ -6,11 +6,11 @@ // Copyright © 2022 LoopKit Authors. All rights reserved. // -import SwiftUI +import LoopAlgorithm import LoopKitUI import LoopKit -import HealthKit import OmniKit +import SwiftUI struct ManualTempBasalEntryView: View { @@ -51,7 +51,7 @@ struct ManualTempBasalEntryView: View { } private static let durationFormatter: QuantityFormatter = { - let quantityFormatter = QuantityFormatter(for: .hour()) + let quantityFormatter = QuantityFormatter(for: .hour) quantityFormatter.numberFormatter.minimumFractionDigits = 1 quantityFormatter.numberFormatter.maximumFractionDigits = 1 quantityFormatter.unitStyle = .long @@ -59,16 +59,16 @@ struct ManualTempBasalEntryView: View { }() private var durationUnitsLabel: some View { - Text(QuantityFormatter(for: .hour()).localizedUnitStringWithPlurality()) + Text(QuantityFormatter(for: .hour).localizedUnitStringWithPlurality()) .foregroundColor(Color(.secondaryLabel)) } func formatRate(_ rate: Double) -> String { - return ManualTempBasalEntryView.rateFormatter.string(from: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: rate)) ?? "" + return ManualTempBasalEntryView.rateFormatter.string(from: LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: rate)) ?? "" } func formatDuration(_ duration: TimeInterval) -> String { - return ManualTempBasalEntryView.durationFormatter.string(from: HKQuantity(unit: .hour(), doubleValue: duration.hours)) ?? "" + return ManualTempBasalEntryView.durationFormatter.string(from: LoopQuantity(unit: .hour, doubleValue: duration.hours)) ?? "" } var body: some View { diff --git a/OmniKitUI/Views/NotificationSettingsView.swift b/OmniKitUI/Views/NotificationSettingsView.swift index d806b67..63d9e97 100644 --- a/OmniKitUI/Views/NotificationSettingsView.swift +++ b/OmniKitUI/Views/NotificationSettingsView.swift @@ -7,9 +7,9 @@ // import SwiftUI +import LoopAlgorithm import LoopKit import LoopKitUI -import HealthKit struct NotificationSettingsView: View { @@ -29,7 +29,7 @@ struct NotificationSettingsView: View { var onSaveLowReservoirReminder: ((_ selectedValue: Int, _ completion: @escaping (_ error: Error?) -> Void) -> Void)? - var insulinQuantityFormatter = QuantityFormatter(for: .internationalUnit()) + var insulinQuantityFormatter = QuantityFormatter(for: .internationalUnit) var body: some View { RoundedCardScrollView { @@ -114,7 +114,7 @@ struct NotificationSettingsView: View { { RoundedCardValueRow( label: LocalizedString("Low Reservoir Reminder", comment: "Label for low reservoir reminder row"), - value: insulinQuantityFormatter.string(from: HKQuantity(unit: .internationalUnit(), doubleValue: Double(lowReservoirReminderValue))) ?? "", + value: insulinQuantityFormatter.string(from: LoopQuantity(unit: .internationalUnit, doubleValue: Double(lowReservoirReminderValue))) ?? "", highlightValue: false, disclosure: true) } From e653d6e368ee08f72d5bdf2cfe405a6600292e5c Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Thu, 21 Nov 2024 22:18:36 -0800 Subject: [PATCH 09/26] [LOOP-5153] Remove HealthKit dependency from LoopAlgorithm --- OmniKit.xcodeproj/project.pbxproj | 8 -------- OmniKit/Extensions/HKUnit.swift | 24 ----------------------- OmniKitUI/Extensions/HKUnit.swift | 24 ----------------------- OmniKitUI/Views/OmnipodSettingsView.swift | 1 - 4 files changed, 57 deletions(-) delete mode 100644 OmniKit/Extensions/HKUnit.swift delete mode 100644 OmniKitUI/Extensions/HKUnit.swift diff --git a/OmniKit.xcodeproj/project.pbxproj b/OmniKit.xcodeproj/project.pbxproj index f445a65..b9f3dc3 100644 --- a/OmniKit.xcodeproj/project.pbxproj +++ b/OmniKit.xcodeproj/project.pbxproj @@ -141,13 +141,11 @@ C12EDA0429C7DDC800435701 /* TimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA0329C7DDC800435701 /* TimeInterval.swift */; }; C12EDA0629C7DE2500435701 /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA0529C7DE2500435701 /* TimeZone.swift */; }; C12EDA0829C7DE6200435701 /* NumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA0729C7DE6200435701 /* NumberFormatter.swift */; }; - C12EDA0A29C7DEAA00435701 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA0929C7DEAA00435701 /* HKUnit.swift */; }; C12EDA0C29C7DED000435701 /* LocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA0B29C7DED000435701 /* LocalizedString.swift */; }; C12EDA0E29C7DEFD00435701 /* NumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA0D29C7DEFD00435701 /* NumberFormatter.swift */; }; C12EDA1029C7DF1900435701 /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA0F29C7DF1900435701 /* NibLoadable.swift */; }; C12EDA1229C7DF4B00435701 /* IdentifiableClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA1129C7DF4B00435701 /* IdentifiableClass.swift */; }; C12EDA1429C7DFBF00435701 /* TimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA1329C7DFBF00435701 /* TimeInterval.swift */; }; - C12EDA1629C7DFF100435701 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA1529C7DFF100435701 /* HKUnit.swift */; }; C12EDA1829C7E01800435701 /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA1729C7E01800435701 /* TimeZone.swift */; }; C12EDA1B29C7E06900435701 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA1A29C7E06900435701 /* OSLog.swift */; }; D80339732A500489004FF953 /* PodInfoPulseLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = C124017E29C7D8E900B32844 /* PodInfoPulseLog.swift */; }; @@ -424,13 +422,11 @@ C12EDA0329C7DDC800435701 /* TimeInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeInterval.swift; sourceTree = ""; }; C12EDA0529C7DE2500435701 /* TimeZone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeZone.swift; sourceTree = ""; }; C12EDA0729C7DE6200435701 /* NumberFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumberFormatter.swift; sourceTree = ""; }; - C12EDA0929C7DEAA00435701 /* HKUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HKUnit.swift; sourceTree = ""; }; C12EDA0B29C7DED000435701 /* LocalizedString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedString.swift; sourceTree = ""; }; C12EDA0D29C7DEFD00435701 /* NumberFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumberFormatter.swift; sourceTree = ""; }; C12EDA0F29C7DF1900435701 /* NibLoadable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NibLoadable.swift; sourceTree = ""; }; C12EDA1129C7DF4B00435701 /* IdentifiableClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentifiableClass.swift; sourceTree = ""; }; C12EDA1329C7DFBF00435701 /* TimeInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeInterval.swift; sourceTree = ""; }; - C12EDA1529C7DFF100435701 /* HKUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HKUnit.swift; sourceTree = ""; }; C12EDA1729C7E01800435701 /* TimeZone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeZone.swift; sourceTree = ""; }; C12EDA1A29C7E06900435701 /* OSLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -538,7 +534,6 @@ C12EDA0329C7DDC800435701 /* TimeInterval.swift */, C12EDA0529C7DE2500435701 /* TimeZone.swift */, C12EDA0729C7DE6200435701 /* NumberFormatter.swift */, - C12EDA0929C7DEAA00435701 /* HKUnit.swift */, ); path = Extensions; sourceTree = ""; @@ -678,7 +673,6 @@ C12EDA0F29C7DF1900435701 /* NibLoadable.swift */, C12EDA1129C7DF4B00435701 /* IdentifiableClass.swift */, C12EDA1329C7DFBF00435701 /* TimeInterval.swift */, - C12EDA1529C7DFF100435701 /* HKUnit.swift */, C12EDA1729C7E01800435701 /* TimeZone.swift */, ); path = Extensions; @@ -1133,7 +1127,6 @@ C12401EB29C7D8E900B32844 /* Packet.swift in Sources */, C12401C029C7D8E900B32844 /* PlaceholderMessageBlock.swift in Sources */, C12401BA29C7D8E900B32844 /* PodInfoActivationTime.swift in Sources */, - C12EDA0A29C7DEAA00435701 /* HKUnit.swift in Sources */, C12401C229C7D8E900B32844 /* BolusExtraCommand.swift in Sources */, C12401E929C7D8E900B32844 /* Packet+RFPacket.swift in Sources */, C12401E729C7D8E900B32844 /* OmnipodPumpManager.swift in Sources */, @@ -1199,7 +1192,6 @@ C124028629C7DA9700B32844 /* NotificationSettingsView.swift in Sources */, C124027D29C7DA9700B32844 /* InsertCannulaView.swift in Sources */, C12EDA0C29C7DED000435701 /* LocalizedString.swift in Sources */, - C12EDA1629C7DFF100435701 /* HKUnit.swift in Sources */, C12EDA1829C7E01800435701 /* TimeZone.swift in Sources */, C124026D29C7DA9700B32844 /* Image.swift in Sources */, C124028E29C7DA9700B32844 /* ScheduledExpirationReminderEditView.swift in Sources */, diff --git a/OmniKit/Extensions/HKUnit.swift b/OmniKit/Extensions/HKUnit.swift deleted file mode 100644 index 8311b6c..0000000 --- a/OmniKit/Extensions/HKUnit.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// HKUnit.swift -// OmniKit -// -// Created by Pete Schwamb on 3/19/23. -// Copyright © 2023 LoopKit Authors. All rights reserved. -// - -import HealthKit - -extension HKUnit { - static let milligramsPerDeciliter: HKUnit = { - return HKUnit.gramUnit(with: .milli).unitDivided(by: .literUnit(with: .deci)) - }() - - static let millimolesPerLiter: HKUnit = { - return HKUnit.moleUnit(with: .milli, molarMass: HKUnitMolarMassBloodGlucose).unitDivided(by: .liter()) - }() - - static let internationalUnitsPerHour: HKUnit = { - return HKUnit.internationalUnit().unitDivided(by: .hour()) - }() - -} diff --git a/OmniKitUI/Extensions/HKUnit.swift b/OmniKitUI/Extensions/HKUnit.swift deleted file mode 100644 index 7bf65cd..0000000 --- a/OmniKitUI/Extensions/HKUnit.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// HKUnit.swift -// OmniKitUI -// -// Created by Pete Schwamb on 3/19/23. -// Copyright © 2023 LoopKit Authors. All rights reserved. -// - -import HealthKit - -extension HKUnit { - static let milligramsPerDeciliter: HKUnit = { - return HKUnit.gramUnit(with: .milli).unitDivided(by: .literUnit(with: .deci)) - }() - - static let millimolesPerLiter: HKUnit = { - return HKUnit.moleUnit(with: .milli, molarMass: HKUnitMolarMassBloodGlucose).unitDivided(by: .liter()) - }() - - static let internationalUnitsPerHour: HKUnit = { - return HKUnit.internationalUnit().unitDivided(by: .hour()) - }() - -} diff --git a/OmniKitUI/Views/OmnipodSettingsView.swift b/OmniKitUI/Views/OmnipodSettingsView.swift index d110670..478a28b 100644 --- a/OmniKitUI/Views/OmnipodSettingsView.swift +++ b/OmniKitUI/Views/OmnipodSettingsView.swift @@ -9,7 +9,6 @@ import SwiftUI import LoopKit import LoopKitUI -import HealthKit import OmniKit import RileyLinkBLEKit From 7bea3c911c363f8f220b44bbfcb12362252b2e04 Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Thu, 21 Nov 2024 22:18:36 -0800 Subject: [PATCH 10/26] [LOOP-5153] Remove HealthKit dependency from LoopAlgorithm --- OmniKit.xcodeproj/project.pbxproj | 8 -------- OmniKit/Extensions/HKUnit.swift | 24 ----------------------- OmniKitUI/Extensions/HKUnit.swift | 24 ----------------------- OmniKitUI/Views/OmnipodSettingsView.swift | 1 - 4 files changed, 57 deletions(-) delete mode 100644 OmniKit/Extensions/HKUnit.swift delete mode 100644 OmniKitUI/Extensions/HKUnit.swift diff --git a/OmniKit.xcodeproj/project.pbxproj b/OmniKit.xcodeproj/project.pbxproj index f445a65..b9f3dc3 100644 --- a/OmniKit.xcodeproj/project.pbxproj +++ b/OmniKit.xcodeproj/project.pbxproj @@ -141,13 +141,11 @@ C12EDA0429C7DDC800435701 /* TimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA0329C7DDC800435701 /* TimeInterval.swift */; }; C12EDA0629C7DE2500435701 /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA0529C7DE2500435701 /* TimeZone.swift */; }; C12EDA0829C7DE6200435701 /* NumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA0729C7DE6200435701 /* NumberFormatter.swift */; }; - C12EDA0A29C7DEAA00435701 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA0929C7DEAA00435701 /* HKUnit.swift */; }; C12EDA0C29C7DED000435701 /* LocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA0B29C7DED000435701 /* LocalizedString.swift */; }; C12EDA0E29C7DEFD00435701 /* NumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA0D29C7DEFD00435701 /* NumberFormatter.swift */; }; C12EDA1029C7DF1900435701 /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA0F29C7DF1900435701 /* NibLoadable.swift */; }; C12EDA1229C7DF4B00435701 /* IdentifiableClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA1129C7DF4B00435701 /* IdentifiableClass.swift */; }; C12EDA1429C7DFBF00435701 /* TimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA1329C7DFBF00435701 /* TimeInterval.swift */; }; - C12EDA1629C7DFF100435701 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA1529C7DFF100435701 /* HKUnit.swift */; }; C12EDA1829C7E01800435701 /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA1729C7E01800435701 /* TimeZone.swift */; }; C12EDA1B29C7E06900435701 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA1A29C7E06900435701 /* OSLog.swift */; }; D80339732A500489004FF953 /* PodInfoPulseLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = C124017E29C7D8E900B32844 /* PodInfoPulseLog.swift */; }; @@ -424,13 +422,11 @@ C12EDA0329C7DDC800435701 /* TimeInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeInterval.swift; sourceTree = ""; }; C12EDA0529C7DE2500435701 /* TimeZone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeZone.swift; sourceTree = ""; }; C12EDA0729C7DE6200435701 /* NumberFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumberFormatter.swift; sourceTree = ""; }; - C12EDA0929C7DEAA00435701 /* HKUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HKUnit.swift; sourceTree = ""; }; C12EDA0B29C7DED000435701 /* LocalizedString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedString.swift; sourceTree = ""; }; C12EDA0D29C7DEFD00435701 /* NumberFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumberFormatter.swift; sourceTree = ""; }; C12EDA0F29C7DF1900435701 /* NibLoadable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NibLoadable.swift; sourceTree = ""; }; C12EDA1129C7DF4B00435701 /* IdentifiableClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentifiableClass.swift; sourceTree = ""; }; C12EDA1329C7DFBF00435701 /* TimeInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeInterval.swift; sourceTree = ""; }; - C12EDA1529C7DFF100435701 /* HKUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HKUnit.swift; sourceTree = ""; }; C12EDA1729C7E01800435701 /* TimeZone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeZone.swift; sourceTree = ""; }; C12EDA1A29C7E06900435701 /* OSLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -538,7 +534,6 @@ C12EDA0329C7DDC800435701 /* TimeInterval.swift */, C12EDA0529C7DE2500435701 /* TimeZone.swift */, C12EDA0729C7DE6200435701 /* NumberFormatter.swift */, - C12EDA0929C7DEAA00435701 /* HKUnit.swift */, ); path = Extensions; sourceTree = ""; @@ -678,7 +673,6 @@ C12EDA0F29C7DF1900435701 /* NibLoadable.swift */, C12EDA1129C7DF4B00435701 /* IdentifiableClass.swift */, C12EDA1329C7DFBF00435701 /* TimeInterval.swift */, - C12EDA1529C7DFF100435701 /* HKUnit.swift */, C12EDA1729C7E01800435701 /* TimeZone.swift */, ); path = Extensions; @@ -1133,7 +1127,6 @@ C12401EB29C7D8E900B32844 /* Packet.swift in Sources */, C12401C029C7D8E900B32844 /* PlaceholderMessageBlock.swift in Sources */, C12401BA29C7D8E900B32844 /* PodInfoActivationTime.swift in Sources */, - C12EDA0A29C7DEAA00435701 /* HKUnit.swift in Sources */, C12401C229C7D8E900B32844 /* BolusExtraCommand.swift in Sources */, C12401E929C7D8E900B32844 /* Packet+RFPacket.swift in Sources */, C12401E729C7D8E900B32844 /* OmnipodPumpManager.swift in Sources */, @@ -1199,7 +1192,6 @@ C124028629C7DA9700B32844 /* NotificationSettingsView.swift in Sources */, C124027D29C7DA9700B32844 /* InsertCannulaView.swift in Sources */, C12EDA0C29C7DED000435701 /* LocalizedString.swift in Sources */, - C12EDA1629C7DFF100435701 /* HKUnit.swift in Sources */, C12EDA1829C7E01800435701 /* TimeZone.swift in Sources */, C124026D29C7DA9700B32844 /* Image.swift in Sources */, C124028E29C7DA9700B32844 /* ScheduledExpirationReminderEditView.swift in Sources */, diff --git a/OmniKit/Extensions/HKUnit.swift b/OmniKit/Extensions/HKUnit.swift deleted file mode 100644 index 8311b6c..0000000 --- a/OmniKit/Extensions/HKUnit.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// HKUnit.swift -// OmniKit -// -// Created by Pete Schwamb on 3/19/23. -// Copyright © 2023 LoopKit Authors. All rights reserved. -// - -import HealthKit - -extension HKUnit { - static let milligramsPerDeciliter: HKUnit = { - return HKUnit.gramUnit(with: .milli).unitDivided(by: .literUnit(with: .deci)) - }() - - static let millimolesPerLiter: HKUnit = { - return HKUnit.moleUnit(with: .milli, molarMass: HKUnitMolarMassBloodGlucose).unitDivided(by: .liter()) - }() - - static let internationalUnitsPerHour: HKUnit = { - return HKUnit.internationalUnit().unitDivided(by: .hour()) - }() - -} diff --git a/OmniKitUI/Extensions/HKUnit.swift b/OmniKitUI/Extensions/HKUnit.swift deleted file mode 100644 index 7bf65cd..0000000 --- a/OmniKitUI/Extensions/HKUnit.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// HKUnit.swift -// OmniKitUI -// -// Created by Pete Schwamb on 3/19/23. -// Copyright © 2023 LoopKit Authors. All rights reserved. -// - -import HealthKit - -extension HKUnit { - static let milligramsPerDeciliter: HKUnit = { - return HKUnit.gramUnit(with: .milli).unitDivided(by: .literUnit(with: .deci)) - }() - - static let millimolesPerLiter: HKUnit = { - return HKUnit.moleUnit(with: .milli, molarMass: HKUnitMolarMassBloodGlucose).unitDivided(by: .liter()) - }() - - static let internationalUnitsPerHour: HKUnit = { - return HKUnit.internationalUnit().unitDivided(by: .hour()) - }() - -} diff --git a/OmniKitUI/Views/OmnipodSettingsView.swift b/OmniKitUI/Views/OmnipodSettingsView.swift index d110670..478a28b 100644 --- a/OmniKitUI/Views/OmnipodSettingsView.swift +++ b/OmniKitUI/Views/OmnipodSettingsView.swift @@ -9,7 +9,6 @@ import SwiftUI import LoopKit import LoopKitUI -import HealthKit import OmniKit import RileyLinkBLEKit From 7391f12a89ef38ba124f9aab9bc910d824534117 Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Fri, 6 Dec 2024 14:33:21 -0800 Subject: [PATCH 11/26] Bump to iOS 17 --- OmniKit.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OmniKit.xcodeproj/project.pbxproj b/OmniKit.xcodeproj/project.pbxproj index b9f3dc3..102cdc0 100644 --- a/OmniKit.xcodeproj/project.pbxproj +++ b/OmniKit.xcodeproj/project.pbxproj @@ -1397,7 +1397,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.2; + IPHONEOS_DEPLOYMENT_TARGET = 17.6; LOCALIZED_STRING_MACRO_NAMES = ( NSLocalizedString, CFCopyLocalizedString, @@ -1461,7 +1461,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.2; + IPHONEOS_DEPLOYMENT_TARGET = 17.6; LOCALIZED_STRING_MACRO_NAMES = ( NSLocalizedString, CFCopyLocalizedString, From 703e641408a93ce563c79a71b1c9ecbec84a3c83 Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Fri, 6 Dec 2024 14:33:21 -0800 Subject: [PATCH 12/26] Bump to iOS 17 --- OmniKit.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OmniKit.xcodeproj/project.pbxproj b/OmniKit.xcodeproj/project.pbxproj index b9f3dc3..102cdc0 100644 --- a/OmniKit.xcodeproj/project.pbxproj +++ b/OmniKit.xcodeproj/project.pbxproj @@ -1397,7 +1397,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.2; + IPHONEOS_DEPLOYMENT_TARGET = 17.6; LOCALIZED_STRING_MACRO_NAMES = ( NSLocalizedString, CFCopyLocalizedString, @@ -1461,7 +1461,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.2; + IPHONEOS_DEPLOYMENT_TARGET = 17.6; LOCALIZED_STRING_MACRO_NAMES = ( NSLocalizedString, CFCopyLocalizedString, From e035d449858e9ec51906322069908bc8b725a869 Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Mon, 2 Jun 2025 13:27:25 -0700 Subject: [PATCH 13/26] [LOOP-5295] decisionId on DoseEntry and PersistedPumpEvent --- OmniKit/OmnipodCommon/UnfinalizedDose.swift | 19 +++++++++++++++---- OmniKit/PumpManager/OmnipodPumpManager.swift | 12 ++++++------ OmniKit/PumpManager/PodCommsSession.swift | 8 ++++---- OmniKit/PumpManager/PodState.swift | 2 +- OmniKitTests/PodCommsSessionTests.swift | 8 ++++---- .../ViewModels/OmnipodSettingsViewModel.swift | 4 ++-- OmniKitUI/Views/OmnipodSettingsView.swift | 4 ++-- 7 files changed, 34 insertions(+), 23 deletions(-) diff --git a/OmniKit/OmnipodCommon/UnfinalizedDose.swift b/OmniKit/OmnipodCommon/UnfinalizedDose.swift index 9e2858b..6ac6daf 100644 --- a/OmniKit/OmnipodCommon/UnfinalizedDose.swift +++ b/OmniKit/OmnipodCommon/UnfinalizedDose.swift @@ -65,6 +65,8 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti var isHighTemp: Bool = false // Track this for situations where cancelling temp basal is unacknowledged, and recovery fails, and we have to assume the most possible delivery var insulinType: InsulinType? + var decisionId: UUID? + var finishTime: Date? { get { return duration != nil ? startTime.addingTimeInterval(duration!) : nil @@ -101,7 +103,8 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti return units } - init(bolusAmount: Double, startTime: Date, scheduledCertainty: ScheduledCertainty, insulinType: InsulinType, automatic: Bool = false) { + init(decisionId: UUID?, bolusAmount: Double, startTime: Date, scheduledCertainty: ScheduledCertainty, insulinType: InsulinType, automatic: Bool = false) { + self.decisionId = decisionId self.doseType = .bolus self.units = bolusAmount self.startTime = startTime @@ -112,7 +115,8 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti self.insulinType = insulinType } - init(tempBasalRate: Double, startTime: Date, duration: TimeInterval, isHighTemp: Bool, automatic: Bool, scheduledCertainty: ScheduledCertainty, insulinType: InsulinType) { + init(decisionId: UUID?, tempBasalRate: Double, startTime: Date, duration: TimeInterval, isHighTemp: Bool, automatic: Bool, scheduledCertainty: ScheduledCertainty, insulinType: InsulinType) { + self.decisionId = decisionId self.doseType = .tempBasal self.units = tempBasalRate * duration.hours self.startTime = startTime @@ -257,6 +261,10 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti if let rawInsulinType = rawValue["insulinType"] as? InsulinType.RawValue { self.insulinType = InsulinType(rawValue: rawInsulinType) } + + if let decisionIdString = rawValue["decisionId"] as? String { + self.decisionId = UUID(uuidString: decisionIdString)! + } } @@ -274,6 +282,7 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti rawValue["scheduledTempRate"] = scheduledTempRate rawValue["duration"] = duration rawValue["insulinType"] = insulinType?.rawValue + rawValue["decisionId"] = decisionId?.uuidString return rawValue } @@ -308,6 +317,7 @@ extension DoseEntry { endDate: dose.finishTime, value: dose.scheduledUnits ?? dose.units, unit: .units, + decisionId: dose.decisionId, deliveredUnits: dose.finalizedUnits, insulinType: dose.insulinType, automatic: dose.automatic, @@ -320,6 +330,7 @@ extension DoseEntry { endDate: dose.finishTime, value: dose.scheduledTempRate ?? dose.rate, unit: .unitsPerHour, + decisionId: dose.decisionId, deliveredUnits: dose.finalizedUnits, insulinType: dose.insulinType, automatic: dose.automatic, @@ -337,9 +348,9 @@ extension StartProgram { func unfinalizedDose(at programDate: Date, withCertainty certainty: UnfinalizedDose.ScheduledCertainty, insulinType: InsulinType) -> UnfinalizedDose? { switch self { case .bolus(volume: let volume, automatic: let automatic): - return UnfinalizedDose(bolusAmount: volume, startTime: programDate, scheduledCertainty: certainty, insulinType: insulinType, automatic: automatic) + return UnfinalizedDose(decisionId: nil, bolusAmount: volume, startTime: programDate, scheduledCertainty: certainty, insulinType: insulinType, automatic: automatic) case .tempBasal(unitsPerHour: let rate, duration: let duration, let isHighTemp, let automatic): - return UnfinalizedDose(tempBasalRate: rate, startTime: programDate, duration: duration, isHighTemp: isHighTemp, automatic: automatic, scheduledCertainty: certainty, insulinType: insulinType) + return UnfinalizedDose(decisionId: nil, tempBasalRate: rate, startTime: programDate, duration: duration, isHighTemp: isHighTemp, automatic: automatic, scheduledCertainty: certainty, insulinType: insulinType) case .basalProgram: return UnfinalizedDose(resumeStartTime: programDate, scheduledCertainty: certainty, insulinType: insulinType) } diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift index e69dde3..99473d9 100644 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -1513,7 +1513,7 @@ extension OmnipodPumpManager: PumpManager { } } - public func enactBolus(units: Double, activationType: BolusActivationType, completion: @escaping (PumpManagerError?) -> Void) { + public func enactBolus(decisionId: UUID?, units: Double, activationType: BolusActivationType, completion: @escaping (PumpManagerError?) -> Void) { guard self.hasActivePod else { completion(.configuration(OmnipodPumpManagerError.noPodPaired)) return @@ -1554,7 +1554,7 @@ extension OmnipodPumpManager: PumpManager { // Use a maximum programReminderInterval value of 0x3F to denote an automatic bolus in the communication log let programReminderInterval: TimeInterval = activationType.isAutomatic ? TimeInterval(minutes: 0x3F) : 0 - let result = session.bolus(units: enactUnits, automatic: activationType.isAutomatic, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep, programReminderInterval: programReminderInterval) + let result = session.bolus(decisionId: decisionId, units: enactUnits, automatic: activationType.isAutomatic, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep, programReminderInterval: programReminderInterval) switch result { case .success: @@ -1631,11 +1631,11 @@ extension OmnipodPumpManager: PumpManager { } } - public func enactTempBasal(unitsPerHour: Double, for duration: TimeInterval, completion: @escaping (PumpManagerError?) -> Void) { - runTemporaryBasalProgram(unitsPerHour: unitsPerHour, for: duration, automatic: true, completion: completion) + public func enactTempBasal(decisionId: UUID?, unitsPerHour: Double, for duration: TimeInterval, completion: @escaping (PumpManagerError?) -> Void) { + runTemporaryBasalProgram(decisionId: decisionId, unitsPerHour: unitsPerHour, for: duration, automatic: true, completion: completion) } - public func runTemporaryBasalProgram(unitsPerHour: Double, for duration: TimeInterval, automatic: Bool, completion: @escaping (PumpManagerError?) -> Void) { + public func runTemporaryBasalProgram(decisionId: UUID?, unitsPerHour: Double, for duration: TimeInterval, automatic: Bool, completion: @escaping (PumpManagerError?) -> Void) { guard self.hasActivePod else { completion(.configuration(OmnipodPumpManagerError.noPodPaired)) @@ -1736,7 +1736,7 @@ extension OmnipodPumpManager: PumpManager { let scheduledRate = self.state.basalSchedule.currentRate(using: calendar, at: self.dateGenerator()) let isHighTemp = rate > scheduledRate - let result = session.setTempBasal(rate: rate, duration: duration, isHighTemp: isHighTemp, automatic: automatic, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep) + let result = session.setTempBasal(decisionId: decisionId, rate: rate, duration: duration, isHighTemp: isHighTemp, automatic: automatic, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep) switch result { case .success: session.dosesForStorage() { (doses) -> Bool in diff --git a/OmniKit/PumpManager/PodCommsSession.swift b/OmniKit/PumpManager/PodCommsSession.swift index c509ea5..243b954 100644 --- a/OmniKit/PumpManager/PodCommsSession.swift +++ b/OmniKit/PumpManager/PodCommsSession.swift @@ -491,7 +491,7 @@ public class PodCommsSession { } - public func bolus(units: Double, automatic: Bool = false, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0, extendedUnits: Double = 0.0, extendedDuration: TimeInterval = 0) -> DeliveryCommandResult { + public func bolus(decisionId: UUID?, units: Double, automatic: Bool = false, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0, extendedUnits: Double = 0.0, extendedDuration: TimeInterval = 0) -> DeliveryCommandResult { guard podState.unacknowledgedCommand == nil else { return DeliveryCommandResult.certainFailure(error: .unacknowledgedCommandPending) @@ -516,7 +516,7 @@ public class PodCommsSession { podState.unacknowledgedCommand = PendingCommand.program(.bolus(volume: units, automatic: automatic), transport.messageNumber, currentDate) let statusResponse: StatusResponse = try send([bolusScheduleCommand, bolusExtraCommand]) podState.unacknowledgedCommand = nil - podState.unfinalizedBolus = UnfinalizedDose(bolusAmount: units, startTime: currentDate, scheduledCertainty: .certain, insulinType: podState.insulinType, automatic: automatic) + podState.unfinalizedBolus = UnfinalizedDose(decisionId: decisionId, bolusAmount: units, startTime: currentDate, scheduledCertainty: .certain, insulinType: podState.insulinType, automatic: automatic) podState.updateFromStatusResponse(statusResponse, at: currentDate) return DeliveryCommandResult.success(statusResponse: statusResponse) } catch PodCommsError.unacknowledgedMessage(let seq, let error) { @@ -529,7 +529,7 @@ public class PodCommsSession { } } - public func setTempBasal(rate: Double, duration: TimeInterval, isHighTemp: Bool, automatic: Bool, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0) -> DeliveryCommandResult { + public func setTempBasal(decisionId: UUID?, rate: Double, duration: TimeInterval, isHighTemp: Bool, automatic: Bool, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0) -> DeliveryCommandResult { guard podState.unacknowledgedCommand == nil else { return DeliveryCommandResult.certainFailure(error: .unacknowledgedCommandPending) @@ -548,7 +548,7 @@ public class PodCommsSession { podState.unacknowledgedCommand = PendingCommand.program(.tempBasal(unitsPerHour: rate, duration: duration, isHighTemp: isHighTemp, automatic: automatic), transport.messageNumber, startTime) let status: StatusResponse = try send([tempBasalCommand, tempBasalExtraCommand]) podState.unacknowledgedCommand = nil - podState.unfinalizedTempBasal = UnfinalizedDose(tempBasalRate: rate, startTime: startTime, duration: duration, isHighTemp: isHighTemp, automatic: automatic, scheduledCertainty: .certain, insulinType: podState.insulinType) + podState.unfinalizedTempBasal = UnfinalizedDose(decisionId: decisionId, tempBasalRate: rate, startTime: startTime, duration: duration, isHighTemp: isHighTemp, automatic: automatic, scheduledCertainty: .certain, insulinType: podState.insulinType) podState.updateFromStatusResponse(status, at: currentDate) return DeliveryCommandResult.success(statusResponse: status) } catch PodCommsError.unacknowledgedMessage(let seq, let error) { diff --git a/OmniKit/PumpManager/PodState.swift b/OmniKit/PumpManager/PodState.swift index fd051b6..5d49eab 100644 --- a/OmniKit/PumpManager/PodState.swift +++ b/OmniKit/PumpManager/PodState.swift @@ -277,7 +277,7 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl if deliveryStatus.bolusing && unfinalizedBolus == nil { // active bolus that Loop doesn't know about? if podProgressStatus.readyForDelivery { // Create an unfinalizedBolus with the remaining bolus amount to capture what we can. - unfinalizedBolus = UnfinalizedDose(bolusAmount: bolusNotDelivered, startTime: date, scheduledCertainty: .certain, insulinType: insulinType, automatic: false) + unfinalizedBolus = UnfinalizedDose(decisionId: nil, bolusAmount: bolusNotDelivered, startTime: date, scheduledCertainty: .certain, insulinType: insulinType, automatic: false) } } diff --git a/OmniKitTests/PodCommsSessionTests.swift b/OmniKitTests/PodCommsSessionTests.swift index 1a8ded1..58ebfd0 100644 --- a/OmniKitTests/PodCommsSessionTests.swift +++ b/OmniKitTests/PodCommsSessionTests.swift @@ -64,7 +64,7 @@ class PodCommsSessionTests: XCTestCase { // Try sending another bolus command: nonce should be 676940027 XCTAssertEqual(545302454, lastPodStateUpdate!.currentNonce) - let _ = session.bolus(units: 2, automatic: false) + let _ = session.bolus(decisionId: nil, units: 2, automatic: false) let bolusTry3 = mockTransport.sentMessages[2].messageBlocks[0] as! SetInsulinScheduleCommand XCTAssertEqual(545302454, bolusTry3.nonce) @@ -75,7 +75,7 @@ class PodCommsSessionTests: XCTestCase { mockTransport.throwSendMessageError = PodCommsError.unacknowledgedMessage(sequenceNumber: 5, error: PodCommsError.noResponse) - let _ = session.bolus(units: 3) + let _ = session.bolus(decisionId: nil, units: 3) XCTAssertNotNil(lastPodStateUpdate?.unacknowledgedCommand) @@ -83,7 +83,7 @@ class PodCommsSessionTests: XCTestCase { func testBolusFinishedEarlyOnPodIsMarkedNonMutable() { let mockStart = Date() - podState.unfinalizedBolus = UnfinalizedDose(bolusAmount: 4.45, startTime: mockStart, scheduledCertainty: .certain, insulinType: .novolog) + podState.unfinalizedBolus = UnfinalizedDose(decisionId: nil, bolusAmount: 4.45, startTime: mockStart, scheduledCertainty: .certain, insulinType: .novolog) let session = PodCommsSession(podState: podState, transport: mockTransport, delegate: self) // Simulate a status request a bit before the bolus is expected to finish @@ -127,7 +127,7 @@ class PodCommsSessionTests: XCTestCase { mockTransport.addResponse(statusResponse) - let _ = session.bolus(units: 3) + let _ = session.bolus(decisionId: nil, units: 3) XCTAssertNil(lastPodStateUpdate?.unacknowledgedCommand) XCTAssertNotNil(lastPodStateUpdate?.unfinalizedBolus) diff --git a/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift b/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift index c9611bf..a0f8a19 100644 --- a/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift +++ b/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift @@ -305,8 +305,8 @@ class OmnipodSettingsViewModel: ObservableObject { } } - func runTemporaryBasalProgram(unitsPerHour: Double, for duration: TimeInterval, completion: @escaping (PumpManagerError?) -> Void) { - pumpManager.runTemporaryBasalProgram(unitsPerHour: unitsPerHour, for: duration, automatic: false, completion: completion) + func runTemporaryBasalProgram(decisionId: UUID?, unitsPerHour: Double, for duration: TimeInterval, completion: @escaping (PumpManagerError?) -> Void) { + pumpManager.runTemporaryBasalProgram(decisionId: decisionId, unitsPerHour: unitsPerHour, for: duration, automatic: false, completion: completion) } func saveScheduledExpirationReminder(_ selectedDate: Date?, _ completion: @escaping (Error?) -> Void) { diff --git a/OmniKitUI/Views/OmnipodSettingsView.swift b/OmniKitUI/Views/OmnipodSettingsView.swift index 478a28b..9bceeae 100644 --- a/OmniKitUI/Views/OmnipodSettingsView.swift +++ b/OmniKitUI/Views/OmnipodSettingsView.swift @@ -209,7 +209,7 @@ struct OmnipodSettingsView: View { .sheet(isPresented: $showManualTempBasalOptions) { ManualTempBasalEntryView( enactBasal: { rate, duration, completion in - viewModel.runTemporaryBasalProgram(unitsPerHour: rate, for: duration) { error in + viewModel.runTemporaryBasalProgram(decisionId: nil, unitsPerHour: rate, for: duration) { error in completion(error) if error == nil { showManualTempBasalOptions = false @@ -565,7 +565,7 @@ struct OmnipodSettingsView: View { func cancelManualBasal() { cancelingTempBasal = true - viewModel.runTemporaryBasalProgram(unitsPerHour: 0, for: 0) { error in + viewModel.runTemporaryBasalProgram(decisionId: nil, unitsPerHour: 0, for: 0) { error in cancelingTempBasal = false if let error = error { self.viewModel.activeAlert = .cancelManualBasalError(error) From fae27e2eb10473cee96a3aa5eb38d35390634e4f Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Mon, 2 Jun 2025 13:27:25 -0700 Subject: [PATCH 14/26] [LOOP-5295] decisionId on DoseEntry and PersistedPumpEvent --- OmniKit/OmnipodCommon/UnfinalizedDose.swift | 19 +++++++++++++++---- OmniKit/PumpManager/OmnipodPumpManager.swift | 12 ++++++------ OmniKit/PumpManager/PodCommsSession.swift | 8 ++++---- OmniKit/PumpManager/PodState.swift | 2 +- OmniKitTests/PodCommsSessionTests.swift | 8 ++++---- .../ViewModels/OmnipodSettingsViewModel.swift | 4 ++-- OmniKitUI/Views/OmnipodSettingsView.swift | 4 ++-- 7 files changed, 34 insertions(+), 23 deletions(-) diff --git a/OmniKit/OmnipodCommon/UnfinalizedDose.swift b/OmniKit/OmnipodCommon/UnfinalizedDose.swift index 9e2858b..6ac6daf 100644 --- a/OmniKit/OmnipodCommon/UnfinalizedDose.swift +++ b/OmniKit/OmnipodCommon/UnfinalizedDose.swift @@ -65,6 +65,8 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti var isHighTemp: Bool = false // Track this for situations where cancelling temp basal is unacknowledged, and recovery fails, and we have to assume the most possible delivery var insulinType: InsulinType? + var decisionId: UUID? + var finishTime: Date? { get { return duration != nil ? startTime.addingTimeInterval(duration!) : nil @@ -101,7 +103,8 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti return units } - init(bolusAmount: Double, startTime: Date, scheduledCertainty: ScheduledCertainty, insulinType: InsulinType, automatic: Bool = false) { + init(decisionId: UUID?, bolusAmount: Double, startTime: Date, scheduledCertainty: ScheduledCertainty, insulinType: InsulinType, automatic: Bool = false) { + self.decisionId = decisionId self.doseType = .bolus self.units = bolusAmount self.startTime = startTime @@ -112,7 +115,8 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti self.insulinType = insulinType } - init(tempBasalRate: Double, startTime: Date, duration: TimeInterval, isHighTemp: Bool, automatic: Bool, scheduledCertainty: ScheduledCertainty, insulinType: InsulinType) { + init(decisionId: UUID?, tempBasalRate: Double, startTime: Date, duration: TimeInterval, isHighTemp: Bool, automatic: Bool, scheduledCertainty: ScheduledCertainty, insulinType: InsulinType) { + self.decisionId = decisionId self.doseType = .tempBasal self.units = tempBasalRate * duration.hours self.startTime = startTime @@ -257,6 +261,10 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti if let rawInsulinType = rawValue["insulinType"] as? InsulinType.RawValue { self.insulinType = InsulinType(rawValue: rawInsulinType) } + + if let decisionIdString = rawValue["decisionId"] as? String { + self.decisionId = UUID(uuidString: decisionIdString)! + } } @@ -274,6 +282,7 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti rawValue["scheduledTempRate"] = scheduledTempRate rawValue["duration"] = duration rawValue["insulinType"] = insulinType?.rawValue + rawValue["decisionId"] = decisionId?.uuidString return rawValue } @@ -308,6 +317,7 @@ extension DoseEntry { endDate: dose.finishTime, value: dose.scheduledUnits ?? dose.units, unit: .units, + decisionId: dose.decisionId, deliveredUnits: dose.finalizedUnits, insulinType: dose.insulinType, automatic: dose.automatic, @@ -320,6 +330,7 @@ extension DoseEntry { endDate: dose.finishTime, value: dose.scheduledTempRate ?? dose.rate, unit: .unitsPerHour, + decisionId: dose.decisionId, deliveredUnits: dose.finalizedUnits, insulinType: dose.insulinType, automatic: dose.automatic, @@ -337,9 +348,9 @@ extension StartProgram { func unfinalizedDose(at programDate: Date, withCertainty certainty: UnfinalizedDose.ScheduledCertainty, insulinType: InsulinType) -> UnfinalizedDose? { switch self { case .bolus(volume: let volume, automatic: let automatic): - return UnfinalizedDose(bolusAmount: volume, startTime: programDate, scheduledCertainty: certainty, insulinType: insulinType, automatic: automatic) + return UnfinalizedDose(decisionId: nil, bolusAmount: volume, startTime: programDate, scheduledCertainty: certainty, insulinType: insulinType, automatic: automatic) case .tempBasal(unitsPerHour: let rate, duration: let duration, let isHighTemp, let automatic): - return UnfinalizedDose(tempBasalRate: rate, startTime: programDate, duration: duration, isHighTemp: isHighTemp, automatic: automatic, scheduledCertainty: certainty, insulinType: insulinType) + return UnfinalizedDose(decisionId: nil, tempBasalRate: rate, startTime: programDate, duration: duration, isHighTemp: isHighTemp, automatic: automatic, scheduledCertainty: certainty, insulinType: insulinType) case .basalProgram: return UnfinalizedDose(resumeStartTime: programDate, scheduledCertainty: certainty, insulinType: insulinType) } diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift index e69dde3..99473d9 100644 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -1513,7 +1513,7 @@ extension OmnipodPumpManager: PumpManager { } } - public func enactBolus(units: Double, activationType: BolusActivationType, completion: @escaping (PumpManagerError?) -> Void) { + public func enactBolus(decisionId: UUID?, units: Double, activationType: BolusActivationType, completion: @escaping (PumpManagerError?) -> Void) { guard self.hasActivePod else { completion(.configuration(OmnipodPumpManagerError.noPodPaired)) return @@ -1554,7 +1554,7 @@ extension OmnipodPumpManager: PumpManager { // Use a maximum programReminderInterval value of 0x3F to denote an automatic bolus in the communication log let programReminderInterval: TimeInterval = activationType.isAutomatic ? TimeInterval(minutes: 0x3F) : 0 - let result = session.bolus(units: enactUnits, automatic: activationType.isAutomatic, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep, programReminderInterval: programReminderInterval) + let result = session.bolus(decisionId: decisionId, units: enactUnits, automatic: activationType.isAutomatic, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep, programReminderInterval: programReminderInterval) switch result { case .success: @@ -1631,11 +1631,11 @@ extension OmnipodPumpManager: PumpManager { } } - public func enactTempBasal(unitsPerHour: Double, for duration: TimeInterval, completion: @escaping (PumpManagerError?) -> Void) { - runTemporaryBasalProgram(unitsPerHour: unitsPerHour, for: duration, automatic: true, completion: completion) + public func enactTempBasal(decisionId: UUID?, unitsPerHour: Double, for duration: TimeInterval, completion: @escaping (PumpManagerError?) -> Void) { + runTemporaryBasalProgram(decisionId: decisionId, unitsPerHour: unitsPerHour, for: duration, automatic: true, completion: completion) } - public func runTemporaryBasalProgram(unitsPerHour: Double, for duration: TimeInterval, automatic: Bool, completion: @escaping (PumpManagerError?) -> Void) { + public func runTemporaryBasalProgram(decisionId: UUID?, unitsPerHour: Double, for duration: TimeInterval, automatic: Bool, completion: @escaping (PumpManagerError?) -> Void) { guard self.hasActivePod else { completion(.configuration(OmnipodPumpManagerError.noPodPaired)) @@ -1736,7 +1736,7 @@ extension OmnipodPumpManager: PumpManager { let scheduledRate = self.state.basalSchedule.currentRate(using: calendar, at: self.dateGenerator()) let isHighTemp = rate > scheduledRate - let result = session.setTempBasal(rate: rate, duration: duration, isHighTemp: isHighTemp, automatic: automatic, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep) + let result = session.setTempBasal(decisionId: decisionId, rate: rate, duration: duration, isHighTemp: isHighTemp, automatic: automatic, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep) switch result { case .success: session.dosesForStorage() { (doses) -> Bool in diff --git a/OmniKit/PumpManager/PodCommsSession.swift b/OmniKit/PumpManager/PodCommsSession.swift index c509ea5..243b954 100644 --- a/OmniKit/PumpManager/PodCommsSession.swift +++ b/OmniKit/PumpManager/PodCommsSession.swift @@ -491,7 +491,7 @@ public class PodCommsSession { } - public func bolus(units: Double, automatic: Bool = false, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0, extendedUnits: Double = 0.0, extendedDuration: TimeInterval = 0) -> DeliveryCommandResult { + public func bolus(decisionId: UUID?, units: Double, automatic: Bool = false, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0, extendedUnits: Double = 0.0, extendedDuration: TimeInterval = 0) -> DeliveryCommandResult { guard podState.unacknowledgedCommand == nil else { return DeliveryCommandResult.certainFailure(error: .unacknowledgedCommandPending) @@ -516,7 +516,7 @@ public class PodCommsSession { podState.unacknowledgedCommand = PendingCommand.program(.bolus(volume: units, automatic: automatic), transport.messageNumber, currentDate) let statusResponse: StatusResponse = try send([bolusScheduleCommand, bolusExtraCommand]) podState.unacknowledgedCommand = nil - podState.unfinalizedBolus = UnfinalizedDose(bolusAmount: units, startTime: currentDate, scheduledCertainty: .certain, insulinType: podState.insulinType, automatic: automatic) + podState.unfinalizedBolus = UnfinalizedDose(decisionId: decisionId, bolusAmount: units, startTime: currentDate, scheduledCertainty: .certain, insulinType: podState.insulinType, automatic: automatic) podState.updateFromStatusResponse(statusResponse, at: currentDate) return DeliveryCommandResult.success(statusResponse: statusResponse) } catch PodCommsError.unacknowledgedMessage(let seq, let error) { @@ -529,7 +529,7 @@ public class PodCommsSession { } } - public func setTempBasal(rate: Double, duration: TimeInterval, isHighTemp: Bool, automatic: Bool, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0) -> DeliveryCommandResult { + public func setTempBasal(decisionId: UUID?, rate: Double, duration: TimeInterval, isHighTemp: Bool, automatic: Bool, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0) -> DeliveryCommandResult { guard podState.unacknowledgedCommand == nil else { return DeliveryCommandResult.certainFailure(error: .unacknowledgedCommandPending) @@ -548,7 +548,7 @@ public class PodCommsSession { podState.unacknowledgedCommand = PendingCommand.program(.tempBasal(unitsPerHour: rate, duration: duration, isHighTemp: isHighTemp, automatic: automatic), transport.messageNumber, startTime) let status: StatusResponse = try send([tempBasalCommand, tempBasalExtraCommand]) podState.unacknowledgedCommand = nil - podState.unfinalizedTempBasal = UnfinalizedDose(tempBasalRate: rate, startTime: startTime, duration: duration, isHighTemp: isHighTemp, automatic: automatic, scheduledCertainty: .certain, insulinType: podState.insulinType) + podState.unfinalizedTempBasal = UnfinalizedDose(decisionId: decisionId, tempBasalRate: rate, startTime: startTime, duration: duration, isHighTemp: isHighTemp, automatic: automatic, scheduledCertainty: .certain, insulinType: podState.insulinType) podState.updateFromStatusResponse(status, at: currentDate) return DeliveryCommandResult.success(statusResponse: status) } catch PodCommsError.unacknowledgedMessage(let seq, let error) { diff --git a/OmniKit/PumpManager/PodState.swift b/OmniKit/PumpManager/PodState.swift index fd051b6..5d49eab 100644 --- a/OmniKit/PumpManager/PodState.swift +++ b/OmniKit/PumpManager/PodState.swift @@ -277,7 +277,7 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl if deliveryStatus.bolusing && unfinalizedBolus == nil { // active bolus that Loop doesn't know about? if podProgressStatus.readyForDelivery { // Create an unfinalizedBolus with the remaining bolus amount to capture what we can. - unfinalizedBolus = UnfinalizedDose(bolusAmount: bolusNotDelivered, startTime: date, scheduledCertainty: .certain, insulinType: insulinType, automatic: false) + unfinalizedBolus = UnfinalizedDose(decisionId: nil, bolusAmount: bolusNotDelivered, startTime: date, scheduledCertainty: .certain, insulinType: insulinType, automatic: false) } } diff --git a/OmniKitTests/PodCommsSessionTests.swift b/OmniKitTests/PodCommsSessionTests.swift index 1a8ded1..58ebfd0 100644 --- a/OmniKitTests/PodCommsSessionTests.swift +++ b/OmniKitTests/PodCommsSessionTests.swift @@ -64,7 +64,7 @@ class PodCommsSessionTests: XCTestCase { // Try sending another bolus command: nonce should be 676940027 XCTAssertEqual(545302454, lastPodStateUpdate!.currentNonce) - let _ = session.bolus(units: 2, automatic: false) + let _ = session.bolus(decisionId: nil, units: 2, automatic: false) let bolusTry3 = mockTransport.sentMessages[2].messageBlocks[0] as! SetInsulinScheduleCommand XCTAssertEqual(545302454, bolusTry3.nonce) @@ -75,7 +75,7 @@ class PodCommsSessionTests: XCTestCase { mockTransport.throwSendMessageError = PodCommsError.unacknowledgedMessage(sequenceNumber: 5, error: PodCommsError.noResponse) - let _ = session.bolus(units: 3) + let _ = session.bolus(decisionId: nil, units: 3) XCTAssertNotNil(lastPodStateUpdate?.unacknowledgedCommand) @@ -83,7 +83,7 @@ class PodCommsSessionTests: XCTestCase { func testBolusFinishedEarlyOnPodIsMarkedNonMutable() { let mockStart = Date() - podState.unfinalizedBolus = UnfinalizedDose(bolusAmount: 4.45, startTime: mockStart, scheduledCertainty: .certain, insulinType: .novolog) + podState.unfinalizedBolus = UnfinalizedDose(decisionId: nil, bolusAmount: 4.45, startTime: mockStart, scheduledCertainty: .certain, insulinType: .novolog) let session = PodCommsSession(podState: podState, transport: mockTransport, delegate: self) // Simulate a status request a bit before the bolus is expected to finish @@ -127,7 +127,7 @@ class PodCommsSessionTests: XCTestCase { mockTransport.addResponse(statusResponse) - let _ = session.bolus(units: 3) + let _ = session.bolus(decisionId: nil, units: 3) XCTAssertNil(lastPodStateUpdate?.unacknowledgedCommand) XCTAssertNotNil(lastPodStateUpdate?.unfinalizedBolus) diff --git a/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift b/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift index c9611bf..a0f8a19 100644 --- a/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift +++ b/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift @@ -305,8 +305,8 @@ class OmnipodSettingsViewModel: ObservableObject { } } - func runTemporaryBasalProgram(unitsPerHour: Double, for duration: TimeInterval, completion: @escaping (PumpManagerError?) -> Void) { - pumpManager.runTemporaryBasalProgram(unitsPerHour: unitsPerHour, for: duration, automatic: false, completion: completion) + func runTemporaryBasalProgram(decisionId: UUID?, unitsPerHour: Double, for duration: TimeInterval, completion: @escaping (PumpManagerError?) -> Void) { + pumpManager.runTemporaryBasalProgram(decisionId: decisionId, unitsPerHour: unitsPerHour, for: duration, automatic: false, completion: completion) } func saveScheduledExpirationReminder(_ selectedDate: Date?, _ completion: @escaping (Error?) -> Void) { diff --git a/OmniKitUI/Views/OmnipodSettingsView.swift b/OmniKitUI/Views/OmnipodSettingsView.swift index 478a28b..9bceeae 100644 --- a/OmniKitUI/Views/OmnipodSettingsView.swift +++ b/OmniKitUI/Views/OmnipodSettingsView.swift @@ -209,7 +209,7 @@ struct OmnipodSettingsView: View { .sheet(isPresented: $showManualTempBasalOptions) { ManualTempBasalEntryView( enactBasal: { rate, duration, completion in - viewModel.runTemporaryBasalProgram(unitsPerHour: rate, for: duration) { error in + viewModel.runTemporaryBasalProgram(decisionId: nil, unitsPerHour: rate, for: duration) { error in completion(error) if error == nil { showManualTempBasalOptions = false @@ -565,7 +565,7 @@ struct OmnipodSettingsView: View { func cancelManualBasal() { cancelingTempBasal = true - viewModel.runTemporaryBasalProgram(unitsPerHour: 0, for: 0) { error in + viewModel.runTemporaryBasalProgram(decisionId: nil, unitsPerHour: 0, for: 0) { error in cancelingTempBasal = false if let error = error { self.viewModel.activeAlert = .cancelManualBasalError(error) From 14f8c97d48a38e4165450d58cded330fa110ffbd Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 3 Jul 2025 13:47:24 -0500 Subject: [PATCH 15/26] async updates --- OmniKit/PumpManager/OmnipodPumpManager.swift | 30 ++++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift index 99473d9..2781508 100644 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -276,11 +276,11 @@ public class OmnipodPumpManager: RileyLinkPumpManager { self.setState { state in state.lastRileyLinkBatteryAlertDate = Date() } - self.pumpDelegate.notify { delegate in + Task { let identifier = Alert.Identifier(managerIdentifier: self.pluginIdentifier, alertIdentifier: "lowRLBattery") let alertBody = String(format: LocalizedString("\"%1$@\" has a low battery", comment: "Format string for low battery alert body for RileyLink. (1: device name)"), device.name ?? "unnamed") let content = Alert.Content(title: LocalizedString("Low RileyLink Battery", comment: "Title for RileyLink low battery alert"), body: alertBody, acknowledgeActionButtonLabel: LocalizedString("OK", comment: "Acknowledge button label for RileyLink low battery alert")) - delegate?.issueAlert(Alert(identifier: identifier, foregroundContent: content, backgroundContent: content, trigger: .immediate)) + await self.pumpDelegate.delegate?.issueAlert(Alert(identifier: identifier, foregroundContent: content, backgroundContent: content, trigger: .immediate)) } } } @@ -1459,9 +1459,9 @@ extension OmnipodPumpManager: PumpManager { } fileprivate func clearSuspendReminder() { - self.pumpDelegate.notify { (delegate) in - delegate?.retractAlert(identifier: Alert.Identifier(managerIdentifier: self.pluginIdentifier, alertIdentifier: PumpManagerAlert.suspendEnded(triggeringSlot: nil).alertIdentifier)) - delegate?.retractAlert(identifier: Alert.Identifier(managerIdentifier: self.pluginIdentifier, alertIdentifier: PumpManagerAlert.suspendEnded(triggeringSlot: nil).repeatingAlertIdentifier)) + Task { + await self.pumpDelegate.delegate?.retractAlert(identifier: Alert.Identifier(managerIdentifier: self.pluginIdentifier, alertIdentifier: PumpManagerAlert.suspendEnded(triggeringSlot: nil).alertIdentifier)) + await self.pumpDelegate.delegate?.retractAlert(identifier: Alert.Identifier(managerIdentifier: self.pluginIdentifier, alertIdentifier: PumpManagerAlert.suspendEnded(triggeringSlot: nil).repeatingAlertIdentifier)) } } @@ -1904,16 +1904,16 @@ extension OmnipodPumpManager: PumpManager { func issueAlert(alert: PumpManagerAlert) { let identifier = Alert.Identifier(managerIdentifier: self.pluginIdentifier, alertIdentifier: alert.alertIdentifier) let loopAlert = Alert(identifier: identifier, foregroundContent: alert.foregroundContent, backgroundContent: alert.backgroundContent, trigger: .immediate) - pumpDelegate.notify { (delegate) in - delegate?.issueAlert(loopAlert) + Task { + await self.pumpDelegate.delegate?.issueAlert(loopAlert) } if let repeatInterval = alert.repeatInterval { // Schedule an additional repeating 15 minute reminder for suspend period ended. let repeatingIdentifier = Alert.Identifier(managerIdentifier: self.pluginIdentifier, alertIdentifier: alert.repeatingAlertIdentifier) let loopAlert = Alert(identifier: repeatingIdentifier, foregroundContent: alert.foregroundContent, backgroundContent: alert.backgroundContent, trigger: .repeating(repeatInterval: repeatInterval)) - pumpDelegate.notify { (delegate) in - delegate?.issueAlert(loopAlert) + Task { + await self.pumpDelegate.delegate?.issueAlert(loopAlert) } } @@ -1924,13 +1924,13 @@ extension OmnipodPumpManager: PumpManager { func retractAlert(alert: PumpManagerAlert) { let identifier = Alert.Identifier(managerIdentifier: self.pluginIdentifier, alertIdentifier: alert.alertIdentifier) - pumpDelegate.notify { (delegate) in - delegate?.retractAlert(identifier: identifier) + Task { + await self.pumpDelegate.delegate?.retractAlert(identifier: identifier) } if alert.isRepeating { let repeatingIdentifier = Alert.Identifier(managerIdentifier: self.pluginIdentifier, alertIdentifier: alert.repeatingAlertIdentifier) - pumpDelegate.notify { (delegate) in - delegate?.retractAlert(identifier: repeatingIdentifier) + Task { + await self.pumpDelegate.delegate?.retractAlert(identifier: repeatingIdentifier) } } self.mutateState { (state) in @@ -2022,11 +2022,11 @@ extension OmnipodPumpManager: PumpManager { static let podAlarmNotificationIdentifier = "Omnipod:\(LoopNotificationCategory.pumpFault.rawValue)" private func notifyPodFault(fault: DetailedStatus) { - pumpDelegate.notify { delegate in + Task { let content = Alert.Content(title: fault.faultEventCode.notificationTitle, body: fault.faultEventCode.notificationBody, acknowledgeActionButtonLabel: LocalizedString("OK", comment: "Alert acknowledgment OK button")) - delegate?.issueAlert(Alert(identifier: Alert.Identifier(managerIdentifier: OmnipodPumpManager.podAlarmNotificationIdentifier, + await self.pumpDelegate.delegate?.issueAlert(Alert(identifier: Alert.Identifier(managerIdentifier: OmnipodPumpManager.podAlarmNotificationIdentifier, alertIdentifier: fault.faultEventCode.description), foregroundContent: content, backgroundContent: content, trigger: .immediate)) From 65f3d69a44dba4546a1f24fafd7d9a85ad565ab1 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 3 Jul 2025 13:47:24 -0500 Subject: [PATCH 16/26] async updates --- OmniKit/PumpManager/OmnipodPumpManager.swift | 30 ++++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift index 99473d9..2781508 100644 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -276,11 +276,11 @@ public class OmnipodPumpManager: RileyLinkPumpManager { self.setState { state in state.lastRileyLinkBatteryAlertDate = Date() } - self.pumpDelegate.notify { delegate in + Task { let identifier = Alert.Identifier(managerIdentifier: self.pluginIdentifier, alertIdentifier: "lowRLBattery") let alertBody = String(format: LocalizedString("\"%1$@\" has a low battery", comment: "Format string for low battery alert body for RileyLink. (1: device name)"), device.name ?? "unnamed") let content = Alert.Content(title: LocalizedString("Low RileyLink Battery", comment: "Title for RileyLink low battery alert"), body: alertBody, acknowledgeActionButtonLabel: LocalizedString("OK", comment: "Acknowledge button label for RileyLink low battery alert")) - delegate?.issueAlert(Alert(identifier: identifier, foregroundContent: content, backgroundContent: content, trigger: .immediate)) + await self.pumpDelegate.delegate?.issueAlert(Alert(identifier: identifier, foregroundContent: content, backgroundContent: content, trigger: .immediate)) } } } @@ -1459,9 +1459,9 @@ extension OmnipodPumpManager: PumpManager { } fileprivate func clearSuspendReminder() { - self.pumpDelegate.notify { (delegate) in - delegate?.retractAlert(identifier: Alert.Identifier(managerIdentifier: self.pluginIdentifier, alertIdentifier: PumpManagerAlert.suspendEnded(triggeringSlot: nil).alertIdentifier)) - delegate?.retractAlert(identifier: Alert.Identifier(managerIdentifier: self.pluginIdentifier, alertIdentifier: PumpManagerAlert.suspendEnded(triggeringSlot: nil).repeatingAlertIdentifier)) + Task { + await self.pumpDelegate.delegate?.retractAlert(identifier: Alert.Identifier(managerIdentifier: self.pluginIdentifier, alertIdentifier: PumpManagerAlert.suspendEnded(triggeringSlot: nil).alertIdentifier)) + await self.pumpDelegate.delegate?.retractAlert(identifier: Alert.Identifier(managerIdentifier: self.pluginIdentifier, alertIdentifier: PumpManagerAlert.suspendEnded(triggeringSlot: nil).repeatingAlertIdentifier)) } } @@ -1904,16 +1904,16 @@ extension OmnipodPumpManager: PumpManager { func issueAlert(alert: PumpManagerAlert) { let identifier = Alert.Identifier(managerIdentifier: self.pluginIdentifier, alertIdentifier: alert.alertIdentifier) let loopAlert = Alert(identifier: identifier, foregroundContent: alert.foregroundContent, backgroundContent: alert.backgroundContent, trigger: .immediate) - pumpDelegate.notify { (delegate) in - delegate?.issueAlert(loopAlert) + Task { + await self.pumpDelegate.delegate?.issueAlert(loopAlert) } if let repeatInterval = alert.repeatInterval { // Schedule an additional repeating 15 minute reminder for suspend period ended. let repeatingIdentifier = Alert.Identifier(managerIdentifier: self.pluginIdentifier, alertIdentifier: alert.repeatingAlertIdentifier) let loopAlert = Alert(identifier: repeatingIdentifier, foregroundContent: alert.foregroundContent, backgroundContent: alert.backgroundContent, trigger: .repeating(repeatInterval: repeatInterval)) - pumpDelegate.notify { (delegate) in - delegate?.issueAlert(loopAlert) + Task { + await self.pumpDelegate.delegate?.issueAlert(loopAlert) } } @@ -1924,13 +1924,13 @@ extension OmnipodPumpManager: PumpManager { func retractAlert(alert: PumpManagerAlert) { let identifier = Alert.Identifier(managerIdentifier: self.pluginIdentifier, alertIdentifier: alert.alertIdentifier) - pumpDelegate.notify { (delegate) in - delegate?.retractAlert(identifier: identifier) + Task { + await self.pumpDelegate.delegate?.retractAlert(identifier: identifier) } if alert.isRepeating { let repeatingIdentifier = Alert.Identifier(managerIdentifier: self.pluginIdentifier, alertIdentifier: alert.repeatingAlertIdentifier) - pumpDelegate.notify { (delegate) in - delegate?.retractAlert(identifier: repeatingIdentifier) + Task { + await self.pumpDelegate.delegate?.retractAlert(identifier: repeatingIdentifier) } } self.mutateState { (state) in @@ -2022,11 +2022,11 @@ extension OmnipodPumpManager: PumpManager { static let podAlarmNotificationIdentifier = "Omnipod:\(LoopNotificationCategory.pumpFault.rawValue)" private func notifyPodFault(fault: DetailedStatus) { - pumpDelegate.notify { delegate in + Task { let content = Alert.Content(title: fault.faultEventCode.notificationTitle, body: fault.faultEventCode.notificationBody, acknowledgeActionButtonLabel: LocalizedString("OK", comment: "Alert acknowledgment OK button")) - delegate?.issueAlert(Alert(identifier: Alert.Identifier(managerIdentifier: OmnipodPumpManager.podAlarmNotificationIdentifier, + await self.pumpDelegate.delegate?.issueAlert(Alert(identifier: Alert.Identifier(managerIdentifier: OmnipodPumpManager.podAlarmNotificationIdentifier, alertIdentifier: fault.faultEventCode.description), foregroundContent: content, backgroundContent: content, trigger: .immediate)) From 0e4b7f0fcdad0dbc021d355ae32c1ceaefab3ba3 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 24 Jul 2025 12:32:10 -0500 Subject: [PATCH 17/26] Changes for protocol updates --- OmniKit/PumpManager/OmnipodPumpManager.swift | 44 ++++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift index 2781508..2bab260 100644 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -2140,10 +2140,9 @@ extension OmnipodPumpManager: PodCommsDelegate { // MARK: - AlertResponder implementation extension OmnipodPumpManager { - public func acknowledgeAlert(alertIdentifier: Alert.AlertIdentifier, completion: @escaping (Error?) -> Void) { + public func acknowledgeAlert(alertIdentifier: LoopKit.Alert.AlertIdentifier) async throws { guard self.hasActivePod else { - completion(OmnipodPumpManagerError.noPodPaired) - return + throw OmnipodPumpManagerError.noPodPaired } for alert in state.activeAlerts { @@ -2151,29 +2150,29 @@ extension OmnipodPumpManager { // If this alert was triggered by the pod find the slot to clear it. if let slot = alert.triggeringSlot { let rileyLinkSelector = self.rileyLinkDeviceProvider.firstConnectedDevice - self.podComms.runSession(withName: "Acknowledge Alert", using: rileyLinkSelector) { (result) in - switch result { - case .success(let session): - do { - let beepBlock = self.beepMessageBlock(beepType: .beep) - let _ = try session.acknowledgeAlerts(alerts: AlertSet(slots: [slot]), beepBlock: beepBlock) - } catch { + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) -> Void in + self.podComms.runSession(withName: "Acknowledge Alert", using: rileyLinkSelector) { (result) in + switch result { + case .success(let session): + do { + let beepBlock = self.beepMessageBlock(beepType: .beep) + let _ = try session.acknowledgeAlerts(alerts: AlertSet(slots: [slot]), beepBlock: beepBlock) + } catch { + self.mutateState { state in + state.alertsWithPendingAcknowledgment.insert(alert) + } + continuation.resume(throwing: error) + } + self.mutateState { state in + state.activeAlerts.remove(alert) + } + continuation.resume() + case .failure(let error): self.mutateState { state in state.alertsWithPendingAcknowledgment.insert(alert) } - completion(error) - return + continuation.resume(throwing: error) } - self.mutateState { state in - state.activeAlerts.remove(alert) - } - completion(nil) - case .failure(let error): - self.mutateState { state in - state.alertsWithPendingAcknowledgment.insert(alert) - } - completion(error) - return } } } else { @@ -2184,7 +2183,6 @@ extension OmnipodPumpManager { state.acknowledgedTimeOffsetAlert = true } } - completion(nil) } } } From 9b39c09fda23b605a2ba0424a3e873c444fbc888 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 24 Jul 2025 12:32:10 -0500 Subject: [PATCH 18/26] Changes for protocol updates --- OmniKit/PumpManager/OmnipodPumpManager.swift | 44 ++++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift index 2781508..2bab260 100644 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -2140,10 +2140,9 @@ extension OmnipodPumpManager: PodCommsDelegate { // MARK: - AlertResponder implementation extension OmnipodPumpManager { - public func acknowledgeAlert(alertIdentifier: Alert.AlertIdentifier, completion: @escaping (Error?) -> Void) { + public func acknowledgeAlert(alertIdentifier: LoopKit.Alert.AlertIdentifier) async throws { guard self.hasActivePod else { - completion(OmnipodPumpManagerError.noPodPaired) - return + throw OmnipodPumpManagerError.noPodPaired } for alert in state.activeAlerts { @@ -2151,29 +2150,29 @@ extension OmnipodPumpManager { // If this alert was triggered by the pod find the slot to clear it. if let slot = alert.triggeringSlot { let rileyLinkSelector = self.rileyLinkDeviceProvider.firstConnectedDevice - self.podComms.runSession(withName: "Acknowledge Alert", using: rileyLinkSelector) { (result) in - switch result { - case .success(let session): - do { - let beepBlock = self.beepMessageBlock(beepType: .beep) - let _ = try session.acknowledgeAlerts(alerts: AlertSet(slots: [slot]), beepBlock: beepBlock) - } catch { + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) -> Void in + self.podComms.runSession(withName: "Acknowledge Alert", using: rileyLinkSelector) { (result) in + switch result { + case .success(let session): + do { + let beepBlock = self.beepMessageBlock(beepType: .beep) + let _ = try session.acknowledgeAlerts(alerts: AlertSet(slots: [slot]), beepBlock: beepBlock) + } catch { + self.mutateState { state in + state.alertsWithPendingAcknowledgment.insert(alert) + } + continuation.resume(throwing: error) + } + self.mutateState { state in + state.activeAlerts.remove(alert) + } + continuation.resume() + case .failure(let error): self.mutateState { state in state.alertsWithPendingAcknowledgment.insert(alert) } - completion(error) - return + continuation.resume(throwing: error) } - self.mutateState { state in - state.activeAlerts.remove(alert) - } - completion(nil) - case .failure(let error): - self.mutateState { state in - state.alertsWithPendingAcknowledgment.insert(alert) - } - completion(error) - return } } } else { @@ -2184,7 +2183,6 @@ extension OmnipodPumpManager { state.acknowledgedTimeOffsetAlert = true } } - completion(nil) } } } From b0d078de4a750f4449220561ba57559da80f0ec5 Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Fri, 24 Oct 2025 17:34:52 -0300 Subject: [PATCH 19/26] [LOOP-5496] adding loop status checks (#13) --- OmniKit/PumpManager/OmnipodPumpManager.swift | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift index 2bab260..6b7a280 100644 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -374,7 +374,7 @@ extension OmnipodPumpManager { 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) { + } else if isSignalLost(at: date) { return PumpStatusHighlight( localizedMessage: LocalizedString("Signal Loss", comment: "Status highlight when communications with the pod haven't happened recently."), imageName: "exclamationmark.circle.fill", @@ -388,6 +388,10 @@ extension OmnipodPumpManager { return nil } } + + private func isSignalLost(at date: Date = Date()) -> Bool { + date.timeIntervalSince(state.lastPumpDataReportDate ?? .distantPast) > .minutes(12) + } public func isRunningManualTempBasal(for state: OmnipodPumpManagerState) -> Bool { if let tempBasal = state.podState?.unfinalizedTempBasal, !tempBasal.automatic { @@ -1193,6 +1197,14 @@ extension OmnipodPumpManager { // MARK: - PumpManager extension OmnipodPumpManager: PumpManager { + public var inSignalLoss: Bool { + isSignalLost() + } + + public var isInoperable: Bool { + basalDeliveryState(for: state) == .pumpInoperable + } + public static var onboardingMaximumBasalScheduleEntryCount: Int { return Pod.maximumBasalScheduleEntryCount } From b10922b470570813b3be9bfb0d59be870082601f Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Fri, 24 Oct 2025 17:34:52 -0300 Subject: [PATCH 20/26] [LOOP-5496] adding loop status checks (#13) --- OmniKit/PumpManager/OmnipodPumpManager.swift | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift index 2bab260..6b7a280 100644 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -374,7 +374,7 @@ extension OmnipodPumpManager { 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) { + } else if isSignalLost(at: date) { return PumpStatusHighlight( localizedMessage: LocalizedString("Signal Loss", comment: "Status highlight when communications with the pod haven't happened recently."), imageName: "exclamationmark.circle.fill", @@ -388,6 +388,10 @@ extension OmnipodPumpManager { return nil } } + + private func isSignalLost(at date: Date = Date()) -> Bool { + date.timeIntervalSince(state.lastPumpDataReportDate ?? .distantPast) > .minutes(12) + } public func isRunningManualTempBasal(for state: OmnipodPumpManagerState) -> Bool { if let tempBasal = state.podState?.unfinalizedTempBasal, !tempBasal.automatic { @@ -1193,6 +1197,14 @@ extension OmnipodPumpManager { // MARK: - PumpManager extension OmnipodPumpManager: PumpManager { + public var inSignalLoss: Bool { + isSignalLost() + } + + public var isInoperable: Bool { + basalDeliveryState(for: state) == .pumpInoperable + } + public static var onboardingMaximumBasalScheduleEntryCount: Int { return Pod.maximumBasalScheduleEntryCount } From f1de48a86f904c727aeef95145e31e5d088e3931 Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Fri, 6 Feb 2026 13:39:20 -0400 Subject: [PATCH 21/26] [LOOP-5743] using pumpStatusHighlight (#14) --- OmniKitUI/PumpManager/OmniPodPumpManager+UI.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OmniKitUI/PumpManager/OmniPodPumpManager+UI.swift b/OmniKitUI/PumpManager/OmniPodPumpManager+UI.swift index 69fdebe..febe2bb 100644 --- a/OmniKitUI/PumpManager/OmniPodPumpManager+UI.swift +++ b/OmniKitUI/PumpManager/OmniPodPumpManager+UI.swift @@ -49,7 +49,7 @@ extension OmnipodPumpManager: PumpManagerUI { // MARK: - PumpStatusIndicator extension OmnipodPumpManager { - public var pumpStatusHighlight: DeviceStatusHighlight? { + public var pumpStatusHighlight: PumpStatusHighlight? { buildPumpStatusHighlight(for: state) } From 89922285537631bc8162d643f6b76fe124ea9fc7 Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Fri, 6 Feb 2026 13:39:20 -0400 Subject: [PATCH 22/26] [LOOP-5743] using pumpStatusHighlight (#14) --- OmniKitUI/PumpManager/OmniPodPumpManager+UI.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OmniKitUI/PumpManager/OmniPodPumpManager+UI.swift b/OmniKitUI/PumpManager/OmniPodPumpManager+UI.swift index 69fdebe..febe2bb 100644 --- a/OmniKitUI/PumpManager/OmniPodPumpManager+UI.swift +++ b/OmniKitUI/PumpManager/OmniPodPumpManager+UI.swift @@ -49,7 +49,7 @@ extension OmnipodPumpManager: PumpManagerUI { // MARK: - PumpStatusIndicator extension OmnipodPumpManager { - public var pumpStatusHighlight: DeviceStatusHighlight? { + public var pumpStatusHighlight: PumpStatusHighlight? { buildPumpStatusHighlight(for: state) } From aa07d3e95ded3dcb70b125a52ad4fb524f98d478 Mon Sep 17 00:00:00 2001 From: LoopKit Developer Date: Wed, 25 Mar 2026 17:11:34 -0500 Subject: [PATCH 23/26] Fix Swift 6 compile errors: private(set) whitespace, completion(nil) in async context, mutateState --- OmniKit/PumpManager/OmnipodPumpManager.swift | 7 +++---- OmniKit/PumpManager/OmnipodPumpManagerState.swift | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift index 18df785..4361c48 100644 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -2603,7 +2603,6 @@ extension OmnipodPumpManager { // Don't clear this pod alert here with the pod still suspended so that the suspend time expired // pod alert beeping will continue until the pod is resumed which will then deactivate this alert. log.default("Skipping acknowledgement of suspend time expired alert with a suspended pod") - completion(nil) return } @@ -2617,17 +2616,17 @@ extension OmnipodPumpManager { let beepBlock = self.beepMessageBlock(beepType: .beep) let _ = try session.acknowledgeAlerts(alerts: AlertSet(slots: [slot]), beepBlock: beepBlock) } catch { - self.mutateState { state in + self.setState { state in state.alertsWithPendingAcknowledgment.insert(alert) } continuation.resume(throwing: error) } - self.mutateState { state in + self.setState { state in state.activeAlerts.remove(alert) } continuation.resume() case .failure(let error): - self.mutateState { state in + self.setState { state in state.alertsWithPendingAcknowledgment.insert(alert) } continuation.resume(throwing: error) diff --git a/OmniKit/PumpManager/OmnipodPumpManagerState.swift b/OmniKit/PumpManager/OmnipodPumpManagerState.swift index 507454a..9490e51 100644 --- a/OmniKit/PumpManager/OmnipodPumpManagerState.swift +++ b/OmniKit/PumpManager/OmnipodPumpManagerState.swift @@ -18,7 +18,7 @@ public struct OmnipodPumpManagerState: RawRepresentable, Equatable { public var isOnboarded: Bool - private (set) public var podState: PodState? + public private(set) var podState: PodState? // podState should only be modifiable by PodComms mutating func updatePodStateFromPodComms(_ podState: PodState?) { From 924f10d951a1245f6d60fe6d6999d4f92c71503b Mon Sep 17 00:00:00 2001 From: LoopKit Developer Date: Thu, 26 Mar 2026 17:41:36 -0500 Subject: [PATCH 24/26] Fix reentrant lock crash in isSignalLost Pass lastPumpDataReportDate as a parameter instead of accessing state inside isSignalLost(), which caused recursive lock acquisition and an EXC_BREAKPOINT crash when called from within a withLock closure. --- OmniKit/PumpManager/OmnipodPumpManager.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift index 4361c48..c7bf404 100644 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -374,7 +374,7 @@ extension OmnipodPumpManager { localizedMessage: LocalizedString("Insulin Suspended", comment: "Status highlight that insulin delivery was suspended."), imageName: "pause.circle.fill", state: .warning) - } else if isSignalLost(at: date) { + } else if isSignalLost(at: date, lastPumpDataReportDate: state.lastPumpDataReportDate) { return PumpStatusHighlight( localizedMessage: LocalizedString("Signal Loss", comment: "Status highlight when communications with the pod haven't happened recently."), imageName: "exclamationmark.circle.fill", @@ -389,8 +389,8 @@ extension OmnipodPumpManager { } } - private func isSignalLost(at date: Date = Date()) -> Bool { - date.timeIntervalSince(state.lastPumpDataReportDate ?? .distantPast) > .minutes(12) + private func isSignalLost(at date: Date, lastPumpDataReportDate: Date?) -> Bool { + date.timeIntervalSince(lastPumpDataReportDate ?? .distantPast) > .minutes(12) } public func isRunningManualTempBasal(for state: OmnipodPumpManagerState) -> Bool { @@ -1561,7 +1561,7 @@ extension OmnipodPumpManager { // MARK: - PumpManager extension OmnipodPumpManager: PumpManager { public var inSignalLoss: Bool { - isSignalLost() + isSignalLost(at: Date(), lastPumpDataReportDate: state.lastPumpDataReportDate) } public var isInoperable: Bool { From 52d2f41c7ee5ce42c8ffa4ea0d524f840714eb02 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 11 May 2026 12:21:07 -0500 Subject: [PATCH 25/26] OmnipodPumpManager: revert 3 stray mutateState back to setState The 2026-05-11 tidepool sync conflict resolution incorrectly took Tidepool's mutateState for 3 hunks in this file. OmnipodPumpManager defines only setState (line 142), so the 3 calls fail to compile. Reverted the 3 calls to setState; behavior matches the surrounding 49 setState call sites. --- OmniKit/PumpManager/OmnipodPumpManager.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift index 7b146ce..c7bf404 100644 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -2616,17 +2616,17 @@ extension OmnipodPumpManager { let beepBlock = self.beepMessageBlock(beepType: .beep) let _ = try session.acknowledgeAlerts(alerts: AlertSet(slots: [slot]), beepBlock: beepBlock) } catch { - self.mutateState { state in + self.setState { state in state.alertsWithPendingAcknowledgment.insert(alert) } continuation.resume(throwing: error) } - self.mutateState { state in + self.setState { state in state.activeAlerts.remove(alert) } continuation.resume() case .failure(let error): - self.mutateState { state in + self.setState { state in state.alertsWithPendingAcknowledgment.insert(alert) } continuation.resume(throwing: error) From e5fad99f65f9c2a80425926715e97eba526871f1 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 21 May 2026 00:17:35 -0500 Subject: [PATCH 26/26] Report pod faults as pump events Emit a NewPumpEvent (.alarm) when a pod fault is first detected so the fault is persisted to the pump event store and uploaded to remote services (e.g. Nightscout), mirroring the existing Pod Change event. --- OmniKit/PumpManager/OmnipodPumpManager.swift | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift index c7bf404..7cf37d7 100644 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -2469,6 +2469,19 @@ extension OmnipodPumpManager: PumpManager { static let podAlarmNotificationIdentifier = "Omnipod:\(LoopNotificationCategory.pumpFault.rawValue)" private func notifyPodFault(fault: DetailedStatus) { + // Record the fault as a pump event so it is persisted to the pump event store and + // uploaded to remote services (e.g. Nightscout, as a note). This fires once per + // fault, on the no-fault→fault transition in podComms(_:didChange:). + pumpDelegate.notify { delegate in + let date = Date() + let event = NewPumpEvent(date: date, + dose: nil, + raw: "Pod Fault \(fault.faultEventCode.rawValue) \(date)".data(using: .utf8)!, + title: fault.faultEventCode.description, + type: .alarm) + delegate?.pumpManager(self, hasNewPumpEvents: [event], lastReconciliation: self.lastSync, replacePendingEvents: false) { _ in } + } + Task { let content = Alert.Content(title: fault.faultEventCode.notificationTitle, body: fault.faultEventCode.notificationBody,