From f5ba973d32143f6dba59a6c3f785536b29b0f732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bj=C3=B6rkert?= Date: Sat, 21 Mar 2026 19:49:55 +0100 Subject: [PATCH 1/2] Add aggressive Nightscout polling after remote commands After a remote command (bolus, carbs, override, temp target) succeeds, poll Nightscout every 3 seconds for up to 30 seconds to quickly reflect the command on the main screen. Polling stops early when fresh data matching the command type is detected. --- .../Controllers/Nightscout/DeviceStatus.swift | 1 + .../Controllers/Nightscout/Treatments.swift | 1 + .../Remote/LoopAPNS/LoopAPNSBolusView.swift | 1 + .../Remote/LoopAPNS/LoopAPNSCarbsView.swift | 1 + .../Remote/LoopAPNS/OverridePresetsView.swift | 2 + LoopFollow/Remote/RemoteType.swift | 4 + LoopFollow/Remote/TRC/BolusView.swift | 1 + LoopFollow/Remote/TRC/MealView.swift | 1 + LoopFollow/Remote/TRC/OverrideView.swift | 2 + LoopFollow/Remote/TRC/TempTargetView.swift | 2 + .../TRC/TrioNightscoutRemoteController.swift | 2 + .../ViewControllers/MainViewController.swift | 105 ++++++++++++++++++ 12 files changed, 123 insertions(+) diff --git a/LoopFollow/Controllers/Nightscout/DeviceStatus.swift b/LoopFollow/Controllers/Nightscout/DeviceStatus.swift index f8bc8f867..42689827c 100644 --- a/LoopFollow/Controllers/Nightscout/DeviceStatus.swift +++ b/LoopFollow/Controllers/Nightscout/DeviceStatus.swift @@ -220,6 +220,7 @@ extension MainViewController { ) } else if secondsAgo >= (5 * 60) { + self.evaluateRemoteCommandPollingCompletion() TaskScheduler.shared.rescheduleTask( id: .deviceStatus, to: Date().addingTimeInterval(10) diff --git a/LoopFollow/Controllers/Nightscout/Treatments.swift b/LoopFollow/Controllers/Nightscout/Treatments.swift index 8ff20df87..4643cd1e3 100644 --- a/LoopFollow/Controllers/Nightscout/Treatments.swift +++ b/LoopFollow/Controllers/Nightscout/Treatments.swift @@ -184,5 +184,6 @@ extension MainViewController { } } processCage(entries: pumpSiteChange) + evaluateRemoteCommandPollingCompletion() } } diff --git a/LoopFollow/Remote/LoopAPNS/LoopAPNSBolusView.swift b/LoopFollow/Remote/LoopAPNS/LoopAPNSBolusView.swift index 011fe0e10..1f73b82ce 100644 --- a/LoopFollow/Remote/LoopAPNS/LoopAPNSBolusView.swift +++ b/LoopFollow/Remote/LoopAPNS/LoopAPNSBolusView.swift @@ -373,6 +373,7 @@ struct LoopAPNSBolusView: View { TOTPService.shared.markTOTPAsUsed(qrCodeURL: Storage.shared.loopAPNSQrCodeURL.value) self.alertMessage = "Insulin sent successfully!" self.alertType = .success + NotificationCenter.default.post(name: .remoteCommandSucceeded, object: nil) LogManager.shared.log( category: .apns, message: "Insulin sent - Amount: \(insulinAmount.doubleValue(for: .internationalUnit()))U" diff --git a/LoopFollow/Remote/LoopAPNS/LoopAPNSCarbsView.swift b/LoopFollow/Remote/LoopAPNS/LoopAPNSCarbsView.swift index 28b5745d8..1494a40ae 100644 --- a/LoopFollow/Remote/LoopAPNS/LoopAPNSCarbsView.swift +++ b/LoopFollow/Remote/LoopAPNS/LoopAPNSCarbsView.swift @@ -403,6 +403,7 @@ struct LoopAPNSCarbsView: View { timeFormatter.timeStyle = .short self.alertMessage = "Carbs sent successfully for \(timeFormatter.string(from: adjustedConsumedDate))!" self.alertType = .success + NotificationCenter.default.post(name: .remoteCommandSucceeded, object: nil) LogManager.shared.log( category: .apns, message: "Carbs sent - Amount: \(carbsAmount.doubleValue(for: .gram()))g, Absorption: \(absorptionTimeString)h, Time: \(adjustedConsumedDate)" diff --git a/LoopFollow/Remote/LoopAPNS/OverridePresetsView.swift b/LoopFollow/Remote/LoopAPNS/OverridePresetsView.swift index ce88f0b90..c09c964a4 100644 --- a/LoopFollow/Remote/LoopAPNS/OverridePresetsView.swift +++ b/LoopFollow/Remote/LoopAPNS/OverridePresetsView.swift @@ -407,6 +407,7 @@ class OverridePresetsViewModel: ObservableObject { self.isActivating = false self.statusMessage = "\(preset.name) override activated successfully." self.alertType = .statusSuccess + NotificationCenter.default.post(name: .remoteCommandSucceeded, object: nil) self.showAlert = true } } catch { @@ -430,6 +431,7 @@ class OverridePresetsViewModel: ObservableObject { self.isActivating = false self.statusMessage = "Active override cancelled successfully." self.alertType = .statusSuccess + NotificationCenter.default.post(name: .remoteCommandSucceeded, object: nil) self.showAlert = true } } catch { diff --git a/LoopFollow/Remote/RemoteType.swift b/LoopFollow/Remote/RemoteType.swift index 1e4b958dd..bbc7ad8db 100644 --- a/LoopFollow/Remote/RemoteType.swift +++ b/LoopFollow/Remote/RemoteType.swift @@ -9,3 +9,7 @@ enum RemoteType: String, Codable { case trc = "Trio Remote Control" case loopAPNS = "Loop APNS" } + +extension Notification.Name { + static let remoteCommandSucceeded = Notification.Name("remoteCommandSucceeded") +} diff --git a/LoopFollow/Remote/TRC/BolusView.swift b/LoopFollow/Remote/TRC/BolusView.swift index 30bfab213..b43af519f 100644 --- a/LoopFollow/Remote/TRC/BolusView.swift +++ b/LoopFollow/Remote/TRC/BolusView.swift @@ -272,6 +272,7 @@ struct BolusView: View { category: .apns, message: "sendBolusPushNotification succeeded - Bolus: \(InsulinFormatter.shared.string(bolusAmount)) U" ) + NotificationCenter.default.post(name: .remoteCommandSucceeded, object: nil) bolusAmount = HKQuantity(unit: .internationalUnit(), doubleValue: 0.0) alertType = .statusSuccess } else { diff --git a/LoopFollow/Remote/TRC/MealView.swift b/LoopFollow/Remote/TRC/MealView.swift index 55dd704e6..bfe03eab2 100644 --- a/LoopFollow/Remote/TRC/MealView.swift +++ b/LoopFollow/Remote/TRC/MealView.swift @@ -305,6 +305,7 @@ struct MealView: View { category: .apns, message: "sendMealPushNotification succeeded - Carbs: \(carbs.doubleValue(for: .gram())) g, Protein: \(protein.doubleValue(for: .gram())) g, Fat: \(fat.doubleValue(for: .gram())) g, Bolus: \(bolusAmount.doubleValue(for: .internationalUnit())) U, Scheduled: \(scheduledDate != nil ? formatDate(scheduledDate!) : "now")" ) + NotificationCenter.default.post(name: .remoteCommandSucceeded, object: nil) // Reset meal values and scheduled data after success carbs = HKQuantity(unit: .gram(), doubleValue: 0.0) diff --git a/LoopFollow/Remote/TRC/OverrideView.swift b/LoopFollow/Remote/TRC/OverrideView.swift index 3a402f847..906f3e7d2 100644 --- a/LoopFollow/Remote/TRC/OverrideView.swift +++ b/LoopFollow/Remote/TRC/OverrideView.swift @@ -175,6 +175,7 @@ struct OverrideView: View { self.statusMessage = "Override command sent successfully." self.alertType = .statusSuccess LogManager.shared.log(category: .apns, message: "sendOverridePushNotification succeeded for override: \(override.name)") + NotificationCenter.default.post(name: .remoteCommandSucceeded, object: nil) } else { self.statusMessage = errorMessage ?? "Failed to send override command." self.alertType = .statusFailure @@ -195,6 +196,7 @@ struct OverrideView: View { self.statusMessage = "Cancel override command sent successfully." self.alertType = .statusSuccess LogManager.shared.log(category: .apns, message: "sendCancelOverridePushNotification succeeded") + NotificationCenter.default.post(name: .remoteCommandSucceeded, object: nil) } else { self.statusMessage = errorMessage ?? "Failed to send cancel override command." self.alertType = .statusFailure diff --git a/LoopFollow/Remote/TRC/TempTargetView.swift b/LoopFollow/Remote/TRC/TempTargetView.swift index 3a047e66f..4219caf9c 100644 --- a/LoopFollow/Remote/TRC/TempTargetView.swift +++ b/LoopFollow/Remote/TRC/TempTargetView.swift @@ -255,6 +255,7 @@ struct TempTargetView: View { self.statusMessage = "Temp target command successfully sent." self.alertType = .statusSuccess LogManager.shared.log(category: .apns, message: "sendTempTargetPushNotification succeeded with target: \(newHKTarget), duration: \(duration)") + NotificationCenter.default.post(name: .remoteCommandSucceeded, object: nil) } else { self.statusMessage = errorMessage ?? "Failed to send temp target command." self.alertType = .statusFailure @@ -275,6 +276,7 @@ struct TempTargetView: View { self.statusMessage = "Cancel temp target command successfully sent." self.alertType = .statusSuccess LogManager.shared.log(category: .apns, message: "sendCancelTempTargetPushNotification succeeded") + NotificationCenter.default.post(name: .remoteCommandSucceeded, object: nil) } else { self.statusMessage = errorMessage ?? "Failed to send cancel temp target command." self.alertType = .statusFailure diff --git a/LoopFollow/Remote/TRC/TrioNightscoutRemoteController.swift b/LoopFollow/Remote/TRC/TrioNightscoutRemoteController.swift index 594619690..e8c8edc13 100644 --- a/LoopFollow/Remote/TRC/TrioNightscoutRemoteController.swift +++ b/LoopFollow/Remote/TRC/TrioNightscoutRemoteController.swift @@ -19,6 +19,7 @@ class TrioNightscoutRemoteController { let response: [TreatmentCancelResponse] = try await NightscoutUtils.executePostRequest(eventType: .treatments, body: tempTargetBody) Observable.shared.tempTarget.value = nil NotificationCenter.default.post(name: NSNotification.Name("refresh"), object: nil) + NotificationCenter.default.post(name: .remoteCommandSucceeded, object: nil) completion(true) } catch { completion(false) @@ -42,6 +43,7 @@ class TrioNightscoutRemoteController { let response: [TreatmentResponse] = try await NightscoutUtils.executePostRequest(eventType: .treatments, body: tempTargetBody) Observable.shared.tempTarget.value = newTarget NotificationCenter.default.post(name: NSNotification.Name("refresh"), object: nil) + NotificationCenter.default.post(name: .remoteCommandSucceeded, object: nil) completion(true) } catch { completion(false) diff --git a/LoopFollow/ViewControllers/MainViewController.swift b/LoopFollow/ViewControllers/MainViewController.swift index e494ea946..027fe5c7c 100644 --- a/LoopFollow/ViewControllers/MainViewController.swift +++ b/LoopFollow/ViewControllers/MainViewController.swift @@ -16,6 +16,29 @@ func IsNightscoutEnabled() -> Bool { } class MainViewController: UIViewController, UITableViewDataSource, ChartViewDelegate, UNUserNotificationCenterDelegate, UIScrollViewDelegate { + private struct RemoteCommandDataSignature { + let carbTimestamp: TimeInterval? + let bolusTimestamp: TimeInterval? + let overrideTimestamp: TimeInterval? + let overrideStateKey: String + + func detectsFreshData(comparedTo baseline: RemoteCommandDataSignature) -> Bool { + if let carbTimestamp, carbTimestamp > (baseline.carbTimestamp ?? 0) { + return true + } + + if let bolusTimestamp, bolusTimestamp > (baseline.bolusTimestamp ?? 0) { + return true + } + + if let overrideTimestamp, overrideTimestamp > (baseline.overrideTimestamp ?? 0) { + return true + } + + return overrideStateKey != baseline.overrideStateKey + } + } + var isPresentedAsModal: Bool = false @IBOutlet var BGText: UILabel! @@ -136,6 +159,11 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele "deviceStatus": false, ] private var loadingTimeoutTimer: Timer? + private var remoteCommandPollingTimer: Timer? + private var remoteCommandPollingStartedAt: Date? + private var remoteCommandPollingBaseline: RemoteCommandDataSignature? + private let remoteCommandPollingInterval: TimeInterval = 3 + private let remoteCommandPollingDuration: TimeInterval = 30 override func viewDidLoad() { super.viewDidLoad() @@ -239,6 +267,13 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele refreshScrollView.delegate = self NotificationCenter.default.addObserver(self, selector: #selector(refresh), name: NSNotification.Name("refresh"), object: nil) + NotificationCenter.default.publisher(for: .remoteCommandSucceeded) + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.startRemoteCommandPolling() + } + .store(in: &cancellables) + Observable.shared.bgText.$value .receive(on: DispatchQueue.main) .sink { [weak self] newValue in @@ -824,6 +859,7 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele } deinit { + remoteCommandPollingTimer?.invalidate() NotificationCenter.default.removeObserver(self, name: NSNotification.Name("refresh"), object: nil) } @@ -864,6 +900,75 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele refreshControl.endRefreshing() } + private func startRemoteCommandPolling() { + guard IsNightscoutEnabled() else { return } + + remoteCommandPollingBaseline = currentRemoteCommandDataSignature() + remoteCommandPollingStartedAt = Date() + remoteCommandPollingTimer?.invalidate() + + performRemoteCommandPollingTick() + + let timer = Timer.scheduledTimer(withTimeInterval: remoteCommandPollingInterval, repeats: true) { [weak self] _ in + self?.performRemoteCommandPollingTick() + } + timer.tolerance = 0.5 + remoteCommandPollingTimer = timer + + LogManager.shared.log(category: .general, message: "Started aggressive polling after remote command success") + } + + private func stopRemoteCommandPolling(reason: String) { + guard remoteCommandPollingTimer != nil || remoteCommandPollingStartedAt != nil else { return } + + remoteCommandPollingTimer?.invalidate() + remoteCommandPollingTimer = nil + remoteCommandPollingStartedAt = nil + remoteCommandPollingBaseline = nil + + LogManager.shared.log(category: .general, message: "Stopped aggressive polling: \(reason)") + } + + private func performRemoteCommandPollingTick() { + guard let remoteCommandPollingStartedAt else { return } + + if Date().timeIntervalSince(remoteCommandPollingStartedAt) >= remoteCommandPollingDuration { + stopRemoteCommandPolling(reason: "timeout reached") + return + } + + bgTaskAction() + deviceStatusAction() + treatmentsTaskAction() + } + + private func currentRemoteCommandDataSignature() -> RemoteCommandDataSignature { + let latestBolusTimestamp = max(bolusData.last?.date ?? 0, smbData.last?.date ?? 0) + let overrideNote = Observable.shared.override.value ?? "" + let overrideStateKey: String + + if currentOverride != 1.0 || !overrideNote.isEmpty { + overrideStateKey = "\(currentOverride)|\(overrideNote)" + } else { + overrideStateKey = "" + } + + return RemoteCommandDataSignature( + carbTimestamp: carbData.last?.date, + bolusTimestamp: latestBolusTimestamp > 0 ? latestBolusTimestamp : nil, + overrideTimestamp: overrideGraphData.last?.date, + overrideStateKey: overrideStateKey + ) + } + + func evaluateRemoteCommandPollingCompletion() { + guard let remoteCommandPollingBaseline else { return } + + if currentRemoteCommandDataSignature().detectsFreshData(comparedTo: remoteCommandPollingBaseline) { + stopRemoteCommandPolling(reason: "fresh remote data received") + } + } + // Scroll down BGText when refreshing func scrollViewDidScroll(_ scrollView: UIScrollView) { if scrollView == refreshScrollView { From 75d483cc3bcd363efb3d9e520b7d7d294cd739c7 Mon Sep 17 00:00:00 2001 From: codebymini Date: Sun, 29 Mar 2026 21:06:19 +0200 Subject: [PATCH 2/2] Refactor remote command notification handling and enhance logging for command results --- LoopFollow/Application/AppDelegate.swift | 29 ++++++++++++++----- .../Controllers/Nightscout/DeviceStatus.swift | 1 - .../Remote/LoopAPNS/LoopAPNSBolusView.swift | 1 - .../Remote/LoopAPNS/LoopAPNSCarbsView.swift | 1 - .../Remote/LoopAPNS/OverridePresetsView.swift | 2 -- LoopFollow/Remote/TRC/BolusView.swift | 1 - LoopFollow/Remote/TRC/MealView.swift | 1 - LoopFollow/Remote/TRC/OverrideView.swift | 2 -- LoopFollow/Remote/TRC/TempTargetView.swift | 2 -- .../TRC/TrioNightscoutRemoteController.swift | 2 -- .../ViewControllers/MainViewController.swift | 27 ++++++++++++----- 11 files changed, 41 insertions(+), 28 deletions(-) diff --git a/LoopFollow/Application/AppDelegate.swift b/LoopFollow/Application/AppDelegate.swift index 604cf3e9e..b943690c0 100644 --- a/LoopFollow/Application/AppDelegate.swift +++ b/LoopFollow/Application/AppDelegate.swift @@ -11,6 +11,21 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? let notificationCenter = UNUserNotificationCenter.current() + private func logRemoteCommandNotificationDetails(userInfo: [AnyHashable: Any]) -> Bool { + let commandStatus = userInfo["command_status"] as? String + let commandType = userInfo["command_type"] as? String + + if let commandStatus { + LogManager.shared.log(category: .general, message: "Command status: \(commandStatus)") + } + + if let commandType { + LogManager.shared.log(category: .general, message: "Command type: \(commandType)") + } + + return commandStatus != nil || commandType != nil + } + func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { LogManager.shared.log(category: .general, message: "App started") LogManager.shared.cleanupOldLogs() @@ -82,14 +97,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Handle silent notification (content-available) if let contentAvailable = aps["content-available"] as? Int, contentAvailable == 1 { // This is a silent push, nothing implemented but logging for now - - if let commandStatus = userInfo["command_status"] as? String { - LogManager.shared.log(category: .general, message: "Command status: \(commandStatus)") - } - - if let commandType = userInfo["command_type"] as? String { - LogManager.shared.log(category: .general, message: "Command type: \(commandType)") - } + _ = logRemoteCommandNotificationDetails(userInfo: userInfo) } } @@ -199,6 +207,11 @@ extension AppDelegate: UNUserNotificationCenterDelegate { let userInfo = notification.request.content.userInfo LogManager.shared.log(category: .general, message: "Will present notification: \(userInfo)") + if logRemoteCommandNotificationDetails(userInfo: userInfo) { + NotificationCenter.default.post(name: .remoteCommandSucceeded, object: nil) + LogManager.shared.log(category: .general, message: "Started remote command polling from foreground result notification") + } + // Show the notification even when app is in foreground completionHandler([.banner, .sound, .badge]) } diff --git a/LoopFollow/Controllers/Nightscout/DeviceStatus.swift b/LoopFollow/Controllers/Nightscout/DeviceStatus.swift index 42689827c..f8bc8f867 100644 --- a/LoopFollow/Controllers/Nightscout/DeviceStatus.swift +++ b/LoopFollow/Controllers/Nightscout/DeviceStatus.swift @@ -220,7 +220,6 @@ extension MainViewController { ) } else if secondsAgo >= (5 * 60) { - self.evaluateRemoteCommandPollingCompletion() TaskScheduler.shared.rescheduleTask( id: .deviceStatus, to: Date().addingTimeInterval(10) diff --git a/LoopFollow/Remote/LoopAPNS/LoopAPNSBolusView.swift b/LoopFollow/Remote/LoopAPNS/LoopAPNSBolusView.swift index 1f73b82ce..011fe0e10 100644 --- a/LoopFollow/Remote/LoopAPNS/LoopAPNSBolusView.swift +++ b/LoopFollow/Remote/LoopAPNS/LoopAPNSBolusView.swift @@ -373,7 +373,6 @@ struct LoopAPNSBolusView: View { TOTPService.shared.markTOTPAsUsed(qrCodeURL: Storage.shared.loopAPNSQrCodeURL.value) self.alertMessage = "Insulin sent successfully!" self.alertType = .success - NotificationCenter.default.post(name: .remoteCommandSucceeded, object: nil) LogManager.shared.log( category: .apns, message: "Insulin sent - Amount: \(insulinAmount.doubleValue(for: .internationalUnit()))U" diff --git a/LoopFollow/Remote/LoopAPNS/LoopAPNSCarbsView.swift b/LoopFollow/Remote/LoopAPNS/LoopAPNSCarbsView.swift index 1494a40ae..28b5745d8 100644 --- a/LoopFollow/Remote/LoopAPNS/LoopAPNSCarbsView.swift +++ b/LoopFollow/Remote/LoopAPNS/LoopAPNSCarbsView.swift @@ -403,7 +403,6 @@ struct LoopAPNSCarbsView: View { timeFormatter.timeStyle = .short self.alertMessage = "Carbs sent successfully for \(timeFormatter.string(from: adjustedConsumedDate))!" self.alertType = .success - NotificationCenter.default.post(name: .remoteCommandSucceeded, object: nil) LogManager.shared.log( category: .apns, message: "Carbs sent - Amount: \(carbsAmount.doubleValue(for: .gram()))g, Absorption: \(absorptionTimeString)h, Time: \(adjustedConsumedDate)" diff --git a/LoopFollow/Remote/LoopAPNS/OverridePresetsView.swift b/LoopFollow/Remote/LoopAPNS/OverridePresetsView.swift index c09c964a4..ce88f0b90 100644 --- a/LoopFollow/Remote/LoopAPNS/OverridePresetsView.swift +++ b/LoopFollow/Remote/LoopAPNS/OverridePresetsView.swift @@ -407,7 +407,6 @@ class OverridePresetsViewModel: ObservableObject { self.isActivating = false self.statusMessage = "\(preset.name) override activated successfully." self.alertType = .statusSuccess - NotificationCenter.default.post(name: .remoteCommandSucceeded, object: nil) self.showAlert = true } } catch { @@ -431,7 +430,6 @@ class OverridePresetsViewModel: ObservableObject { self.isActivating = false self.statusMessage = "Active override cancelled successfully." self.alertType = .statusSuccess - NotificationCenter.default.post(name: .remoteCommandSucceeded, object: nil) self.showAlert = true } } catch { diff --git a/LoopFollow/Remote/TRC/BolusView.swift b/LoopFollow/Remote/TRC/BolusView.swift index b43af519f..30bfab213 100644 --- a/LoopFollow/Remote/TRC/BolusView.swift +++ b/LoopFollow/Remote/TRC/BolusView.swift @@ -272,7 +272,6 @@ struct BolusView: View { category: .apns, message: "sendBolusPushNotification succeeded - Bolus: \(InsulinFormatter.shared.string(bolusAmount)) U" ) - NotificationCenter.default.post(name: .remoteCommandSucceeded, object: nil) bolusAmount = HKQuantity(unit: .internationalUnit(), doubleValue: 0.0) alertType = .statusSuccess } else { diff --git a/LoopFollow/Remote/TRC/MealView.swift b/LoopFollow/Remote/TRC/MealView.swift index bfe03eab2..55dd704e6 100644 --- a/LoopFollow/Remote/TRC/MealView.swift +++ b/LoopFollow/Remote/TRC/MealView.swift @@ -305,7 +305,6 @@ struct MealView: View { category: .apns, message: "sendMealPushNotification succeeded - Carbs: \(carbs.doubleValue(for: .gram())) g, Protein: \(protein.doubleValue(for: .gram())) g, Fat: \(fat.doubleValue(for: .gram())) g, Bolus: \(bolusAmount.doubleValue(for: .internationalUnit())) U, Scheduled: \(scheduledDate != nil ? formatDate(scheduledDate!) : "now")" ) - NotificationCenter.default.post(name: .remoteCommandSucceeded, object: nil) // Reset meal values and scheduled data after success carbs = HKQuantity(unit: .gram(), doubleValue: 0.0) diff --git a/LoopFollow/Remote/TRC/OverrideView.swift b/LoopFollow/Remote/TRC/OverrideView.swift index 906f3e7d2..3a402f847 100644 --- a/LoopFollow/Remote/TRC/OverrideView.swift +++ b/LoopFollow/Remote/TRC/OverrideView.swift @@ -175,7 +175,6 @@ struct OverrideView: View { self.statusMessage = "Override command sent successfully." self.alertType = .statusSuccess LogManager.shared.log(category: .apns, message: "sendOverridePushNotification succeeded for override: \(override.name)") - NotificationCenter.default.post(name: .remoteCommandSucceeded, object: nil) } else { self.statusMessage = errorMessage ?? "Failed to send override command." self.alertType = .statusFailure @@ -196,7 +195,6 @@ struct OverrideView: View { self.statusMessage = "Cancel override command sent successfully." self.alertType = .statusSuccess LogManager.shared.log(category: .apns, message: "sendCancelOverridePushNotification succeeded") - NotificationCenter.default.post(name: .remoteCommandSucceeded, object: nil) } else { self.statusMessage = errorMessage ?? "Failed to send cancel override command." self.alertType = .statusFailure diff --git a/LoopFollow/Remote/TRC/TempTargetView.swift b/LoopFollow/Remote/TRC/TempTargetView.swift index 4219caf9c..3a047e66f 100644 --- a/LoopFollow/Remote/TRC/TempTargetView.swift +++ b/LoopFollow/Remote/TRC/TempTargetView.swift @@ -255,7 +255,6 @@ struct TempTargetView: View { self.statusMessage = "Temp target command successfully sent." self.alertType = .statusSuccess LogManager.shared.log(category: .apns, message: "sendTempTargetPushNotification succeeded with target: \(newHKTarget), duration: \(duration)") - NotificationCenter.default.post(name: .remoteCommandSucceeded, object: nil) } else { self.statusMessage = errorMessage ?? "Failed to send temp target command." self.alertType = .statusFailure @@ -276,7 +275,6 @@ struct TempTargetView: View { self.statusMessage = "Cancel temp target command successfully sent." self.alertType = .statusSuccess LogManager.shared.log(category: .apns, message: "sendCancelTempTargetPushNotification succeeded") - NotificationCenter.default.post(name: .remoteCommandSucceeded, object: nil) } else { self.statusMessage = errorMessage ?? "Failed to send cancel temp target command." self.alertType = .statusFailure diff --git a/LoopFollow/Remote/TRC/TrioNightscoutRemoteController.swift b/LoopFollow/Remote/TRC/TrioNightscoutRemoteController.swift index e8c8edc13..594619690 100644 --- a/LoopFollow/Remote/TRC/TrioNightscoutRemoteController.swift +++ b/LoopFollow/Remote/TRC/TrioNightscoutRemoteController.swift @@ -19,7 +19,6 @@ class TrioNightscoutRemoteController { let response: [TreatmentCancelResponse] = try await NightscoutUtils.executePostRequest(eventType: .treatments, body: tempTargetBody) Observable.shared.tempTarget.value = nil NotificationCenter.default.post(name: NSNotification.Name("refresh"), object: nil) - NotificationCenter.default.post(name: .remoteCommandSucceeded, object: nil) completion(true) } catch { completion(false) @@ -43,7 +42,6 @@ class TrioNightscoutRemoteController { let response: [TreatmentResponse] = try await NightscoutUtils.executePostRequest(eventType: .treatments, body: tempTargetBody) Observable.shared.tempTarget.value = newTarget NotificationCenter.default.post(name: NSNotification.Name("refresh"), object: nil) - NotificationCenter.default.post(name: .remoteCommandSucceeded, object: nil) completion(true) } catch { completion(false) diff --git a/LoopFollow/ViewControllers/MainViewController.swift b/LoopFollow/ViewControllers/MainViewController.swift index 027fe5c7c..ce8e04342 100644 --- a/LoopFollow/ViewControllers/MainViewController.swift +++ b/LoopFollow/ViewControllers/MainViewController.swift @@ -20,7 +20,9 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele let carbTimestamp: TimeInterval? let bolusTimestamp: TimeInterval? let overrideTimestamp: TimeInterval? + let tempTargetTimestamp: TimeInterval? let overrideStateKey: String + let tempTargetStateKey: String func detectsFreshData(comparedTo baseline: RemoteCommandDataSignature) -> Bool { if let carbTimestamp, carbTimestamp > (baseline.carbTimestamp ?? 0) { @@ -35,7 +37,15 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele return true } - return overrideStateKey != baseline.overrideStateKey + if let tempTargetTimestamp, tempTargetTimestamp > (baseline.tempTargetTimestamp ?? 0) { + return true + } + + if overrideStateKey != baseline.overrideStateKey { + return true + } + + return tempTargetStateKey != baseline.tempTargetStateKey } } @@ -915,7 +925,7 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele timer.tolerance = 0.5 remoteCommandPollingTimer = timer - LogManager.shared.log(category: .general, message: "Started aggressive polling after remote command success") + LogManager.shared.log(category: .general, message: "Started aggressive polling after remote command result notification") } private func stopRemoteCommandPolling(reason: String) { @@ -945,19 +955,22 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele private func currentRemoteCommandDataSignature() -> RemoteCommandDataSignature { let latestBolusTimestamp = max(bolusData.last?.date ?? 0, smbData.last?.date ?? 0) let overrideNote = Observable.shared.override.value ?? "" - let overrideStateKey: String + let overrideStateKey = overrideNote + let tempTargetStateKey: String - if currentOverride != 1.0 || !overrideNote.isEmpty { - overrideStateKey = "\(currentOverride)|\(overrideNote)" + if let tempTarget = Observable.shared.tempTarget.value { + tempTargetStateKey = Localizer.formatQuantity(tempTarget) } else { - overrideStateKey = "" + tempTargetStateKey = "" } return RemoteCommandDataSignature( carbTimestamp: carbData.last?.date, bolusTimestamp: latestBolusTimestamp > 0 ? latestBolusTimestamp : nil, overrideTimestamp: overrideGraphData.last?.date, - overrideStateKey: overrideStateKey + tempTargetTimestamp: tempTargetGraphData.last?.date, + overrideStateKey: overrideStateKey, + tempTargetStateKey: tempTargetStateKey ) }