Просмотр исходного кода

Merge branch 'dev' into feat/cgm-lifetime-indicator

Deniz Cengiz недель назад: 2
Родитель
Сommit
26aa7249c4

+ 2 - 2
Config.xcconfig

@@ -18,8 +18,8 @@ BUNDLE_IDENTIFIER = org.nightscout.$(DEVELOPMENT_TEAM).trio
 TRIO_APP_GROUP_ID = group.org.nightscout.$(DEVELOPMENT_TEAM).trio.trio-app-group
 
 // The developers set the version numbers, please leave them alone
-APP_VERSION = 0.8.2
-APP_DEV_VERSION = 0.8.2.1
+APP_VERSION = 0.8.3
+APP_DEV_VERSION = 0.8.3.2
 APP_BUILD_NUMBER = 1
 COPYRIGHT_NOTICE =
 

+ 1 - 1
OmnipodKit

@@ -1 +1 @@
-Subproject commit 80069aa0d7562814187e6b0eed499277f5841cf5
+Subproject commit 4d254498cf016806b3e0ec33dd5fbfdf6e2135a6

+ 4 - 0
Trio.xcodeproj/project.pbxproj

@@ -588,6 +588,7 @@
 		CEE9A6592BBB418300EB5194 /* CalibrationsDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE9A6542BBB418300EB5194 /* CalibrationsDataFlow.swift */; };
 		CEE9A65C2BBB41C800EB5194 /* CalibrationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE9A65B2BBB41C800EB5194 /* CalibrationService.swift */; };
 		CEE9A65E2BBC9F6500EB5194 /* CalibrationsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE9A65D2BBC9F6500EB5194 /* CalibrationsTests.swift */; };
+		CA02000000000000000010C2 /* DeliveryLimitsSyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA02000000000000000010C1 /* DeliveryLimitsSyncTests.swift */; };
 		CEF1ED6B2D58FB5800FAF41E /* CGMOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEF1ED6A2D58FB4600FAF41E /* CGMOptions.swift */; };
 		D6D02515BBFBE64FEBE89856 /* HistoryRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881E04BA5E0A003DE8E0A9C6 /* HistoryRootView.swift */; };
 		D6DEC113821A7F1056C4AA1E /* NightscoutConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2A13DF0EDEEEDC4106AA2A /* NightscoutConfigDataFlow.swift */; };
@@ -1530,6 +1531,7 @@
 		CEE9A6542BBB418300EB5194 /* CalibrationsDataFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CalibrationsDataFlow.swift; sourceTree = "<group>"; };
 		CEE9A65B2BBB41C800EB5194 /* CalibrationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalibrationService.swift; sourceTree = "<group>"; };
 		CEE9A65D2BBC9F6500EB5194 /* CalibrationsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalibrationsTests.swift; sourceTree = "<group>"; };
+		CA02000000000000000010C1 /* DeliveryLimitsSyncTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeliveryLimitsSyncTests.swift; sourceTree = "<group>"; };
 		CEF1ED6A2D58FB4600FAF41E /* CGMOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGMOptions.swift; sourceTree = "<group>"; };
 		CFCFE0781F9074C2917890E8 /* ManualTempBasalStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ManualTempBasalStateModel.swift; sourceTree = "<group>"; };
 		D0BDC6993C1087310EDFC428 /* CarbRatioEditorRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CarbRatioEditorRootView.swift; sourceTree = "<group>"; };
@@ -2887,6 +2889,7 @@
 				BD8FC0552D66187700B95AED /* CoreDataTests */,
 				38FCF3F125E9028E0078B0D1 /* Info.plist */,
 				CEE9A65D2BBC9F6500EB5194 /* CalibrationsTests.swift */,
+				CA02000000000000000010C1 /* DeliveryLimitsSyncTests.swift */,
 				3BAAE60B2DE776630049589B /* DynamicISFEnableTests.swift */,
 				38FCF3F825E902C20078B0D1 /* FileStorageTests.swift */,
 				3B997DCE2DC00A3A006B6BB2 /* JSONImporterTests.swift */,
@@ -5343,6 +5346,7 @@
 				BD8FC0542D66186000B95AED /* TestError.swift in Sources */,
 				DDDD0FFB2F4E22C000F9C645 /* GlucoseSmoothingTests.swift in Sources */,
 				CEE9A65E2BBC9F6500EB5194 /* CalibrationsTests.swift in Sources */,
+				CA02000000000000000010C2 /* DeliveryLimitsSyncTests.swift in Sources */,
 				BD8FC0622D6619E600B95AED /* OverrideStorageTests.swift in Sources */,
 				BD8FC0592D66189700B95AED /* TestAssembly.swift in Sources */,
 				DDC6CA6D2DD90A2A0060EE25 /* LocalizationTests.swift in Sources */,

+ 20 - 0
Trio/Sources/APS/DeviceDataManager.swift

@@ -3,6 +3,7 @@ import Combine
 import CoreData
 import DanaKit
 import Foundation
+import HealthKit
 import LoopKit
 import LoopKitUI
 import MedtrumKit
@@ -106,6 +107,25 @@ final class BaseDeviceDataManager: DeviceDataManager, Injectable {
                     .bolusIncrement = bolusIncrement > 0 ? bolusIncrement : 0.1
                 storage.save(modifiedPreferences, as: OpenAPS.Settings.preferences)
 
+                // Ensure the pump manager's delivery limits always reflect the user's
+                // current settings. Without this, the active pump manager instance
+                // may use a stale or default value from deserialized state
+                // — silently rejecting temp basals that oref correctly
+                // determines are within the user's configured maxBasal.
+                let pumpSettings = settingsManager.pumpSettings
+                let deliveryLimits = DeliveryLimits(
+                    maximumBasalRate: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: Double(pumpSettings.maxBasal)),
+                    maximumBolus: HKQuantity(unit: .internationalUnit(), doubleValue: Double(pumpSettings.maxBolus))
+                )
+
+                processQueue.async {
+                    pumpManager.syncDeliveryLimits(limits: deliveryLimits) { result in
+                        if case let .failure(error) = result {
+                            debug(.deviceManager, "syncDeliveryLimits on pump manager init failed: \(error)")
+                        }
+                    }
+                }
+
                 if let medtrumPump = pumpManager as? MedtrumPumpManager {
                     // Medtrum's state.patchExpiresAt is actually lifespan + grace
                     // keeping this in line with omnipod, we will use just the lifetime

+ 2 - 2
Trio/Sources/APS/OpenAPSSwift/DetermineBasal/DetermineBasalGenerator.swift

@@ -377,7 +377,7 @@ enum DeterminationGenerator {
             temp: nil,
             bg: currentGlucose,
             reservoir: nil,
-            isf: nil,
+            isf: adjustedSensitivity.jsRounded(),
             timestamp: currentTime,
             tdd: nil,
             current_target: adjustedGlucoseTargets.targetGlucose,
@@ -486,7 +486,7 @@ enum DeterminationGenerator {
         if forecastResult.eventualGlucose >= adjustedGlucoseTargets.maxGlucose {
             determination
                 .reason +=
-                "Eventual BG \(DosingEngine.convertGlucose(profile: profile, glucose: forecastResult.eventualGlucose)) >= \(DosingEngine.convertGlucose(profile: profile, glucose: adjustedGlucoseTargets.maxGlucose)), "
+                "Eventual BG \(forecastResult.eventualGlucose.jsRounded()) >= \(adjustedGlucoseTargets.maxGlucose.jsRounded()), "
         }
 
         let (shouldSetTempBasalForIobGreaterThanMax, iobGreaterThanMaxDetermination) = try DosingEngine.iobGreaterThanMax(

+ 11 - 20
Trio/Sources/APS/OpenAPSSwift/DetermineBasal/DosingEngine.swift

@@ -88,15 +88,6 @@ enum DosingEngine {
         return false
     }
 
-    /// helper function for reason string glucose output
-    static func convertGlucose(profile: Profile, glucose: Decimal) -> Decimal {
-        let units = profile.outUnits ?? .mgdL
-        switch units {
-        case .mgdL: return glucose.jsRounded()
-        case .mmolL: return glucose.asMmolL
-        }
-    }
-
     /// Top level smb enabling logic
     ///
     /// This function includes both the profile / customOrefVariable checks from JS `enable_smb` as
@@ -135,7 +126,7 @@ enum DosingEngine {
         let maxDeltaGlucoseThreshold = min(profile.maxDeltaBgThreshold, 0.4)
         if glucoseStatus.maxDelta > maxDeltaGlucoseThreshold * currentGlucose {
             reason =
-                "maxDelta \(convertGlucose(profile: profile, glucose: glucoseStatus.maxDelta)) > \(100 * maxDeltaGlucoseThreshold)% of BG \(convertGlucose(profile: profile, glucose: currentGlucose)) - SMB disabled!, "
+                "maxDelta \(glucoseStatus.maxDelta.jsRounded()) > \(100 * maxDeltaGlucoseThreshold)% of BG \(currentGlucose.jsRounded()) - SMB disabled!, "
             smbIsEnabled = false
         }
 
@@ -287,16 +278,16 @@ enum DosingEngine {
         if currentGlucose < threshold, iob < suspendThreshold, minDelta > 0, minDelta > expectedDelta {
             let iobString = String(describing: iob)
             let suspendString = String(describing: suspendThreshold.jsRounded(scale: 2))
-            let minDeltaString = String(describing: convertGlucose(profile: profile, glucose: minDelta))
-            let expectedDeltaString = String(describing: convertGlucose(profile: profile, glucose: expectedDelta))
+            let minDeltaString = String(describing: minDelta.jsRounded())
+            let expectedDeltaString = String(describing: expectedDelta.jsRounded())
 
             newDetermination
                 .reason +=
                 "IOB \(iobString) < \(suspendString) and minDelta \(minDeltaString) > expectedDelta \(expectedDeltaString); "
             return (shouldSetTempBasal: false, determination: newDetermination)
         } else if currentGlucose < threshold || minGuardGlucose < threshold {
-            let minGuardGlucoseString = String(describing: convertGlucose(profile: profile, glucose: minGuardGlucose))
-            let thresholdString = String(describing: convertGlucose(profile: profile, glucose: threshold))
+            let minGuardGlucoseString = String(describing: minGuardGlucose.jsRounded())
+            let thresholdString = String(describing: threshold.jsRounded())
             newDetermination.reason += "minGuardBG \(minGuardGlucoseString)<\(thresholdString)"
 
             let glucoseUndershoot = targetGlucose - minGuardGlucose
@@ -396,7 +387,7 @@ enum DosingEngine {
         var newDetermination = determination
         newDetermination
             .reason +=
-            "Eventual BG \(convertGlucose(profile: profile, glucose: eventualGlucose)) < \(convertGlucose(profile: profile, glucose: minGlucose))"
+            "Eventual BG \(eventualGlucose.jsRounded()) < \(minGlucose.jsRounded())"
 
         // if 5m or 30m avg glucose is rising faster than expected delta
         // BUG: in JS it's doing a "truthiness" check for carbs required
@@ -419,12 +410,12 @@ enum DosingEngine {
             if glucoseStatus.delta > minDelta {
                 newDetermination
                     .reason +=
-                    ", but Delta \(convertGlucose(profile: profile, glucose: glucoseStatus.delta)) > expectedDelta \(convertGlucose(profile: profile, glucose: expectedDelta))"
+                    ", but Delta \(glucoseStatus.delta.jsRounded()) > expectedDelta \(expectedDelta.jsRounded())"
             } else {
                 let minDeltaFormatted = String(format: "%.2f", Double(truncating: minDelta.jsRounded(scale: 2) as NSNumber))
                 newDetermination
                     .reason +=
-                    ", but Min. Delta \(minDeltaFormatted) > Exp. Delta \(convertGlucose(profile: profile, glucose: expectedDelta))"
+                    ", but Min. Delta \(minDeltaFormatted) > Exp. Delta \(expectedDelta.jsRounded())"
             }
 
             let roundedBasal = TempBasalFunctions.roundBasal(profile: profile, basalRate: basal)
@@ -549,12 +540,12 @@ enum DosingEngine {
             if glucoseStatus.delta < minDelta {
                 newDetermination
                     .reason +=
-                    "Eventual BG \(convertGlucose(profile: profile, glucose: eventualGlucose)) > \(convertGlucose(profile: profile, glucose: minGlucose)) but Delta \(convertGlucose(profile: profile, glucose: glucoseStatus.delta)) < Exp. Delta \(convertGlucose(profile: profile, glucose: expectedDelta))"
+                    "Eventual BG \(eventualGlucose.jsRounded()) > \(minGlucose.jsRounded()) but Delta \(glucoseStatus.delta.jsRounded()) < Exp. Delta \(expectedDelta.jsRounded())"
             } else {
                 let minDeltaFormatted = String(format: "%.2f", Double(truncating: minDelta.jsRounded(scale: 2) as NSNumber))
                 newDetermination
                     .reason +=
-                    "Eventual BG \(convertGlucose(profile: profile, glucose: eventualGlucose)) > \(convertGlucose(profile: profile, glucose: minGlucose)) but Min. Delta \(minDeltaFormatted) < Exp. Delta \(convertGlucose(profile: profile, glucose: expectedDelta))"
+                    "Eventual BG \(eventualGlucose.jsRounded()) > \(minGlucose.jsRounded()) but Min. Delta \(minDeltaFormatted) < Exp. Delta \(expectedDelta.jsRounded())"
             }
 
             let roundedBasal = TempBasalFunctions.roundBasal(profile: profile, basalRate: basal)
@@ -604,7 +595,7 @@ enum DosingEngine {
         if !smbIsEnabled {
             newDetermination
                 .reason +=
-                "\(convertGlucose(profile: profile, glucose: eventualGlucose))-\(convertGlucose(profile: profile, glucose: minForecastGlucose)) in range: no temp required"
+                "\(eventualGlucose.jsRounded())-\(minForecastGlucose.jsRounded()) in range: no temp required"
 
             let roundedBasal = TempBasalFunctions.roundBasal(profile: profile, basalRate: basal)
             let roundedCurrentRate = TempBasalFunctions.roundBasal(profile: profile, basalRate: currentTemp.rate)

+ 1 - 0
Trio/Sources/Localizations/Main/Localizable.xcstrings

@@ -183477,6 +183477,7 @@
       }
     },
     "Note to Medtronic Pump Users: You must also manually set the max basal rate on the pump to this value or higher." : {
+      "extractionState" : "stale",
       "localizations" : {
         "bg" : {
           "stringUnit" : {

+ 0 - 3
Trio/Sources/Modules/GeneralSettings/View/UnitsLimitsSettingsRootView.swift

@@ -129,9 +129,6 @@ extension UnitsLimitsSettings {
                         Text(
                             "This is the maximum basal rate allowed to be set or scheduled. This applies to both automatic and manual basal rates."
                         )
-                        Text(
-                            "Note to Medtronic Pump Users: You must also manually set the max basal rate on the pump to this value or higher."
-                        )
                     }
                 )
 

+ 0 - 3
Trio/Sources/Modules/Onboarding/View/OnboardingView+Util.swift

@@ -435,9 +435,6 @@ enum DeliveryLimitSubstep: Int, CaseIterable, Identifiable {
                 Text(
                     "This is the maximum basal rate allowed to be set or scheduled. This applies to both automatic and manual basal rates."
                 )
-                Text(
-                    "Note to Medtronic Pump Users: You must also manually set the max basal rate on the pump to this value or higher."
-                )
             }
         case .maxCOB:
             return VStack(alignment: .leading, spacing: 8) {

+ 67 - 0
TrioTests/DeliveryLimitsSyncTests.swift

@@ -0,0 +1,67 @@
+import Foundation
+import HealthKit
+import LoopKit
+import Testing
+
+@testable import Trio
+
+/// Pins the `PumpSettings` → `DeliveryLimits` mapping that
+/// `BaseDeviceDataManager.pumpManager.didSet` performs inline before calling
+/// `pumpManager.syncDeliveryLimits`. Keep this builder identical to the inline
+/// expression in `DeviceDataManager.swift` so a regression that re-introduces
+/// the default-collapse bug breaks here too.
+@Suite("Delivery Limits Sync Tests") struct DeliveryLimitsSyncTests {
+    private let basalUnit = HKUnit.internationalUnitsPerHour
+    private let bolusUnit = HKUnit.internationalUnit()
+
+    private func makeSettings(maxBasal: Decimal, maxBolus: Decimal) -> PumpSettings {
+        PumpSettings(insulinActionCurve: 6, maxBolus: maxBolus, maxBasal: maxBasal)
+    }
+
+    private func deliveryLimits(from settings: PumpSettings) -> DeliveryLimits {
+        DeliveryLimits(
+            maximumBasalRate: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: Double(settings.maxBasal)),
+            maximumBolus: HKQuantity(unit: .internationalUnit(), doubleValue: Double(settings.maxBolus))
+        )
+    }
+
+    @Test("maxBasal maps to maximumBasalRate in U/hr") func testMaxBasalMapping() {
+        let settings = makeSettings(maxBasal: 5.0, maxBolus: 10.0)
+
+        let limits = deliveryLimits(from: settings)
+
+        #expect(limits.maximumBasalRate?.doubleValue(for: basalUnit) == 5.0)
+    }
+
+    @Test("maxBolus maps to maximumBolus in U") func testMaxBolusMapping() {
+        let settings = makeSettings(maxBasal: 5.0, maxBolus: 10.0)
+
+        let limits = deliveryLimits(from: settings)
+
+        #expect(limits.maximumBolus?.doubleValue(for: bolusUnit) == 10.0)
+    }
+
+    /// The derived limit must be the user's configured value, not the `PumpInitialSettings` default.
+    @Test("User-configured limit is preserved, not collapsed to the 2 U/hr default") func testDoesNotFallBackToDefault() {
+        let configuredMaxBasal: Decimal = 3.0
+        let defaultMaxBasal = PumpConfig.PumpInitialSettings.default.maxBasalRateUnitsPerHour
+
+        let settings = makeSettings(maxBasal: configuredMaxBasal, maxBolus: 10.0)
+        let limits = deliveryLimits(from: settings)
+
+        #expect(limits.maximumBasalRate?.doubleValue(for: basalUnit) == Double(configuredMaxBasal))
+        #expect(limits.maximumBasalRate?.doubleValue(for: basalUnit) != defaultMaxBasal)
+    }
+
+    /// The derived limit must be the user's configured value, not the `PumpInitialSettings` default.
+    @Test("User-configured bolus limit is preserved, not collapsed to the default") func testBolusDoesNotFallBackToDefault() {
+        let configuredMaxBolus: Decimal = 25.0
+        let defaultMaxBolus = PumpConfig.PumpInitialSettings.default.maxBolusUnits
+
+        let settings = makeSettings(maxBasal: 3.0, maxBolus: configuredMaxBolus)
+        let limits = deliveryLimits(from: settings)
+
+        #expect(limits.maximumBolus?.doubleValue(for: bolusUnit) == Double(configuredMaxBolus))
+        #expect(limits.maximumBolus?.doubleValue(for: bolusUnit) != defaultMaxBolus)
+    }
+}

+ 45 - 0
blacklisted-versions.json

@@ -5,6 +5,51 @@
         },
         {
             "version": "0.0.2"
+        },
+        {
+            "version": "0.2.0"
+        },
+        {
+            "version": "0.2.1"
+        },
+        {
+            "version": "0.2.2"
+        },
+        {
+            "version": "0.2.3"
+        },
+        {
+            "version": "0.2.4"
+        },
+        {
+            "version": "0.2.5"
+        },
+        {
+            "version": "0.2.6"
+        },
+        {
+            "version": "0.2.7"
+        },
+        {
+            "version": "0.2.8"
+        },
+        {
+            "version": "0.2.9"
+        },
+        {
+            "version": "0.5.0"
+        },
+        {
+            "version": "0.5.1"
+        },
+        {
+            "version": "0.6.0"
+        },
+        {
+            "version": "0.8.1"
+        },
+        {
+            "version": "0.8.2"
         }
     ]
 }