From 3cf3432d96f9ff563169b6316b04e6e11e56a966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bj=C3=B6rkert?= Date: Wed, 1 Jul 2026 20:19:50 +0200 Subject: [PATCH] Carry an opaque bolusReference through the pod bolus path Thread an optional UUID bolusReference from enactBolus through StartProgram, UnfinalizedDose and PodCommsSession, and stamp it on the reported DoseEntry, so it survives an app restart while a bolus is in progress. Stored as a uuid string in the pod raw state. A default enactBolus keeps existing callers unchanged. --- 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..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) + case bolus(volume: Double, automatic: Bool, bolusReference: UUID? = 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?.uuidString + 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).flatMap { UUID(uuidString: $0) }) 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..8e179a5 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: UUID? // 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: UUID? = 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).flatMap { UUID(uuidString: $0) } } 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?.uuidString 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 42d846f..f3a6ccf 100644 --- a/OmnipodKit/PumpManager/OmniPumpManager.swift +++ b/OmnipodKit/PumpManager/OmniPumpManager.swift @@ -2488,6 +2488,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: UUID?, completion: @escaping (PumpManagerError?) -> Void) { guard self.hasActivePod else { completion(.configuration(OmniPumpManagerError.noPodPaired)) return @@ -2552,7 +2556,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 3b93281..971f0cd 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: UUID? = 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) {