Jonas Björkert 1 éve
szülő
commit
7c8c6dbbd1

+ 9 - 2
LoopFollow/BackgroundRefresh/BT/BLEDeviceSelectionView.swift

@@ -13,13 +13,14 @@ struct BLEDeviceSelectionView: View {
     var body: some View {
         VStack {
             List {
-                if bleManager.devices.filter({ selectedFilter.matches($0) && !isSelected($0) }).isEmpty {
+                let filteredDevices = bleManager.devices.filter { selectedFilter.matches($0) && !isSelected($0) }
+                if filteredDevices.isEmpty {
                     Text("No devices found yet. They'll appear here when discovered.")
                         .foregroundColor(.secondary)
                         .multilineTextAlignment(.center)
                         .padding()
                 } else {
-                    ForEach(bleManager.devices.filter { selectedFilter.matches($0) && !isSelected($0) }, id: \.id) { device in
+                    ForEach(filteredDevices, id: \.id) { device in
                         HStack {
                             VStack(alignment: .leading) {
                                 Text(device.name ?? "Unknown")
@@ -27,6 +28,12 @@ struct BLEDeviceSelectionView: View {
                                 Text("RSSI: \(device.rssi) dBm")
                                     .foregroundColor(.secondary)
                                     .font(.footnote)
+
+                                if let offset = BLEManager.shared.expectedSensorFetchOffsetString(for: device) {
+                                    Text("Expected fetch offset: \(offset)")
+                                        .foregroundColor(.secondary)
+                                        .font(.footnote)
+                                }
                             }
                             Spacer()
                         }

+ 45 - 0
LoopFollow/BackgroundRefresh/BT/BLEManager.swift

@@ -213,3 +213,48 @@ extension BLEManager: BluetoothDeviceDelegate {
         TaskScheduler.shared.checkTasksNow()
     }
 }
+
+extension BLEManager {
+    /// Returns the expected sensor fetch offset as a formatted string ("mm:ss (fetch delay: XX sec)")
+    /// for Dexcom devices. The expected offset is computed as the sensor's schedule offset plus the polling delay.
+    /// The device’s lastSeen time is used (mod 300) to calculate the effective delay between when the sensor value
+    /// becomes available and when the fetch is actually triggered.
+    func expectedSensorFetchOffsetString(for device: BLEDevice) -> String? {
+        // Determine the device type using your BackgroundRefreshType matching.
+        guard let matchedType = BackgroundRefreshType.allCases.first(where: { $0.matches(device) }) else {
+            return nil
+        }
+
+        // We only calculate this for Dexcom (G7) devices.
+        if matchedType == .dexcom {
+            // Return nil if the sensor schedule offset hasn't been set.
+            guard let sensorOffset = Storage.shared.sensorScheduleOffset.value else {
+                return nil
+            }
+
+            // Polling delay: use dynamic setting if enabled, otherwise the default.
+            let pollingDelay: TimeInterval = Storage.shared.bgDelayDynamicEnabled.value
+            ? Storage.shared.bgDelayDynamicDelay.value
+            : Double(UserDefaultsRepository.bgUpdateDelay.value)
+
+            // T_expected: the time (in seconds) after the sensor reading when the value is available.
+            let expectedOffset = sensorOffset + pollingDelay
+
+            // Compute the device’s heartbeat offset within the 5-minute (300 sec) cycle.
+            let calendar = Calendar(identifier: .gregorian)
+            let startOfDay = calendar.startOfDay(for: device.lastSeen)
+            let heartbeatOffset = device.lastSeen.timeIntervalSince(startOfDay).truncatingRemainder(dividingBy: 300)
+
+            // Calculate effective delay:
+            // If the heartbeat happens after the sensor value is available, delay = heartbeatOffset - expectedOffset.
+            // Otherwise, the fetch will occur on the next cycle:
+            // delay = (heartbeatOffset + 300) - expectedOffset.
+            let effectiveDelay: TimeInterval = (heartbeatOffset >= expectedOffset)
+            ? (heartbeatOffset - expectedOffset)
+            : (heartbeatOffset + 300 - expectedOffset)
+
+            return "\(Int(effectiveDelay)) sec"
+        }
+        return nil
+    }
+}

+ 5 - 0
LoopFollow/BackgroundRefresh/BackgroundRefreshSettingsView.swift

@@ -97,6 +97,11 @@ struct BackgroundRefreshSettingsView: View {
                             .foregroundColor(.secondary)
                             .font(.footnote)
                     }
+                    if let offset = BLEManager.shared.expectedSensorFetchOffsetString(for: storedDevice) {
+                        Text("Expected fetch offset: \(offset)")
+                            .foregroundColor(.secondary)
+                            .font(.footnote)
+                    }
 
                     HStack {
                         Spacer()

+ 36 - 4
LoopFollow/Controllers/Nightscout/BGData.swift

@@ -135,6 +135,16 @@ extension MainViewController {
         // secondsAgo is how old the newest reading is
         let secondsAgo = now - sensorTimestamp
 
+        // Compute the current sensor schedule offset.
+        let currentOffset = sensorScheduleOffset(for: sensorTimestamp)
+
+        if Storage.shared.sensorScheduleOffset.value != currentOffset {
+            Storage.shared.sensorScheduleOffset.value = currentOffset
+            LogManager.shared.log(category: .nightscout,
+                                  message: "Sensor schedule offset: \(currentOffset) seconds.",
+                                  isDebug: true)
+        }
+
         LogManager.shared.log(category: .nightscout,
                               message: "Processing BG Data. Latest sensor reading: \(sensorTimestamp), now: \(now), secondsAgo: \(secondsAgo).",
                               isDebug: true)
@@ -142,13 +152,20 @@ extension MainViewController {
         // Check if we have a new reading (i.e. sensor timestamp is greater than what we last saw).
         if let lastTS = lastProcessedTimestamp {
             if sensorTimestamp > lastTS {
-                let observedDelay = now - sensorTimestamp
-                addObservedDelay(observedDelay: observedDelay)
+                if lastBgFetchMiss, let delay = lastBgFetchDelay {
+                    addObservedDelay(observedDelay: delay)
+                    LogManager.shared.log(category: .nightscout,
+                                          message: "Last attempt did not get any new data, storing the delay of \(delay) seconds.",
+                                          isDebug: true)
+                }
+
+                lastBgFetchMiss = false
                 lastProcessedTimestamp = sensorTimestamp
                 LogManager.shared.log(category: .nightscout,
-                                      message: "New reading detected. Sensor time: \(sensorTimestamp) updated (was \(lastTS)). Observed delay: \(observedDelay) seconds.",
+                                      message: "New reading detected. Sensor time: \(sensorTimestamp) updated (was \(lastTS)). Observed delay: \(secondsAgo) seconds.",
                                       isDebug: true)
             } else {
+                lastBgFetchMiss = true
                 LogManager.shared.log(category: .nightscout,
                                       message: "No new reading. Last processed sensor timestamp remains \(lastTS).",
                                       isDebug: true)
@@ -217,6 +234,7 @@ extension MainViewController {
                     delayToSchedule = exploredDelay
                 }
             }
+            self.lastBgFetchDelay = secondsAgo - delayToSchedule
 
             // Log and schedule the next fetch.
             LogManager.shared.log(category: .nightscout,
@@ -256,6 +274,20 @@ extension MainViewController {
         viewUpdateNSBG(sourceName: sourceName)
     }
 
+    /// Computes the sensor schedule offset (in seconds) for a given time interval.
+    /// The offset is the remainder (in seconds) of the time elapsed since midnight (UTC) divided by 300 seconds.
+    /// For example, if the sensor reports a time that corresponds to 13:06:30, the offset is 90 seconds.
+    func sensorScheduleOffset(for timeInterval: TimeInterval) -> TimeInterval {
+        var calendar = Calendar(identifier: .gregorian)
+        // Use UTC to be consistent with our sensor timestamps.
+        calendar.timeZone = TimeZone(secondsFromGMT: 0)!
+
+        let date = Date(timeIntervalSince1970: timeInterval)
+        let startOfDay = calendar.startOfDay(for: date)
+        let secondsSinceStartOfDay = date.timeIntervalSince(startOfDay)
+        return secondsSinceStartOfDay.truncatingRemainder(dividingBy: 300)
+    }
+
     private func computeOptimalDelay() -> Double {
         if Storage.shared.bgDelayDynamicEnabled.value {
             guard let optimal = observedDelays.min() else {
@@ -265,7 +297,7 @@ extension MainViewController {
                 return Storage.shared.bgDelayDynamicDelay.value
             }
             Storage.shared.bgDelayDynamicDelay.value = optimal
-            
+
             LogManager.shared.log(category: .nightscout,
                                   message: "Computed optimal delay from observations \(observedDelays) is \(optimal) seconds.",
                                   isDebug: true)

+ 2 - 0
LoopFollow/Storage/Storage.swift

@@ -40,6 +40,8 @@ class Storage {
     var bgDelayDynamicEnabled = StorageValue<Bool>(key: "bgDelayDynamicEnabled", defaultValue: false)
     var bgDelayDynamicDelay = StorageValue<Double>(key: "bgDelayDynamicDelay", defaultValue: 10)
 
+    var sensorScheduleOffset = StorageValue<Double?>(key: "sensorScheduleOffset", defaultValue: nil)
+
     static let shared = Storage()
 
     private init() { }

+ 6 - 0
LoopFollow/ViewControllers/MainViewController.swift

@@ -149,6 +149,12 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
     /// Historical delays observed (in seconds) between sensor measurement and our fetch.
     var observedDelays: [Double] = []
 
+    /// If last fetch did not generate new bg data, we regard it an too early fetch
+    var lastBgFetchMiss: Bool = false
+
+    /// If last fetch did not generate new bg data, we regard it an too early fetch
+    var lastBgFetchDelay: Double?
+
     override func viewDidLoad() {
         super.viewDidLoad()