Просмотр исходного кода

Release/0.1.15 (#30)


* JavaScript logs

* fix typo

* Adding Carbs with stale BG's

* Middleware support

* remove microbolusAllowed from middleware

* middleware reason

* Fix missing pumpResume events

* show last autotune date

* bump version
Ivan 5 лет назад
Родитель
Сommit
3191dc8099

+ 1 - 1
FreeAPS/Resources/Config.xcconfig

@@ -1 +1 @@
-BUILD_VERSION = 0.1.14
+BUILD_VERSION = 0.1.15

+ 5 - 0
FreeAPS/Resources/javascript/middleware/determine_basal.js

@@ -0,0 +1,5 @@
+function middleware(iob, currenttemp, glucose, profile, autosens, meal, reservoir, clock) {
+    // modify anything
+    // return any reason what has changed.
+    return "Nothing changed";
+}

+ 0 - 2
FreeAPS/Resources/javascript/prepare/autosens.js

@@ -1,6 +1,4 @@
 // для settings/autosens.json параметры: monitor/glucose.json monitor/pumphistory-24h-zoned.json settings/basal_profile.json settings/profile.json monitor/carbhistory.json settings/temptargets.json
-var printLog = function(...args) {};
-var process = { stderr: { write: printLog } };
 
 function generate(glucose_data, pumphistory_data, basalprofile, profile_data, carb_data = {}, temptarget_data = {}) {
     if (glucose_data.length < 72) {

+ 17 - 12
FreeAPS/Resources/javascript/prepare/determine-basal.js

@@ -1,25 +1,30 @@
 //для enact/smb-suggested.json параметры: monitor/iob.json monitor/temp_basal.json monitor/glucose.json settings/profile.json settings/autosens.json --meal monitor/meal.json --microbolus --reservoir monitor/reservoir.json
-var printLog = function(...args) {};
-var process = { stderr: { write: printLog } };
 
+function generate(iob, currenttemp, glucose, profile, autosens = null, meal = null, microbolusAllowed = false, reservoir = null, clock = new Date()) {
 
-function generate(iob_data, currenttemp, glucose_data, profile, autosens_input = false, meal_input = false, microbolus = false, reservoir_input = false, clock = new Date()){
-    var glucose_status = freeaps_glucoseGetLast(glucose_data);
+    try {
+        var middlewareReason = middleware(iob, currenttemp, glucose, profile, autosens, meal, reservoir, clock);
+        console.log("Middleware reason: " + (middlewareReason || "Nothing changed"));
+    } catch (error) {
+        console.log("Invalid middleware: " + error);
+    }
+
+    var glucose_status = freeaps_glucoseGetLast(glucose);
     var autosens_data = null;
 
-    if (autosens_input) {
-        autosens_data = autosens_input;
+    if (autosens) {
+        autosens_data = autosens;
     }
 
     var reservoir_data = null;
-    if (reservoir_input) {
-        reservoir_data = reservoir_input;
+    if (reservoir) {
+        reservoir_data = reservoir;
     }
 
-    var meal_data = { };
-    if (meal_input) {
-        meal_data = meal_input;
+    var meal_data = {};
+    if (meal) {
+        meal_data = meal;
     }
 
-    return freeaps_determineBasal(glucose_status, currenttemp, iob_data, profile, autosens_data, meal_data, freeaps_basalSetTemp, microbolus, reservoir_data, clock);
+    return freeaps_determineBasal(glucose_status, currenttemp, iob, profile, autosens_data, meal_data, freeaps_basalSetTemp, microbolusAllowed, reservoir_data, clock);
 }

+ 1 - 0
FreeAPS/Resources/javascript/prepare/iob.js

@@ -1,4 +1,5 @@
 //для monitor/iob.json параметры: monitor/pumphistory-24h-zoned.json settings/profile.json monitor/clock-zoned.json settings/autosens.json
+
 function generate(pumphistory_data, profile_data, clock_data, autosens_data = null){
     var inputs = {
         history: pumphistory_data

+ 6 - 0
FreeAPS/Resources/javascript/prepare/log.js

@@ -0,0 +1,6 @@
+var console = {
+    log: function(...args) { _consoleLog(args); },
+    error: function(...args) { _consoleLog(args); }
+};
+var printLog = function(...args) { console.log(args); };
+var process = { stderr: { write: printLog } };

+ 1 - 0
FreeAPS/Resources/javascript/prepare/meal.js

@@ -1,4 +1,5 @@
 //для monitor/meal.json параметры: monitor/pumphistory-24h-zoned.json settings/profile.json monitor/clock-zoned.json monitor/glucose.json settings/basal_profile.json monitor/carbhistory.json
+
 function generate(pumphistory_data, profile_data, clock_data, glucose_data, basalprofile_data, carbhistory = false){
     if ( typeof(profile_data.carb_ratio) === 'undefined' || profile_data.carb_ratio < 3 ) {
         return {"error":"Error: carb_ratio " + profile_data.carb_ratio + " out of bounds"};

+ 1 - 0
FreeAPS/Resources/javascript/prepare/profile.js

@@ -1,5 +1,6 @@
 //для pumpprofile.json параметры: settings/settings.json settings/bg_targets.json settings/insulin_sensitivities.json settings/basal_profile.json preferences.json settings/carb_ratios.json settings/temptargets.json settings/model.json
 //для profile.json параметры: settings/settings.json settings/bg_targets.json settings/insulin_sensitivities.json settings/basal_profile.json preferences.json settings/carb_ratios.json settings/temptargets.json settings/model.json settings/autotune.json
+
 function generate(pumpsettings_data, bgtargets_data, isf_data, basalprofile_data, preferences_input = false, carbratio_input = false, temptargets_input = false, model_input = false, autotune_input = false){
     if (bgtargets_data.units !== 'mg/dL') {
         if (bgtargets_data.units === 'mmol/L') {

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

@@ -60,7 +60,7 @@ final class BaseAPSManager: APSManager, Injectable {
     @Injected() private var nightscout: NightscoutManager!
     @Injected() private var settingsManager: SettingsManager!
     @Injected() private var broadcaster: Broadcaster!
-    @Persisted(key: "lastAutotuneDate") private var lastAutotuneDate: Date = .distantPast
+    @Persisted(key: "lastAutotuneDate") private var lastAutotuneDate = Date()
     @Persisted(key: "lastLoopDate") var lastLoopDate: Date = .distantPast {
         didSet {
             lastLoopDateSubject.send(lastLoopDate)

+ 21 - 16
FreeAPS/Sources/APS/OpenAPS/Constants.swift

@@ -1,24 +1,29 @@
 extension OpenAPS {
     enum Bundle {
-        static let iob = "bundle/iob"
-        static let meal = "bundle/meal"
-        static let autotunePrep = "bundle/autotune-prep"
-        static let autotuneCore = "bundle/autotune-core"
-        static let getLastGlucose = "bundle/glucose-get-last"
-        static let basalSetTemp = "bundle/basal-set-temp"
-        static let determineBasal = "bundle/determine-basal"
-        static let autosens = "bundle/autosens"
-        static let profile = "bundle/profile"
+        static let iob = "bundle/iob.js"
+        static let meal = "bundle/meal.js"
+        static let autotunePrep = "bundle/autotune-prep.js"
+        static let autotuneCore = "bundle/autotune-core.js"
+        static let getLastGlucose = "bundle/glucose-get-last.js"
+        static let basalSetTemp = "bundle/basal-set-temp.js"
+        static let determineBasal = "bundle/determine-basal.js"
+        static let autosens = "bundle/autosens.js"
+        static let profile = "bundle/profile.js"
     }
 
     enum Prepare {
-        static let iob = "prepare/iob"
-        static let meal = "prepare/meal"
-        static let autotunePrep = "prepare/autotune-prep"
-        static let autotuneCore = "prepare/autotune-core"
-        static let determineBasal = "prepare/determine-basal"
-        static let autosens = "prepare/autosens"
-        static let profile = "prepare/profile"
+        static let iob = "prepare/iob.js"
+        static let meal = "prepare/meal.js"
+        static let autotunePrep = "prepare/autotune-prep.js"
+        static let autotuneCore = "prepare/autotune-core.js"
+        static let determineBasal = "prepare/determine-basal.js"
+        static let autosens = "prepare/autosens.js"
+        static let profile = "prepare/profile.js"
+        static let log = "prepare/log.js"
+    }
+
+    enum Middleware {
+        static let determineBasal = "middleware/determine_basal.js"
     }
 
     enum Settings {

+ 8 - 0
FreeAPS/Sources/APS/OpenAPS/JavaScriptWorker.swift

@@ -19,6 +19,14 @@ final class JavaScriptWorker {
                 warning(.openAPS, "JavaScript Error: \(error)")
             }
         }
+        let consoleLog: @convention(block) (String) -> Void = { message in
+            debug(.openAPS, "JavaScript log: \(message)")
+        }
+
+        context.setObject(
+            consoleLog,
+            forKeyedSubscript: "_consoleLog" as NSString
+        )
         return context
     }
 

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

@@ -15,6 +15,7 @@ final class OpenAPS {
     func determineBasal(currentTemp: TempBasal, clock: Date = Date()) -> Future<Suggestion?, Never> {
         Future { promise in
             self.processQueue.async {
+                debug(.openAPS, "Start determineBasal")
                 // clock
                 self.storage.save(clock, as: Monitor.clock)
 
@@ -80,6 +81,7 @@ final class OpenAPS {
     func autosense() -> Future<Autosens?, Never> {
         Future { promise in
             self.processQueue.async {
+                debug(.openAPS, "Start autosens")
                 let pumpHistory = self.loadFileFromStorage(name: OpenAPS.Monitor.pumpHistory)
                 let carbs = self.loadFileFromStorage(name: Monitor.carbHistory)
                 let glucose = self.loadFileFromStorage(name: Monitor.glucose)
@@ -110,6 +112,7 @@ final class OpenAPS {
     func autotune(categorizeUamAsBasal: Bool = false, tuneInsulinCurve: Bool = false) -> Future<Autotune?, Never> {
         Future { promise in
             self.processQueue.async {
+                debug(.openAPS, "Start autotune")
                 let pumpHistory = self.loadFileFromStorage(name: OpenAPS.Monitor.pumpHistory)
                 let glucose = self.loadFileFromStorage(name: Monitor.glucose)
                 let profile = self.loadFileFromStorage(name: Settings.profile)
@@ -146,6 +149,7 @@ final class OpenAPS {
 
     func makeProfiles(useAutotune: Bool) -> Future<Autotune?, Never> {
         Future { promise in
+            debug(.openAPS, "Start makeProfiles")
             self.processQueue.async {
                 var preferences = self.loadFileFromStorage(name: Settings.preferences)
                 if preferences.isEmpty {
@@ -202,6 +206,7 @@ final class OpenAPS {
     private func iob(pumphistory: JSON, profile: JSON, clock: JSON, autosens: JSON) -> RawJSON {
         dispatchPrecondition(condition: .onQueue(processQueue))
         return jsWorker.inCommonContext { worker in
+            worker.evaluate(script: Script(name: Prepare.log))
             worker.evaluate(script: Script(name: Bundle.iob))
             worker.evaluate(script: Script(name: Prepare.iob))
             return worker.call(function: Function.generate, with: [
@@ -216,6 +221,7 @@ final class OpenAPS {
     private func meal(pumphistory: JSON, profile: JSON, basalProfile: JSON, clock: JSON, carbs: JSON, glucose: JSON) -> RawJSON {
         dispatchPrecondition(condition: .onQueue(processQueue))
         return jsWorker.inCommonContext { worker in
+            worker.evaluate(script: Script(name: Prepare.log))
             worker.evaluate(script: Script(name: Bundle.meal))
             worker.evaluate(script: Script(name: Prepare.meal))
             return worker.call(function: Function.generate, with: [
@@ -239,6 +245,7 @@ final class OpenAPS {
     ) -> RawJSON {
         dispatchPrecondition(condition: .onQueue(processQueue))
         return jsWorker.inCommonContext { worker in
+            worker.evaluate(script: Script(name: Prepare.log))
             worker.evaluate(script: Script(name: Bundle.autotunePrep))
             worker.evaluate(script: Script(name: Prepare.autotunePrep))
             return worker.call(function: Function.generate, with: [
@@ -259,6 +266,7 @@ final class OpenAPS {
     ) -> RawJSON {
         dispatchPrecondition(condition: .onQueue(processQueue))
         return jsWorker.inCommonContext { worker in
+            worker.evaluate(script: Script(name: Prepare.log))
             worker.evaluate(script: Script(name: Bundle.autotuneCore))
             worker.evaluate(script: Script(name: Prepare.autotuneCore))
             return worker.call(function: Function.generate, with: [
@@ -281,10 +289,16 @@ final class OpenAPS {
     ) -> RawJSON {
         dispatchPrecondition(condition: .onQueue(processQueue))
         return jsWorker.inCommonContext { worker in
+            worker.evaluate(script: Script(name: Prepare.log))
+            worker.evaluate(script: Script(name: Prepare.determineBasal))
             worker.evaluate(script: Script(name: Bundle.basalSetTemp))
             worker.evaluate(script: Script(name: Bundle.getLastGlucose))
             worker.evaluate(script: Script(name: Bundle.determineBasal))
-            worker.evaluate(script: Script(name: Prepare.determineBasal))
+
+            if let middleware = self.middlewareScript(name: OpenAPS.Middleware.determineBasal) {
+                worker.evaluate(script: middleware)
+            }
+
             return worker.call(
                 function: Function.generate,
                 with: [
@@ -311,6 +325,7 @@ final class OpenAPS {
     ) -> RawJSON {
         dispatchPrecondition(condition: .onQueue(processQueue))
         return jsWorker.inCommonContext { worker in
+            worker.evaluate(script: Script(name: Prepare.log))
             worker.evaluate(script: Script(name: Bundle.autosens))
             worker.evaluate(script: Script(name: Prepare.autosens))
             return worker.call(
@@ -330,6 +345,7 @@ final class OpenAPS {
     private func exportDefaultPreferences() -> RawJSON {
         dispatchPrecondition(condition: .onQueue(processQueue))
         return jsWorker.inCommonContext { worker in
+            worker.evaluate(script: Script(name: Prepare.log))
             worker.evaluate(script: Script(name: Bundle.profile))
             worker.evaluate(script: Script(name: Prepare.profile))
             return worker.call(function: Function.exportDefaults, with: [])
@@ -349,6 +365,7 @@ final class OpenAPS {
     ) -> RawJSON {
         dispatchPrecondition(condition: .onQueue(processQueue))
         return jsWorker.inCommonContext { worker in
+            worker.evaluate(script: Script(name: Prepare.log))
             worker.evaluate(script: Script(name: Bundle.profile))
             worker.evaluate(script: Script(name: Prepare.profile))
             return worker.call(
@@ -376,8 +393,21 @@ final class OpenAPS {
         storage.retrieveRaw(name) ?? OpenAPS.defaults(for: name)
     }
 
+    private func middlewareScript(name: String) -> Script? {
+        if let body = storage.retrieveRaw(name) {
+            return Script(name: "Middleware", body: body)
+        }
+
+        if let url = Foundation.Bundle.main.url(forResource: "js/\(name)", withExtension: "") {
+            return Script(name: "Middleware", body: try! String(contentsOf: url))
+        }
+
+        return nil
+    }
+
     static func defaults(for file: String) -> RawJSON {
-        guard let url = Foundation.Bundle.main.url(forResource: "json/defaults/\(file)", withExtension: "") else {
+        let prefix = file.hasSuffix(".json") ? "json/defaults" : "javascript"
+        guard let url = Foundation.Bundle.main.url(forResource: "\(prefix)/\(file)", withExtension: "") else {
             return ""
         }
         return (try? String(contentsOf: url)) ?? ""

+ 6 - 1
FreeAPS/Sources/APS/OpenAPS/Script.swift

@@ -6,6 +6,11 @@ struct Script {
 
     init(name: String) {
         self.name = name
-        body = try! String(contentsOf: Bundle.main.url(forResource: "javascript/\(name)", withExtension: "js")!)
+        body = try! String(contentsOf: Bundle.main.url(forResource: "javascript/\(name)", withExtension: "")!)
+    }
+
+    init(name: String, body: String) {
+        self.name = name
+        self.body = body
     }
 }

+ 10 - 1
FreeAPS/Sources/Modules/AutotuneConfig/AutotuneConfigViewModel.swift

@@ -8,11 +8,18 @@ extension AutotuneConfig {
         @Published var useAutotune = false
         @Published var autotune: Autotune?
         private(set) var units: GlucoseUnits = .mmolL
+        @Published var publishedDate = Date()
+        @Persisted(key: "lastAutotuneDate") private var lastAutotuneDate = Date() {
+            didSet {
+                publishedDate = lastAutotuneDate
+            }
+        }
 
         override func subscribe() {
             autotune = provider.autotune
             units = settingsManager.settings.units
             useAutotune = settingsManager.settings.useAutotune
+            publishedDate = lastAutotuneDate
 
             $useAutotune
                 .removeDuplicates()
@@ -31,7 +38,9 @@ extension AutotuneConfig {
                     self.autotune = result
                     return self.apsManager.makeProfiles()
                 }
-                .sink { _ in }.store(in: &lifetime)
+                .sink { _ in
+                    self.lastAutotuneDate = Date()
+                }.store(in: &lifetime)
         }
 
         func delete() {

+ 12 - 0
FreeAPS/Sources/Modules/AutotuneConfig/View/AutotuneConfigRootView.swift

@@ -18,6 +18,13 @@ extension AutotuneConfig {
             return formatter
         }
 
+        private var dateFormatter: DateFormatter {
+            let formatter = DateFormatter()
+            formatter.dateStyle = .medium
+            formatter.timeStyle = .short
+            return formatter
+        }
+
         var body: some View {
             Form {
                 Section {
@@ -25,6 +32,11 @@ extension AutotuneConfig {
                 }
 
                 Section {
+                    HStack {
+                        Text("Last run")
+                        Spacer()
+                        Text(dateFormatter.string(from: viewModel.publishedDate))
+                    }
                     Button { viewModel.run() }
                     label: { Text("Run now") }
                 }

+ 9 - 1
FreeAPS/Sources/Modules/Bolus/BolusViewModel.swift

@@ -29,7 +29,15 @@ extension Bolus {
             broadcaster.register(SuggestionObserver.self, observer: self)
 
             if waitForSuggestionInitial {
-                apsManager.determineBasal().sink { _ in }.store(in: &lifetime)
+                apsManager.determineBasal()
+                    .receive(on: DispatchQueue.main)
+                    .sink { ok in
+                        if !ok {
+                            self.waitForSuggestion = false
+                            self.inslinRequired = 0
+                            self.inslinRecommended = 0
+                        }
+                    }.store(in: &lifetime)
             }
         }
 

+ 5 - 0
FreeAPS/Sources/Modules/ConfigEditor/ConfigEditorProvider.swift

@@ -11,6 +11,11 @@ extension ConfigEditor {
         }
 
         func save(_ value: RawJSON, as file: String) {
+            if file.hasSuffix(".js") {
+                storage.save(value, as: file)
+                return
+            }
+
             guard let data = value.data(using: .utf8), (try? JSONSerialization.jsonObject(with: data, options: [])) != nil else {
                 warning(.service, "Invalid JSON")
                 return

+ 5 - 1
FreeAPS/Sources/Modules/Home/HomeViewModel.swift

@@ -182,7 +182,11 @@ extension Home {
                 self.suspensions = self.provider.pumpHistory(hours: self.filteredHours).filter {
                     $0.type == .pumpSuspend || $0.type == .pumpResume
                 }
-                self.pumpSuspended = self.suspensions.last?.type == .pumpSuspend
+
+                let last = self.suspensions.last
+                let tbr = self.tempBasals.first { $0.timestamp > (last?.timestamp ?? .distantPast) }
+
+                self.pumpSuspended = tbr == nil && last?.type == .pumpSuspend
             }
         }
 

+ 8 - 3
FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift

@@ -64,7 +64,7 @@ struct MainChartView: View {
 
     private let calculationQueue = DispatchQueue(label: "MainChartView.calculationQueue")
 
-    private var dateDormatter: DateFormatter {
+    private var dateFormatter: DateFormatter {
         let formatter = DateFormatter()
         formatter.timeStyle = .short
         return formatter
@@ -230,7 +230,7 @@ struct MainChartView: View {
         ZStack {
             // X time labels
             ForEach(0 ..< hours + hours) { hour in
-                Text(dateDormatter.string(from: firstHourDate().addingTimeInterval(hour.hours.timeInterval)))
+                Text(dateFormatter.string(from: firstHourDate().addingTimeInterval(hour.hours.timeInterval)))
                     .font(.caption)
                     .position(
                         x: firstHourPosition(viewWidth: fullSize.width) +
@@ -546,8 +546,13 @@ extension MainChartView {
 
             let lastRec = self.suspensions.last.flatMap { event -> CGRect? in
                 guard event.type == .pumpSuspend else { return nil }
+                let tbrTimeX = self.tempBasals.first { $0.timestamp > event.timestamp }
+                    .map { self.timeToXCoordinate($0.timestamp.timeIntervalSince1970, fullSize: fullSize) }
                 let x0 = self.timeToXCoordinate(event.timestamp.timeIntervalSince1970, fullSize: fullSize)
-                let x1 = self.fullGlucoseWidth(viewWidth: fullSize.width) + self.additionalWidth(viewWidth: fullSize.width)
+
+                let x1 = tbrTimeX ?? self.fullGlucoseWidth(viewWidth: fullSize.width) + self
+                    .additionalWidth(viewWidth: fullSize.width)
+
                 return CGRect(x: x0, y: 0, width: x1 - x0, height: Config.basalHeight)
             }
             rects.append(firstRec)

+ 6 - 0
FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorViewModel.swift

@@ -80,6 +80,12 @@ extension PreferencesEditor {
                     settable: self
                 ),
                 Field(
+                    displayName: "Advanced Target Adjustments",
+                    keypath: \.advTargetAdjustments,
+                    value: preferences.advTargetAdjustments,
+                    settable: self
+                ),
+                Field(
                     displayName: "Exercise Mode",
                     keypath: \.exerciseMode,
                     value: preferences.exerciseMode,

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

@@ -80,6 +80,8 @@ extension Settings {
                         Group {
                             Text("Target presets").chevronCell()
                                 .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.tempTargetsPresets), from: self)
+                            Text("Middleware").chevronCell()
+                                .navigationLink(to: .configEditor(file: OpenAPS.Middleware.determineBasal), from: self)
                         }
                     }
                 }