From de51406510afc9f4d8347a5e1253c370cbfdcd30 Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Mon, 25 Sep 2023 15:53:02 -0300 Subject: [PATCH 01/29] [COASTAL-1291] plugin identifier is no longer class property (#8) --- OmniBLE/PumpManager/OmniBLEPumpManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OmniBLE/PumpManager/OmniBLEPumpManager.swift b/OmniBLE/PumpManager/OmniBLEPumpManager.swift index 2d0da5bd..fec2d548 100644 --- a/OmniBLE/PumpManager/OmniBLEPumpManager.swift +++ b/OmniBLE/PumpManager/OmniBLEPumpManager.swift @@ -80,7 +80,7 @@ extension OmniBLEPumpManagerError: LocalizedError { public class OmniBLEPumpManager: DeviceManager { - public static let pluginIdentifier: String = "Omnipod-Dash" // use a single token to make parsing log files easier + public let pluginIdentifier: String = "Omnipod-Dash" // use a single token to make parsing log files easier public let localizedTitle = LocalizedString("Omnipod DASH", comment: "Generic title of the OmniBLE pump manager") From 0a5fff05bfca65cce4004920323fec2fd83e8c48 Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Mon, 25 Sep 2023 15:53:02 -0300 Subject: [PATCH 02/29] [COASTAL-1291] plugin identifier is no longer class property (#8) --- OmniBLE/PumpManager/OmniBLEPumpManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OmniBLE/PumpManager/OmniBLEPumpManager.swift b/OmniBLE/PumpManager/OmniBLEPumpManager.swift index 2d0da5bd..fec2d548 100644 --- a/OmniBLE/PumpManager/OmniBLEPumpManager.swift +++ b/OmniBLE/PumpManager/OmniBLEPumpManager.swift @@ -80,7 +80,7 @@ extension OmniBLEPumpManagerError: LocalizedError { public class OmniBLEPumpManager: DeviceManager { - public static let pluginIdentifier: String = "Omnipod-Dash" // use a single token to make parsing log files easier + public let pluginIdentifier: String = "Omnipod-Dash" // use a single token to make parsing log files easier public let localizedTitle = LocalizedString("Omnipod DASH", comment: "Generic title of the OmniBLE pump manager") From 85922303856080c15458c6631ead1dead8aa462f Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Fri, 12 Jan 2024 11:09:48 -0800 Subject: [PATCH 03/29] [COASTAL-1335] Update Picker Component --- .../Views/ExpirationReminderPickerView.swift | 11 ++++++--- .../Views/ManualTempBasalEntryView.swift | 23 ++++++++++++++----- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/OmniBLE/PumpManagerUI/Views/ExpirationReminderPickerView.swift b/OmniBLE/PumpManagerUI/Views/ExpirationReminderPickerView.swift index 7d69c975..257d8f84 100644 --- a/OmniBLE/PumpManagerUI/Views/ExpirationReminderPickerView.swift +++ b/OmniBLE/PumpManagerUI/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/OmniBLE/PumpManagerUI/Views/ManualTempBasalEntryView.swift b/OmniBLE/PumpManagerUI/Views/ManualTempBasalEntryView.swift index 5922b55b..beace326 100644 --- a/OmniBLE/PumpManagerUI/Views/ManualTempBasalEntryView.swift +++ b/OmniBLE/PumpManagerUI/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 84c36184eca1600026c0f00e5670109a1e414a12 Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Fri, 12 Jan 2024 11:09:48 -0800 Subject: [PATCH 04/29] [COASTAL-1335] Update Picker Component --- .../Views/ExpirationReminderPickerView.swift | 11 ++++++--- .../Views/ManualTempBasalEntryView.swift | 23 ++++++++++++++----- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/OmniBLE/PumpManagerUI/Views/ExpirationReminderPickerView.swift b/OmniBLE/PumpManagerUI/Views/ExpirationReminderPickerView.swift index 7d69c975..257d8f84 100644 --- a/OmniBLE/PumpManagerUI/Views/ExpirationReminderPickerView.swift +++ b/OmniBLE/PumpManagerUI/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/OmniBLE/PumpManagerUI/Views/ManualTempBasalEntryView.swift b/OmniBLE/PumpManagerUI/Views/ManualTempBasalEntryView.swift index 5922b55b..beace326 100644 --- a/OmniBLE/PumpManagerUI/Views/ManualTempBasalEntryView.swift +++ b/OmniBLE/PumpManagerUI/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 3758511d27f0d7c0a752a2390de5f4bbb78eefc0 Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Fri, 7 Jun 2024 14:11:45 -0300 Subject: [PATCH 05/29] [LOOP-4801] adding pump inoperable (#11) * adding pump inoperable * fault returns pumpInoperable --- OmniBLE/PumpManager/OmniBLEPumpManager.swift | 2 +- OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OmniBLE/PumpManager/OmniBLEPumpManager.swift b/OmniBLE/PumpManager/OmniBLEPumpManager.swift index e70842ad..30541f25 100644 --- a/OmniBLE/PumpManager/OmniBLEPumpManager.swift +++ b/OmniBLE/PumpManager/OmniBLEPumpManager.swift @@ -361,7 +361,7 @@ extension OmniBLEPumpManager { switch podCommState(for: state) { case .fault: - return .active(.distantPast) + return .pumpInoperable default: break } diff --git a/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift b/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift index 5780c26f..8087c0a2 100644 --- a/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift +++ b/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift @@ -164,7 +164,7 @@ class OmniBLESettingsViewModel: ObservableObject { switch basalDeliveryState { case .active(_), .initiatingTempBasal: return true - case .tempBasal(_), .cancelingTempBasal, .suspending, .suspended(_), .resuming, .none: + default: return false } } From 2dafc55a9352e78a28116bf8d5bdc041ed493402 Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Fri, 7 Jun 2024 14:11:45 -0300 Subject: [PATCH 06/29] [LOOP-4801] adding pump inoperable (#11) * adding pump inoperable * fault returns pumpInoperable --- OmniBLE/PumpManager/OmniBLEPumpManager.swift | 2 +- OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OmniBLE/PumpManager/OmniBLEPumpManager.swift b/OmniBLE/PumpManager/OmniBLEPumpManager.swift index e70842ad..30541f25 100644 --- a/OmniBLE/PumpManager/OmniBLEPumpManager.swift +++ b/OmniBLE/PumpManager/OmniBLEPumpManager.swift @@ -361,7 +361,7 @@ extension OmniBLEPumpManager { switch podCommState(for: state) { case .fault: - return .active(.distantPast) + return .pumpInoperable default: break } diff --git a/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift b/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift index 5780c26f..8087c0a2 100644 --- a/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift +++ b/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift @@ -164,7 +164,7 @@ class OmniBLESettingsViewModel: ObservableObject { switch basalDeliveryState { case .active(_), .initiatingTempBasal: return true - case .tempBasal(_), .cancelingTempBasal, .suspending, .suspended(_), .resuming, .none: + default: return false } } From 00ea07f3e8534abdb6b4be7d21dd618ab402325a Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Thu, 21 Nov 2024 15:49:05 -0800 Subject: [PATCH 07/29] [LOOP-5153] Remove HealthKit dependency from LoopAlgorithm --- OmniBLE/OmnipodCommon/PumpManagerAlert.swift | 6 +++--- .../ViewModels/OmniBLESettingsViewModel.swift | 10 +++++----- .../Views/ExpirationReminderPickerView.swift | 8 ++++---- .../Views/LowReservoirReminderEditView.swift | 8 ++++---- .../Views/LowReservoirReminderSetupView.swift | 8 ++++---- .../Views/ManualTempBasalEntryView.swift | 12 ++++++------ .../Views/NotificationSettingsView.swift | 8 ++++---- 7 files changed, 30 insertions(+), 30 deletions(-) diff --git a/OmniBLE/OmnipodCommon/PumpManagerAlert.swift b/OmniBLE/OmnipodCommon/PumpManagerAlert.swift index 146787df..b80f0c86 100644 --- a/OmniBLE/OmnipodCommon/PumpManagerAlert.swift +++ b/OmniBLE/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/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift b/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift index 8087c0a2..aaa7abe5 100644 --- a/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift +++ b/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift @@ -6,10 +6,10 @@ // Copyright © 2021 LoopKit Authors. All rights reserved. // -import SwiftUI +import LoopAlgorithm import LoopKit import LoopKitUI -import HealthKit +import SwiftUI enum DashSettingsViewAlert { @@ -210,7 +210,7 @@ class OmniBLESettingsViewModel: ObservableObject { return nil } - let reservoirVolumeFormatter = QuantityFormatter(for: .internationalUnit()) + let reservoirVolumeFormatter = QuantityFormatter(for: .internationalUnit) var didFinish: (() -> Void)? @@ -379,13 +379,13 @@ class OmniBLESettingsViewModel: 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/OmniBLE/PumpManagerUI/Views/ExpirationReminderPickerView.swift b/OmniBLE/PumpManagerUI/Views/ExpirationReminderPickerView.swift index 257d8f84..cb7e03ac 100644 --- a/OmniBLE/PumpManagerUI/Views/ExpirationReminderPickerView.swift +++ b/OmniBLE/PumpManagerUI/Views/ExpirationReminderPickerView.swift @@ -6,10 +6,10 @@ // Copyright © 2021 LoopKit Authors. All rights reserved. // -import SwiftUI +import LoopAlgorithm import LoopKit import LoopKitUI -import HealthKit +import SwiftUI 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/OmniBLE/PumpManagerUI/Views/LowReservoirReminderEditView.swift b/OmniBLE/PumpManagerUI/Views/LowReservoirReminderEditView.swift index ced8101a..13341543 100644 --- a/OmniBLE/PumpManagerUI/Views/LowReservoirReminderEditView.swift +++ b/OmniBLE/PumpManagerUI/Views/LowReservoirReminderEditView.swift @@ -6,10 +6,10 @@ // Copyright © 2021 LoopKit Authors. All rights reserved. // -import SwiftUI +import LoopAlgorithm import LoopKit import LoopKitUI -import HealthKit +import SwiftUI struct LowReservoirReminderEditView: View { @@ -88,7 +88,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 { @@ -147,7 +147,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/OmniBLE/PumpManagerUI/Views/LowReservoirReminderSetupView.swift b/OmniBLE/PumpManagerUI/Views/LowReservoirReminderSetupView.swift index d5dbfe00..6e717e81 100644 --- a/OmniBLE/PumpManagerUI/Views/LowReservoirReminderSetupView.swift +++ b/OmniBLE/PumpManagerUI/Views/LowReservoirReminderSetupView.swift @@ -6,10 +6,10 @@ // Copyright © 2021 LoopKit Authors. All rights reserved. // -import SwiftUI +import LoopAlgorithm import LoopKitUI import LoopKit -import HealthKit +import SwiftUI struct LowReservoirReminderSetupView: View { @@ -19,10 +19,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/OmniBLE/PumpManagerUI/Views/ManualTempBasalEntryView.swift b/OmniBLE/PumpManagerUI/Views/ManualTempBasalEntryView.swift index beace326..1cadd114 100644 --- a/OmniBLE/PumpManagerUI/Views/ManualTempBasalEntryView.swift +++ b/OmniBLE/PumpManagerUI/Views/ManualTempBasalEntryView.swift @@ -6,10 +6,10 @@ // Copyright © 2022 LoopKit Authors. All rights reserved. // -import SwiftUI +import LoopAlgorithm import LoopKitUI import LoopKit -import HealthKit +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/OmniBLE/PumpManagerUI/Views/NotificationSettingsView.swift b/OmniBLE/PumpManagerUI/Views/NotificationSettingsView.swift index a941362c..a1783e0a 100644 --- a/OmniBLE/PumpManagerUI/Views/NotificationSettingsView.swift +++ b/OmniBLE/PumpManagerUI/Views/NotificationSettingsView.swift @@ -6,10 +6,10 @@ // Copyright © 2021 LoopKit Authors. All rights reserved. // -import SwiftUI +import LoopAlgorithm import LoopKit import LoopKitUI -import HealthKit +import SwiftUI 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 1ed1af7ee97757a438bbe03ac49db97beb02246d Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Thu, 21 Nov 2024 15:49:05 -0800 Subject: [PATCH 08/29] [LOOP-5153] Remove HealthKit dependency from LoopAlgorithm --- OmniBLE/OmnipodCommon/PumpManagerAlert.swift | 6 +++--- .../ViewModels/OmniBLESettingsViewModel.swift | 10 +++++----- .../Views/ExpirationReminderPickerView.swift | 8 ++++---- .../Views/LowReservoirReminderEditView.swift | 8 ++++---- .../Views/LowReservoirReminderSetupView.swift | 8 ++++---- .../Views/ManualTempBasalEntryView.swift | 12 ++++++------ .../Views/NotificationSettingsView.swift | 8 ++++---- 7 files changed, 30 insertions(+), 30 deletions(-) diff --git a/OmniBLE/OmnipodCommon/PumpManagerAlert.swift b/OmniBLE/OmnipodCommon/PumpManagerAlert.swift index 146787df..b80f0c86 100644 --- a/OmniBLE/OmnipodCommon/PumpManagerAlert.swift +++ b/OmniBLE/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/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift b/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift index 8087c0a2..aaa7abe5 100644 --- a/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift +++ b/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift @@ -6,10 +6,10 @@ // Copyright © 2021 LoopKit Authors. All rights reserved. // -import SwiftUI +import LoopAlgorithm import LoopKit import LoopKitUI -import HealthKit +import SwiftUI enum DashSettingsViewAlert { @@ -210,7 +210,7 @@ class OmniBLESettingsViewModel: ObservableObject { return nil } - let reservoirVolumeFormatter = QuantityFormatter(for: .internationalUnit()) + let reservoirVolumeFormatter = QuantityFormatter(for: .internationalUnit) var didFinish: (() -> Void)? @@ -379,13 +379,13 @@ class OmniBLESettingsViewModel: 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/OmniBLE/PumpManagerUI/Views/ExpirationReminderPickerView.swift b/OmniBLE/PumpManagerUI/Views/ExpirationReminderPickerView.swift index 257d8f84..cb7e03ac 100644 --- a/OmniBLE/PumpManagerUI/Views/ExpirationReminderPickerView.swift +++ b/OmniBLE/PumpManagerUI/Views/ExpirationReminderPickerView.swift @@ -6,10 +6,10 @@ // Copyright © 2021 LoopKit Authors. All rights reserved. // -import SwiftUI +import LoopAlgorithm import LoopKit import LoopKitUI -import HealthKit +import SwiftUI 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/OmniBLE/PumpManagerUI/Views/LowReservoirReminderEditView.swift b/OmniBLE/PumpManagerUI/Views/LowReservoirReminderEditView.swift index ced8101a..13341543 100644 --- a/OmniBLE/PumpManagerUI/Views/LowReservoirReminderEditView.swift +++ b/OmniBLE/PumpManagerUI/Views/LowReservoirReminderEditView.swift @@ -6,10 +6,10 @@ // Copyright © 2021 LoopKit Authors. All rights reserved. // -import SwiftUI +import LoopAlgorithm import LoopKit import LoopKitUI -import HealthKit +import SwiftUI struct LowReservoirReminderEditView: View { @@ -88,7 +88,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 { @@ -147,7 +147,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/OmniBLE/PumpManagerUI/Views/LowReservoirReminderSetupView.swift b/OmniBLE/PumpManagerUI/Views/LowReservoirReminderSetupView.swift index d5dbfe00..6e717e81 100644 --- a/OmniBLE/PumpManagerUI/Views/LowReservoirReminderSetupView.swift +++ b/OmniBLE/PumpManagerUI/Views/LowReservoirReminderSetupView.swift @@ -6,10 +6,10 @@ // Copyright © 2021 LoopKit Authors. All rights reserved. // -import SwiftUI +import LoopAlgorithm import LoopKitUI import LoopKit -import HealthKit +import SwiftUI struct LowReservoirReminderSetupView: View { @@ -19,10 +19,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/OmniBLE/PumpManagerUI/Views/ManualTempBasalEntryView.swift b/OmniBLE/PumpManagerUI/Views/ManualTempBasalEntryView.swift index beace326..1cadd114 100644 --- a/OmniBLE/PumpManagerUI/Views/ManualTempBasalEntryView.swift +++ b/OmniBLE/PumpManagerUI/Views/ManualTempBasalEntryView.swift @@ -6,10 +6,10 @@ // Copyright © 2022 LoopKit Authors. All rights reserved. // -import SwiftUI +import LoopAlgorithm import LoopKitUI import LoopKit -import HealthKit +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/OmniBLE/PumpManagerUI/Views/NotificationSettingsView.swift b/OmniBLE/PumpManagerUI/Views/NotificationSettingsView.swift index a941362c..a1783e0a 100644 --- a/OmniBLE/PumpManagerUI/Views/NotificationSettingsView.swift +++ b/OmniBLE/PumpManagerUI/Views/NotificationSettingsView.swift @@ -6,10 +6,10 @@ // Copyright © 2021 LoopKit Authors. All rights reserved. // -import SwiftUI +import LoopAlgorithm import LoopKit import LoopKitUI -import HealthKit +import SwiftUI 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 b85be85c2e64bad27c7898b0acf0e7c915ca38fa Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Thu, 21 Nov 2024 22:16:54 -0800 Subject: [PATCH 09/29] [LOOP-5153] Remove HealthKit dependency from LoopAlgorithm --- Common/HKUnit.swift | 25 ------------------- OmniBLE.xcodeproj/project.pbxproj | 4 --- .../Views/OmniBLESettingsView.swift | 1 - 3 files changed, 30 deletions(-) delete mode 100644 Common/HKUnit.swift diff --git a/Common/HKUnit.swift b/Common/HKUnit.swift deleted file mode 100644 index 04559931..00000000 --- a/Common/HKUnit.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// HKUnit.swift -// OmniBLE -// -// Created by Nathan Racklyeft on 1/17/16. -// Copyright © 2016 Nathan Racklyeft. 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/OmniBLE.xcodeproj/project.pbxproj b/OmniBLE.xcodeproj/project.pbxproj index c9baca23..666864c6 100644 --- a/OmniBLE.xcodeproj/project.pbxproj +++ b/OmniBLE.xcodeproj/project.pbxproj @@ -90,7 +90,6 @@ 8475315726EDA193009FD801 /* LocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8475314A26EDA193009FD801 /* LocalizedString.swift */; }; 8475315826EDA193009FD801 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8475314B26EDA193009FD801 /* OSLog.swift */; }; 8475315926EDA193009FD801 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8475314C26EDA193009FD801 /* UIColor.swift */; }; - 8475315A26EDA193009FD801 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8475314D26EDA193009FD801 /* HKUnit.swift */; }; 8475315B26EDA193009FD801 /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8475314E26EDA193009FD801 /* NibLoadable.swift */; }; 8475315C26EDA193009FD801 /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8475314F26EDA193009FD801 /* TimeZone.swift */; }; 8475315D26EDA193009FD801 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8475315026EDA193009FD801 /* Data.swift */; }; @@ -354,7 +353,6 @@ 8475314A26EDA193009FD801 /* LocalizedString.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalizedString.swift; sourceTree = ""; }; 8475314B26EDA193009FD801 /* OSLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = ""; }; 8475314C26EDA193009FD801 /* UIColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; - 8475314D26EDA193009FD801 /* HKUnit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HKUnit.swift; sourceTree = ""; }; 8475314E26EDA193009FD801 /* NibLoadable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NibLoadable.swift; sourceTree = ""; }; 8475314F26EDA193009FD801 /* TimeZone.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimeZone.swift; sourceTree = ""; }; 8475315026EDA193009FD801 /* Data.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = ""; }; @@ -705,7 +703,6 @@ isa = PBXGroup; children = ( 8475315026EDA193009FD801 /* Data.swift */, - 8475314D26EDA193009FD801 /* HKUnit.swift */, 10289E69271B2A3E000339E6 /* IdentifiableClass.swift */, 8475314A26EDA193009FD801 /* LocalizedString.swift */, 8475314E26EDA193009FD801 /* NibLoadable.swift */, @@ -1104,7 +1101,6 @@ 10389A2B26FF7841002115E9 /* PlaceholderMessageBlock.swift in Sources */, 10389A3026FF7841002115E9 /* StatusResponse.swift in Sources */, 1021114A2709462300784F13 /* PodDoseProgressEstimator.swift in Sources */, - 8475315A26EDA193009FD801 /* HKUnit.swift in Sources */, 10389A2626FF7841002115E9 /* TempBasalExtraCommand.swift in Sources */, 8475315C26EDA193009FD801 /* TimeZone.swift in Sources */, 8475315726EDA193009FD801 /* LocalizedString.swift in Sources */, diff --git a/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift b/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift index 88085192..5bf05222 100644 --- a/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift +++ b/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift @@ -9,7 +9,6 @@ import SwiftUI import LoopKit import LoopKitUI -import HealthKit struct OmniBLESettingsView: View { From e3b668bdcd191a491c552c9d203caee2ce61f280 Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Thu, 21 Nov 2024 22:16:54 -0800 Subject: [PATCH 10/29] [LOOP-5153] Remove HealthKit dependency from LoopAlgorithm --- Common/HKUnit.swift | 25 ------------------- OmniBLE.xcodeproj/project.pbxproj | 4 --- .../Views/OmniBLESettingsView.swift | 1 - 3 files changed, 30 deletions(-) delete mode 100644 Common/HKUnit.swift diff --git a/Common/HKUnit.swift b/Common/HKUnit.swift deleted file mode 100644 index 04559931..00000000 --- a/Common/HKUnit.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// HKUnit.swift -// OmniBLE -// -// Created by Nathan Racklyeft on 1/17/16. -// Copyright © 2016 Nathan Racklyeft. 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/OmniBLE.xcodeproj/project.pbxproj b/OmniBLE.xcodeproj/project.pbxproj index c9baca23..666864c6 100644 --- a/OmniBLE.xcodeproj/project.pbxproj +++ b/OmniBLE.xcodeproj/project.pbxproj @@ -90,7 +90,6 @@ 8475315726EDA193009FD801 /* LocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8475314A26EDA193009FD801 /* LocalizedString.swift */; }; 8475315826EDA193009FD801 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8475314B26EDA193009FD801 /* OSLog.swift */; }; 8475315926EDA193009FD801 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8475314C26EDA193009FD801 /* UIColor.swift */; }; - 8475315A26EDA193009FD801 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8475314D26EDA193009FD801 /* HKUnit.swift */; }; 8475315B26EDA193009FD801 /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8475314E26EDA193009FD801 /* NibLoadable.swift */; }; 8475315C26EDA193009FD801 /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8475314F26EDA193009FD801 /* TimeZone.swift */; }; 8475315D26EDA193009FD801 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8475315026EDA193009FD801 /* Data.swift */; }; @@ -354,7 +353,6 @@ 8475314A26EDA193009FD801 /* LocalizedString.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalizedString.swift; sourceTree = ""; }; 8475314B26EDA193009FD801 /* OSLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = ""; }; 8475314C26EDA193009FD801 /* UIColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; - 8475314D26EDA193009FD801 /* HKUnit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HKUnit.swift; sourceTree = ""; }; 8475314E26EDA193009FD801 /* NibLoadable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NibLoadable.swift; sourceTree = ""; }; 8475314F26EDA193009FD801 /* TimeZone.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimeZone.swift; sourceTree = ""; }; 8475315026EDA193009FD801 /* Data.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = ""; }; @@ -705,7 +703,6 @@ isa = PBXGroup; children = ( 8475315026EDA193009FD801 /* Data.swift */, - 8475314D26EDA193009FD801 /* HKUnit.swift */, 10289E69271B2A3E000339E6 /* IdentifiableClass.swift */, 8475314A26EDA193009FD801 /* LocalizedString.swift */, 8475314E26EDA193009FD801 /* NibLoadable.swift */, @@ -1104,7 +1101,6 @@ 10389A2B26FF7841002115E9 /* PlaceholderMessageBlock.swift in Sources */, 10389A3026FF7841002115E9 /* StatusResponse.swift in Sources */, 1021114A2709462300784F13 /* PodDoseProgressEstimator.swift in Sources */, - 8475315A26EDA193009FD801 /* HKUnit.swift in Sources */, 10389A2626FF7841002115E9 /* TempBasalExtraCommand.swift in Sources */, 8475315C26EDA193009FD801 /* TimeZone.swift in Sources */, 8475315726EDA193009FD801 /* LocalizedString.swift in Sources */, diff --git a/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift b/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift index 88085192..5bf05222 100644 --- a/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift +++ b/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift @@ -9,7 +9,6 @@ import SwiftUI import LoopKit import LoopKitUI -import HealthKit struct OmniBLESettingsView: View { From abc4c7a2a4dcf5c730334f6bf8104b29f456282e Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Fri, 6 Dec 2024 14:33:31 -0800 Subject: [PATCH 11/29] Bump to iOS 17 --- OmniBLE.xcodeproj/project.pbxproj | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/OmniBLE.xcodeproj/project.pbxproj b/OmniBLE.xcodeproj/project.pbxproj index 666864c6..f21eba68 100644 --- a/OmniBLE.xcodeproj/project.pbxproj +++ b/OmniBLE.xcodeproj/project.pbxproj @@ -1386,7 +1386,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.1; + IPHONEOS_DEPLOYMENT_TARGET = 17.6; LOCALIZED_STRING_MACRO_NAMES = ( NSLocalizedString, CFCopyLocalizedString, @@ -1451,7 +1451,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.1; + IPHONEOS_DEPLOYMENT_TARGET = 17.6; LOCALIZED_STRING_MACRO_NAMES = ( NSLocalizedString, CFCopyLocalizedString, @@ -1597,7 +1597,6 @@ INFOPLIST_FILE = OmniBLEPlugin/Info.plist; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 LoopKit Authors. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 15.2; LD_DYLIB_INSTALL_NAME = ""; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -1631,7 +1630,6 @@ INFOPLIST_FILE = OmniBLEPlugin/Info.plist; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 LoopKit Authors. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 15.2; LD_DYLIB_INSTALL_NAME = ""; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", From c8cff6f6e254b626b81b5902822fbdc85c621bf1 Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Fri, 6 Dec 2024 14:33:31 -0800 Subject: [PATCH 12/29] Bump to iOS 17 --- OmniBLE.xcodeproj/project.pbxproj | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/OmniBLE.xcodeproj/project.pbxproj b/OmniBLE.xcodeproj/project.pbxproj index 666864c6..f21eba68 100644 --- a/OmniBLE.xcodeproj/project.pbxproj +++ b/OmniBLE.xcodeproj/project.pbxproj @@ -1386,7 +1386,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.1; + IPHONEOS_DEPLOYMENT_TARGET = 17.6; LOCALIZED_STRING_MACRO_NAMES = ( NSLocalizedString, CFCopyLocalizedString, @@ -1451,7 +1451,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.1; + IPHONEOS_DEPLOYMENT_TARGET = 17.6; LOCALIZED_STRING_MACRO_NAMES = ( NSLocalizedString, CFCopyLocalizedString, @@ -1597,7 +1597,6 @@ INFOPLIST_FILE = OmniBLEPlugin/Info.plist; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 LoopKit Authors. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 15.2; LD_DYLIB_INSTALL_NAME = ""; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -1631,7 +1630,6 @@ INFOPLIST_FILE = OmniBLEPlugin/Info.plist; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 LoopKit Authors. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 15.2; LD_DYLIB_INSTALL_NAME = ""; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", From 33594d74e886f66eae46176dfcfde15f0caa5ce2 Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Mon, 2 Jun 2025 13:27:20 -0700 Subject: [PATCH 13/29] [LOOP-5295] decisionId on DoseEntry and PersistedPumpEvent --- OmniBLE/OmnipodCommon/UnfinalizedDose.swift | 18 ++++++++++++++---- OmniBLE/PumpManager/OmniBLEPumpManager.swift | 12 ++++++------ OmniBLE/PumpManager/PodCommsSession.swift | 8 ++++---- OmniBLE/PumpManager/PodState.swift | 6 +++--- .../ViewModels/OmniBLESettingsViewModel.swift | 4 ++-- .../Views/OmniBLESettingsView.swift | 4 ++-- OmniBLETests/PodCommsSessionTests.swift | 2 +- 7 files changed, 32 insertions(+), 22 deletions(-) diff --git a/OmniBLE/OmnipodCommon/UnfinalizedDose.swift b/OmniBLE/OmnipodCommon/UnfinalizedDose.swift index be60d187..ccaeb1f5 100644 --- a/OmniBLE/OmnipodCommon/UnfinalizedDose.swift +++ b/OmniBLE/OmnipodCommon/UnfinalizedDose.swift @@ -64,6 +64,8 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti var scheduledCertainty: ScheduledCertainty 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 { @@ -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 @@ -258,6 +262,9 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti self.insulinType = InsulinType(rawValue: rawInsulinType) } + if let decisionIdString = rawValue["decisionId"] as? String { + self.decisionId = UUID(uuidString: decisionIdString)! + } } public var rawValue: RawValue { @@ -274,6 +281,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 +316,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 +329,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 +347,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/OmniBLE/PumpManager/OmniBLEPumpManager.swift b/OmniBLE/PumpManager/OmniBLEPumpManager.swift index 30541f25..17f420d4 100644 --- a/OmniBLE/PumpManager/OmniBLEPumpManager.swift +++ b/OmniBLE/PumpManager/OmniBLEPumpManager.swift @@ -1535,7 +1535,7 @@ extension OmniBLEPumpManager: PumpManager { // MARK: - Programming Delivery - 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(OmniBLEPumpManagerError.noPodPaired)) return @@ -1580,7 +1580,7 @@ extension OmniBLEPumpManager: PumpManager { // in 63 minutes if bolus had not completed by then. let bolusWasAutomaticIndicator: TimeInterval = activationType.isAutomatic ? TimeInterval(minutes: 0x3F) : 0 - let result = session.bolus(units: enactUnits, automatic: activationType.isAutomatic, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep, programReminderInterval: bolusWasAutomaticIndicator) + let result = session.bolus(decisionId: decisionId, units: enactUnits, automatic: activationType.isAutomatic, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep, programReminderInterval: bolusWasAutomaticIndicator) switch result { case .success: @@ -1655,11 +1655,11 @@ extension OmniBLEPumpManager: 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(.deviceState(OmniBLEPumpManagerError.noPodPaired)) return @@ -1745,7 +1745,7 @@ extension OmniBLEPumpManager: 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: diff --git a/OmniBLE/PumpManager/PodCommsSession.swift b/OmniBLE/PumpManager/PodCommsSession.swift index 2434eb95..76445de6 100644 --- a/OmniBLE/PumpManager/PodCommsSession.swift +++ b/OmniBLE/PumpManager/PodCommsSession.swift @@ -485,7 +485,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) @@ -510,7 +510,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) { @@ -523,7 +523,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) @@ -542,7 +542,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/OmniBLE/PumpManager/PodState.swift b/OmniBLE/PumpManager/PodState.swift index ecc0b92e..48d2fa59 100644 --- a/OmniBLE/PumpManager/PodState.swift +++ b/OmniBLE/PumpManager/PodState.swift @@ -202,7 +202,7 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl public mutating func updateFromStatusResponse(_ response: StatusResponse, at date: Date = Date()) { let now = updatePodTimes(timeActive: response.timeActive) - updateDeliveryStatus(deliveryStatus: response.deliveryStatus, podProgressStatus: response.podProgressStatus, bolusNotDelivered: response.bolusNotDelivered, at: date) + updateDeliveryStatus(decisionId: nil, deliveryStatus: response.deliveryStatus, podProgressStatus: response.podProgressStatus, bolusNotDelivered: response.bolusNotDelivered, at: date) let setupUnits = setupUnitsDelivered ?? Pod.primeUnits + Pod.cannulaInsertionUnits + Pod.cannulaInsertionUnitsExtra @@ -283,13 +283,13 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl } - private mutating func updateDeliveryStatus(deliveryStatus: DeliveryStatus, podProgressStatus: PodProgressStatus, bolusNotDelivered: Double, at date: Date) { + private mutating func updateDeliveryStatus(decisionId: UUID?, deliveryStatus: DeliveryStatus, podProgressStatus: PodProgressStatus, bolusNotDelivered: Double, at date: Date) { // See if the pod deliveryStatus indicates an active bolus or temp basal that the PodState isn't tracking (possible Loop restart) 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: decisionId, bolusAmount: bolusNotDelivered, startTime: date, scheduledCertainty: .certain, insulinType: insulinType, automatic: false) } } diff --git a/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift b/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift index aaa7abe5..a87ce890 100644 --- a/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift +++ b/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift @@ -289,8 +289,8 @@ class OmniBLESettingsViewModel: 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/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift b/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift index 5bf05222..f5a006b4 100644 --- a/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift +++ b/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift @@ -202,7 +202,7 @@ struct OmniBLESettingsView: 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 @@ -527,7 +527,7 @@ struct OmniBLESettingsView: 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) diff --git a/OmniBLETests/PodCommsSessionTests.swift b/OmniBLETests/PodCommsSessionTests.swift index f5b7c9d2..095c9569 100644 --- a/OmniBLETests/PodCommsSessionTests.swift +++ b/OmniBLETests/PodCommsSessionTests.swift @@ -66,7 +66,7 @@ class PodCommsSessionTests: XCTestCase, PodCommsSessionDelegate { 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 From 820333390a8a1fa52da15aec1a79012d4d81441b Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Mon, 2 Jun 2025 13:27:20 -0700 Subject: [PATCH 14/29] [LOOP-5295] decisionId on DoseEntry and PersistedPumpEvent --- OmniBLE/OmnipodCommon/UnfinalizedDose.swift | 18 ++++++++++++++---- OmniBLE/PumpManager/OmniBLEPumpManager.swift | 12 ++++++------ OmniBLE/PumpManager/PodCommsSession.swift | 8 ++++---- OmniBLE/PumpManager/PodState.swift | 6 +++--- .../ViewModels/OmniBLESettingsViewModel.swift | 4 ++-- .../Views/OmniBLESettingsView.swift | 4 ++-- OmniBLETests/PodCommsSessionTests.swift | 2 +- 7 files changed, 32 insertions(+), 22 deletions(-) diff --git a/OmniBLE/OmnipodCommon/UnfinalizedDose.swift b/OmniBLE/OmnipodCommon/UnfinalizedDose.swift index be60d187..ccaeb1f5 100644 --- a/OmniBLE/OmnipodCommon/UnfinalizedDose.swift +++ b/OmniBLE/OmnipodCommon/UnfinalizedDose.swift @@ -64,6 +64,8 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti var scheduledCertainty: ScheduledCertainty 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 { @@ -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 @@ -258,6 +262,9 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti self.insulinType = InsulinType(rawValue: rawInsulinType) } + if let decisionIdString = rawValue["decisionId"] as? String { + self.decisionId = UUID(uuidString: decisionIdString)! + } } public var rawValue: RawValue { @@ -274,6 +281,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 +316,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 +329,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 +347,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/OmniBLE/PumpManager/OmniBLEPumpManager.swift b/OmniBLE/PumpManager/OmniBLEPumpManager.swift index 30541f25..17f420d4 100644 --- a/OmniBLE/PumpManager/OmniBLEPumpManager.swift +++ b/OmniBLE/PumpManager/OmniBLEPumpManager.swift @@ -1535,7 +1535,7 @@ extension OmniBLEPumpManager: PumpManager { // MARK: - Programming Delivery - 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(OmniBLEPumpManagerError.noPodPaired)) return @@ -1580,7 +1580,7 @@ extension OmniBLEPumpManager: PumpManager { // in 63 minutes if bolus had not completed by then. let bolusWasAutomaticIndicator: TimeInterval = activationType.isAutomatic ? TimeInterval(minutes: 0x3F) : 0 - let result = session.bolus(units: enactUnits, automatic: activationType.isAutomatic, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep, programReminderInterval: bolusWasAutomaticIndicator) + let result = session.bolus(decisionId: decisionId, units: enactUnits, automatic: activationType.isAutomatic, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep, programReminderInterval: bolusWasAutomaticIndicator) switch result { case .success: @@ -1655,11 +1655,11 @@ extension OmniBLEPumpManager: 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(.deviceState(OmniBLEPumpManagerError.noPodPaired)) return @@ -1745,7 +1745,7 @@ extension OmniBLEPumpManager: 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: diff --git a/OmniBLE/PumpManager/PodCommsSession.swift b/OmniBLE/PumpManager/PodCommsSession.swift index 2434eb95..76445de6 100644 --- a/OmniBLE/PumpManager/PodCommsSession.swift +++ b/OmniBLE/PumpManager/PodCommsSession.swift @@ -485,7 +485,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) @@ -510,7 +510,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) { @@ -523,7 +523,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) @@ -542,7 +542,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/OmniBLE/PumpManager/PodState.swift b/OmniBLE/PumpManager/PodState.swift index ecc0b92e..48d2fa59 100644 --- a/OmniBLE/PumpManager/PodState.swift +++ b/OmniBLE/PumpManager/PodState.swift @@ -202,7 +202,7 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl public mutating func updateFromStatusResponse(_ response: StatusResponse, at date: Date = Date()) { let now = updatePodTimes(timeActive: response.timeActive) - updateDeliveryStatus(deliveryStatus: response.deliveryStatus, podProgressStatus: response.podProgressStatus, bolusNotDelivered: response.bolusNotDelivered, at: date) + updateDeliveryStatus(decisionId: nil, deliveryStatus: response.deliveryStatus, podProgressStatus: response.podProgressStatus, bolusNotDelivered: response.bolusNotDelivered, at: date) let setupUnits = setupUnitsDelivered ?? Pod.primeUnits + Pod.cannulaInsertionUnits + Pod.cannulaInsertionUnitsExtra @@ -283,13 +283,13 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl } - private mutating func updateDeliveryStatus(deliveryStatus: DeliveryStatus, podProgressStatus: PodProgressStatus, bolusNotDelivered: Double, at date: Date) { + private mutating func updateDeliveryStatus(decisionId: UUID?, deliveryStatus: DeliveryStatus, podProgressStatus: PodProgressStatus, bolusNotDelivered: Double, at date: Date) { // See if the pod deliveryStatus indicates an active bolus or temp basal that the PodState isn't tracking (possible Loop restart) 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: decisionId, bolusAmount: bolusNotDelivered, startTime: date, scheduledCertainty: .certain, insulinType: insulinType, automatic: false) } } diff --git a/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift b/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift index aaa7abe5..a87ce890 100644 --- a/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift +++ b/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift @@ -289,8 +289,8 @@ class OmniBLESettingsViewModel: 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/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift b/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift index 5bf05222..f5a006b4 100644 --- a/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift +++ b/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift @@ -202,7 +202,7 @@ struct OmniBLESettingsView: 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 @@ -527,7 +527,7 @@ struct OmniBLESettingsView: 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) diff --git a/OmniBLETests/PodCommsSessionTests.swift b/OmniBLETests/PodCommsSessionTests.swift index f5b7c9d2..095c9569 100644 --- a/OmniBLETests/PodCommsSessionTests.swift +++ b/OmniBLETests/PodCommsSessionTests.swift @@ -66,7 +66,7 @@ class PodCommsSessionTests: XCTestCase, PodCommsSessionDelegate { 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 From 157ad3cffd293d768151322e7a351f7a68083482 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 3 Jul 2025 13:47:09 -0500 Subject: [PATCH 15/29] async updates --- OmniBLE/PumpManager/OmniBLEPumpManager.swift | 26 ++++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/OmniBLE/PumpManager/OmniBLEPumpManager.swift b/OmniBLE/PumpManager/OmniBLEPumpManager.swift index 17f420d4..f0ed7f90 100644 --- a/OmniBLE/PumpManager/OmniBLEPumpManager.swift +++ b/OmniBLE/PumpManager/OmniBLEPumpManager.swift @@ -1493,9 +1493,9 @@ extension OmniBLEPumpManager: 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)) } } @@ -1912,16 +1912,16 @@ extension OmniBLEPumpManager: 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) } } @@ -1932,13 +1932,13 @@ extension OmniBLEPumpManager: 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 @@ -2027,11 +2027,11 @@ extension OmniBLEPumpManager: PumpManager { } 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: OmniBLEPumpManager.podAlarmNotificationIdentifier, + await self.pumpDelegate.delegate?.issueAlert(Alert(identifier: Alert.Identifier(managerIdentifier: OmniBLEPumpManager.podAlarmNotificationIdentifier, alertIdentifier: fault.faultEventCode.description), foregroundContent: content, backgroundContent: content, trigger: .immediate)) From e94c470f7782022283a5e6737f0bb628f8453a15 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 3 Jul 2025 13:47:09 -0500 Subject: [PATCH 16/29] async updates --- OmniBLE/PumpManager/OmniBLEPumpManager.swift | 26 ++++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/OmniBLE/PumpManager/OmniBLEPumpManager.swift b/OmniBLE/PumpManager/OmniBLEPumpManager.swift index 17f420d4..f0ed7f90 100644 --- a/OmniBLE/PumpManager/OmniBLEPumpManager.swift +++ b/OmniBLE/PumpManager/OmniBLEPumpManager.swift @@ -1493,9 +1493,9 @@ extension OmniBLEPumpManager: 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)) } } @@ -1912,16 +1912,16 @@ extension OmniBLEPumpManager: 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) } } @@ -1932,13 +1932,13 @@ extension OmniBLEPumpManager: 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 @@ -2027,11 +2027,11 @@ extension OmniBLEPumpManager: PumpManager { } 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: OmniBLEPumpManager.podAlarmNotificationIdentifier, + await self.pumpDelegate.delegate?.issueAlert(Alert(identifier: Alert.Identifier(managerIdentifier: OmniBLEPumpManager.podAlarmNotificationIdentifier, alertIdentifier: fault.faultEventCode.description), foregroundContent: content, backgroundContent: content, trigger: .immediate)) From 8ba639cf7ff268ceb8296eb03a3acb092ae9df7f Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 24 Jul 2025 12:32:00 -0500 Subject: [PATCH 17/29] Changes for protocol updates --- OmniBLE/PumpManager/OmniBLEPumpManager.swift | 44 ++++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/OmniBLE/PumpManager/OmniBLEPumpManager.swift b/OmniBLE/PumpManager/OmniBLEPumpManager.swift index f0ed7f90..cd7caeed 100644 --- a/OmniBLE/PumpManager/OmniBLEPumpManager.swift +++ b/OmniBLE/PumpManager/OmniBLEPumpManager.swift @@ -2176,39 +2176,40 @@ extension OmniBLEPumpManager: AlertSoundVendor { // MARK: - AlertResponder implementation extension OmniBLEPumpManager { - public func acknowledgeAlert(alertIdentifier: Alert.AlertIdentifier, completion: @escaping (Error?) -> Void) { + public func acknowledgeAlert(alertIdentifier: LoopKit.Alert.AlertIdentifier) async throws { guard self.hasActivePod else { - completion(OmniBLEPumpManagerError.noPodPaired) - return + throw OmniBLEPumpManagerError.noPodPaired } for alert in state.activeAlerts { if alert.alertIdentifier == alertIdentifier { // If this alert was triggered by the pod find the slot to clear it. if let slot = alert.triggeringSlot { - self.podComms.runSession(withName: "Acknowledge Alert") { (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") { (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) + return + } + self.mutateState { state in + state.activeAlerts.remove(alert) + } + continuation.resume() + case .failure(let error): self.mutateState { state in state.alertsWithPendingAcknowledgment.insert(alert) } - completion(error) + continuation.resume(throwing: error) return } - 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 { @@ -2219,7 +2220,6 @@ extension OmniBLEPumpManager { state.acknowledgedTimeOffsetAlert = true } } - completion(nil) } } } From 57bdef324e6d23ef03642812b75c8d9d5c72f6be Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 24 Jul 2025 12:32:00 -0500 Subject: [PATCH 18/29] Changes for protocol updates --- OmniBLE/PumpManager/OmniBLEPumpManager.swift | 44 ++++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/OmniBLE/PumpManager/OmniBLEPumpManager.swift b/OmniBLE/PumpManager/OmniBLEPumpManager.swift index f0ed7f90..cd7caeed 100644 --- a/OmniBLE/PumpManager/OmniBLEPumpManager.swift +++ b/OmniBLE/PumpManager/OmniBLEPumpManager.swift @@ -2176,39 +2176,40 @@ extension OmniBLEPumpManager: AlertSoundVendor { // MARK: - AlertResponder implementation extension OmniBLEPumpManager { - public func acknowledgeAlert(alertIdentifier: Alert.AlertIdentifier, completion: @escaping (Error?) -> Void) { + public func acknowledgeAlert(alertIdentifier: LoopKit.Alert.AlertIdentifier) async throws { guard self.hasActivePod else { - completion(OmniBLEPumpManagerError.noPodPaired) - return + throw OmniBLEPumpManagerError.noPodPaired } for alert in state.activeAlerts { if alert.alertIdentifier == alertIdentifier { // If this alert was triggered by the pod find the slot to clear it. if let slot = alert.triggeringSlot { - self.podComms.runSession(withName: "Acknowledge Alert") { (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") { (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) + return + } + self.mutateState { state in + state.activeAlerts.remove(alert) + } + continuation.resume() + case .failure(let error): self.mutateState { state in state.alertsWithPendingAcknowledgment.insert(alert) } - completion(error) + continuation.resume(throwing: error) return } - 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 { @@ -2219,7 +2220,6 @@ extension OmniBLEPumpManager { state.acknowledgedTimeOffsetAlert = true } } - completion(nil) } } } From ab1b67120b91d84117e756c6e668e04717089a12 Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Fri, 24 Oct 2025 17:33:53 -0300 Subject: [PATCH 19/29] [LOOP-5496] adding loop status checks (#16) --- OmniBLE/PumpManager/OmniBLEPumpManager.swift | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/OmniBLE/PumpManager/OmniBLEPumpManager.swift b/OmniBLE/PumpManager/OmniBLEPumpManager.swift index cd7caeed..de1a359e 100644 --- a/OmniBLE/PumpManager/OmniBLEPumpManager.swift +++ b/OmniBLE/PumpManager/OmniBLEPumpManager.swift @@ -79,6 +79,13 @@ extension OmniBLEPumpManagerError: LocalizedError { } public class OmniBLEPumpManager: DeviceManager { + public var inSignalLoss: Bool { + isSignalLost() + } + + public var isInoperable: Bool { + basalDeliveryState(for: state) == .pumpInoperable + } public let pluginIdentifier: String = "Omnipod-Dash" // use a single token to make parsing log files easier @@ -631,7 +638,7 @@ extension OmniBLEPumpManager { 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", @@ -645,6 +652,10 @@ extension OmniBLEPumpManager { return nil } } + + private func isSignalLost(at date: Date = Date()) -> Bool { + date.timeIntervalSince(state.lastPumpDataReportDate ?? .distantPast) > .minutes(12) + } public func isRunningManualTempBasal(for state: OmniBLEPumpManagerState) -> Bool { if let tempBasal = state.podState?.unfinalizedTempBasal, !tempBasal.isFinished(), !tempBasal.automatic { From d93525bc3826a9b0ef0675783cd6c60e18367d9e Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Fri, 24 Oct 2025 17:33:53 -0300 Subject: [PATCH 20/29] [LOOP-5496] adding loop status checks (#16) --- OmniBLE/PumpManager/OmniBLEPumpManager.swift | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/OmniBLE/PumpManager/OmniBLEPumpManager.swift b/OmniBLE/PumpManager/OmniBLEPumpManager.swift index cd7caeed..de1a359e 100644 --- a/OmniBLE/PumpManager/OmniBLEPumpManager.swift +++ b/OmniBLE/PumpManager/OmniBLEPumpManager.swift @@ -79,6 +79,13 @@ extension OmniBLEPumpManagerError: LocalizedError { } public class OmniBLEPumpManager: DeviceManager { + public var inSignalLoss: Bool { + isSignalLost() + } + + public var isInoperable: Bool { + basalDeliveryState(for: state) == .pumpInoperable + } public let pluginIdentifier: String = "Omnipod-Dash" // use a single token to make parsing log files easier @@ -631,7 +638,7 @@ extension OmniBLEPumpManager { 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", @@ -645,6 +652,10 @@ extension OmniBLEPumpManager { return nil } } + + private func isSignalLost(at date: Date = Date()) -> Bool { + date.timeIntervalSince(state.lastPumpDataReportDate ?? .distantPast) > .minutes(12) + } public func isRunningManualTempBasal(for state: OmniBLEPumpManagerState) -> Bool { if let tempBasal = state.podState?.unfinalizedTempBasal, !tempBasal.isFinished(), !tempBasal.automatic { From a8770e083e6f23dccf792d28bede70575e3a470a Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Mon, 2 Feb 2026 01:05:16 -0800 Subject: [PATCH 21/29] Support Xcode 26 --- OmniBLETests/Driver/Comm/message/MessagePacketTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OmniBLETests/Driver/Comm/message/MessagePacketTests.swift b/OmniBLETests/Driver/Comm/message/MessagePacketTests.swift index 72fc91c2..2a431334 100644 --- a/OmniBLETests/Driver/Comm/message/MessagePacketTests.swift +++ b/OmniBLETests/Driver/Comm/message/MessagePacketTests.swift @@ -49,7 +49,7 @@ class MessagePacketTests: XCTestCase { tfs: false, version: 0) - assert(msg.payload.bytes.toHexString() == payloadString) + assert(msg.payload.hexadecimalString == payloadString) } - } + From 4f93a8969e0c3c7a81054413341a6f8b327ec286 Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Mon, 2 Feb 2026 01:05:16 -0800 Subject: [PATCH 22/29] Support Xcode 26 --- OmniBLETests/Driver/Comm/message/MessagePacketTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OmniBLETests/Driver/Comm/message/MessagePacketTests.swift b/OmniBLETests/Driver/Comm/message/MessagePacketTests.swift index 72fc91c2..2a431334 100644 --- a/OmniBLETests/Driver/Comm/message/MessagePacketTests.swift +++ b/OmniBLETests/Driver/Comm/message/MessagePacketTests.swift @@ -49,7 +49,7 @@ class MessagePacketTests: XCTestCase { tfs: false, version: 0) - assert(msg.payload.bytes.toHexString() == payloadString) + assert(msg.payload.hexadecimalString == payloadString) } - } + From e8f1812d4327134530e155bbf0ae1f4373177004 Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Fri, 6 Feb 2026 13:38:30 -0400 Subject: [PATCH 23/29] [LOOP-5743] using pumpStatusHighlight (#18) --- OmniBLE/PumpManagerUI/OmniBLEPumpManager+UI.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OmniBLE/PumpManagerUI/OmniBLEPumpManager+UI.swift b/OmniBLE/PumpManagerUI/OmniBLEPumpManager+UI.swift index 6a5f3946..f918f924 100644 --- a/OmniBLE/PumpManagerUI/OmniBLEPumpManager+UI.swift +++ b/OmniBLE/PumpManagerUI/OmniBLEPumpManager+UI.swift @@ -67,7 +67,7 @@ public enum OmniBLEStatusBadge: DeviceStatusBadge { // MARK: - PumpStatusIndicator extension OmniBLEPumpManager { - public var pumpStatusHighlight: DeviceStatusHighlight? { + public var pumpStatusHighlight: PumpStatusHighlight? { return buildPumpStatusHighlight(for: state) } From 2c2d503010be131fa132604cc288b3e7a2a54311 Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Fri, 6 Feb 2026 13:38:30 -0400 Subject: [PATCH 24/29] [LOOP-5743] using pumpStatusHighlight (#18) --- OmniBLE/PumpManagerUI/OmniBLEPumpManager+UI.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OmniBLE/PumpManagerUI/OmniBLEPumpManager+UI.swift b/OmniBLE/PumpManagerUI/OmniBLEPumpManager+UI.swift index 6a5f3946..f918f924 100644 --- a/OmniBLE/PumpManagerUI/OmniBLEPumpManager+UI.swift +++ b/OmniBLE/PumpManagerUI/OmniBLEPumpManager+UI.swift @@ -67,7 +67,7 @@ public enum OmniBLEStatusBadge: DeviceStatusBadge { // MARK: - PumpStatusIndicator extension OmniBLEPumpManager { - public var pumpStatusHighlight: DeviceStatusHighlight? { + public var pumpStatusHighlight: PumpStatusHighlight? { return buildPumpStatusHighlight(for: state) } From d5fa71f7f055ddd48a5fd314234b7425f1465199 Mon Sep 17 00:00:00 2001 From: LoopKit Developer Date: Wed, 25 Mar 2026 16:25:14 -0500 Subject: [PATCH 25/29] Fix private(set) whitespace for Swift 6 compatibility --- OmniBLE/PumpManager/OmniBLEPumpManagerState.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OmniBLE/PumpManager/OmniBLEPumpManagerState.swift b/OmniBLE/PumpManager/OmniBLEPumpManagerState.swift index d86ce58d..ee008a1a 100644 --- a/OmniBLE/PumpManager/OmniBLEPumpManagerState.swift +++ b/OmniBLE/PumpManager/OmniBLEPumpManagerState.swift @@ -17,7 +17,7 @@ public struct OmniBLEPumpManagerState: RawRepresentable, Equatable { public var isOnboarded: Bool = false - 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 db9d08a86a3fdbfd57bc78a9c52a3f32b162395a Mon Sep 17 00:00:00 2001 From: LoopKit Developer Date: Wed, 25 Mar 2026 16:33:45 -0500 Subject: [PATCH 26/29] Fix Swift 6 compile errors: podState scope, throwing closure, mutateState --- OmniBLE/PumpManager/OmniBLEPumpManager.swift | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/OmniBLE/PumpManager/OmniBLEPumpManager.swift b/OmniBLE/PumpManager/OmniBLEPumpManager.swift index 1bed471c..8a3a820f 100644 --- a/OmniBLE/PumpManager/OmniBLEPumpManager.swift +++ b/OmniBLE/PumpManager/OmniBLEPumpManager.swift @@ -379,6 +379,9 @@ extension OmniBLEPumpManager { case .disengaging: return .cancelingTempBasal case .stable: + guard let podState = state.podState else { + return .active(Date()) + } if let tempBasal = podState.unfinalizedTempBasal, !tempBasal.isFinished(at: date) { return .tempBasal(DoseEntry(tempBasal)) } @@ -2149,9 +2152,11 @@ extension OmniBLEPumpManager: PumpManager { } completion(nil) case .unacknowledged(let error): - throw error + completion(.communication(error)) + return case .certainFailure(let error): - throw error + completion(.communication(error)) + return } } } @@ -2611,18 +2616,18 @@ extension OmniBLEPumpManager { 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) return } - 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 7f4163343b90d7de09069b96819837bfa1626df5 Mon Sep 17 00:00:00 2001 From: LoopKit Developer Date: Thu, 23 Apr 2026 18:07:55 -0500 Subject: [PATCH 27/29] 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 lockedState.mutate closure. Ports the same fix applied to OmniKit in 924f10d. --- OmniBLE/PumpManager/OmniBLEPumpManager.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/OmniBLE/PumpManager/OmniBLEPumpManager.swift b/OmniBLE/PumpManager/OmniBLEPumpManager.swift index 8a3a820f..871f168e 100644 --- a/OmniBLE/PumpManager/OmniBLEPumpManager.swift +++ b/OmniBLE/PumpManager/OmniBLEPumpManager.swift @@ -80,7 +80,7 @@ extension OmniBLEPumpManagerError: LocalizedError { public class OmniBLEPumpManager: DeviceManager { public var inSignalLoss: Bool { - isSignalLost() + isSignalLost(at: Date(), lastPumpDataReportDate: state.lastPumpDataReportDate) } public var isInoperable: Bool { @@ -651,7 +651,7 @@ extension OmniBLEPumpManager { 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", @@ -666,8 +666,8 @@ extension OmniBLEPumpManager { } } - 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: OmniBLEPumpManagerState) -> Bool { From 166ecfb11a35846ab9987f514807c31745e47ab3 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 11 May 2026 12:21:09 -0500 Subject: [PATCH 28/29] OmniBLEPumpManager: revert 3 stray mutateState back to setState Same regression as OmniKit. OmniBLEPumpManager defines only setState (line 176); 3 mutateState calls from the sync conflict resolution failed to compile. Reverted to setState; matches the 48 other call sites. --- OmniBLE/PumpManager/OmniBLEPumpManager.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OmniBLE/PumpManager/OmniBLEPumpManager.swift b/OmniBLE/PumpManager/OmniBLEPumpManager.swift index 928d6e59..99ed1fb6 100644 --- a/OmniBLE/PumpManager/OmniBLEPumpManager.swift +++ b/OmniBLE/PumpManager/OmniBLEPumpManager.swift @@ -2698,18 +2698,18 @@ extension OmniBLEPumpManager { 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) return } - 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 87606c4b88e25cf3f991c5561645a434838d2054 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 21 May 2026 00:17:31 -0500 Subject: [PATCH 29/29] 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. --- OmniBLE/PumpManager/OmniBLEPumpManager.swift | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/OmniBLE/PumpManager/OmniBLEPumpManager.swift b/OmniBLE/PumpManager/OmniBLEPumpManager.swift index 99ed1fb6..1e69d191 100644 --- a/OmniBLE/PumpManager/OmniBLEPumpManager.swift +++ b/OmniBLE/PumpManager/OmniBLEPumpManager.swift @@ -2494,6 +2494,19 @@ extension OmniBLEPumpManager: PumpManager { } 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,