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 e4c1475c..e671933e 100644 --- a/OmniBLE.xcodeproj/project.pbxproj +++ b/OmniBLE.xcodeproj/project.pbxproj @@ -91,7 +91,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 */; }; @@ -351,7 +350,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 = ""; }; @@ -712,7 +710,6 @@ isa = PBXGroup; children = ( 8475315026EDA193009FD801 /* Data.swift */, - 8475314D26EDA193009FD801 /* HKUnit.swift */, 10289E69271B2A3E000339E6 /* IdentifiableClass.swift */, 8475314A26EDA193009FD801 /* LocalizedString.swift */, 8475314E26EDA193009FD801 /* NibLoadable.swift */, @@ -1137,7 +1134,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 */, @@ -1398,7 +1394,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; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZED_STRING_MACRO_NAMES = ( NSLocalizedString, @@ -1465,7 +1461,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; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZED_STRING_MACRO_NAMES = ( NSLocalizedString, @@ -1627,7 +1623,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)", @@ -1664,7 +1659,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)", diff --git a/OmniBLE/OmnipodCommon/PumpManagerAlert.swift b/OmniBLE/OmnipodCommon/PumpManagerAlert.swift index dff0e217..92078eda 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 podExpireImminent(triggeringSlot: AlertSlot?) @@ -70,8 +70,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/OmnipodCommon/UnfinalizedDose.swift b/OmniBLE/OmnipodCommon/UnfinalizedDose.swift index 039f007a..0e64560f 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 @@ -261,6 +265,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 { @@ -277,6 +284,7 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti rawValue["scheduledTempRate"] = scheduledTempRate rawValue["duration"] = duration rawValue["insulinType"] = insulinType?.rawValue + rawValue["decisionId"] = decisionId?.uuidString return rawValue } @@ -311,6 +319,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, @@ -323,6 +332,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, @@ -340,9 +350,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 855f1444..1e69d191 100644 --- a/OmniBLE/PumpManager/OmniBLEPumpManager.swift +++ b/OmniBLE/PumpManager/OmniBLEPumpManager.swift @@ -93,8 +93,15 @@ extension OmniBLEPumpManagerError: LocalizedError { } public class OmniBLEPumpManager: DeviceManager { + public var inSignalLoss: Bool { + isSignalLost(at: Date(), lastPumpDataReportDate: state.lastPumpDataReportDate) + } + + public var isInoperable: Bool { + basalDeliveryState(for: state) == .pumpInoperable + } - 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") @@ -394,9 +401,11 @@ extension OmniBLEPumpManager { private func basalDeliveryState(for state: OmniBLEPumpManagerState, at date: Date = Date()) -> PumpManagerStatus.BasalDeliveryState { - // Treat a non-active (faulted or setup incomplete) pod just like no pod - guard let podState = state.podState, podState.isActive else { - return .active(.distantPast) + switch podCommState(for: state) { + case .fault: + return .pumpInoperable + default: + break } switch state.suspendEngageState { @@ -414,6 +423,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)) } @@ -683,7 +695,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, 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", @@ -697,6 +709,10 @@ extension OmniBLEPumpManager { return nil } } + + private func isSignalLost(at date: Date, lastPumpDataReportDate: Date?) -> Bool { + date.timeIntervalSince(lastPumpDataReportDate ?? .distantPast) > .minutes(12) + } public func isRunningManualTempBasal(for state: OmniBLEPumpManagerState) -> Bool { if let tempBasal = state.podState?.unfinalizedTempBasal, !tempBasal.isFinished(), !tempBasal.automatic { @@ -1871,9 +1887,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)) } } @@ -1913,7 +1929,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 @@ -1971,7 +1987,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: @@ -2052,12 +2068,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(.configuration(OmniBLEPumpManagerError.noPodPaired)) return @@ -2182,7 +2197,8 @@ 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: session.dosesForStorage() { (doses) -> Bool in @@ -2190,11 +2206,11 @@ extension OmniBLEPumpManager: PumpManager { } completion(nil) case .unacknowledged(let error): - self.log.error("Temp basal uncertain error: %@", String(describing: error)) - completion(nil) + completion(.communication(error)) + return case .certainFailure(let error): - self.log.error("setTempBasal failed: %{public}@", String(describing: error)) completion(.communication(error)) + return } } } @@ -2371,16 +2387,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) } } @@ -2391,13 +2407,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.setState { (state) in @@ -2478,11 +2494,24 @@ 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, 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)) @@ -2653,11 +2682,9 @@ extension OmniBLEPumpManager: AlertSoundVendor { // MARK: - AlertResponder implementation extension OmniBLEPumpManager { - public func acknowledgeAlert(alertIdentifier: Alert.AlertIdentifier, completion: @escaping (Error?) -> Void) { - guard self.hasActivePod, !state.activeAlerts.isEmpty else { - log.default("Skipping acknowledge alert %{public}@ with no active pod or alerts", alertIdentifier) - completion(nil) - return + public func acknowledgeAlert(alertIdentifier: LoopKit.Alert.AlertIdentifier) async throws { + guard self.hasActivePod else { + throw OmniBLEPumpManagerError.noPodPaired } var found = false @@ -2666,40 +2693,41 @@ extension OmniBLEPumpManager { found = true // If this alert was triggered by the pod find the slot to clear it. if let slot = alert.triggeringSlot { - // Special case handling for the suspend time expired alert + // Special case handling for the suspend time expired alert (DIY) if (self.state.podState?.isSuspended == true || self.state.podState?.lastDeliveryStatusReceived?.suspended == true) && slot == .slot6SuspendTimeExpired { // Don't clear this pod alert here with the pod still suspended so that the suspend time expired // pod alert beeping will continue until the pod is resumed which will then deactivate this alert. log.default("Skipping acknowledgement of suspend time expired alert with a suspended pod") - completion(nil) return } - // Acknowledge the pod alert for the triggering slot - 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.setState { state in + state.alertsWithPendingAcknowledgment.insert(alert) + } + continuation.resume(throwing: error) + return + } + self.setState { state in + state.activeAlerts.remove(alert) + } + continuation.resume() + case .failure(let error): self.setState { state in state.alertsWithPendingAcknowledgment.insert(alert) } - completion(error) + continuation.resume(throwing: error) return } - self.setState { state in - state.activeAlerts.remove(alert) - } - completion(nil) - case .failure(let error): - self.setState { state in - state.alertsWithPendingAcknowledgment.insert(alert) - } - completion(error) } } } else { @@ -2710,14 +2738,13 @@ extension OmniBLEPumpManager { state.acknowledgedTimeOffsetAlert = true } } - completion(nil) } } } if !found { log.error("acknowledge alert %{public}@ not found!", alertIdentifier) - completion(nil) + // alert not found — no-op in async version } } } 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?) { diff --git a/OmniBLE/PumpManager/PodCommsSession.swift b/OmniBLE/PumpManager/PodCommsSession.swift index 0743f5f1..f3e01234 100644 --- a/OmniBLE/PumpManager/PodCommsSession.swift +++ b/OmniBLE/PumpManager/PodCommsSession.swift @@ -591,7 +591,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 { if podState.unacknowledgedCommand != nil { do { @@ -629,7 +629,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) { @@ -642,7 +642,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 { if podState.unacknowledgedCommand != nil { do { @@ -665,7 +665,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 b269c383..1fafa0c3 100644 --- a/OmniBLE/PumpManager/PodState.swift +++ b/OmniBLE/PumpManager/PodState.swift @@ -219,7 +219,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 @@ -299,7 +299,8 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl self.unacknowledgedCommand = nil } - 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) { // save the current pod delivery state for verification before any insulin delivery command self.lastDeliveryStatusReceived = deliveryStatus @@ -308,7 +309,7 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl if deliveryStatus.bolusing && unfinalizedBolus == nil { // active bolus that we aren't tracking 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) } } if deliveryStatus.tempBasalRunning && unfinalizedTempBasal == nil { // active temp basal that we aren't tracking 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) } diff --git a/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift b/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift index c9378f13..fe411e0d 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 { @@ -168,7 +168,7 @@ class OmniBLESettingsViewModel: ObservableObject { switch basalDeliveryState { case .active(_), .initiatingTempBasal: return true - case .tempBasal(_), .cancelingTempBasal, .suspending, .suspended(_), .resuming, .none: + default: return false } } @@ -214,7 +214,7 @@ class OmniBLESettingsViewModel: ObservableObject { return nil } - let reservoirVolumeFormatter = QuantityFormatter(for: .internationalUnit()) + let reservoirVolumeFormatter = QuantityFormatter(for: .internationalUnit) var didFinish: (() -> Void)? @@ -295,8 +295,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) { @@ -408,13 +408,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 7d69c975..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) @@ -43,16 +43,21 @@ 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) } } } 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 6e2809ae..d53c0a53 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 7a6a4208..a04497b5 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 { @@ -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 }) diff --git a/OmniBLE/PumpManagerUI/Views/NotificationSettingsView.swift b/OmniBLE/PumpManagerUI/Views/NotificationSettingsView.swift index 000e9377..4e9a73e2 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 { @@ -115,7 +115,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) } diff --git a/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift b/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift index 66267447..a3deb634 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 { @@ -203,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 @@ -575,7 +574,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/Driver/Comm/message/MessagePacketTests.swift b/OmniBLETests/Driver/Comm/message/MessagePacketTests.swift index 280005a3..2a431334 100644 --- a/OmniBLETests/Driver/Comm/message/MessagePacketTests.swift +++ b/OmniBLETests/Driver/Comm/message/MessagePacketTests.swift @@ -51,5 +51,5 @@ class MessagePacketTests: XCTestCase { assert(msg.payload.hexadecimalString == payloadString) } - } + 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