Ivan Valkou преди 5 години
родител
ревизия
e1892ef513

+ 45 - 1
FreeAPS.xcodeproj/project.pbxproj

@@ -7,6 +7,8 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		384E803425C385E60086DB71 /* JavaScriptWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 384E803325C385E60086DB71 /* JavaScriptWorker.swift */; };
+		384E803825C388640086DB71 /* Script.swift in Sources */ = {isa = PBXBuildFile; fileRef = 384E803725C388640086DB71 /* Script.swift */; };
 		388E595C25AD948C0019842D /* FreeAPSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 388E595B25AD948C0019842D /* FreeAPSApp.swift */; };
 		388E595E25AD948C0019842D /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 388E595D25AD948C0019842D /* ContentView.swift */; };
 		388E596025AD948E0019842D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 388E595F25AD948E0019842D /* Assets.xcassets */; };
@@ -14,9 +16,14 @@
 		388E596C25AD95110019842D /* OpenAPS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 388E596B25AD95110019842D /* OpenAPS.swift */; };
 		388E596F25AD96040019842D /* javascript in Resources */ = {isa = PBXBuildFile; fileRef = 388E596E25AD96040019842D /* javascript */; };
 		388E597225AD9CF10019842D /* json in Resources */ = {isa = PBXBuildFile; fileRef = 388E597125AD9CF10019842D /* json */; };
+		388E5A5C25B6F0770019842D /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 388E5A5B25B6F0770019842D /* JSON.swift */; };
+		388E5A6025B6F2310019842D /* Autosens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 388E5A5F25B6F2310019842D /* Autosens.swift */; };
+		3895E4C625B9E00D00214B37 /* Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3895E4C525B9E00D00214B37 /* Profile.swift */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXFileReference section */
+		384E803325C385E60086DB71 /* JavaScriptWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JavaScriptWorker.swift; sourceTree = "<group>"; };
+		384E803725C388640086DB71 /* Script.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Script.swift; sourceTree = "<group>"; };
 		388E595825AD948C0019842D /* FreeAPS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FreeAPS.app; sourceTree = BUILT_PRODUCTS_DIR; };
 		388E595B25AD948C0019842D /* FreeAPSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FreeAPSApp.swift; sourceTree = "<group>"; };
 		388E595D25AD948C0019842D /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
@@ -26,6 +33,9 @@
 		388E596B25AD95110019842D /* OpenAPS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenAPS.swift; sourceTree = "<group>"; };
 		388E596E25AD96040019842D /* javascript */ = {isa = PBXFileReference; lastKnownFileType = folder; path = javascript; sourceTree = "<group>"; };
 		388E597125AD9CF10019842D /* json */ = {isa = PBXFileReference; lastKnownFileType = folder; path = json; sourceTree = "<group>"; };
+		388E5A5B25B6F0770019842D /* JSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = "<group>"; };
+		388E5A5F25B6F2310019842D /* Autosens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Autosens.swift; sourceTree = "<group>"; };
+		3895E4C525B9E00D00214B37 /* Profile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Profile.swift; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -60,12 +70,14 @@
 		388E595A25AD948C0019842D /* FreeAPS */ = {
 			isa = PBXGroup;
 			children = (
+				388E5A5A25B6F05F0019842D /* Helpers */,
+				388E5A5925B6F0250019842D /* Models */,
+				388E5A5825B6F0070019842D /* OpenAPS */,
 				388E595B25AD948C0019842D /* FreeAPSApp.swift */,
 				388E595D25AD948C0019842D /* ContentView.swift */,
 				388E595F25AD948E0019842D /* Assets.xcassets */,
 				388E596425AD948E0019842D /* Info.plist */,
 				388E596125AD948E0019842D /* Preview Content */,
-				388E596B25AD95110019842D /* OpenAPS.swift */,
 			);
 			path = FreeAPS;
 			sourceTree = "<group>";
@@ -78,6 +90,33 @@
 			path = "Preview Content";
 			sourceTree = "<group>";
 		};
+		388E5A5825B6F0070019842D /* OpenAPS */ = {
+			isa = PBXGroup;
+			children = (
+				388E596B25AD95110019842D /* OpenAPS.swift */,
+				384E803325C385E60086DB71 /* JavaScriptWorker.swift */,
+				384E803725C388640086DB71 /* Script.swift */,
+			);
+			path = OpenAPS;
+			sourceTree = "<group>";
+		};
+		388E5A5925B6F0250019842D /* Models */ = {
+			isa = PBXGroup;
+			children = (
+				388E5A5F25B6F2310019842D /* Autosens.swift */,
+				3895E4C525B9E00D00214B37 /* Profile.swift */,
+			);
+			path = Models;
+			sourceTree = "<group>";
+		};
+		388E5A5A25B6F05F0019842D /* Helpers */ = {
+			isa = PBXGroup;
+			children = (
+				388E5A5B25B6F0770019842D /* JSON.swift */,
+			);
+			path = Helpers;
+			sourceTree = "<group>";
+		};
 /* End PBXGroup section */
 
 /* Begin PBXNativeTarget section */
@@ -149,9 +188,14 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				388E5A6025B6F2310019842D /* Autosens.swift in Sources */,
+				3895E4C625B9E00D00214B37 /* Profile.swift in Sources */,
 				388E596C25AD95110019842D /* OpenAPS.swift in Sources */,
 				388E595E25AD948C0019842D /* ContentView.swift in Sources */,
+				384E803825C388640086DB71 /* Script.swift in Sources */,
 				388E595C25AD948C0019842D /* FreeAPSApp.swift in Sources */,
+				388E5A5C25B6F0770019842D /* JSON.swift in Sources */,
+				384E803425C385E60086DB71 /* JavaScriptWorker.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};

+ 28 - 0
FreeAPS/Helpers/JSON.swift

@@ -0,0 +1,28 @@
+//
+//  JSON.swift
+//  FreeAPS
+//
+//  Created by Ivan Valkou on 19.01.2021.
+//
+
+import Foundation
+
+
+protocol JSON: Codable {
+    func toString() -> String
+    init?(from: String)
+}
+
+extension JSON {
+    func toString() -> String {
+        String(data: try! JSONEncoder().encode(self), encoding: .utf8)!
+    }
+
+    init?(from: String) {
+        guard let data = from.data(using: .utf8),
+            let object = try? JSONDecoder().decode(Self.self, from: data) else {
+            return nil
+        }
+        self = object
+    }
+}

+ 12 - 0
FreeAPS/Models/Autosens.swift

@@ -0,0 +1,12 @@
+//
+//  Autosens.swift
+//  FreeAPS
+//
+//  Created by Ivan Valkou on 19.01.2021.
+//
+
+import Foundation
+
+struct Autosens: JSON {
+    let ratio: Double
+}

+ 190 - 0
FreeAPS/Models/Profile.swift

@@ -0,0 +1,190 @@
+//
+//  Profile.swift
+//  FreeAPS
+//
+//  Created by Ivan Valkou on 21.01.2021.
+//
+
+import Foundation
+
+struct Profile: JSON {
+    var maxIOB: Double
+    var maxDailySafetyMultiplier: Double
+    var currentBasalSafetyMultiplier: Double
+    var autosensMax: Double
+    var autosensMin: Double
+    var rewindResetsAutosens: Bool
+    var highTemptargetRaisesSensitivity: Bool
+    var lowTemptargetLowersSensitivity: Bool
+    var sensitivityRaisesTarget: Bool
+    var resistanceLowersTarget: Bool
+    var advTargetAdjustments: Bool
+    var exerciseMode: Bool
+    var halfBasalExerciseTarget: Double
+    var maxCOB: Double
+    var wideBGTargetRange: Bool
+    var skipNeutralTemps: Bool
+    var unsuspendIfNoTemp: Bool
+    var bolusSnoozeDIADivisor: Double
+    var min5mCarbimpact: Double
+    var autotuneISFAdjustmentFraction: Double
+    var remainingCarbsFraction: Double
+    var remainingCarbsCap: Double
+    var enableUAM: Bool
+    var a52RiskEnable: Bool
+    var enableSMBWithCOB: Bool
+    var enableSMBWithTemptarget: Bool
+    var enableSMBAlways: Bool
+    var enableSMBAfterCarbs: Bool
+    var allowSMBWithHighTemptarget: Bool
+    var maxSMBBasalMinutes: Double
+    var maxUAMSMBBasalMinutes: Double
+    var smbInterval: Double
+    var bolusIncrement: Double
+    var curve: InsulinCurve
+    var useCustomPeakTime: Bool
+    var insulinPeakTime: Double
+    var carbsReqThreshold: Double
+    var offlineHotspot: Bool // unused, for compatibility
+    var noisyCGMTargetMultiplier: Double
+    var suspendZerosIOB: Bool
+    var enableEnliteBgproxy: Bool // unused, for compatibility
+
+    init(
+        maxIOB: Double = 0,
+        maxDailySafetyMultiplier: Double = 3,
+        currentBasalSafetyMultiplier: Double = 4,
+        autosensMax: Double = 1.2,
+        autosensMin: Double = 0.7,
+        rewindResetsAutosens: Bool = true,
+        highTemptargetRaisesSensitivity: Bool = false,
+        lowTemptargetLowersSensitivity: Bool = false,
+        sensitivityRaisesTarget: Bool = true,
+        resistanceLowersTarget: Bool = false,
+        advTargetAdjustments: Bool = false,
+        exerciseMode: Bool = false,
+        halfBasalExerciseTarget: Double = 160,
+        maxCOB: Double = 120,
+        wideBGTargetRange: Bool = false,
+        skipNeutralTemps: Bool = false,
+        unsuspendIfNoTemp: Bool = false,
+        bolusSnoozeDIADivisor: Double = 2,
+        min5mCarbimpact: Double = 8,
+        autotuneISFAdjustmentFraction: Double = 1.0,
+        remainingCarbsFraction: Double = 1.0,
+        remainingCarbsCap: Double = 90,
+        enableUAM: Bool = false,
+        a52RiskEnable: Bool = false,
+        enableSMBWithCOB: Bool = false,
+        enableSMBWithTemptarget: Bool = false,
+        enableSMBAlways: Bool = false,
+        enableSMBAfterCarbs: Bool = false,
+        allowSMBWithHighTemptarget: Bool = false,
+        maxSMBBasalMinutes: Double = 30,
+        maxUAMSMBBasalMinutes: Double = 30,
+        smbInterval: Double = 3,
+        bolusIncrement: Double = 0.1,
+        curve: InsulinCurve = .rapidActing,
+        useCustomPeakTime: Bool = false,
+        insulinPeakTime: Double = 75,
+        carbsReqThreshold: Double = 1,
+        offlineHotspot: Bool = false, // unused, for compatibility
+        noisyCGMTargetMultiplier: Double = 1.3,
+        suspendZerosIOB: Bool = true,
+        enableEnliteBgproxy: Bool = false // unused, for compatibility
+    ) {
+        self.maxIOB = maxIOB
+        self.maxDailySafetyMultiplier = maxDailySafetyMultiplier
+        self.currentBasalSafetyMultiplier = currentBasalSafetyMultiplier
+        self.autosensMax = autosensMax
+        self.autosensMin = autosensMin
+        self.rewindResetsAutosens = rewindResetsAutosens
+        self.highTemptargetRaisesSensitivity = highTemptargetRaisesSensitivity
+        self.lowTemptargetLowersSensitivity = lowTemptargetLowersSensitivity
+        self.sensitivityRaisesTarget = sensitivityRaisesTarget
+        self.resistanceLowersTarget = resistanceLowersTarget
+        self.advTargetAdjustments = advTargetAdjustments
+        self.exerciseMode = exerciseMode
+        self.halfBasalExerciseTarget = halfBasalExerciseTarget
+        self.maxCOB = maxCOB
+        self.wideBGTargetRange = wideBGTargetRange
+        self.skipNeutralTemps = skipNeutralTemps
+        self.unsuspendIfNoTemp = unsuspendIfNoTemp
+        self.bolusSnoozeDIADivisor = bolusSnoozeDIADivisor
+        self.min5mCarbimpact = min5mCarbimpact
+        self.autotuneISFAdjustmentFraction = autotuneISFAdjustmentFraction
+        self.remainingCarbsFraction = remainingCarbsFraction
+        self.remainingCarbsCap = remainingCarbsCap
+        self.enableUAM = enableUAM
+        self.a52RiskEnable = a52RiskEnable
+        self.enableSMBWithCOB = enableSMBWithCOB
+        self.enableSMBWithTemptarget = enableSMBWithTemptarget
+        self.enableSMBAlways = enableSMBAlways
+        self.enableSMBAfterCarbs = enableSMBAfterCarbs
+        self.allowSMBWithHighTemptarget = allowSMBWithHighTemptarget
+        self.maxSMBBasalMinutes = maxSMBBasalMinutes
+        self.maxUAMSMBBasalMinutes = maxUAMSMBBasalMinutes
+        self.smbInterval = smbInterval
+        self.bolusIncrement = bolusIncrement
+        self.curve = curve
+        self.useCustomPeakTime = useCustomPeakTime
+        self.insulinPeakTime = insulinPeakTime
+        self.carbsReqThreshold = carbsReqThreshold
+        self.offlineHotspot = offlineHotspot
+        self.noisyCGMTargetMultiplier = noisyCGMTargetMultiplier
+        self.suspendZerosIOB = suspendZerosIOB
+        self.enableEnliteBgproxy = enableEnliteBgproxy
+    }
+}
+
+extension Profile {
+    private enum CodingKeys: String, CodingKey {
+        case maxIOB = "max_iob"
+        case maxDailySafetyMultiplier = "max_daily_safety_multiplier"
+        case currentBasalSafetyMultiplier = "current_basal_safety_multiplier"
+        case autosensMax = "autosens_max"
+        case autosensMin = "autosens_min"
+        case rewindResetsAutosens = "rewind_resets_autosens"
+        case highTemptargetRaisesSensitivity = "high_temptarget_raises_sensitivity"
+        case lowTemptargetLowersSensitivity = "low_temptarget_lowers_sensitivity"
+        case sensitivityRaisesTarget = "sensitivity_raises_target"
+        case resistanceLowersTarget = "resistanceLowersTarget"
+        case advTargetAdjustments = "adv_target_adjustments"
+        case exerciseMode = "exercise_mode"
+        case halfBasalExerciseTarget = "half_basal_exercise_target"
+        case maxCOB = "maxCOB"
+        case wideBGTargetRange = "wide_bg_target_range"
+        case skipNeutralTemps = "skip_neutral_temps"
+        case unsuspendIfNoTemp = "unsuspend_if_no_temp"
+        case bolusSnoozeDIADivisor = "bolussnooze_dia_divisor"
+        case min5mCarbimpact = "min_5m_carbimpact"
+        case autotuneISFAdjustmentFraction = "autotune_isf_adjustmentFraction"
+        case remainingCarbsFraction = "remainingCarbsFraction"
+        case remainingCarbsCap = "remainingCarbsCap"
+        case enableUAM = "enableUAM"
+        case a52RiskEnable = "A52_risk_enable"
+        case enableSMBWithCOB = "enableSMB_with_COB"
+        case enableSMBWithTemptarget = "enableSMB_with_temptarget"
+        case enableSMBAlways = "enableSMB_always"
+        case enableSMBAfterCarbs = "enableSMB_after_carbs"
+        case allowSMBWithHighTemptarget = "allowSMB_with_high_temptarget"
+        case maxSMBBasalMinutes = "maxSMBBasalMinutes"
+        case maxUAMSMBBasalMinutes = "maxUAMSMBBasalMinutes"
+        case smbInterval = "SMBInterval"
+        case bolusIncrement = "bolus_increment"
+        case curve = "curve"
+        case useCustomPeakTime = "useCustomPeakTime"
+        case insulinPeakTime = "insulinPeakTime"
+        case carbsReqThreshold = "carbsReqThreshold"
+        case offlineHotspot = "offline_hotspot"
+        case noisyCGMTargetMultiplier = "noisyCGMTargetMultiplier"
+        case suspendZerosIOB = "suspend_zeros_iob"
+        case enableEnliteBgproxy = "enableEnliteBgproxy"
+    }
+}
+
+enum InsulinCurve: String, Codable {
+    case rapidActing = "rapid-acting"
+    case ultraRapid = "ultra-rapid"
+    case bilinear = "bilinear"
+}

+ 0 - 53
FreeAPS/OpenAPS.swift

@@ -1,53 +0,0 @@
-//
-//  OpenAPS.swift
-//  FreeAPS
-//
-//  Created by Ivan Valkou on 12.01.2021.
-//
-
-import Foundation
-import JavaScriptCore
-
-final class OpenAPS {
-    private let vmQueue = DispatchQueue(label: "DispatchQueue.JSVirtualMachine")
-
-    func determineBasal() {
-        let vm = vmQueue.sync { JSVirtualMachine()! }
-        let context = JSContext(virtualMachine: vm)!
-
-        context.exceptionHandler = { context, exception in
-            print(exception!.toString()!)
-        }
-
-        let scripts = [
-            loadScript(name: "prepare"),
-            loadScript(name: "basal-set-temp"),
-            loadScript(name: "determine-basal"),
-            loadScript(name: "glucose-get-last")
-        ]
-
-        scripts.forEach { context.evaluateScript($0) }
-
-        let glucose = loadJSON(name: "glucose")
-        let currentTemp = loadJSON(name: "temp_basal")
-        let iobData = loadJSON(name: "iob")
-        let profile = loadJSON(name: "profile")
-        let autosensData = loadJSON(name: "autosens")
-        let mealData = loadJSON(name: "meal")
-
-        context.evaluateScript("var glucoseStatus = getLastGlucose(\(glucose));")
-        let result = context.evaluateScript("determine_basal(glucoseStatus, \(currentTemp), \(iobData), \(profile), \(autosensData), \(mealData), tempBasalFunctions, true, 100, 1527924300000);")
-        print(result!.toDictionary()!)
-        print(context.objectForKeyedSubscript("logError")!
-               .toString()!)
-
-    }
-
-    private func loadScript(name: String) -> String {
-        try! String(contentsOf: Bundle.main.url(forResource: "javascript/\(name)", withExtension: "js")!)
-    }
-
-    private func loadJSON(name: String) -> String {
-        try! String(contentsOf: Bundle.main.url(forResource: "json/\(name)", withExtension: "json")!)
-    }
-}

+ 37 - 0
FreeAPS/OpenAPS/JavaScriptWorker.swift

@@ -0,0 +1,37 @@
+//
+//  JavaScriptWorker.swift
+//  FreeAPS
+//
+//  Created by Ivan Valkou on 29.01.2021.
+//
+
+import Foundation
+import JavaScriptCore
+
+final class JavaScriptWorker {
+    private let processQueue = DispatchQueue(label: "DispatchQueue.JavaScriptWorker")
+    private let virtualMachine: JSVirtualMachine
+    private let context: JSContext
+
+    init() {
+        virtualMachine = processQueue.sync { JSVirtualMachine()! }
+        context = JSContext(virtualMachine: virtualMachine)!
+        context.exceptionHandler = { context, exception in
+            print(exception!.toString()!)
+        }
+    }
+
+    @discardableResult
+    func evaluate(script: Script) -> JSValue! {
+        context.evaluateScript(script.body)
+    }
+
+    @discardableResult
+    func evaluate(string: String) -> JSValue! {
+        context.evaluateScript(string)
+    }
+
+    subscript(key: String) -> JSValue! {
+        context.objectForKeyedSubscript(key)
+    }
+}

+ 47 - 0
FreeAPS/OpenAPS/OpenAPS.swift

@@ -0,0 +1,47 @@
+//
+//  OpenAPS.swift
+//  FreeAPS
+//
+//  Created by Ivan Valkou on 12.01.2021.
+//
+
+import Foundation
+import JavaScriptCore
+
+final class OpenAPS {
+    private let vmQueue = DispatchQueue(label: "DispatchQueue.JSVirtualMachine")
+    private let jsWorker = JavaScriptWorker()
+
+    init() {
+        loadScripts()
+    }
+
+    private func loadScripts() {
+        let scripts = [
+            Script(name: "prepare"),
+            Script(name: "basal-set-temp"),
+            Script(name: "determine-basal"),
+            Script(name: "glucose-get-last")
+        ]
+
+        scripts.forEach { jsWorker.evaluate(script: $0) }
+    }
+
+    func determineBasal() {
+        let glucose = loadJSON(name: "glucose")
+        let currentTemp = loadJSON(name: "temp_basal")
+        let iobData = loadJSON(name: "iob")
+        let profile = loadJSON(name: "profile")
+        let autosensData = Autosens(ratio: 1.0).toString()
+        let mealData = loadJSON(name: "meal")
+
+        jsWorker.evaluate(string: "var glucoseStatus = getLastGlucose(\(glucose));")
+        let result = jsWorker.evaluate(string: "determine_basal(glucoseStatus, \(currentTemp), \(iobData), \(profile), \(autosensData), \(mealData), tempBasalFunctions, true, 100, 1527924300000);")
+        print(result!.toDictionary()!)
+        print(jsWorker["logError"]!.toString()!)
+    }
+
+    private func loadJSON(name: String) -> String {
+        try! String(contentsOf: Bundle.main.url(forResource: "json/\(name)", withExtension: "json")!)
+    }
+}

+ 18 - 0
FreeAPS/OpenAPS/Script.swift

@@ -0,0 +1,18 @@
+//
+//  Script.swift
+//  FreeAPS
+//
+//  Created by Ivan Valkou on 29.01.2021.
+//
+
+import Foundation
+
+struct Script {
+    let name: String
+    let body: String
+
+    init(name: String) {
+        self.name = name
+        self.body = try! String(contentsOf: Bundle.main.url(forResource: "javascript/\(name)", withExtension: "js")!)
+    }
+}