From ac1775cbb95c436a7783b9f4eaa30b0384b26e63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bj=C3=B6rkert?= Date: Wed, 1 Jul 2026 11:48:39 +0200 Subject: [PATCH 1/2] Thread bolusReference through bolus delivery Persist and echo the caller-supplied bolus reference via UnfinalizedDose and PendingCommand, so it survives an app restart while delivery is in progress and is stamped on the reported DoseEntry for both certain and uncertain delivery. --- OmnipodKit/OmnipodCommon/PendingCommand.swift | 14 ++++++++------ OmnipodKit/OmnipodCommon/UnfinalizedDose.swift | 16 ++++++++++++---- OmnipodKit/PumpManager/OmniPumpManager.swift | 6 +++++- OmnipodKit/PumpManager/PodCommsSession.swift | 6 +++--- 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/OmnipodKit/OmnipodCommon/PendingCommand.swift b/OmnipodKit/OmnipodCommon/PendingCommand.swift index bdfdd2e..3e7cef8 100644 --- a/OmnipodKit/OmnipodCommon/PendingCommand.swift +++ b/OmnipodKit/OmnipodCommon/PendingCommand.swift @@ -13,7 +13,7 @@ import Foundation enum StartProgram: RawRepresentable { typealias RawValue = [String: Any] - case bolus(volume: Double, automatic: Bool) + case bolus(volume: Double, automatic: Bool, bolusReference: String? = nil) case basalProgram(schedule: BasalSchedule) case tempBasal(unitsPerHour: Double, duration: TimeInterval, isHighTemp: Bool, automatic: Bool) @@ -23,12 +23,14 @@ enum StartProgram: RawRepresentable { var rawValue: RawValue { switch self { - case .bolus(let volume, let automatic): - return [ + case .bolus(let volume, let automatic, let bolusReference): + var raw: RawValue = [ "programType": StartProgramType.bolus.rawValue, "volume": volume, "automatic": automatic ] + raw["bolusReference"] = bolusReference + return raw case .basalProgram(let schedule): return [ "programType": StartProgramType.basalProgram.rawValue, @@ -58,7 +60,7 @@ enum StartProgram: RawRepresentable { { return nil } - self = .bolus(volume: volume, automatic: automatic) + self = .bolus(volume: volume, automatic: automatic, bolusReference: rawValue["bolusReference"] as? String) case .basalProgram: guard let rawSchedule = rawValue["schedule"] as? BasalSchedule.RawValue, let schedule = BasalSchedule(rawValue: rawSchedule) else @@ -80,8 +82,8 @@ enum StartProgram: RawRepresentable { static func == (lhs: StartProgram, rhs: StartProgram) -> Bool { switch(lhs, rhs) { - case (.bolus(let lhsVolume, let lhsAutomatic), .bolus(let rhsVolume, let rhsAutomatic)): - return lhsVolume == rhsVolume && lhsAutomatic == rhsAutomatic + case (.bolus(let lhsVolume, let lhsAutomatic, let lhsBolusReference), .bolus(let rhsVolume, let rhsAutomatic, let rhsBolusReference)): + return lhsVolume == rhsVolume && lhsAutomatic == rhsAutomatic && lhsBolusReference == rhsBolusReference case (.basalProgram(let lhsSchedule), .basalProgram(let rhsSchedule)): return lhsSchedule == rhsSchedule case (.tempBasal(let lhsUnitsPerHour, let lhsDuration, let lhsIsHighTemp, let lhsAutomatic), .tempBasal(let rhsUnitsPerHour, let rhsDuration, let rhsIsHighTemp, let rhsAutomatic)): diff --git a/OmnipodKit/OmnipodCommon/UnfinalizedDose.swift b/OmnipodKit/OmnipodCommon/UnfinalizedDose.swift index dc31da6..7d5bc55 100644 --- a/OmnipodKit/OmnipodCommon/UnfinalizedDose.swift +++ b/OmnipodKit/OmnipodCommon/UnfinalizedDose.swift @@ -67,6 +67,7 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti var isHighTemp: Bool = false // Track this for situations where cancelling temp basal is unacknowledged, and recovery fails, and we have to assume the most possible delivery var insulinType: InsulinType? + var bolusReference: String? // Opaque, caller-supplied reference echoed back on the resulting DoseEntry; not interpreted by the pump var finishTime: Date? { get { @@ -110,7 +111,8 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti startTime: Date, scheduledCertainty: ScheduledCertainty, insulinType: InsulinType, - automatic: Bool = false + automatic: Bool = false, + bolusReference: String? = nil ) { doseType = .bolus units = bolusAmount @@ -120,6 +122,7 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti scheduledUnits = nil self.automatic = automatic self.insulinType = insulinType + self.bolusReference = bolusReference } init( @@ -324,6 +327,8 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti if let rawInsulinType = rawValue["insulinType"] as? InsulinType.RawValue { insulinType = InsulinType(rawValue: rawInsulinType) } + + bolusReference = rawValue["bolusReference"] as? String } public var rawValue: RawValue { @@ -340,6 +345,7 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti rawValue["scheduledTempRate"] = scheduledTempRate rawValue["duration"] = duration rawValue["insulinType"] = insulinType?.rawValue + rawValue["bolusReference"] = bolusReference return rawValue } @@ -377,7 +383,8 @@ extension DoseEntry { deliveredUnits: dose.finalizedUnits, insulinType: dose.insulinType, automatic: dose.automatic, - isMutable: dose.isMutable() + isMutable: dose.isMutable(), + bolusReference: dose.bolusReference ) case .tempBasal: self = DoseEntry( @@ -406,13 +413,14 @@ extension StartProgram { insulinType: InsulinType ) -> UnfinalizedDose? { switch self { - case let .bolus(volume: volume, automatic: automatic): + case let .bolus(volume: volume, automatic: automatic, bolusReference: bolusReference): return UnfinalizedDose( bolusAmount: volume, startTime: programDate, scheduledCertainty: certainty, insulinType: insulinType, - automatic: automatic + automatic: automatic, + bolusReference: bolusReference ) case let .tempBasal(unitsPerHour: rate, duration: duration, isHighTemp, automatic): return UnfinalizedDose( diff --git a/OmnipodKit/PumpManager/OmniPumpManager.swift b/OmnipodKit/PumpManager/OmniPumpManager.swift index dc55983..532c6c7 100644 --- a/OmnipodKit/PumpManager/OmniPumpManager.swift +++ b/OmnipodKit/PumpManager/OmniPumpManager.swift @@ -2471,6 +2471,10 @@ extension OmniPumpManager: PumpManager { // MARK: - Programming Delivery public func enactBolus(units: Double, activationType: BolusActivationType, completion: @escaping (PumpManagerError?) -> Void) { + enactBolus(units: units, activationType: activationType, bolusReference: nil, completion: completion) + } + + public func enactBolus(units: Double, activationType: BolusActivationType, bolusReference: String?, completion: @escaping (PumpManagerError?) -> Void) { guard self.hasActivePod else { completion(.configuration(OmniPumpManagerError.noPodPaired)) return @@ -2535,7 +2539,7 @@ extension OmniPumpManager: 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(units: enactUnits, automatic: activationType.isAutomatic, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep, programReminderInterval: bolusWasAutomaticIndicator, bolusReference: bolusReference) switch result { case .success: diff --git a/OmnipodKit/PumpManager/PodCommsSession.swift b/OmnipodKit/PumpManager/PodCommsSession.swift index c9d9136..8781b39 100644 --- a/OmnipodKit/PumpManager/PodCommsSession.swift +++ b/OmnipodKit/PumpManager/PodCommsSession.swift @@ -666,7 +666,7 @@ class PodCommsSession: MessageTransportDelegate { case unacknowledged(error: PodCommsError) } - func bolus(units: Double, automatic: Bool = false, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0, extendedUnits: Double = 0.0, extendedDuration: TimeInterval = 0) -> DeliveryCommandResult { + func bolus(units: Double, automatic: Bool = false, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0, extendedUnits: Double = 0.0, extendedDuration: TimeInterval = 0, bolusReference: String? = nil) -> DeliveryCommandResult { if podState.unacknowledgedCommand != nil { do { @@ -710,10 +710,10 @@ class PodCommsSession: MessageTransportDelegate { let bolusExtraCommand = BolusExtraCommand(units: units, timeBetweenPulses: timeBetweenPulses, extendedUnits: extendedUnits, extendedDuration: extendedDuration, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep, programReminderInterval: programReminderInterval, bolusInfo: bolusInfo) do { - podState.unacknowledgedCommand = PendingCommand.program(.bolus(volume: units, automatic: automatic), transport.messageNumber, currentDate) + podState.unacknowledgedCommand = PendingCommand.program(.bolus(volume: units, automatic: automatic, bolusReference: bolusReference), 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(bolusAmount: units, startTime: currentDate, scheduledCertainty: .certain, insulinType: podState.insulinType, automatic: automatic, bolusReference: bolusReference) podState.updateFromStatusResponse(statusResponse, at: currentDate) return DeliveryCommandResult.success(statusResponse: statusResponse) } catch PodCommsError.unacknowledgedMessage(let seq, let error) { From e441cce2023603176f5508d3da5a77c7186f2158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bj=C3=B6rkert?= Date: Wed, 1 Jul 2026 13:24:58 +0200 Subject: [PATCH 2/2] Make bolusReference a UUID Match the LoopKit change: thread UUID instead of String, serialized as uuidString in the rawValue round-trips. --- OmnipodKit/OmnipodCommon/PendingCommand.swift | 6 +++--- OmnipodKit/OmnipodCommon/UnfinalizedDose.swift | 8 ++++---- OmnipodKit/PumpManager/OmniPumpManager.swift | 2 +- OmnipodKit/PumpManager/PodCommsSession.swift | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/OmnipodKit/OmnipodCommon/PendingCommand.swift b/OmnipodKit/OmnipodCommon/PendingCommand.swift index 3e7cef8..2480357 100644 --- a/OmnipodKit/OmnipodCommon/PendingCommand.swift +++ b/OmnipodKit/OmnipodCommon/PendingCommand.swift @@ -13,7 +13,7 @@ import Foundation enum StartProgram: RawRepresentable { typealias RawValue = [String: Any] - case bolus(volume: Double, automatic: Bool, bolusReference: String? = nil) + case bolus(volume: Double, automatic: Bool, bolusReference: UUID? = nil) case basalProgram(schedule: BasalSchedule) case tempBasal(unitsPerHour: Double, duration: TimeInterval, isHighTemp: Bool, automatic: Bool) @@ -29,7 +29,7 @@ enum StartProgram: RawRepresentable { "volume": volume, "automatic": automatic ] - raw["bolusReference"] = bolusReference + raw["bolusReference"] = bolusReference?.uuidString return raw case .basalProgram(let schedule): return [ @@ -60,7 +60,7 @@ enum StartProgram: RawRepresentable { { return nil } - self = .bolus(volume: volume, automatic: automatic, bolusReference: rawValue["bolusReference"] as? String) + self = .bolus(volume: volume, automatic: automatic, bolusReference: (rawValue["bolusReference"] as? String).flatMap { UUID(uuidString: $0) }) case .basalProgram: guard let rawSchedule = rawValue["schedule"] as? BasalSchedule.RawValue, let schedule = BasalSchedule(rawValue: rawSchedule) else diff --git a/OmnipodKit/OmnipodCommon/UnfinalizedDose.swift b/OmnipodKit/OmnipodCommon/UnfinalizedDose.swift index 7d5bc55..8e179a5 100644 --- a/OmnipodKit/OmnipodCommon/UnfinalizedDose.swift +++ b/OmnipodKit/OmnipodCommon/UnfinalizedDose.swift @@ -67,7 +67,7 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti var isHighTemp: Bool = false // Track this for situations where cancelling temp basal is unacknowledged, and recovery fails, and we have to assume the most possible delivery var insulinType: InsulinType? - var bolusReference: String? // Opaque, caller-supplied reference echoed back on the resulting DoseEntry; not interpreted by the pump + var bolusReference: UUID? // Opaque, caller-supplied reference echoed back on the resulting DoseEntry; not interpreted by the pump var finishTime: Date? { get { @@ -112,7 +112,7 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti scheduledCertainty: ScheduledCertainty, insulinType: InsulinType, automatic: Bool = false, - bolusReference: String? = nil + bolusReference: UUID? = nil ) { doseType = .bolus units = bolusAmount @@ -328,7 +328,7 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti insulinType = InsulinType(rawValue: rawInsulinType) } - bolusReference = rawValue["bolusReference"] as? String + bolusReference = (rawValue["bolusReference"] as? String).flatMap { UUID(uuidString: $0) } } public var rawValue: RawValue { @@ -345,7 +345,7 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti rawValue["scheduledTempRate"] = scheduledTempRate rawValue["duration"] = duration rawValue["insulinType"] = insulinType?.rawValue - rawValue["bolusReference"] = bolusReference + rawValue["bolusReference"] = bolusReference?.uuidString return rawValue } diff --git a/OmnipodKit/PumpManager/OmniPumpManager.swift b/OmnipodKit/PumpManager/OmniPumpManager.swift index 532c6c7..f00fe2a 100644 --- a/OmnipodKit/PumpManager/OmniPumpManager.swift +++ b/OmnipodKit/PumpManager/OmniPumpManager.swift @@ -2474,7 +2474,7 @@ extension OmniPumpManager: PumpManager { enactBolus(units: units, activationType: activationType, bolusReference: nil, completion: completion) } - public func enactBolus(units: Double, activationType: BolusActivationType, bolusReference: String?, completion: @escaping (PumpManagerError?) -> Void) { + public func enactBolus(units: Double, activationType: BolusActivationType, bolusReference: UUID?, completion: @escaping (PumpManagerError?) -> Void) { guard self.hasActivePod else { completion(.configuration(OmniPumpManagerError.noPodPaired)) return diff --git a/OmnipodKit/PumpManager/PodCommsSession.swift b/OmnipodKit/PumpManager/PodCommsSession.swift index 8781b39..c93ce23 100644 --- a/OmnipodKit/PumpManager/PodCommsSession.swift +++ b/OmnipodKit/PumpManager/PodCommsSession.swift @@ -666,7 +666,7 @@ class PodCommsSession: MessageTransportDelegate { case unacknowledged(error: PodCommsError) } - func bolus(units: Double, automatic: Bool = false, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0, extendedUnits: Double = 0.0, extendedDuration: TimeInterval = 0, bolusReference: String? = nil) -> DeliveryCommandResult { + func bolus(units: Double, automatic: Bool = false, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0, extendedUnits: Double = 0.0, extendedDuration: TimeInterval = 0, bolusReference: UUID? = nil) -> DeliveryCommandResult { if podState.unacknowledgedCommand != nil { do {