Explorar o código

Upload NS OpenAPS status from Core Data determination WIP

Deniz Cengiz hai 1 ano
pai
achega
948743f410

+ 1 - 16
FreeAPS/Sources/APS/APSManager.swift

@@ -246,9 +246,6 @@ final class BaseAPSManager: APSManager, Injectable {
 
                 // Open loop completed
                 guard settings.closedLoop else {
-                    Task.detached(priority: .low) {
-                        await self.nightscout.uploadStatus()
-                    }
                     loopStatRecord.end = Date()
                     loopStatRecord.duration = roundDouble((loopStatRecord.end! - loopStatRecord.start).timeInterval / 60, 2)
                     loopStatRecord.loopStatus = "Success"
@@ -256,10 +253,6 @@ final class BaseAPSManager: APSManager, Injectable {
                     return
                 }
 
-                Task.detached(priority: .low) {
-                    await self.nightscout.uploadStatus()
-                }
-
                 // Closed loop - enact Determination
                 try await enactDetermination()
                 loopStatRecord.end = Date()
@@ -580,9 +573,6 @@ final class BaseAPSManager: APSManager, Injectable {
                     } else {
                         debug(.apsManager, "Pump suspended by Announcement")
                         self.announcementsStorage.storeAnnouncements([announcement], enacted: true)
-                        Task.detached(priority: .low) {
-                            await self.nightscout.uploadStatus()
-                        }
                     }
                 }
             case .resume:
@@ -595,9 +585,6 @@ final class BaseAPSManager: APSManager, Injectable {
                     } else {
                         debug(.apsManager, "Pump resumed by Announcement")
                         self.announcementsStorage.storeAnnouncements([announcement], enacted: true)
-                        Task.detached(priority: .low) {
-                            await self.nightscout.uploadStatus()
-                        }
                     }
                 }
             }
@@ -733,6 +720,7 @@ final class BaseAPSManager: APSManager, Injectable {
             if let determinationUpdated = self.privateContext.object(with: determinationID) as? OrefDetermination {
                 determinationUpdated.timestamp = Date()
                 determinationUpdated.enacted = wasEnacted
+                determinationUpdated.isUploadedToNS = false
 
                 do {
                     guard self.privateContext.hasChanges else { return }
@@ -745,9 +733,6 @@ final class BaseAPSManager: APSManager, Injectable {
                 }
 
                 debug(.apsManager, "Determination enacted. Enacted: \(wasEnacted)")
-                Task.detached(priority: .low) {
-                    await self.nightscout.uploadStatus()
-                }
 
                 Task.detached(priority: .low) {
                     await self.statistics()

+ 2 - 1
FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift

@@ -34,7 +34,6 @@ final class OpenAPS {
         await context.perform {
             let newOrefDetermination = OrefDetermination(context: self.context)
             newOrefDetermination.id = UUID()
-
             newOrefDetermination.totalDailyDose = self.decimalToNSDecimalNumber(determination.tdd)
             newOrefDetermination.insulinSensitivity = self.decimalToNSDecimalNumber(determination.isf)
             newOrefDetermination.currentTarget = self.decimalToNSDecimalNumber(determination.current_target)
@@ -61,6 +60,7 @@ final class OpenAPS {
             newOrefDetermination.bolus = determination.insulin?.bolus.map { NSDecimalNumber(decimal: $0) }
             newOrefDetermination.smbToDeliver = determination.units.map { NSDecimalNumber(decimal: $0) }
             newOrefDetermination.carbsRequired = Int16(Int(determination.carbsReq ?? 0))
+            newOrefDetermination.isUploadedToNS = false
 
             if let predictions = determination.predictions {
                 ["iob": predictions.iob, "zt": predictions.zt, "cob": predictions.cob, "uam": predictions.uam]
@@ -260,6 +260,7 @@ final class OpenAPS {
         debug(.openAPS, "Determinated: \(orefDetermination)")
 
         if var determination = Determination(from: orefDetermination) {
+            // TODO: this is so DRASTICALLY wrong… FIX THIS OMFG
             determination.timestamp = determination.deliverAt ?? clock
 
             // save to core data asynchronously

+ 5 - 4
FreeAPS/Sources/APS/Storage/DeterminationStorage.swift

@@ -6,7 +6,7 @@ protocol DeterminationStorage {
     func fetchLastDeterminationObjectID(predicate: NSPredicate) async -> [NSManagedObjectID]
     func getForecastIDs(for determinationID: NSManagedObjectID, in context: NSManagedObjectContext) async -> [NSManagedObjectID]
     func getForecastValueIDs(for forecastID: NSManagedObjectID, in context: NSManagedObjectContext) async -> [NSManagedObjectID]
-    func parseOrefDetermination(_ determinationIds: [NSManagedObjectID]) async -> Determination?
+    func getOrefDeterminationNotYetUploadedToNightscout(_ determinationIds: [NSManagedObjectID]) async -> Determination?
 }
 
 final class BaseDeterminationStorage: DeterminationStorage, Injectable {
@@ -35,12 +35,12 @@ final class BaseDeterminationStorage: DeterminationStorage, Injectable {
         await context.perform {
             do {
                 guard let determination = try context.existingObject(with: determinationID) as? OrefDetermination,
-                      let forecastSet = determination.forecasts
+                      let forecastSet = determination.forecasts as? Set<NSManagedObject>
                 else {
                     return []
                 }
                 let forecasts = Array(forecastSet)
-                return forecasts.map(\.objectID)
+                return forecasts.map(\.objectID) as [NSManagedObjectID]
             } catch {
                 debugPrint(
                     "Failed \(DebuggingIdentifiers.failed) to fetch Forecast IDs for OrefDetermination with ID \(determinationID): \(error.localizedDescription)"
@@ -96,7 +96,7 @@ final class BaseDeterminationStorage: DeterminationStorage, Injectable {
         return forecastValuesList
     }
 
-    func parseOrefDetermination(_ determinationIds: [NSManagedObjectID]) async -> Determination? {
+    func getOrefDeterminationNotYetUploadedToNightscout(_ determinationIds: [NSManagedObjectID]) async -> Determination? {
         var result: Determination?
 
         guard let determinationId = determinationIds.first else {
@@ -129,6 +129,7 @@ final class BaseDeterminationStorage: DeterminationStorage, Injectable {
                     print("Fetched forecast set: \(forecastSet)")
 
                     result = Determination(
+                        id: orefDetermination.id ?? UUID(),
                         reason: orefDetermination.reason ?? "",
                         units: orefDetermination.smbToDeliver as Decimal?,
                         insulinReq: self.decimal(from: orefDetermination.insulinReq),

+ 2 - 0
FreeAPS/Sources/Models/Determination.swift

@@ -1,6 +1,7 @@
 import Foundation
 
 struct Determination: JSON, Equatable {
+    let id: UUID?
     let reason: String
     let units: Decimal?
     let insulinReq: Decimal?
@@ -48,6 +49,7 @@ struct Insulin: JSON, Equatable {
 
 extension Determination {
     private enum CodingKeys: String, CodingKey {
+        case id
         case reason
         case units
         case insulinReq

+ 11 - 1
FreeAPS/Sources/Services/Network/NightscoutAPI.swift

@@ -438,7 +438,17 @@ extension NightscoutAPI {
         if let secret = secret {
             request.addValue(secret.sha1(), forHTTPHeaderField: "api-secret")
         }
-        request.httpBody = try JSONCoding.encoder.encode(status)
+
+        do {
+            let encodedBody = try JSONCoding.encoder.encode(status)
+            request.httpBody = encodedBody
+            debugPrint("Payload glucose size: \(encodedBody.count) bytes")
+            debugPrint(String(data: encodedBody, encoding: .utf8) ?? "Invalid payload")
+        } catch {
+            debugPrint("Error encoding payload: \(error.localizedDescription)")
+            throw error
+        }
+
         request.httpMethod = "POST"
 
         let (data, response) = try await URLSession.shared.data(for: request)

+ 42 - 16
FreeAPS/Sources/Services/Network/NightscoutManager.swift

@@ -335,21 +335,12 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
     }
 
     func uploadStatus() async {
-//        var enacted: Determination?
-//        var suggested: Determination?
-        let enacted = await determinationStorage.parseOrefDetermination(
+        let determination = await determinationStorage.getOrefDeterminationNotYetUploadedToNightscout(
             await determinationStorage
-                .fetchLastDeterminationObjectID(predicate: NSPredicate.enactedDetermination)
+                .fetchLastDeterminationObjectID(predicate: NSPredicate.determinationsNotYetUploadedToNightscout)
         )
 
-        let suggested = await determinationStorage.parseOrefDetermination(
-            await determinationStorage
-                .fetchLastDeterminationObjectID(predicate: NSPredicate(
-                    format: "(enacted == %@ OR enacted == NULL) AND deliverAt >= %@",
-                    false as NSNumber,
-                    Date.halfHourAgo as NSDate
-                ))
-        )
+        let wasDeterminationEnacted = determination?.deliverAt != nil && determination?.timestamp != nil
 
         let iob = storage.retrieve(OpenAPS.Monitor.iob, as: [IOBEntry].self)
 
@@ -360,14 +351,14 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
         if loopIsClosed {
             openapsStatus = OpenAPSStatus(
                 iob: iob?.first,
-                suggested: suggested,
-                enacted: enacted,
+                suggested: wasDeterminationEnacted ? nil : determination,
+                enacted: wasDeterminationEnacted ? determination : nil,
                 version: "0.7.1"
             )
         } else {
             openapsStatus = OpenAPSStatus(
                 iob: iob?.first,
-                suggested: suggested,
+                suggested: determination,
                 enacted: nil,
                 version: "0.7.1"
             )
@@ -398,7 +389,7 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
 
         storage.save(status, as: OpenAPS.Upload.nsStatus)
 
-        guard let nightscout = nightscoutAPI, isUploadEnabled else {
+        guard let determination = determination, let nightscout = nightscoutAPI, isUploadEnabled else {
             return
         }
 
@@ -409,11 +400,40 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
             debug(.nightscout, error.localizedDescription)
         }
 
+        // If successful, update the isUploadedToNS property of the OrefDetermination objects
+        await updateOrefDeterminationAsUploaded([determination])
+
+        debug(.nightscout, "NSDeviceStatus with Determination uploaded")
+
         Task.detached {
             await self.uploadPodAge()
         }
     }
 
+    private func updateOrefDeterminationAsUploaded(_ determination: [Determination]) async {
+        await backgroundContext.perform {
+            let ids = determination.map(\.id) as NSArray
+            print("\(DebuggingIdentifiers.inProgress) ids: \(ids)")
+            let fetchRequest: NSFetchRequest<OrefDetermination> = OrefDetermination.fetchRequest()
+            fetchRequest.predicate = NSPredicate(format: "id IN %@", ids)
+
+            do {
+                let results = try self.backgroundContext.fetch(fetchRequest)
+                print("\(DebuggingIdentifiers.inProgress) results: \(results)")
+                for result in results {
+                    result.isUploadedToNS = true
+                }
+
+                guard self.backgroundContext.hasChanges else { return }
+                try self.backgroundContext.save()
+            } catch let error as NSError {
+                debugPrint(
+                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to update isUploadedToNS: \(error.userInfo)"
+                )
+            }
+        }
+    }
+
     func uploadPodAge() async {
         let uploadedPodAge = storage.retrieve(OpenAPS.Nightscout.uploadedPodAge, as: [NightscoutTreatment].self) ?? []
         if let podAge = storage.retrieve(OpenAPS.Monitor.podAge, as: Date.self),
@@ -926,6 +946,7 @@ extension BaseNightscoutManager {
         let carbUpdates = objects.filter { $0 is CarbEntryStored }
         let pumpHistoryUpdates = objects.filter { $0 is PumpEventStored }
         let overrideUpdates = objects.filter { $0 is OverrideStored || $0 is OverrideRunStored }
+        let determinationUpdates = objects.filter { $0 is OrefDetermination }
 
         if manualGlucoseUpdates.isNotEmpty {
             Task.detached {
@@ -947,5 +968,10 @@ extension BaseNightscoutManager {
                 await self.uploadOverrides()
             }
         }
+        if determinationUpdates.isNotEmpty {
+            Task.detached {
+                await self.uploadStatus()
+            }
+        }
     }
 }

+ 8 - 0
Model/Helper/Determination+helper.swift

@@ -31,4 +31,12 @@ extension NSPredicate {
         let date = Date.oneDayAgo
         return NSPredicate(format: "deliverAt >= %@", date as NSDate)
     }
+
+    static var determinationsNotYetUploadedToNightscout: NSPredicate {
+        NSPredicate(
+            format: "deliverAt >= %@ AND isUploadedToNS == %@",
+            Date.oneDayAgo as NSDate,
+            false as NSNumber
+        )
+    }
 }

+ 1 - 0
Model/TriosPersistentContainer.xcdatamodeld/Core_Data.xcdatamodel/contents

@@ -100,6 +100,7 @@
         <attribute name="insulinReq" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
         <attribute name="insulinSensitivity" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
         <attribute name="iob" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
+        <attribute name="isUploadedToNS" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
         <attribute name="manualBolusErrorString" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
         <attribute name="minDelta" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
         <attribute name="rate" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>

+ 3 - 2
OrefDetermination+CoreDataProperties.swift

@@ -33,11 +33,12 @@ public extension OrefDetermination {
     @NSManaged var smbToDeliver: NSDecimalNumber?
     @NSManaged var temp: String?
     @NSManaged var tempBasal: NSDecimalNumber?
+    @NSManaged var threshold: NSDecimalNumber?
     @NSManaged var timestamp: Date?
     @NSManaged var timestampEnacted: Date?
     @NSManaged var totalDailyDose: NSDecimalNumber?
-    @NSManaged var threshold: NSDecimalNumber?
-    @NSManaged var forecasts: Set<Forecast>?
+    @NSManaged var isUploadedToNS: Bool
+    @NSManaged var forecasts: NSSet?
 }
 
 // MARK: Generated accessors for forecasts