From cc158e2660e2b307bb527a2e3766801f6a8432c7 Mon Sep 17 00:00:00 2001 From: Pierre L Date: Sun, 15 Oct 2023 15:23:47 +0200 Subject: [PATCH] Fix healthkit issue for Insulin treatment Fix the healthKit sync when stopped before the end #231 Fix the 0U basal in healthKit #149 --- FreeAPS/Sources/APS/APSManager.swift | 5 -- .../Services/HealthKit/HealthKitManager.swift | 47 +++++++++++++++++-- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/FreeAPS/Sources/APS/APSManager.swift b/FreeAPS/Sources/APS/APSManager.swift index e795af9fb..106ce4252 100644 --- a/FreeAPS/Sources/APS/APSManager.swift +++ b/FreeAPS/Sources/APS/APSManager.swift @@ -72,7 +72,6 @@ final class BaseAPSManager: APSManager, Injectable { @Injected() private var nightscout: NightscoutManager! @Injected() private var settingsManager: SettingsManager! @Injected() private var broadcaster: Broadcaster! - @Injected() private var healthKitManager: HealthKitManager! @Persisted(key: "lastAutotuneDate") private var lastAutotuneDate = Date() @Persisted(key: "lastStartLoopDate") private var lastStartLoopDate: Date = .distantPast @Persisted(key: "lastLoopDate") var lastLoopDate: Date = .distantPast { @@ -268,10 +267,6 @@ final class BaseAPSManager: APSManager, Injectable { private func loopCompleted(error: Error? = nil, loopStatRecord: LoopStats) { isLooping.send(false) - // save AH events - let events = pumpHistoryStorage.recent() - healthKitManager.saveIfNeeded(pumpEvents: events) - if let error = error { warning(.apsManager, "Loop failed with error: \(error.localizedDescription)") if let backgroundTask = backGroundTaskID { diff --git a/FreeAPS/Sources/Services/HealthKit/HealthKitManager.swift b/FreeAPS/Sources/Services/HealthKit/HealthKitManager.swift index e098713b5..0b043b018 100644 --- a/FreeAPS/Sources/Services/HealthKit/HealthKitManager.swift +++ b/FreeAPS/Sources/Services/HealthKit/HealthKitManager.swift @@ -31,7 +31,7 @@ protocol HealthKitManager: GlucoseSource { func deleteInsulin(syncID: String) } -final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsObserver { +final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsObserver, PumpHistoryObserver { private enum Config { // unwraped HKObjects static var readPermissions: Set { @@ -68,7 +68,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsObserver { } get { guard let data = persistedBGAnchor else { return nil } - return try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? HKQueryAnchor + return try? NSKeyedUnarchiver.unarchivedObject(ofClass: HKQueryAnchor.self, from: data) } } @@ -115,6 +115,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsObserver { enableBackgroundDelivery() broadcaster.register(CarbsObserver.self, observer: self) + broadcaster.register(PumpHistoryObserver.self, observer: self) debug(.service, "HealthKitManager did create") } @@ -226,6 +227,21 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsObserver { events.isNotEmpty else { return } + func delete(syncIds: [String]?) { + syncIds?.forEach { syncID in + let predicate = HKQuery.predicateForObjects( + withMetadataKey: HKMetadataKeySyncIdentifier, + operatorType: .equalTo, + value: syncID + ) + + self.healthKitStore.deleteObjects(of: sampleType, predicate: predicate) { _, _, error in + guard let error = error else { return } + warning(.service, "Cannot delete sample with syncID: \(syncID)", error: error) + } + } + } + func save(bolus: [InsulinBolus], basal: [InsulinBasal]) { let bolusSamples = bolus .map { @@ -264,6 +280,26 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsObserver { healthKitStore.save(bolusSamples + basalSamples) { _, _ in } } + // delete existing event in HK where the amount is not the last value in the pumphistory + loadSamplesFromHealth(sampleType: sampleType, withIDs: events.map(\.id)) + .receive(on: processQueue) + .compactMap { samples -> [String] in + let sampleIDs = samples.compactMap(\.syncIdentifier) + let bolusToDelete = events + .filter { $0.type == .bolus && sampleIDs.contains($0.id) } + .compactMap { event -> String? in + guard let amount = event.amount else { return nil } + guard let sampleAmount = samples.first(where: { $0.syncIdentifier == event.id }) as? HKQuantitySample + else { return nil } + if Double(amount) != sampleAmount.quantity.doubleValue(for: .internationalUnit()) { + return sampleAmount.syncIdentifier + } else { return nil } + } + return bolusToDelete + } + .sink(receiveValue: delete) + .store(in: &lifetime) + loadSamplesFromHealth(sampleType: sampleType, withIDs: events.map(\.id)) .receive(on: processQueue) .compactMap { samples -> ([InsulinBolus], [InsulinBasal]) in @@ -276,6 +312,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsObserver { } let basalEvents = events .filter { $0.type == .tempBasal && !sampleIDs.contains($0.id) } + .sorted(by: { $0.timestamp < $1.timestamp }) let basal = basalEvents.enumerated() .compactMap { item -> InsulinBasal? in let nextElementEventIndex = item.offset + 1 @@ -300,7 +337,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsObserver { } let id = String(item.element.id.dropFirst()) - guard amountRounded >= 0, + guard amountRounded > 0, id != "" else { return nil } @@ -317,6 +354,10 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsObserver { .store(in: &lifetime) } + func pumpHistoryDidUpdate(_ events: [PumpHistoryEvent]) { + saveIfNeeded(pumpEvents: events) + } + func createBGObserver() { guard settingsManager.settings.useAppleHealth else { return }