diff --git a/app/build.gradle b/app/build.gradle index 08589fc270..30158bf591 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -43,6 +43,7 @@ android { applicationId "info.nightscout.androidaps" minSdkVersion 21 targetSdkVersion 23 + multiDexEnabled true versionCode 1500 version "1.54-dev" buildConfigField "String", "VERSION", '"' + version + '"' diff --git a/app/libs/rhino-1.7.7.2.jar b/app/libs/rhino-1.7.7.2.jar new file mode 100644 index 0000000000..4a18d33609 Binary files /dev/null and b/app/libs/rhino-1.7.7.2.jar differ diff --git a/app/src/main/assets/OpenAPSAMA/loggerhelper.js b/app/src/main/assets/OpenAPSAMA/loggerhelper.js new file mode 100644 index 0000000000..e790f465c8 --- /dev/null +++ b/app/src/main/assets/OpenAPSAMA/loggerhelper.js @@ -0,0 +1,12 @@ +var console = { }; +console.error = function error(){ + for (var i = 0, len = arguments.length; i < len; i++) { + console2.log(arguments[i]); + } +}; + +console.log = function log(){ + for (var i = 0, len = arguments.length; i < len; i++) { + console2.log(arguments[i]); + } +}; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalAdapterAMAJS.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalAdapterAMAJS.java index 44ab8ecb14..3102ac95d6 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalAdapterAMAJS.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalAdapterAMAJS.java @@ -1,16 +1,22 @@ package info.nightscout.androidaps.plugins.OpenAPSAMA; -import com.eclipsesource.v8.JavaVoidCallback; -import com.eclipsesource.v8.V8; -import com.eclipsesource.v8.V8Array; -import com.eclipsesource.v8.V8Object; - +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import org.mozilla.javascript.Callable; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Function; +import org.mozilla.javascript.NativeJSON; +import org.mozilla.javascript.NativeObject; +import org.mozilla.javascript.RhinoException; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.ScriptableObject; +import org.mozilla.javascript.Undefined; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.util.Date; import info.nightscout.androidaps.Config; @@ -24,6 +30,7 @@ import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin; import info.nightscout.androidaps.plugins.Loop.ScriptReader; import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.plugins.OpenAPSMA.LoggerCallback; import info.nightscout.utils.SP; public class DetermineBasalAdapterAMAJS { @@ -31,20 +38,13 @@ public class DetermineBasalAdapterAMAJS { private ScriptReader mScriptReader = null; - V8 mV8rt; - private V8Object mProfile; - private V8Object mGlucoseStatus; - private V8Array mIobData; - private V8Object mMealData; - private V8Object mCurrentTemp; - private V8Object mAutosensData = null; - private final String PARAM_currentTemp = "currentTemp"; - private final String PARAM_iobData = "iobData"; - private final String PARAM_glucoseStatus = "glucose_status"; - private final String PARAM_profile = "profile"; - private final String PARAM_meal_data = "meal_data"; - private final String PARAM_autosens_data = "autosens_data"; + private JSONObject mProfile; + private JSONObject mGlucoseStatus; + private JSONArray mIobData; + private JSONObject mMealData; + private JSONObject mCurrentTemp; + private JSONObject mAutosensData = null; private String storedCurrentTemp = null; private String storedIobData = null; @@ -55,58 +55,101 @@ public class DetermineBasalAdapterAMAJS { private String scriptDebug = ""; - /** - * Main code - */ - public DetermineBasalAdapterAMAJS(ScriptReader scriptReader) throws IOException { - mV8rt = V8.createV8Runtime(); mScriptReader = scriptReader; - - initLogCallback(); - initProcessExitCallback(); - initModuleParent(); - loadScript(); } public DetermineBasalResultAMA invoke() { + log.debug(">>> Invoking detemine_basal <<<"); - log.debug("Glucose status: " + (storedGlucoseStatus = mV8rt.executeStringScript("JSON.stringify(" + PARAM_glucoseStatus + ");"))); - log.debug("IOB data: " + (storedIobData = mV8rt.executeStringScript("JSON.stringify(" + PARAM_iobData + ");"))); - log.debug("Current temp: " + (storedCurrentTemp = mV8rt.executeStringScript("JSON.stringify(" + PARAM_currentTemp + ");"))); - log.debug("Profile: " + (storedProfile = mV8rt.executeStringScript("JSON.stringify(" + PARAM_profile + ");"))); - log.debug("Meal data: " + (storedMeal_data = mV8rt.executeStringScript("JSON.stringify(" + PARAM_meal_data + ");"))); + log.debug("Glucose status: " + (storedGlucoseStatus = mGlucoseStatus.toString())); + log.debug("IOB data: " + (storedIobData = mIobData.toString())); + log.debug("Current temp: " + (storedCurrentTemp = mCurrentTemp.toString())); + log.debug("Profile: " + (storedProfile = mProfile.toString())); + log.debug("Meal data: " + (storedMeal_data = mMealData.toString())); if (mAutosensData != null) - log.debug("Autosens data: " + (storedAutosens_data = mV8rt.executeStringScript("JSON.stringify(" + PARAM_autosens_data + ");"))); + log.debug("Autosens data: " + (storedAutosens_data = mAutosensData.toString())); else log.debug("Autosens data: " + (storedAutosens_data = "undefined")); - mV8rt.executeVoidScript( - "var rT = determine_basal(" + - PARAM_glucoseStatus + ", " + - PARAM_currentTemp + ", " + - PARAM_iobData + ", " + - PARAM_profile + ", " + - PARAM_autosens_data + ", " + - PARAM_meal_data + ", " + - "tempBasalFunctions" + - ");"); + DetermineBasalResultAMA determineBasalResultAMA = null; + Context rhino = Context.enter(); + Scriptable scope = rhino.initStandardObjects(); + // Turn off optimization to make Rhino Android compatible + rhino.setOptimizationLevel(-1); - String ret = mV8rt.executeStringScript("JSON.stringify(rT);"); - log.debug("Result: " + ret); - - V8Object v8ObjectReuslt = mV8rt.getObject("rT"); - - DetermineBasalResultAMA result = null; try { - result = new DetermineBasalResultAMA(v8ObjectReuslt, new JSONObject(ret)); - } catch (JSONException e) { - log.error("Unhandled exception", e); + + //register logger callback for console.log and console.error + ScriptableObject.defineClass(scope, LoggerCallback.class); + Scriptable myLogger = rhino.newObject(scope, "LoggerCallback", null); + scope.put("console2", scope, myLogger); + rhino.evaluateString(scope, readFile("OpenAPSAMA/loggerhelper.js"), "JavaScript", 0, null); + + //set module parent + rhino.evaluateString(scope, "var module = {\"parent\":Boolean(1)};", "JavaScript", 0, null); + rhino.evaluateString(scope, "var round_basal = function round_basal(basal, profile) { return basal; };", "JavaScript", 0, null); + rhino.evaluateString(scope, "require = function() {return round_basal;};", "JavaScript", 0, null); + + //generate functions "determine_basal" and "setTempBasal" + rhino.evaluateString(scope, readFile("OpenAPSAMA/determine-basal.js"), "JavaScript", 0, null); + rhino.evaluateString(scope, readFile("OpenAPSAMA/basal-set-temp.js"), "setTempBasal.js", 0, null); + Object determineBasalObj = scope.get("determine_basal", scope); + Object setTempBasalFunctionsObj = scope.get("tempBasalFunctions", scope); + + //call determine-basal + if (determineBasalObj instanceof Function && setTempBasalFunctionsObj instanceof NativeObject) { + Function determineBasalJS = (Function) determineBasalObj; + + //prepare parameters + Object[] params = new Object[]{ + makeParam(mGlucoseStatus, rhino, scope), + makeParam(mCurrentTemp, rhino, scope), + makeParamArray(mIobData, rhino, scope), + makeParam(mProfile, rhino, scope), + makeParam(mAutosensData, rhino, scope), + makeParam(mMealData, rhino, scope), + setTempBasalFunctionsObj}; + + NativeObject jsResult = (NativeObject) determineBasalJS.call(rhino, scope, scope, params); + scriptDebug = LoggerCallback.getScriptDebug(); + + // Parse the jsResult object to a JSON-String + String result = NativeJSON.stringify(rhino, scope, jsResult, null, null).toString(); + if (Config.logAPSResult) + log.debug("Result: " + result); + try { + determineBasalResultAMA = new DetermineBasalResultAMA(jsResult, new JSONObject(result)); + } catch (JSONException e) { + log.error("Unhandled exception", e); + } + } else { + log.debug("Problem loading JS Functions"); + } + } catch (IOException e) { + log.debug("IOException"); + } catch (RhinoException e) { + log.error("RhinoException: (" + e.lineNumber() + "," + e.columnNumber() + ") " + e.toString()); + } catch (IllegalAccessException e) { + log.error(e.toString()); + } catch (InstantiationException e) { + log.error(e.toString()); + } catch (InvocationTargetException e) { + log.error(e.toString()); + } finally { + Context.exit(); } - return result; + storedGlucoseStatus = mGlucoseStatus.toString(); + storedIobData = mIobData.toString(); + storedCurrentTemp = mCurrentTemp.toString(); + storedProfile = mProfile.toString(); + storedMeal_data = mMealData.toString(); + + return determineBasalResultAMA; + } String getGlucoseStatusParam() { @@ -137,60 +180,6 @@ public class DetermineBasalAdapterAMAJS { return scriptDebug; } - private void loadScript() throws IOException { - mV8rt.executeVoidScript("var round_basal = function round_basal(basal, profile) { return basal; };"); - mV8rt.executeVoidScript("require = function() {return round_basal;};"); - - mV8rt.executeVoidScript(readFile("OpenAPSAMA/basal-set-temp.js"), "OpenAPSAMA/basal-set-temp.js ", 0); - mV8rt.executeVoidScript("var tempBasalFunctions = module.exports;"); - - mV8rt.executeVoidScript( - readFile("OpenAPSAMA/determine-basal.js"), - "OpenAPSAMA/determine-basal.js", - 0); - mV8rt.executeVoidScript("var determine_basal = module.exports;"); - } - - private void initModuleParent() { - mV8rt.executeVoidScript("var module = {\"parent\":Boolean(1)};"); - } - - private void initProcessExitCallback() { - JavaVoidCallback callbackProccessExit = new JavaVoidCallback() { - @Override - public void invoke(V8Object arg0, V8Array parameters) { - if (parameters.length() > 0) { - Object arg1 = parameters.get(0); - log.error("ProccessExit " + arg1); - } - } - }; - mV8rt.registerJavaMethod(callbackProccessExit, "proccessExit"); - mV8rt.executeVoidScript("var process = {\"exit\": function () { proccessExit(); } };"); - } - - private void initLogCallback() { - JavaVoidCallback callbackLog = new JavaVoidCallback() { - @Override - public void invoke(V8Object arg0, V8Array parameters) { - int i = 0; - String s = ""; - while (i < parameters.length()) { - Object arg = parameters.get(i); - s += arg + " "; - i++; - } - if (!s.equals("") && Config.logAPSResult) { - log.debug("Script debug: " + s); - scriptDebug += s + "\n"; - } - } - }; - mV8rt.registerJavaMethod(callbackLog, "log"); - mV8rt.executeVoidScript("var console = {\"log\":log, \"error\":log};"); - } - - public void setData(Profile profile, double maxIob, double maxBasal, @@ -203,89 +192,94 @@ public class DetermineBasalAdapterAMAJS { MealData mealData, double autosensDataRatio, boolean tempTargetSet, - double min_5m_carbimpact) { + double min_5m_carbimpact) throws JSONException { String units = profile.getUnits(); - mProfile = new V8Object(mV8rt); - mProfile.add("max_iob", maxIob); - mProfile.add("dia", Math.min(profile.getDia(), 3d)); - mProfile.add("type", "current"); - mProfile.add("max_daily_basal", profile.getMaxDailyBasal()); - mProfile.add("max_basal", maxBasal); - mProfile.add("min_bg", minBg); - mProfile.add("max_bg", maxBg); - mProfile.add("target_bg", targetBg); - mProfile.add("carb_ratio", profile.getIc()); - mProfile.add("sens", Profile.toMgdl(profile.getIsf().doubleValue(), units)); - mProfile.add("max_daily_safety_multiplier", SP.getInt("openapsama_max_daily_safety_multiplier", 3)); - mProfile.add("current_basal_safety_multiplier", SP.getInt("openapsama_current_basal_safety_multiplier", 4)); - mProfile.add("skip_neutral_temps", true); - mProfile.add("current_basal", pump.getBaseBasalRate()); - mProfile.add("temptargetSet", tempTargetSet); - mProfile.add("autosens_adjust_targets", SP.getBoolean("openapsama_autosens_adjusttargets", true)); - mProfile.add("min_5m_carbimpact", SP.getDouble("openapsama_min_5m_carbimpact", 3d)); + mProfile = new JSONObject(); + mProfile.put("max_iob", maxIob); + mProfile.put("dia", Math.min(profile.getDia(), 3d)); + mProfile.put("type", "current"); + mProfile.put("max_daily_basal", profile.getMaxDailyBasal()); + mProfile.put("max_basal", maxBasal); + mProfile.put("min_bg", minBg); + mProfile.put("max_bg", maxBg); + mProfile.put("target_bg", targetBg); + mProfile.put("carb_ratio", profile.getIc()); + mProfile.put("sens", Profile.toMgdl(profile.getIsf().doubleValue(), units)); + mProfile.put("max_daily_safety_multiplier", SP.getInt("openapsama_max_daily_safety_multiplier", 3)); + mProfile.put("current_basal_safety_multiplier", SP.getInt("openapsama_current_basal_safety_multiplier", 4)); + mProfile.put("skip_neutral_temps", true); + mProfile.put("current_basal", pump.getBaseBasalRate()); + mProfile.put("temptargetSet", tempTargetSet); + mProfile.put("autosens_adjust_targets", SP.getBoolean("openapsama_autosens_adjusttargets", true)); + mProfile.put("min_5m_carbimpact", SP.getDouble("openapsama_min_5m_carbimpact", 3d)); if (units.equals(Constants.MMOL)) { - mProfile.add("out_units", "mmol/L"); + mProfile.put("out_units", "mmol/L"); } - mV8rt.add(PARAM_profile, mProfile); - mCurrentTemp = new V8Object(mV8rt); - mCurrentTemp.add("temp", "absolute"); - mCurrentTemp.add("duration", MainApp.getConfigBuilder().getTempBasalRemainingMinutesFromHistory()); - mCurrentTemp.add("rate", MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory()); + mCurrentTemp = new JSONObject(); + mCurrentTemp.put("temp", "absolute"); + mCurrentTemp.put("duration", MainApp.getConfigBuilder().getTempBasalRemainingMinutesFromHistory()); + mCurrentTemp.put("rate", MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory()); // as we have non default temps longer than 30 mintues TemporaryBasal tempBasal = MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis()); if (tempBasal != null) { - mCurrentTemp.add("minutesrunning", tempBasal.getRealDuration()); + mCurrentTemp.put("minutesrunning", tempBasal.getRealDuration()); } - mV8rt.add(PARAM_currentTemp, mCurrentTemp); + mIobData = IobCobCalculatorPlugin.convertToJSONArray(iobArray); - mIobData = mV8rt.executeArrayScript(IobCobCalculatorPlugin.convertToJSONArray(iobArray).toString()); - mV8rt.add(PARAM_iobData, mIobData); - - mGlucoseStatus = new V8Object(mV8rt); - mGlucoseStatus.add("glucose", glucoseStatus.glucose); + mGlucoseStatus = new JSONObject(); + mGlucoseStatus.put("glucose", glucoseStatus.glucose); if (SP.getBoolean("always_use_shortavg", false)) { - mGlucoseStatus.add("delta", glucoseStatus.short_avgdelta); + mGlucoseStatus.put("delta", glucoseStatus.short_avgdelta); } else { - mGlucoseStatus.add("delta", glucoseStatus.delta); + mGlucoseStatus.put("delta", glucoseStatus.delta); } - mGlucoseStatus.add("short_avgdelta", glucoseStatus.short_avgdelta); - mGlucoseStatus.add("long_avgdelta", glucoseStatus.long_avgdelta); - mV8rt.add(PARAM_glucoseStatus, mGlucoseStatus); + mGlucoseStatus.put("short_avgdelta", glucoseStatus.short_avgdelta); + mGlucoseStatus.put("long_avgdelta", glucoseStatus.long_avgdelta); - mMealData = new V8Object(mV8rt); - mMealData.add("carbs", mealData.carbs); - mMealData.add("boluses", mealData.boluses); - mMealData.add("mealCOB", mealData.mealCOB); - mV8rt.add(PARAM_meal_data, mMealData); + mMealData = new JSONObject(); + mMealData.put("carbs", mealData.carbs); + mMealData.put("boluses", mealData.boluses); + mMealData.put("mealCOB", mealData.mealCOB); if (MainApp.getConfigBuilder().isAMAModeEnabled()) { - mAutosensData = new V8Object(mV8rt); - mAutosensData.add("ratio", autosensDataRatio); - mV8rt.add(PARAM_autosens_data, mAutosensData); + mAutosensData = new JSONObject(); + mAutosensData.put("ratio", autosensDataRatio); } else { - mV8rt.addUndefined(PARAM_autosens_data); + mAutosensData = null; } } - public void release() { - mProfile.release(); - mCurrentTemp.release(); - mIobData.release(); - mMealData.release(); - mGlucoseStatus.release(); - if (mAutosensData != null) { - mAutosensData.release(); - } - mV8rt.release(); + public Object makeParam(JSONObject jsonObject, Context rhino, Scriptable scope) { + + if(jsonObject == null) return Undefined.instance; + + Object param = NativeJSON.parse(rhino, scope, jsonObject.toString(), new Callable() { + @Override + public Object call(Context context, Scriptable scriptable, Scriptable scriptable1, Object[] objects) { + return objects[1]; + } + }); + return param; + } + + public Object makeParamArray(JSONArray jsonArray, Context rhino, Scriptable scope) { + //Object param = NativeJSON.parse(rhino, scope, "{myarray: " + jsonArray.toString() + " }", new Callable() { + Object param = NativeJSON.parse(rhino, scope, jsonArray.toString(), new Callable() { + @Override + public Object call(Context context, Scriptable scriptable, Scriptable scriptable1, Object[] objects) { + return objects[1]; + } + }); + return param; } public String readFile(String filename) throws IOException { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalResultAMA.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalResultAMA.java index 31501b7032..774ff55401 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalResultAMA.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalResultAMA.java @@ -5,6 +5,7 @@ import com.eclipsesource.v8.V8Object; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import org.mozilla.javascript.NativeObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,35 +26,34 @@ public class DetermineBasalResultAMA extends APSResult { public double snoozeBG; public IobTotal iob; - public DetermineBasalResultAMA(V8Object result, JSONObject j) { + public DetermineBasalResultAMA(NativeObject result, JSONObject j) { date = new Date(); json = j; - if (result.contains("error")) { - reason = result.getString("error"); + if (result.containsKey("error")) { + reason = result.get("error").toString(); changeRequested = false; rate = -1; duration = -1; } else { - reason = result.getString("reason"); - if (result.contains("eventualBG")) eventualBG = result.getDouble("eventualBG"); - if (result.contains("snoozeBG")) snoozeBG = result.getDouble("snoozeBG"); - if (result.contains("rate")) { - rate = result.getDouble("rate"); + reason = result.get("reason").toString(); + if (result.containsKey("eventualBG")) eventualBG = (Double) result.get("eventualBG"); + if (result.containsKey("snoozeBG")) snoozeBG = (Double) result.get("snoozeBG"); + if (result.containsKey("rate")) { + rate = (Double) result.get("rate"); if (rate < 0d) rate = 0d; changeRequested = true; } else { rate = -1; changeRequested = false; } - if (result.contains("duration")) { - duration = result.getInteger("duration"); + if (result.containsKey("duration")) { + duration = ((Double)result.get("duration")).intValue(); //changeRequested as above } else { duration = -1; changeRequested = false; } } - result.release(); } public DetermineBasalResultAMA() { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAPlugin.java index dfad17de2e..1e574bcc6c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAPlugin.java @@ -222,11 +222,16 @@ public class OpenAPSAMAPlugin implements PluginBase, APSInterface { Profiler.log(log, "AMA data gathering", start); start = new Date(); - determineBasalAdapterAMAJS.setData(profile, maxIob, maxBasal, minBg, maxBg, targetBg, pump, iobArray, glucoseStatus, mealData, - lastAutosensResult.ratio, //autosensDataRatio - isTempTarget, - SafeParse.stringToDouble(SP.getString("openapsama_min_5m_carbimpact", "3.0"))//min_5m_carbimpact - ); + + try { + determineBasalAdapterAMAJS.setData(profile, maxIob, maxBasal, minBg, maxBg, targetBg, pump, iobArray, glucoseStatus, mealData, + lastAutosensResult.ratio, //autosensDataRatio + isTempTarget, + SafeParse.stringToDouble(SP.getString("openapsama_min_5m_carbimpact", "3.0"))//min_5m_carbimpact + ); + } catch (JSONException e) { + log.error("Unable to set data: " + e.toString()); + } DetermineBasalResultAMA determineBasalResultAMA = determineBasalAdapterAMAJS.invoke(); @@ -244,8 +249,6 @@ public class OpenAPSAMAPlugin implements PluginBase, APSInterface { determineBasalResultAMA.iob = iobArray[0]; - determineBasalAdapterAMAJS.release(); - Date now = new Date(); try { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/DetermineBasalAdapterMAJS.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/DetermineBasalAdapterMAJS.java index a8f797f8c1..a7e1d5da58 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/DetermineBasalAdapterMAJS.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/DetermineBasalAdapterMAJS.java @@ -1,16 +1,20 @@ package info.nightscout.androidaps.plugins.OpenAPSMA; -import com.eclipsesource.v8.JavaVoidCallback; -import com.eclipsesource.v8.V8; -import com.eclipsesource.v8.V8Array; -import com.eclipsesource.v8.V8Object; - import org.json.JSONException; import org.json.JSONObject; +import org.mozilla.javascript.Callable; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Function; +import org.mozilla.javascript.NativeJSON; +import org.mozilla.javascript.NativeObject; +import org.mozilla.javascript.RhinoException; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.ScriptableObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.Constants; @@ -26,20 +30,12 @@ import info.nightscout.utils.SP; public class DetermineBasalAdapterMAJS { private static Logger log = LoggerFactory.getLogger(DetermineBasalAdapterMAJS.class); - private ScriptReader mScriptReader = null; - V8 mV8rt; - private V8Object mProfile; - private V8Object mGlucoseStatus; - private V8Object mIobData; - private V8Object mMealData; - private V8Object mCurrentTemp; - - private final String PARAM_currentTemp = "currentTemp"; - private final String PARAM_iobData = "iobData"; - private final String PARAM_glucoseStatus = "glucose_status"; - private final String PARAM_profile = "profile"; - private final String PARAM_meal_data = "meal_data"; + private JSONObject mProfile; + private JSONObject mGlucoseStatus; + private JSONObject mIobData; + private JSONObject mMealData; + private JSONObject mCurrentTemp; private String storedCurrentTemp = null; public String storedIobData = null; @@ -47,104 +43,90 @@ public class DetermineBasalAdapterMAJS { private String storedProfile = null; private String storedMeal_data = null; - /** - * Main code - */ - public DetermineBasalAdapterMAJS(ScriptReader scriptReader) throws IOException { - mV8rt = V8.createV8Runtime(); mScriptReader = scriptReader; - - init(); - initLogCallback(); - initProcessExitCallback(); - initModuleParent(); - loadScript(); - } - - public void init() { - // Profile - mProfile = new V8Object(mV8rt); - mProfile.add("max_iob", 0); - mProfile.add("dia", 0); - mProfile.add("type", "current"); - mProfile.add("max_daily_basal", 0); - mProfile.add("max_basal", 0); - mProfile.add("max_bg", 0); - mProfile.add("min_bg", 0); - mProfile.add("carb_ratio", 0); - mProfile.add("sens", 0); - mProfile.add("current_basal", 0); - mV8rt.add(PARAM_profile, mProfile); - // Current temp - mCurrentTemp = new V8Object(mV8rt); - mCurrentTemp.add("temp", "absolute"); - mCurrentTemp.add("duration", 0); - mCurrentTemp.add("rate", 0); - mV8rt.add(PARAM_currentTemp, mCurrentTemp); - // IOB data - mIobData = new V8Object(mV8rt); - mIobData.add("iob", 0); //netIob - mIobData.add("activity", 0); //netActivity - mIobData.add("bolussnooze", 0); //bolusIob - mIobData.add("basaliob", 0); - mIobData.add("netbasalinsulin", 0); - mIobData.add("hightempinsulin", 0); - mV8rt.add(PARAM_iobData, mIobData); - // Glucose status - mGlucoseStatus = new V8Object(mV8rt); - mGlucoseStatus.add("glucose", 0); - mGlucoseStatus.add("delta", 0); - mGlucoseStatus.add("avgdelta", 0); - mV8rt.add(PARAM_glucoseStatus, mGlucoseStatus); - // Meal data - mMealData = new V8Object(mV8rt); - mMealData.add("carbs", 0); - mMealData.add("boluses", 0); - mV8rt.add(PARAM_meal_data, mMealData); } public DetermineBasalResultMA invoke() { - mV8rt.executeVoidScript( - "console.error(\"determine_basal(\"+\n" + - "JSON.stringify(" + PARAM_glucoseStatus + ")+ \", \" +\n" + - "JSON.stringify(" + PARAM_currentTemp + ")+ \", \" +\n" + - "JSON.stringify(" + PARAM_iobData + ")+ \", \" +\n" + - "JSON.stringify(" + PARAM_profile + ")+ \", \" +\n" + - "JSON.stringify(" + PARAM_meal_data + ")+ \") \");" - ); - mV8rt.executeVoidScript( - "var rT = determine_basal(" + - PARAM_glucoseStatus + ", " + - PARAM_currentTemp + ", " + - PARAM_iobData + ", " + - PARAM_profile + ", " + - "undefined, " + - PARAM_meal_data + ", " + - "setTempBasal" + - ");"); + DetermineBasalResultMA determineBasalResultMA = null; + Context rhino = Context.enter(); + Scriptable scope = rhino.initStandardObjects(); + // Turn off optimization to make Rhino Android compatible + rhino.setOptimizationLevel(-1); - String ret = mV8rt.executeStringScript("JSON.stringify(rT);"); - if (Config.logAPSResult) - log.debug("Result: " + ret); - - V8Object v8ObjectReuslt = mV8rt.getObject("rT"); - - DetermineBasalResultMA result = null; try { - result = new DetermineBasalResultMA(v8ObjectReuslt, new JSONObject(ret)); - } catch (JSONException e) { - log.error("Unhandled exception", e); + + //register logger callback for console.log and console.error + ScriptableObject.defineClass(scope, LoggerCallback.class); + Scriptable myLogger = rhino.newObject(scope, "LoggerCallback", null); + scope.put("console", scope, myLogger); + + //set module parent + rhino.evaluateString(scope, "var module = {\"parent\":Boolean(1)};", "JavaScript", 0, null); + + //generate functions "determine_basal" and "setTempBasal" + rhino.evaluateString(scope, readFile("OpenAPSMA/determine-basal.js"), "JavaScript", 0, null); + + String setTempBasalCode = "var setTempBasal = function (rate, duration, profile, rT, offline) {" + + "rT.duration = duration;\n" + + " rT.rate = rate;" + + "return rT;" + + "};"; + rhino.evaluateString(scope, setTempBasalCode, "setTempBasal.js", 0, null); + Object determineBasalObj = scope.get("determine_basal", scope); + Object setTempBasalObj = scope.get("setTempBasal", scope); + + //call determine-basal + if (determineBasalObj instanceof Function && setTempBasalObj instanceof Function) { + Function determineBasalJS = (Function) determineBasalObj; + Function setTempBasalJS = (Function) setTempBasalObj; + + //prepare parameters + Object[] params = new Object[]{ + makeParam(mGlucoseStatus, rhino, scope), + makeParam(mCurrentTemp, rhino, scope), + makeParam(mIobData, rhino, scope), + makeParam(mProfile, rhino, scope), + "undefined", + makeParam(mMealData, rhino, scope), + setTempBasalJS}; + + NativeObject jsResult = (NativeObject) determineBasalJS.call(rhino, scope, scope, params); + + // Parse the jsResult object to a JSON-String + String result = NativeJSON.stringify(rhino, scope, jsResult, null, null).toString(); + if (Config.logAPSResult) + log.debug("Result: " + result); + try { + determineBasalResultMA = new DetermineBasalResultMA(jsResult, new JSONObject(result)); + } catch (JSONException e) { + log.error("Unhandled exception", e); + } + } else { + log.debug("Problem loading JS Functions"); + } + } catch (IOException e) { + log.debug("IOException"); + } catch (RhinoException e) { + log.error("RhinoException: (" + e.lineNumber() + "," + e.columnNumber() + ") " + e.toString()); + } catch (IllegalAccessException e) { + log.error(e.toString()); + } catch (InstantiationException e) { + log.error(e.toString()); + } catch (InvocationTargetException e) { + log.error(e.toString()); + } finally { + Context.exit(); } - storedGlucoseStatus = mV8rt.executeStringScript("JSON.stringify(" + PARAM_glucoseStatus + ");"); - storedIobData = mV8rt.executeStringScript("JSON.stringify(" + PARAM_iobData + ");"); - storedCurrentTemp = mV8rt.executeStringScript("JSON.stringify(" + PARAM_currentTemp + ");"); - storedProfile = mV8rt.executeStringScript("JSON.stringify(" + PARAM_profile + ");"); - storedMeal_data = mV8rt.executeStringScript("JSON.stringify(" + PARAM_meal_data + ");"); + storedGlucoseStatus = mGlucoseStatus.toString(); + storedIobData = mIobData.toString(); + storedCurrentTemp = mCurrentTemp.toString(); + storedProfile = mProfile.toString(); + storedMeal_data = mMealData.toString(); - return result; + return determineBasalResultMA; } String getGlucoseStatusParam() { @@ -167,57 +149,6 @@ public class DetermineBasalAdapterMAJS { return storedMeal_data; } - private void loadScript() throws IOException { - mV8rt.executeVoidScript( - readFile("OpenAPSMA/determine-basal.js"), - "OpenAPSMA/bin/oref0-determine-basal.js", - 0); - mV8rt.executeVoidScript("var determine_basal = module.exports;"); - mV8rt.executeVoidScript( - "var setTempBasal = function (rate, duration, profile, rT, offline) {" + - "rT.duration = duration;\n" + - " rT.rate = rate;" + - "return rT;" + - "};", - "setTempBasal.js", - 0 - ); - } - - private void initModuleParent() { - mV8rt.executeVoidScript("var module = {\"parent\":Boolean(1)};"); - } - - private void initProcessExitCallback() { - JavaVoidCallback callbackProccessExit = new JavaVoidCallback() { - @Override - public void invoke(V8Object arg0, V8Array parameters) { - if (parameters.length() > 0) { - Object arg1 = parameters.get(0); - log.error("ProccessExit " + arg1); - } - } - }; - mV8rt.registerJavaMethod(callbackProccessExit, "proccessExit"); - mV8rt.executeVoidScript("var process = {\"exit\": function () { proccessExit(); } };"); - } - - private void initLogCallback() { - JavaVoidCallback callbackLog = new JavaVoidCallback() { - @Override - public void invoke(V8Object arg0, V8Array parameters) { - if (parameters.length() > 0) { - Object arg1 = parameters.get(0); - if (Config.logAPSResult) - log.debug("Input params: " + arg1); - } - } - }; - mV8rt.registerJavaMethod(callbackLog, "log"); - mV8rt.executeVoidScript("var console = {\"log\":log, \"error\":log};"); - } - - public void setData(Profile profile, double maxIob, double maxBasal, @@ -227,57 +158,52 @@ public class DetermineBasalAdapterMAJS { PumpInterface pump, IobTotal iobData, GlucoseStatus glucoseStatus, - MealData mealData) { + MealData mealData) throws JSONException { String units = profile.getUnits(); - mProfile.add("max_iob", maxIob); - mProfile.add("dia", Math.min(profile.getDia(), 3d)); - mProfile.add("type", "current"); - mProfile.add("max_daily_basal", profile.getMaxDailyBasal()); - mProfile.add("max_basal", maxBasal); - mProfile.add("min_bg", minBg); - mProfile.add("max_bg", maxBg); - mProfile.add("target_bg", targetBg); - mProfile.add("carb_ratio", profile.getIc()); - mProfile.add("sens", Profile.toMgdl(profile.getIsf().doubleValue(), units)); + mProfile = new JSONObject(); + mProfile.put("max_iob", maxIob); + mProfile.put("dia", Math.min(profile.getDia(), 3d)); + mProfile.put("type", "current"); + mProfile.put("max_daily_basal", profile.getMaxDailyBasal()); + mProfile.put("max_basal", maxBasal); + mProfile.put("min_bg", minBg); + mProfile.put("max_bg", maxBg); + mProfile.put("target_bg", targetBg); + mProfile.put("carb_ratio", profile.getIc()); + mProfile.put("sens", Profile.toMgdl(profile.getIsf().doubleValue(), units)); - mProfile.add("current_basal", pump.getBaseBasalRate()); + mProfile.put("current_basal", pump.getBaseBasalRate()); if (units.equals(Constants.MMOL)) { - mProfile.add("out_units", "mmol/L"); + mProfile.put("out_units", "mmol/L"); } - mCurrentTemp.add("duration", MainApp.getConfigBuilder().getTempBasalRemainingMinutesFromHistory()); - mCurrentTemp.add("rate", MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory()); + mCurrentTemp = new JSONObject(); + mCurrentTemp.put("duration", MainApp.getConfigBuilder().getTempBasalRemainingMinutesFromHistory()); + mCurrentTemp.put("rate", MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory()); - mIobData.add("iob", iobData.iob); //netIob - mIobData.add("activity", iobData.activity); //netActivity - mIobData.add("bolussnooze", iobData.bolussnooze); //bolusIob - mIobData.add("basaliob", iobData.basaliob); - mIobData.add("netbasalinsulin", iobData.netbasalinsulin); - mIobData.add("hightempinsulin", iobData.hightempinsulin); + mIobData = new JSONObject(); + mIobData.put("iob", iobData.iob); //netIob + mIobData.put("activity", iobData.activity); //netActivity + mIobData.put("bolussnooze", iobData.bolussnooze); //bolusIob + mIobData.put("basaliob", iobData.basaliob); + mIobData.put("netbasalinsulin", iobData.netbasalinsulin); + mIobData.put("hightempinsulin", iobData.hightempinsulin); - mGlucoseStatus.add("glucose", glucoseStatus.glucose); + mGlucoseStatus = new JSONObject(); + mGlucoseStatus.put("glucose", glucoseStatus.glucose); if (SP.getBoolean("always_use_shortavg", false)) { - mGlucoseStatus.add("delta", glucoseStatus.short_avgdelta); + mGlucoseStatus.put("delta", glucoseStatus.short_avgdelta); } else { - mGlucoseStatus.add("delta", glucoseStatus.delta); + mGlucoseStatus.put("delta", glucoseStatus.delta); } - mGlucoseStatus.add("avgdelta", glucoseStatus.avgdelta); + mGlucoseStatus.put("avgdelta", glucoseStatus.avgdelta); - mMealData.add("carbs", mealData.carbs); - mMealData.add("boluses", mealData.boluses); - } - - - public void release() { - mProfile.release(); - mCurrentTemp.release(); - mIobData.release(); - mMealData.release(); - mGlucoseStatus.release(); - mV8rt.release(); + mMealData = new JSONObject(); + mMealData.put("carbs", mealData.carbs); + mMealData.put("boluses", mealData.boluses); } public String readFile(String filename) throws IOException { @@ -289,4 +215,14 @@ public class DetermineBasalAdapterMAJS { return string; } + public Object makeParam(JSONObject jsonObject, Context rhino, Scriptable scope) { + Object param = NativeJSON.parse(rhino, scope, jsonObject.toString(), new Callable() { + @Override + public Object call(Context context, Scriptable scriptable, Scriptable scriptable1, Object[] objects) { + return objects[1]; + } + }); + return param; + } + } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/DetermineBasalResultMA.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/DetermineBasalResultMA.java index 23f7c14a88..e132c9f953 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/DetermineBasalResultMA.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/DetermineBasalResultMA.java @@ -1,12 +1,8 @@ package info.nightscout.androidaps.plugins.OpenAPSMA; -import android.os.Parcel; -import android.os.Parcelable; - -import com.eclipsesource.v8.V8Object; - import org.json.JSONException; import org.json.JSONObject; +import org.mozilla.javascript.NativeObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,38 +18,37 @@ public class DetermineBasalResultMA extends APSResult { public String mealAssist; public IobTotal iob; - public DetermineBasalResultMA(V8Object result, JSONObject j) { + public DetermineBasalResultMA(NativeObject result, JSONObject j) { json = j; - if (result.contains("error")) { - reason = result.getString("error"); + if (result.containsKey("error")) { + reason = (String) result.get("error"); changeRequested = false; rate = -1; duration = -1; mealAssist = ""; } else { - reason = result.getString("reason"); - eventualBG = result.getDouble("eventualBG"); - snoozeBG = result.getDouble("snoozeBG"); - if (result.contains("rate")) { - rate = result.getDouble("rate"); + reason = result.get("reason").toString(); + eventualBG = (Double) result.get("eventualBG"); + snoozeBG = (Double) result.get("snoozeBG"); + if (result.containsKey("rate")) { + rate = (Double) result.get("rate"); if (rate < 0d) rate = 0d; changeRequested = true; } else { rate = -1; changeRequested = false; } - if (result.contains("duration")) { - duration = result.getInteger("duration"); + if (result.containsKey("duration")) { + duration = ((Double) result.get("duration")).intValue(); //changeRequested as above } else { duration = -1; changeRequested = false; } - if (result.contains("mealAssist")) { - mealAssist = result.getString("mealAssist"); + if (result.containsKey("mealAssist")) { + mealAssist = result.get("mealAssist").toString(); } else mealAssist = ""; } - result.release(); } public DetermineBasalResultMA() { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/LoggerCallback.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/LoggerCallback.java new file mode 100644 index 0000000000..991ec2b25e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/LoggerCallback.java @@ -0,0 +1,63 @@ +package info.nightscout.androidaps.plugins.OpenAPSMA; + +import org.mozilla.javascript.ScriptableObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.utils.ToastUtils; + +/** + * Created by adrian on 15/10/17. + */ + + +public class LoggerCallback extends ScriptableObject { + + private static Logger log = LoggerFactory.getLogger(DetermineBasalAdapterMAJS.class); + + static StringBuffer errorBuffer = new StringBuffer(); + static StringBuffer logBuffer = new StringBuffer(); + + + public LoggerCallback() { + //empty constructor needed for Rhino + errorBuffer = new StringBuffer(); + logBuffer = new StringBuffer(); + } + + @Override + public String getClassName() { + return "LoggerCallback"; + } + + public void jsConstructor() { + //empty constructor on JS site; could work as setter + } + + public void jsFunction_log(Object obj1) { + log.debug(obj1.toString()); + logBuffer.append(obj1.toString()); + logBuffer.append(' '); + } + + public void jsFunction_error(Object obj1) { + log.error(obj1.toString()); + errorBuffer.append(obj1.toString()); + errorBuffer.append(' '); + } + + + + public static String getScriptDebug(){ + String ret = ""; + if(errorBuffer.length() > 0){ + ret += "e:\n" + errorBuffer.toString(); + } + if(ret.length() > 0 && logBuffer.length() > 0) ret += '\n'; + if(logBuffer.length() > 0){ + ret += "d:\n" + logBuffer.toString(); + } + return ret; + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/OpenAPSMAPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/OpenAPSMAPlugin.java index 42741340b5..9b9bdcbbf8 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/OpenAPSMAPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/OpenAPSMAPlugin.java @@ -211,12 +211,16 @@ public class OpenAPSMAPlugin implements PluginBase, APSInterface { if (!checkOnlyHardLimits(pump.getBaseBasalRate(), "current_basal", 0.01, 5)) return; start = new Date(); - determineBasalAdapterMAJS.setData(profile, maxIob, maxBasal, minBg, maxBg, targetBg, pump, iobTotal, glucoseStatus, mealData); + try { + determineBasalAdapterMAJS.setData(profile, maxIob, maxBasal, minBg, maxBg, targetBg, pump, iobTotal, glucoseStatus, mealData); + } catch (JSONException e) { + log.error("Unhandled exception", e); + } Profiler.log(log, "MA calculation", start); DetermineBasalResultMA determineBasalResultMA = determineBasalAdapterMAJS.invoke(); - // Fix bug determine basal + // Fix bug determinef basal if (determineBasalResultMA.rate == 0d && determineBasalResultMA.duration == 0 && !MainApp.getConfigBuilder().isTempBasalInProgress()) determineBasalResultMA.changeRequested = false; // limit requests on openloop mode @@ -229,8 +233,6 @@ public class OpenAPSMAPlugin implements PluginBase, APSInterface { determineBasalResultMA.iob = iobTotal; - determineBasalAdapterMAJS.release(); - try { determineBasalResultMA.json.put("timestamp", DateUtil.toISOString(now)); } catch (JSONException e) { @@ -241,8 +243,6 @@ public class OpenAPSMAPlugin implements PluginBase, APSInterface { lastAPSResult = determineBasalResultMA; lastAPSRun = now; MainApp.bus().post(new EventOpenAPSUpdateGui()); - - //deviceStatus.suggested = determineBasalResultMA.json; }