Explorar o código

Cleanup and Refactor in Nightscout Manager
* Remove old iAPS functions for preference upload 👋
* Refactor uploading of profiles
* Profiles upload is only triggered when changing the data that is actually uploaded, i.e. basal, cr, isf, targets or dia
* Fix typo in set rate screen for basal

Deniz Cengiz hai 1 ano
pai
achega
0820e7a336

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

@@ -1027,7 +1027,6 @@ final class BaseAPSManager: APSManager, Injectable {
                 )
                 )
             )
             )
             storage.save(dailystat, as: file)
             storage.save(dailystat, as: file)
-            await nightscout.uploadStatistics(dailystat: dailystat)
 
 
             await saveStatsToCoreData()
             await saveStatsToCoreData()
         }
         }

+ 9 - 6
FreeAPS/Sources/APS/Storage/CarbsStorage.swift

@@ -30,25 +30,28 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
 
 
     func storeCarbs(_ entries: [CarbsEntry], areFetchedFromRemote: Bool) async {
     func storeCarbs(_ entries: [CarbsEntry], areFetchedFromRemote: Bool) async {
         var entriesToStore = entries
         var entriesToStore = entries
-        
+
         if areFetchedFromRemote {
         if areFetchedFromRemote {
             let existing24hCarbEntries: [CarbEntryStored] = await CoreDataStack.shared.fetchEntitiesAsync(
             let existing24hCarbEntries: [CarbEntryStored] = await CoreDataStack.shared.fetchEntitiesAsync(
                 ofType: CarbEntryStored.self,
                 ofType: CarbEntryStored.self,
-                onContext: self.coredataContext,
+                onContext: coredataContext,
                 predicate: NSPredicate.predicateForOneDayAgo,
                 predicate: NSPredicate.predicateForOneDayAgo,
                 key: "date",
                 key: "date",
                 ascending: false,
                 ascending: false,
                 batchSize: 50
                 batchSize: 50
             )
             )
-            
+
             if !existing24hCarbEntries.isEmpty {
             if !existing24hCarbEntries.isEmpty {
                 // compare entries createdAt and existing24hCarbEntries date and remove duplicates in entries
                 // compare entries createdAt and existing24hCarbEntries date and remove duplicates in entries
                 // TODO: refactor this when properties-to-fetch has landed to only fetch dates
                 // TODO: refactor this when properties-to-fetch has landed to only fetch dates
-                let existingTimestamps = Set(existing24hCarbEntries.map { $0.date })
-                entriesToStore = entriesToStore.filter { !existingTimestamps.contains($0.actualDate ?? $0.createdAt) || !existingTimestamps.contains($0.createdAt) }
+                let existingTimestamps = Set(existing24hCarbEntries.map(\.date))
+                entriesToStore = entriesToStore
+                    .filter {
+                        !existingTimestamps.contains($0.actualDate ?? $0.createdAt) || !existingTimestamps.contains($0.createdAt)
+                    }
             }
             }
         }
         }
-        
+
         await saveCarbEquivalents(entries: entriesToStore, areFetchedFromRemote: areFetchedFromRemote)
         await saveCarbEquivalents(entries: entriesToStore, areFetchedFromRemote: areFetchedFromRemote)
         await saveCarbsToCoreData(entries: entriesToStore, areFetchedFromRemote: areFetchedFromRemote)
         await saveCarbsToCoreData(entries: entriesToStore, areFetchedFromRemote: areFetchedFromRemote)
     }
     }

+ 7 - 0
FreeAPS/Sources/Modules/BasalProfileEditor/BasalProfileEditorStateModel.swift

@@ -2,6 +2,8 @@ import SwiftUI
 
 
 extension BasalProfileEditor {
 extension BasalProfileEditor {
     final class StateModel: BaseStateModel<Provider> {
     final class StateModel: BaseStateModel<Provider> {
+        @Injected() private var nightscout: NightscoutManager!
+
         @Published var syncInProgress: Bool = false
         @Published var syncInProgress: Bool = false
         @Published var initialItems: [Item] = []
         @Published var initialItems: [Item] = []
         @Published var items: [Item] = []
         @Published var items: [Item] = []
@@ -83,6 +85,11 @@ extension BasalProfileEditor {
                 .sink { _ in
                 .sink { _ in
                     self.syncInProgress = false
                     self.syncInProgress = false
                     self.initialItems = self.items.map { Item(rateIndex: $0.rateIndex, timeIndex: $0.timeIndex) }
                     self.initialItems = self.items.map { Item(rateIndex: $0.rateIndex, timeIndex: $0.timeIndex) }
+
+                    Task.detached(priority: .low) {
+                        debug(.nightscout, "Attempting to upload basal rates to Nightscout")
+                        await self.nightscout.uploadProfiles()
+                    }
                 } receiveValue: {}
                 } receiveValue: {}
                 .store(in: &lifetime)
                 .store(in: &lifetime)
         }
         }

+ 1 - 1
FreeAPS/Sources/Modules/BasalProfileEditor/View/BasalProfileEditorRootView.swift

@@ -115,7 +115,7 @@ extension BasalProfileEditor {
                 }.listRowBackground(Color.chart)
                 }.listRowBackground(Color.chart)
 
 
                 Section {
                 Section {
-                    Picker(selection: $state.items[index].timeIndex, label: Text("Text")) {
+                    Picker(selection: $state.items[index].timeIndex, label: Text("Time")) {
                         ForEach(0 ..< state.timeValues.count, id: \.self) { i in
                         ForEach(0 ..< state.timeValues.count, id: \.self) { i in
                             Text(
                             Text(
                                 self.dateFormatter
                                 self.dateFormatter

+ 5 - 0
FreeAPS/Sources/Modules/CarbRatioEditor/CarbRatioEditorStateModel.swift

@@ -2,6 +2,7 @@ import SwiftUI
 
 
 extension CarbRatioEditor {
 extension CarbRatioEditor {
     final class StateModel: BaseStateModel<Provider> {
     final class StateModel: BaseStateModel<Provider> {
+        @Injected() private var nightscout: NightscoutManager!
         @Published var items: [Item] = []
         @Published var items: [Item] = []
         @Published var initialItems: [Item] = []
         @Published var initialItems: [Item] = []
         @Published var autotune: Autotune?
         @Published var autotune: Autotune?
@@ -71,6 +72,10 @@ extension CarbRatioEditor {
             let profile = CarbRatios(units: .grams, schedule: schedule)
             let profile = CarbRatios(units: .grams, schedule: schedule)
             provider.saveProfile(profile)
             provider.saveProfile(profile)
             initialItems = items.map { Item(rateIndex: $0.rateIndex, timeIndex: $0.timeIndex) }
             initialItems = items.map { Item(rateIndex: $0.rateIndex, timeIndex: $0.timeIndex) }
+            Task.detached(priority: .low) {
+                debug(.nightscout, "Attempting to upload CRs to Nightscout")
+                await self.nightscout.uploadProfiles()
+            }
         }
         }
 
 
         func validate() {
         func validate() {

+ 7 - 0
FreeAPS/Sources/Modules/ISFEditor/ISFEditorStateModel.swift

@@ -4,6 +4,8 @@ import SwiftUI
 extension ISFEditor {
 extension ISFEditor {
     final class StateModel: BaseStateModel<Provider> {
     final class StateModel: BaseStateModel<Provider> {
         @Injected() var determinationStorage: DeterminationStorage!
         @Injected() var determinationStorage: DeterminationStorage!
+        @Injected() private var nightscout: NightscoutManager!
+
         @Published var items: [Item] = []
         @Published var items: [Item] = []
         @Published var initialItems: [Item] = []
         @Published var initialItems: [Item] = []
         @Published var shouldDisplaySaving: Bool = false
         @Published var shouldDisplaySaving: Bool = false
@@ -94,6 +96,11 @@ extension ISFEditor {
             )
             )
             provider.saveProfile(profile)
             provider.saveProfile(profile)
             initialItems = items.map { Item(rateIndex: $0.rateIndex, timeIndex: $0.timeIndex) }
             initialItems = items.map { Item(rateIndex: $0.rateIndex, timeIndex: $0.timeIndex) }
+
+            Task.detached(priority: .low) {
+                debug(.nightscout, "Attempting to upload ISF to Nightscout")
+                await self.nightscout.uploadProfiles()
+            }
         }
         }
 
 
         func validate() {
         func validate() {

+ 7 - 1
FreeAPS/Sources/Modules/PumpSettingsEditor/PumpSettingsEditorStateModel.swift

@@ -3,8 +3,9 @@ import SwiftUI
 
 
 extension PumpSettingsEditor {
 extension PumpSettingsEditor {
     final class StateModel: BaseStateModel<Provider> {
     final class StateModel: BaseStateModel<Provider> {
-        @Published var units: GlucoseUnits = .mgdL
+        @Injected() private var nightscout: NightscoutManager!
 
 
+        @Published var units: GlucoseUnits = .mgdL
         @Published var maxBasal: Decimal = 0.0 {
         @Published var maxBasal: Decimal = 0.0 {
             didSet {
             didSet {
                 checkForChanges()
                 checkForChanges()
@@ -76,6 +77,11 @@ extension PumpSettingsEditor {
                     self.initialDia = settings.insulinActionCurve
                     self.initialDia = settings.insulinActionCurve
 
 
                     self.checkForChanges()
                     self.checkForChanges()
+
+                    Task.detached(priority: .low) {
+                        debug(.nightscout, "Attempting to upload DIA to Nightscout")
+                        await self.nightscout.uploadProfiles()
+                    }
                 } receiveValue: {}
                 } receiveValue: {}
                 .store(in: &lifetime)
                 .store(in: &lifetime)
         }
         }

+ 0 - 5
FreeAPS/Sources/Modules/Settings/SettingsStateModel.swift

@@ -54,11 +54,6 @@ extension Settings {
             return items
             return items
         }
         }
 
 
-        func uploadProfileAndSettings(_ force: Bool) async {
-            NSLog("SettingsState Upload Profile and Settings")
-            await nightscoutManager.uploadProfileAndSettings(force)
-        }
-
         func hideSettingsModal() {
         func hideSettingsModal() {
             hideModal()
             hideModal()
         }
         }

+ 0 - 5
FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift

@@ -326,11 +326,6 @@ extension Settings {
                     }
                     }
                 }
                 }
                 .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always))
                 .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always))
-                .onDisappear(perform: {
-                    Task.detached(priority: .low) {
-                        await state.uploadProfileAndSettings(false)
-                    }
-                })
                 .screenNavigation(self)
                 .screenNavigation(self)
         }
         }
     }
     }

+ 7 - 0
FreeAPS/Sources/Modules/TargetsEditor/TargetsEditorStateModel.swift

@@ -2,6 +2,8 @@ import SwiftUI
 
 
 extension TargetsEditor {
 extension TargetsEditor {
     final class StateModel: BaseStateModel<Provider> {
     final class StateModel: BaseStateModel<Provider> {
+        @Injected() private var nightscout: NightscoutManager!
+
         @Published var items: [Item] = []
         @Published var items: [Item] = []
         @Published var initialItems: [Item] = []
         @Published var initialItems: [Item] = []
         @Published var shouldDisplaySaving: Bool = false
         @Published var shouldDisplaySaving: Bool = false
@@ -76,6 +78,11 @@ extension TargetsEditor {
             let profile = BGTargets(units: .mgdL, userPreferredUnits: .mgdL, targets: targets)
             let profile = BGTargets(units: .mgdL, userPreferredUnits: .mgdL, targets: targets)
             provider.saveProfile(profile)
             provider.saveProfile(profile)
             initialItems = items.map { Item(lowIndex: $0.lowIndex, highIndex: $0.highIndex, timeIndex: $0.timeIndex) }
             initialItems = items.map { Item(lowIndex: $0.lowIndex, highIndex: $0.highIndex, timeIndex: $0.timeIndex) }
+
+            Task.detached(priority: .low) {
+                debug(.nightscout, "Attempting to upload targets to Nightscout")
+                await self.nightscout.uploadProfiles()
+            }
         }
         }
 
 
         func validate() {
         func validate() {

+ 9 - 105
FreeAPS/Sources/Services/Network/NightscoutAPI.swift

@@ -398,31 +398,6 @@ extension NightscoutAPI {
 //        debugPrint("Upload successful, response data: \(String(data: data, encoding: .utf8) ?? "No data")")
 //        debugPrint("Upload successful, response data: \(String(data: data, encoding: .utf8) ?? "No data")")
     }
     }
 
 
-    func uploadStats(_ stats: NightscoutStatistics) async throws {
-        var components = URLComponents()
-        components.scheme = url.scheme
-        components.host = url.host
-        components.port = url.port
-        components.path = Config.statusPath
-
-        var request = URLRequest(url: components.url!)
-        request.allowsConstrainedNetworkAccess = false
-        request.timeoutInterval = Config.timeout
-        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
-
-        if let secret = secret {
-            request.addValue(secret.sha1(), forHTTPHeaderField: "api-secret")
-        }
-        request.httpBody = try JSONCoding.encoder.encode(stats)
-        request.httpMethod = "POST"
-
-        let (_, response) = try await URLSession.shared.data(for: request)
-
-        guard let httpResponse = response as? HTTPURLResponse, (200 ... 299).contains(httpResponse.statusCode) else {
-            throw URLError(.badServerResponse)
-        }
-    }
-
     func uploadStatus(_ status: NightscoutStatus) async throws {
     func uploadStatus(_ status: NightscoutStatus) async throws {
         var components = URLComponents()
         var components = URLComponents()
         components.scheme = url.scheme
         components.scheme = url.scheme
@@ -442,7 +417,7 @@ extension NightscoutAPI {
         do {
         do {
             let encodedBody = try JSONCoding.encoder.encode(status)
             let encodedBody = try JSONCoding.encoder.encode(status)
             request.httpBody = encodedBody
             request.httpBody = encodedBody
-//            debugPrint("Payload glucose size: \(encodedBody.count) bytes")
+//            debugPrint("Payload status size: \(encodedBody.count) bytes")
 //            debugPrint(String(data: encodedBody, encoding: .utf8) ?? "Invalid payload")
 //            debugPrint(String(data: encodedBody, encoding: .utf8) ?? "Invalid payload")
         } catch {
         } catch {
             debugPrint("Error encoding payload: \(error.localizedDescription)")
             debugPrint("Error encoding payload: \(error.localizedDescription)")
@@ -458,58 +433,6 @@ extension NightscoutAPI {
         }
         }
     }
     }
 
 
-    func uploadPrefs(_ prefs: NightscoutPreferences) -> AnyPublisher<Void, Swift.Error> {
-        var components = URLComponents()
-        components.scheme = url.scheme
-        components.host = url.host
-        components.port = url.port
-        components.path = Config.statusPath
-
-        var request = URLRequest(url: components.url!)
-        request.allowsConstrainedNetworkAccess = false
-        request.timeoutInterval = Config.timeout
-        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
-
-        if let secret = secret {
-            request.addValue(secret.sha1(), forHTTPHeaderField: "api-secret")
-        }
-        request.httpBody = try! JSONCoding.encoder.encode(prefs)
-        request.httpMethod = "POST"
-
-        return service.run(request)
-            .retry(Config.retryCount)
-            .map { _ in () }
-            .eraseToAnyPublisher()
-    }
-
-    func uploadSettings(_ settings: NightscoutSettings) async throws {
-        var components = URLComponents()
-        components.scheme = url.scheme
-        components.host = url.host
-        components.port = url.port
-        components.path = Config.statusPath
-
-        guard let url = components.url else {
-            throw URLError(.badURL)
-        }
-
-        var request = URLRequest(url: url)
-        request.allowsConstrainedNetworkAccess = false
-        request.timeoutInterval = Config.timeout
-        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
-
-        if let secret = secret {
-            request.addValue(secret.sha1(), forHTTPHeaderField: "api-secret")
-        }
-        request.httpBody = try JSONCoding.encoder.encode(settings)
-        request.httpMethod = "POST"
-
-        let (_, response) = try await URLSession.shared.data(for: request)
-        if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 200 {
-            throw URLError(.badServerResponse)
-        }
-    }
-
     func uploadProfile(_ profile: NightscoutProfileStore) async throws {
     func uploadProfile(_ profile: NightscoutProfileStore) async throws {
         var components = URLComponents()
         var components = URLComponents()
         components.scheme = url.scheme
         components.scheme = url.scheme
@@ -529,35 +452,16 @@ extension NightscoutAPI {
         if let secret = secret {
         if let secret = secret {
             request.addValue(secret.sha1(), forHTTPHeaderField: "api-secret")
             request.addValue(secret.sha1(), forHTTPHeaderField: "api-secret")
         }
         }
-        request.httpBody = try JSONCoding.encoder.encode(profile)
-        request.httpMethod = "POST"
 
 
-        let (_, response) = try await URLSession.shared.data(for: request)
-        if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 200 {
-            throw URLError(.badServerResponse)
-        }
-    }
-
-    func uploadPreferences(_ preferences: Preferences) async throws {
-        var components = URLComponents()
-        components.scheme = url.scheme
-        components.host = url.host
-        components.port = url.port
-        components.path = Config.profilePath
-
-        guard let url = components.url else {
-            throw URLError(.badURL)
-        }
-
-        var request = URLRequest(url: url)
-        request.allowsConstrainedNetworkAccess = false
-        request.timeoutInterval = Config.timeout
-        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
-
-        if let secret = secret {
-            request.addValue(secret.sha1(), forHTTPHeaderField: "api-secret")
+        do {
+            let encodedBody = try JSONCoding.encoder.encode(profile)
+            request.httpBody = encodedBody
+//            debugPrint("Payload profile upload size: \(encodedBody.count) bytes")
+//            debugPrint(String(data: encodedBody, encoding: .utf8) ?? "Invalid payload")
+        } catch {
+            debugPrint("Error encoding payload: \(error.localizedDescription)")
+            throw error
         }
         }
-        request.httpBody = try JSONCoding.encoder.encode(preferences)
         request.httpMethod = "POST"
         request.httpMethod = "POST"
 
 
         let (_, response) = try await URLSession.shared.data(for: request)
         let (_, response) = try await URLSession.shared.data(for: request)

+ 109 - 190
FreeAPS/Sources/Services/Network/NightscoutManager.swift

@@ -16,9 +16,7 @@ protocol NightscoutManager: GlucoseSource {
     func uploadStatus() async
     func uploadStatus() async
     func uploadGlucose() async
     func uploadGlucose() async
     func uploadManualGlucose() async
     func uploadManualGlucose() async
-    func uploadStatistics(dailystat: Statistics) async
-    func uploadPreferences(_ preferences: Preferences) async
-    func uploadProfileAndSettings(_: Bool) async
+    func uploadProfiles() async
     func importSettings() async -> ScheduledNightscoutProfile?
     func importSettings() async -> ScheduledNightscoutProfile?
     var cgmURL: URL? { get }
     var cgmURL: URL? { get }
 }
 }
@@ -285,53 +283,6 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
         }
         }
     }
     }
 
 
-    func uploadStatistics(dailystat: Statistics) async {
-        let stats = NightscoutStatistics(dailystats: dailystat)
-
-        guard let nightscout = nightscoutAPI, isUploadEnabled else {
-            return
-        }
-
-        do {
-            try await nightscout.uploadStats(stats)
-            debug(.nightscout, "Statistics uploaded")
-        } catch {
-            debug(.nightscout, error.localizedDescription)
-        }
-    }
-
-    func uploadPreferences(_ preferences: Preferences) async {
-        let prefs = NightscoutPreferences(preferences: settingsManager.preferences)
-
-        guard let nightscout = nightscoutAPI, isUploadEnabled else {
-            return
-        }
-
-        do {
-            try await nightscout.uploadPrefs(prefs)
-            debug(.nightscout, "Preferences uploaded")
-            storage.save(preferences, as: OpenAPS.Nightscout.uploadedPreferences)
-        } catch {
-            debug(.nightscout, error.localizedDescription)
-        }
-    }
-
-    func uploadSettings(_ settings: FreeAPSSettings) async {
-        let sets = NightscoutSettings(settings: settingsManager.settings)
-
-        guard let nightscout = nightscoutAPI, isUploadEnabled else {
-            return
-        }
-
-        do {
-            try await nightscout.uploadSettings(sets)
-            debug(.nightscout, "Settings uploaded")
-            storage.save(settings, as: OpenAPS.Nightscout.uploadedSettings)
-        } catch {
-            debug(.nightscout, error.localizedDescription)
-        }
-    }
-
     private func fetchBattery() async -> Battery {
     private func fetchBattery() async -> Battery {
         await backgroundContext.perform {
         await backgroundContext.perform {
             do {
             do {
@@ -513,160 +464,128 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
         }
         }
     }
     }
 
 
-    func uploadProfileAndSettings(_ force: Bool) async {
-        do {
-            guard let sensitivities = await storage.retrieveAsync(
-                OpenAPS.Settings.insulinSensitivities,
-                as: InsulinSensitivities.self
-            ) else {
-                debug(.nightscout, "NightscoutManager uploadProfile: error loading insulinSensitivities")
-                return
-            }
-            guard let settings = await storage.retrieveAsync(OpenAPS.FreeAPS.settings, as: FreeAPSSettings.self) else {
-                debug(.nightscout, "NightscoutManager uploadProfile: error loading settings")
-                return
-            }
-            guard let preferences = await storage.retrieveAsync(OpenAPS.Settings.preferences, as: Preferences.self) else {
-                debug(.nightscout, "NightscoutManager uploadProfile: error loading preferences")
-                return
-            }
-            guard let targets = await storage.retrieveAsync(OpenAPS.Settings.bgTargets, as: BGTargets.self) else {
-                debug(.nightscout, "NightscoutManager uploadProfile: error loading bgTargets")
-                return
-            }
-            guard let carbRatios = await storage.retrieveAsync(OpenAPS.Settings.carbRatios, as: CarbRatios.self) else {
-                debug(.nightscout, "NightscoutManager uploadProfile: error loading carbRatios")
-                return
-            }
-            guard let basalProfile = await storage.retrieveAsync(OpenAPS.Settings.basalProfile, as: [BasalProfileEntry].self)
-            else {
-                debug(.nightscout, "NightscoutManager uploadProfile: error loading basalProfile")
-                return
-            }
-
-            let sens = sensitivities.sensitivities.map { item in
-                NightscoutTimevalue(
-                    time: String(item.start.prefix(5)),
-                    value: item.sensitivity,
-                    timeAsSeconds: item.offset * 60
-                )
-            }
-            let targetLow = targets.targets.map { item in
-                NightscoutTimevalue(
-                    time: String(item.start.prefix(5)),
-                    value: item.low,
-                    timeAsSeconds: item.offset * 60
-                )
-            }
-            let targetHigh = targets.targets.map { item in
-                NightscoutTimevalue(
-                    time: String(item.start.prefix(5)),
-                    value: item.high,
-                    timeAsSeconds: item.offset * 60
-                )
-            }
-            let cr = carbRatios.schedule.map { item in
-                NightscoutTimevalue(
-                    time: String(item.start.prefix(5)),
-                    value: item.ratio,
-                    timeAsSeconds: item.offset * 60
-                )
-            }
-            let basal = basalProfile.map { item in
-                NightscoutTimevalue(
-                    time: String(item.start.prefix(5)),
-                    value: item.rate,
-                    timeAsSeconds: item.minutes * 60
-                )
-            }
-
-            let nsUnits: String = {
-                switch settingsManager.settings.units {
-                case .mgdL:
-                    return "mg/dl"
-                case .mmolL:
-                    return "mmol"
+    func uploadProfiles() async {
+        if isUploadEnabled {
+            do {
+                guard let sensitivities = await storage.retrieveAsync(
+                    OpenAPS.Settings.insulinSensitivities,
+                    as: InsulinSensitivities.self
+                ) else {
+                    debug(.nightscout, "NightscoutManager uploadProfile: error loading insulinSensitivities")
+                    return
                 }
                 }
-            }()
-
-            var carbsHr: Decimal = 0
-            if let isf = sensitivities.sensitivities.map(\.sensitivity).first,
-               let cr = carbRatios.schedule.map(\.ratio).first,
-               isf > 0, cr > 0
-            {
-                carbsHr = settingsManager.preferences.min5mCarbimpact * 12 / isf * cr
-                if settingsManager.settings.units == .mmolL {
-                    carbsHr *= GlucoseUnits.exchangeRate
+                guard let targets = await storage.retrieveAsync(OpenAPS.Settings.bgTargets, as: BGTargets.self) else {
+                    debug(.nightscout, "NightscoutManager uploadProfile: error loading bgTargets")
+                    return
+                }
+                guard let carbRatios = await storage.retrieveAsync(OpenAPS.Settings.carbRatios, as: CarbRatios.self) else {
+                    debug(.nightscout, "NightscoutManager uploadProfile: error loading carbRatios")
+                    return
+                }
+                guard let basalProfile = await storage.retrieveAsync(OpenAPS.Settings.basalProfile, as: [BasalProfileEntry].self)
+                else {
+                    debug(.nightscout, "NightscoutManager uploadProfile: error loading basalProfile")
+                    return
                 }
                 }
-                carbsHr = Decimal(round(Double(carbsHr) * 10.0)) / 10
-            }
 
 
-            let scheduledProfile = ScheduledNightscoutProfile(
-                dia: settingsManager.pumpSettings.insulinActionCurve,
-                carbs_hr: Int(carbsHr),
-                delay: 0,
-                timezone: TimeZone.current.identifier,
-                target_low: targetLow,
-                target_high: targetHigh,
-                sens: sens,
-                basal: basal,
-                carbratio: cr,
-                units: nsUnits
-            )
-            let defaultProfile = "default"
-
-            let now = Date()
-            let profileStore = NightscoutProfileStore(
-                defaultProfile: defaultProfile,
-                startDate: now,
-                mills: Int(now.timeIntervalSince1970) * 1000,
-                units: nsUnits,
-                enteredBy: NightscoutTreatment.local,
-                store: [defaultProfile: scheduledProfile]
-            )
+                let sens = sensitivities.sensitivities.map { item in
+                    NightscoutTimevalue(
+                        time: String(item.start.prefix(5)),
+                        value: item.sensitivity,
+                        timeAsSeconds: item.offset * 60
+                    )
+                }
+                let targetLow = targets.targets.map { item in
+                    NightscoutTimevalue(
+                        time: String(item.start.prefix(5)),
+                        value: item.low,
+                        timeAsSeconds: item.offset * 60
+                    )
+                }
+                let targetHigh = targets.targets.map { item in
+                    NightscoutTimevalue(
+                        time: String(item.start.prefix(5)),
+                        value: item.high,
+                        timeAsSeconds: item.offset * 60
+                    )
+                }
+                let cr = carbRatios.schedule.map { item in
+                    NightscoutTimevalue(
+                        time: String(item.start.prefix(5)),
+                        value: item.ratio,
+                        timeAsSeconds: item.offset * 60
+                    )
+                }
+                let basal = basalProfile.map { item in
+                    NightscoutTimevalue(
+                        time: String(item.start.prefix(5)),
+                        value: item.rate,
+                        timeAsSeconds: item.minutes * 60
+                    )
+                }
 
 
-            guard let nightscout = nightscoutAPI, isNetworkReachable, isUploadEnabled else {
-                return
-            }
+                let nsUnits: String = {
+                    switch settingsManager.settings.units {
+                    case .mgdL:
+                        return "mg/dl"
+                    case .mmolL:
+                        return "mmol"
+                    }
+                }()
+
+                var carbsHr: Decimal = 0
+                if let isf = sensitivities.sensitivities.map(\.sensitivity).first,
+                   let cr = carbRatios.schedule.map(\.ratio).first,
+                   isf > 0, cr > 0
+                {
+                    carbsHr = settingsManager.preferences.min5mCarbimpact * 12 / isf * cr
+                    if settingsManager.settings.units == .mmolL {
+                        carbsHr *= GlucoseUnits.exchangeRate
+                    }
+                    carbsHr = Decimal(round(Double(carbsHr) * 10.0)) / 10
+                }
 
 
-            // Upload Preferences when changed
-            if let uploadedPreferences = await storage.retrieveAsync(
-                OpenAPS.Nightscout.uploadedPreferences,
-                as: Preferences.self
-            ),
-                uploadedPreferences.rawJSON.sorted() == preferences.rawJSON.sorted(), !force
-            {
-                NSLog("NightscoutManager Preferences, preferences unchanged")
-            } else {
-                await uploadPreferences(preferences)
-            }
+                let scheduledProfile = ScheduledNightscoutProfile(
+                    dia: settingsManager.pumpSettings.insulinActionCurve,
+                    carbs_hr: Int(carbsHr),
+                    delay: 0,
+                    timezone: TimeZone.current.identifier,
+                    target_low: targetLow,
+                    target_high: targetHigh,
+                    sens: sens,
+                    basal: basal,
+                    carbratio: cr,
+                    units: nsUnits
+                )
+                let defaultProfile = "default"
+
+                let now = Date()
+                let profileStore = NightscoutProfileStore(
+                    defaultProfile: defaultProfile,
+                    startDate: now,
+                    mills: Int(now.timeIntervalSince1970) * 1000,
+                    units: nsUnits,
+                    enteredBy: NightscoutTreatment.local,
+                    store: [defaultProfile: scheduledProfile]
+                )
 
 
-            // Upload FreeAPS Settings when changed
-            if let uploadedSettings = await storage.retrieveAsync(OpenAPS.Nightscout.uploadedSettings, as: FreeAPSSettings.self),
-               uploadedSettings.rawJSON.sorted() == settings.rawJSON.sorted(), !force
-            {
-                NSLog("NightscoutManager Settings, settings unchanged")
-            } else {
-                await uploadSettings(settings)
-            }
+                guard let nightscout = nightscoutAPI, isNetworkReachable else {
+                    if !isNetworkReachable {
+                        debug(.nightscout, "Network issues; aborting upload")
+                    }
+                    debug(.nightscout, "Nightscout API service not available; aborting upload")
+                    return
+                }
 
 
-            // Upload Profiles when changed
-            if let uploadedProfile = await storage.retrieveAsync(
-                OpenAPS.Nightscout.uploadedProfile,
-                as: NightscoutProfileStore.self
-            ),
-                (uploadedProfile.store["default"]?.rawJSON ?? "").sorted() == scheduledProfile.rawJSON.sorted(), !force
-            {
-                NSLog("NightscoutManager uploadProfile, no profile change")
-            } else {
                 do {
                 do {
                     try await nightscout.uploadProfile(profileStore)
                     try await nightscout.uploadProfile(profileStore)
-                    storage.save(profileStore, as: OpenAPS.Nightscout.uploadedProfile)
                     debug(.nightscout, "Profile uploaded")
                     debug(.nightscout, "Profile uploaded")
                 } catch {
                 } catch {
                     debug(.nightscout, "NightscoutManager uploadProfile: \(error.localizedDescription)")
                     debug(.nightscout, "NightscoutManager uploadProfile: \(error.localizedDescription)")
                 }
                 }
             }
             }
+        } else {
+            debug(.nightscout, "Upload to NS disabled; aborting profile uploaded")
         }
         }
     }
     }
 
 

+ 56 - 2
Trio.xcworkspace/xcshareddata/swiftpm/Package.resolved

@@ -1,5 +1,5 @@
 {
 {
-  "originHash" : "cef813f4bbb01679d4ac9bf4a9f82c1a0a61e44dc839643e81aa92e4d00642bc",
+  "originHash" : "f5c836c216c4ca7d356e3777e58d6d4f9502b03f3974891349eb775f4c4cf750",
   "pins" : [
   "pins" : [
     {
     {
       "identity" : "cryptoswift",
       "identity" : "cryptoswift",
@@ -11,6 +11,15 @@
       }
       }
     },
     },
     {
     {
+      "identity" : "mkringprogressview",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/maxkonovalov/MKRingProgressView.git",
+      "state" : {
+        "branch" : "master",
+        "revision" : "660888aab1d2ab0ed7eb9eb53caec12af4955fa7"
+      }
+    },
+    {
       "identity" : "slidebutton",
       "identity" : "slidebutton",
       "kind" : "remoteSourceControl",
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/no-comment/SlideButton",
       "location" : "https://github.com/no-comment/SlideButton",
@@ -20,15 +29,60 @@
       }
       }
     },
     },
     {
     {
+      "identity" : "swift-algorithms",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/apple/swift-algorithms",
+      "state" : {
+        "revision" : "2327673b0e9c7e90e6b1826376526ec3627210e4",
+        "version" : "0.2.1"
+      }
+    },
+    {
+      "identity" : "swift-numerics",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/apple/swift-numerics",
+      "state" : {
+        "revision" : "6583ac70c326c3ee080c1d42d9ca3361dca816cd",
+        "version" : "0.1.0"
+      }
+    },
+    {
       "identity" : "swiftcharts",
       "identity" : "swiftcharts",
       "kind" : "remoteSourceControl",
       "kind" : "remoteSourceControl",
-      "location" : "https://github.com/ivanschuetz/SwiftCharts.git",
+      "location" : "https://github.com/ivanschuetz/SwiftCharts",
       "state" : {
       "state" : {
         "branch" : "master",
         "branch" : "master",
         "revision" : "c354c1945bb35a1f01b665b22474f6db28cba4a2"
         "revision" : "c354c1945bb35a1f01b665b22474f6db28cba4a2"
       }
       }
     },
     },
     {
     {
+      "identity" : "swiftdate",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/malcommac/SwiftDate",
+      "state" : {
+        "revision" : "6190d0cefff3013e77ed567e6b074f324e5c5bf5",
+        "version" : "6.3.1"
+      }
+    },
+    {
+      "identity" : "swiftmessages",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/SwiftKickMobile/SwiftMessages",
+      "state" : {
+        "revision" : "62e12e138fc3eedf88c7553dd5d98712aa119f40",
+        "version" : "9.0.9"
+      }
+    },
+    {
+      "identity" : "swinject",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/Swinject/Swinject",
+      "state" : {
+        "revision" : "be9dbcc7b86811bc131539a20c6f9c2d3e56919f",
+        "version" : "2.9.1"
+      }
+    },
+    {
       "identity" : "tidepoolkit",
       "identity" : "tidepoolkit",
       "kind" : "remoteSourceControl",
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/tidepool-org/TidepoolKit",
       "location" : "https://github.com/tidepool-org/TidepoolKit",