Merge pull request #461 from AdrianLxM/rhinoAMA

Rhino ama
This commit is contained in:
AdrianLxM 2017-10-15 22:39:54 +02:00 committed by GitHub
commit 2a8ab96fe3
10 changed files with 404 additions and 399 deletions

View file

@ -43,6 +43,7 @@ android {
applicationId "info.nightscout.androidaps" applicationId "info.nightscout.androidaps"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 23 targetSdkVersion 23
multiDexEnabled true
versionCode 1500 versionCode 1500
version "1.54-dev" version "1.54-dev"
buildConfigField "String", "VERSION", '"' + version + '"' buildConfigField "String", "VERSION", '"' + version + '"'

BIN
app/libs/rhino-1.7.7.2.jar Normal file

Binary file not shown.

View file

@ -0,0 +1,13 @@
var console = { };
console.error = function error(){
console2.error(arguments.length);
for (var i = 0, len = arguments.length; i < len; i++) {
console2.error(arguments[i]);
}
};
console.log = function log(){
for (var i = 0, len = arguments.length; i < len; i++) {
console2.log(arguments[i]);
}
};

View file

@ -1,16 +1,22 @@
package info.nightscout.androidaps.plugins.OpenAPSAMA; package info.nightscout.androidaps.plugins.OpenAPSAMA;
import com.eclipsesource.v8.JavaVoidCallback; import org.json.JSONArray;
import com.eclipsesource.v8.V8;
import com.eclipsesource.v8.V8Array;
import com.eclipsesource.v8.V8Object;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Date; import java.util.Date;
import info.nightscout.androidaps.Config; 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.IobCobCalculator.IobCobCalculatorPlugin;
import info.nightscout.androidaps.plugins.Loop.ScriptReader; import info.nightscout.androidaps.plugins.Loop.ScriptReader;
import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.Profile;
import info.nightscout.androidaps.plugins.OpenAPSMA.LoggerCallback;
import info.nightscout.utils.SP; import info.nightscout.utils.SP;
public class DetermineBasalAdapterAMAJS { public class DetermineBasalAdapterAMAJS {
@ -31,20 +38,13 @@ public class DetermineBasalAdapterAMAJS {
private ScriptReader mScriptReader = null; 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 JSONObject mProfile;
private final String PARAM_iobData = "iobData"; private JSONObject mGlucoseStatus;
private final String PARAM_glucoseStatus = "glucose_status"; private JSONArray mIobData;
private final String PARAM_profile = "profile"; private JSONObject mMealData;
private final String PARAM_meal_data = "meal_data"; private JSONObject mCurrentTemp;
private final String PARAM_autosens_data = "autosens_data"; private JSONObject mAutosensData = null;
private String storedCurrentTemp = null; private String storedCurrentTemp = null;
private String storedIobData = null; private String storedIobData = null;
@ -55,58 +55,101 @@ public class DetermineBasalAdapterAMAJS {
private String scriptDebug = ""; private String scriptDebug = "";
/**
* Main code
*/
public DetermineBasalAdapterAMAJS(ScriptReader scriptReader) throws IOException { public DetermineBasalAdapterAMAJS(ScriptReader scriptReader) throws IOException {
mV8rt = V8.createV8Runtime();
mScriptReader = scriptReader; mScriptReader = scriptReader;
initLogCallback();
initProcessExitCallback();
initModuleParent();
loadScript();
} }
public DetermineBasalResultAMA invoke() { public DetermineBasalResultAMA invoke() {
log.debug(">>> Invoking detemine_basal <<<"); log.debug(">>> Invoking detemine_basal <<<");
log.debug("Glucose status: " + (storedGlucoseStatus = mV8rt.executeStringScript("JSON.stringify(" + PARAM_glucoseStatus + ");"))); log.debug("Glucose status: " + (storedGlucoseStatus = mGlucoseStatus.toString()));
log.debug("IOB data: " + (storedIobData = mV8rt.executeStringScript("JSON.stringify(" + PARAM_iobData + ");"))); log.debug("IOB data: " + (storedIobData = mIobData.toString()));
log.debug("Current temp: " + (storedCurrentTemp = mV8rt.executeStringScript("JSON.stringify(" + PARAM_currentTemp + ");"))); log.debug("Current temp: " + (storedCurrentTemp = mCurrentTemp.toString()));
log.debug("Profile: " + (storedProfile = mV8rt.executeStringScript("JSON.stringify(" + PARAM_profile + ");"))); log.debug("Profile: " + (storedProfile = mProfile.toString()));
log.debug("Meal data: " + (storedMeal_data = mV8rt.executeStringScript("JSON.stringify(" + PARAM_meal_data + ");"))); log.debug("Meal data: " + (storedMeal_data = mMealData.toString()));
if (mAutosensData != null) 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 else
log.debug("Autosens data: " + (storedAutosens_data = "undefined")); log.debug("Autosens data: " + (storedAutosens_data = "undefined"));
mV8rt.executeVoidScript( DetermineBasalResultAMA determineBasalResultAMA = null;
"var rT = determine_basal(" +
PARAM_glucoseStatus + ", " +
PARAM_currentTemp + ", " +
PARAM_iobData + ", " +
PARAM_profile + ", " +
PARAM_autosens_data + ", " +
PARAM_meal_data + ", " +
"tempBasalFunctions" +
");");
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 { try {
result = new DetermineBasalResultAMA(v8ObjectReuslt, new JSONObject(ret));
} catch (JSONException e) { //register logger callback for console.log and console.error
log.error("Unhandled exception", e); 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() { String getGlucoseStatusParam() {
@ -137,60 +180,6 @@ public class DetermineBasalAdapterAMAJS {
return scriptDebug; 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, public void setData(Profile profile,
double maxIob, double maxIob,
double maxBasal, double maxBasal,
@ -203,89 +192,94 @@ public class DetermineBasalAdapterAMAJS {
MealData mealData, MealData mealData,
double autosensDataRatio, double autosensDataRatio,
boolean tempTargetSet, boolean tempTargetSet,
double min_5m_carbimpact) { double min_5m_carbimpact) throws JSONException {
String units = profile.getUnits(); String units = profile.getUnits();
mProfile = new V8Object(mV8rt); mProfile = new JSONObject();
mProfile.add("max_iob", maxIob); mProfile.put("max_iob", maxIob);
mProfile.add("dia", Math.min(profile.getDia(), 3d)); mProfile.put("dia", Math.min(profile.getDia(), 3d));
mProfile.add("type", "current"); mProfile.put("type", "current");
mProfile.add("max_daily_basal", profile.getMaxDailyBasal()); mProfile.put("max_daily_basal", profile.getMaxDailyBasal());
mProfile.add("max_basal", maxBasal); mProfile.put("max_basal", maxBasal);
mProfile.add("min_bg", minBg); mProfile.put("min_bg", minBg);
mProfile.add("max_bg", maxBg); mProfile.put("max_bg", maxBg);
mProfile.add("target_bg", targetBg); mProfile.put("target_bg", targetBg);
mProfile.add("carb_ratio", profile.getIc()); mProfile.put("carb_ratio", profile.getIc());
mProfile.add("sens", Profile.toMgdl(profile.getIsf().doubleValue(), units)); mProfile.put("sens", Profile.toMgdl(profile.getIsf().doubleValue(), units));
mProfile.add("max_daily_safety_multiplier", SP.getInt("openapsama_max_daily_safety_multiplier", 3)); mProfile.put("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.put("current_basal_safety_multiplier", SP.getInt("openapsama_current_basal_safety_multiplier", 4));
mProfile.add("skip_neutral_temps", true); mProfile.put("skip_neutral_temps", true);
mProfile.add("current_basal", pump.getBaseBasalRate()); mProfile.put("current_basal", pump.getBaseBasalRate());
mProfile.add("temptargetSet", tempTargetSet); mProfile.put("temptargetSet", tempTargetSet);
mProfile.add("autosens_adjust_targets", SP.getBoolean("openapsama_autosens_adjusttargets", true)); mProfile.put("autosens_adjust_targets", SP.getBoolean("openapsama_autosens_adjusttargets", true));
mProfile.add("min_5m_carbimpact", SP.getDouble("openapsama_min_5m_carbimpact", 3d)); mProfile.put("min_5m_carbimpact", SP.getDouble("openapsama_min_5m_carbimpact", 3d));
if (units.equals(Constants.MMOL)) { 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 = new JSONObject();
mCurrentTemp.add("temp", "absolute"); mCurrentTemp.put("temp", "absolute");
mCurrentTemp.add("duration", MainApp.getConfigBuilder().getTempBasalRemainingMinutesFromHistory()); mCurrentTemp.put("duration", MainApp.getConfigBuilder().getTempBasalRemainingMinutesFromHistory());
mCurrentTemp.add("rate", MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory()); mCurrentTemp.put("rate", MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory());
// as we have non default temps longer than 30 mintues // as we have non default temps longer than 30 mintues
TemporaryBasal tempBasal = MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis()); TemporaryBasal tempBasal = MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis());
if (tempBasal != null) { 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()); mGlucoseStatus = new JSONObject();
mV8rt.add(PARAM_iobData, mIobData); mGlucoseStatus.put("glucose", glucoseStatus.glucose);
mGlucoseStatus = new V8Object(mV8rt);
mGlucoseStatus.add("glucose", glucoseStatus.glucose);
if (SP.getBoolean("always_use_shortavg", false)) { if (SP.getBoolean("always_use_shortavg", false)) {
mGlucoseStatus.add("delta", glucoseStatus.short_avgdelta); mGlucoseStatus.put("delta", glucoseStatus.short_avgdelta);
} else { } else {
mGlucoseStatus.add("delta", glucoseStatus.delta); mGlucoseStatus.put("delta", glucoseStatus.delta);
} }
mGlucoseStatus.add("short_avgdelta", glucoseStatus.short_avgdelta); mGlucoseStatus.put("short_avgdelta", glucoseStatus.short_avgdelta);
mGlucoseStatus.add("long_avgdelta", glucoseStatus.long_avgdelta); mGlucoseStatus.put("long_avgdelta", glucoseStatus.long_avgdelta);
mV8rt.add(PARAM_glucoseStatus, mGlucoseStatus);
mMealData = new V8Object(mV8rt); mMealData = new JSONObject();
mMealData.add("carbs", mealData.carbs); mMealData.put("carbs", mealData.carbs);
mMealData.add("boluses", mealData.boluses); mMealData.put("boluses", mealData.boluses);
mMealData.add("mealCOB", mealData.mealCOB); mMealData.put("mealCOB", mealData.mealCOB);
mV8rt.add(PARAM_meal_data, mMealData);
if (MainApp.getConfigBuilder().isAMAModeEnabled()) { if (MainApp.getConfigBuilder().isAMAModeEnabled()) {
mAutosensData = new V8Object(mV8rt); mAutosensData = new JSONObject();
mAutosensData.add("ratio", autosensDataRatio); mAutosensData.put("ratio", autosensDataRatio);
mV8rt.add(PARAM_autosens_data, mAutosensData);
} else { } else {
mV8rt.addUndefined(PARAM_autosens_data); mAutosensData = null;
} }
} }
public void release() { public Object makeParam(JSONObject jsonObject, Context rhino, Scriptable scope) {
mProfile.release();
mCurrentTemp.release(); if(jsonObject == null) return Undefined.instance;
mIobData.release();
mMealData.release(); Object param = NativeJSON.parse(rhino, scope, jsonObject.toString(), new Callable() {
mGlucoseStatus.release(); @Override
if (mAutosensData != null) { public Object call(Context context, Scriptable scriptable, Scriptable scriptable1, Object[] objects) {
mAutosensData.release(); return objects[1];
} }
mV8rt.release(); });
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 { public String readFile(String filename) throws IOException {

View file

@ -5,6 +5,7 @@ import com.eclipsesource.v8.V8Object;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.mozilla.javascript.NativeObject;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -25,35 +26,34 @@ public class DetermineBasalResultAMA extends APSResult {
public double snoozeBG; public double snoozeBG;
public IobTotal iob; public IobTotal iob;
public DetermineBasalResultAMA(V8Object result, JSONObject j) { public DetermineBasalResultAMA(NativeObject result, JSONObject j) {
date = new Date(); date = new Date();
json = j; json = j;
if (result.contains("error")) { if (result.containsKey("error")) {
reason = result.getString("error"); reason = result.get("error").toString();
changeRequested = false; changeRequested = false;
rate = -1; rate = -1;
duration = -1; duration = -1;
} else { } else {
reason = result.getString("reason"); reason = result.get("reason").toString();
if (result.contains("eventualBG")) eventualBG = result.getDouble("eventualBG"); if (result.containsKey("eventualBG")) eventualBG = (Double) result.get("eventualBG");
if (result.contains("snoozeBG")) snoozeBG = result.getDouble("snoozeBG"); if (result.containsKey("snoozeBG")) snoozeBG = (Double) result.get("snoozeBG");
if (result.contains("rate")) { if (result.containsKey("rate")) {
rate = result.getDouble("rate"); rate = (Double) result.get("rate");
if (rate < 0d) rate = 0d; if (rate < 0d) rate = 0d;
changeRequested = true; changeRequested = true;
} else { } else {
rate = -1; rate = -1;
changeRequested = false; changeRequested = false;
} }
if (result.contains("duration")) { if (result.containsKey("duration")) {
duration = result.getInteger("duration"); duration = ((Double)result.get("duration")).intValue();
//changeRequested as above //changeRequested as above
} else { } else {
duration = -1; duration = -1;
changeRequested = false; changeRequested = false;
} }
} }
result.release();
} }
public DetermineBasalResultAMA() { public DetermineBasalResultAMA() {

View file

@ -222,11 +222,16 @@ public class OpenAPSAMAPlugin implements PluginBase, APSInterface {
Profiler.log(log, "AMA data gathering", start); Profiler.log(log, "AMA data gathering", start);
start = new Date(); start = new Date();
determineBasalAdapterAMAJS.setData(profile, maxIob, maxBasal, minBg, maxBg, targetBg, pump, iobArray, glucoseStatus, mealData,
lastAutosensResult.ratio, //autosensDataRatio try {
isTempTarget, determineBasalAdapterAMAJS.setData(profile, maxIob, maxBasal, minBg, maxBg, targetBg, pump, iobArray, glucoseStatus, mealData,
SafeParse.stringToDouble(SP.getString("openapsama_min_5m_carbimpact", "3.0"))//min_5m_carbimpact 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(); DetermineBasalResultAMA determineBasalResultAMA = determineBasalAdapterAMAJS.invoke();
@ -244,8 +249,6 @@ public class OpenAPSAMAPlugin implements PluginBase, APSInterface {
determineBasalResultAMA.iob = iobArray[0]; determineBasalResultAMA.iob = iobArray[0];
determineBasalAdapterAMAJS.release();
Date now = new Date(); Date now = new Date();
try { try {

View file

@ -1,16 +1,20 @@
package info.nightscout.androidaps.plugins.OpenAPSMA; 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.JSONException;
import org.json.JSONObject; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import info.nightscout.androidaps.Config; import info.nightscout.androidaps.Config;
import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.Constants;
@ -26,20 +30,12 @@ import info.nightscout.utils.SP;
public class DetermineBasalAdapterMAJS { public class DetermineBasalAdapterMAJS {
private static Logger log = LoggerFactory.getLogger(DetermineBasalAdapterMAJS.class); private static Logger log = LoggerFactory.getLogger(DetermineBasalAdapterMAJS.class);
private ScriptReader mScriptReader = null; private ScriptReader mScriptReader = null;
V8 mV8rt; private JSONObject mProfile;
private V8Object mProfile; private JSONObject mGlucoseStatus;
private V8Object mGlucoseStatus; private JSONObject mIobData;
private V8Object mIobData; private JSONObject mMealData;
private V8Object mMealData; private JSONObject mCurrentTemp;
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 String storedCurrentTemp = null; private String storedCurrentTemp = null;
public String storedIobData = null; public String storedIobData = null;
@ -47,104 +43,90 @@ public class DetermineBasalAdapterMAJS {
private String storedProfile = null; private String storedProfile = null;
private String storedMeal_data = null; private String storedMeal_data = null;
/**
* Main code
*/
public DetermineBasalAdapterMAJS(ScriptReader scriptReader) throws IOException { public DetermineBasalAdapterMAJS(ScriptReader scriptReader) throws IOException {
mV8rt = V8.createV8Runtime();
mScriptReader = scriptReader; 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() { public DetermineBasalResultMA invoke() {
mV8rt.executeVoidScript( DetermineBasalResultMA determineBasalResultMA = null;
"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" +
");");
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 { try {
result = new DetermineBasalResultMA(v8ObjectReuslt, new JSONObject(ret));
} catch (JSONException e) { //register logger callback for console.log and console.error
log.error("Unhandled exception", e); 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 + ");"); storedGlucoseStatus = mGlucoseStatus.toString();
storedIobData = mV8rt.executeStringScript("JSON.stringify(" + PARAM_iobData + ");"); storedIobData = mIobData.toString();
storedCurrentTemp = mV8rt.executeStringScript("JSON.stringify(" + PARAM_currentTemp + ");"); storedCurrentTemp = mCurrentTemp.toString();
storedProfile = mV8rt.executeStringScript("JSON.stringify(" + PARAM_profile + ");"); storedProfile = mProfile.toString();
storedMeal_data = mV8rt.executeStringScript("JSON.stringify(" + PARAM_meal_data + ");"); storedMeal_data = mMealData.toString();
return result; return determineBasalResultMA;
} }
String getGlucoseStatusParam() { String getGlucoseStatusParam() {
@ -167,57 +149,6 @@ public class DetermineBasalAdapterMAJS {
return storedMeal_data; 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, public void setData(Profile profile,
double maxIob, double maxIob,
double maxBasal, double maxBasal,
@ -227,57 +158,52 @@ public class DetermineBasalAdapterMAJS {
PumpInterface pump, PumpInterface pump,
IobTotal iobData, IobTotal iobData,
GlucoseStatus glucoseStatus, GlucoseStatus glucoseStatus,
MealData mealData) { MealData mealData) throws JSONException {
String units = profile.getUnits(); String units = profile.getUnits();
mProfile.add("max_iob", maxIob); mProfile = new JSONObject();
mProfile.add("dia", Math.min(profile.getDia(), 3d)); mProfile.put("max_iob", maxIob);
mProfile.add("type", "current"); mProfile.put("dia", Math.min(profile.getDia(), 3d));
mProfile.add("max_daily_basal", profile.getMaxDailyBasal()); mProfile.put("type", "current");
mProfile.add("max_basal", maxBasal); mProfile.put("max_daily_basal", profile.getMaxDailyBasal());
mProfile.add("min_bg", minBg); mProfile.put("max_basal", maxBasal);
mProfile.add("max_bg", maxBg); mProfile.put("min_bg", minBg);
mProfile.add("target_bg", targetBg); mProfile.put("max_bg", maxBg);
mProfile.add("carb_ratio", profile.getIc()); mProfile.put("target_bg", targetBg);
mProfile.add("sens", Profile.toMgdl(profile.getIsf().doubleValue(), units)); 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)) { if (units.equals(Constants.MMOL)) {
mProfile.add("out_units", "mmol/L"); mProfile.put("out_units", "mmol/L");
} }
mCurrentTemp.add("duration", MainApp.getConfigBuilder().getTempBasalRemainingMinutesFromHistory()); mCurrentTemp = new JSONObject();
mCurrentTemp.add("rate", MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory()); mCurrentTemp.put("duration", MainApp.getConfigBuilder().getTempBasalRemainingMinutesFromHistory());
mCurrentTemp.put("rate", MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory());
mIobData.add("iob", iobData.iob); //netIob mIobData = new JSONObject();
mIobData.add("activity", iobData.activity); //netActivity mIobData.put("iob", iobData.iob); //netIob
mIobData.add("bolussnooze", iobData.bolussnooze); //bolusIob mIobData.put("activity", iobData.activity); //netActivity
mIobData.add("basaliob", iobData.basaliob); mIobData.put("bolussnooze", iobData.bolussnooze); //bolusIob
mIobData.add("netbasalinsulin", iobData.netbasalinsulin); mIobData.put("basaliob", iobData.basaliob);
mIobData.add("hightempinsulin", iobData.hightempinsulin); 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)) { if (SP.getBoolean("always_use_shortavg", false)) {
mGlucoseStatus.add("delta", glucoseStatus.short_avgdelta); mGlucoseStatus.put("delta", glucoseStatus.short_avgdelta);
} else { } 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 = new JSONObject();
mMealData.add("boluses", mealData.boluses); mMealData.put("carbs", mealData.carbs);
} mMealData.put("boluses", mealData.boluses);
public void release() {
mProfile.release();
mCurrentTemp.release();
mIobData.release();
mMealData.release();
mGlucoseStatus.release();
mV8rt.release();
} }
public String readFile(String filename) throws IOException { public String readFile(String filename) throws IOException {
@ -289,4 +215,14 @@ public class DetermineBasalAdapterMAJS {
return string; 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;
}
} }

View file

@ -1,12 +1,8 @@
package info.nightscout.androidaps.plugins.OpenAPSMA; 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.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.mozilla.javascript.NativeObject;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -22,38 +18,37 @@ public class DetermineBasalResultMA extends APSResult {
public String mealAssist; public String mealAssist;
public IobTotal iob; public IobTotal iob;
public DetermineBasalResultMA(V8Object result, JSONObject j) { public DetermineBasalResultMA(NativeObject result, JSONObject j) {
json = j; json = j;
if (result.contains("error")) { if (result.containsKey("error")) {
reason = result.getString("error"); reason = (String) result.get("error");
changeRequested = false; changeRequested = false;
rate = -1; rate = -1;
duration = -1; duration = -1;
mealAssist = ""; mealAssist = "";
} else { } else {
reason = result.getString("reason"); reason = result.get("reason").toString();
eventualBG = result.getDouble("eventualBG"); eventualBG = (Double) result.get("eventualBG");
snoozeBG = result.getDouble("snoozeBG"); snoozeBG = (Double) result.get("snoozeBG");
if (result.contains("rate")) { if (result.containsKey("rate")) {
rate = result.getDouble("rate"); rate = (Double) result.get("rate");
if (rate < 0d) rate = 0d; if (rate < 0d) rate = 0d;
changeRequested = true; changeRequested = true;
} else { } else {
rate = -1; rate = -1;
changeRequested = false; changeRequested = false;
} }
if (result.contains("duration")) { if (result.containsKey("duration")) {
duration = result.getInteger("duration"); duration = ((Double) result.get("duration")).intValue();
//changeRequested as above //changeRequested as above
} else { } else {
duration = -1; duration = -1;
changeRequested = false; changeRequested = false;
} }
if (result.contains("mealAssist")) { if (result.containsKey("mealAssist")) {
mealAssist = result.getString("mealAssist"); mealAssist = result.get("mealAssist").toString();
} else mealAssist = ""; } else mealAssist = "";
} }
result.release();
} }
public DetermineBasalResultMA() { public DetermineBasalResultMA() {

View file

@ -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;
}
}

View file

@ -211,12 +211,16 @@ public class OpenAPSMAPlugin implements PluginBase, APSInterface {
if (!checkOnlyHardLimits(pump.getBaseBasalRate(), "current_basal", 0.01, 5)) return; if (!checkOnlyHardLimits(pump.getBaseBasalRate(), "current_basal", 0.01, 5)) return;
start = new Date(); 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); Profiler.log(log, "MA calculation", start);
DetermineBasalResultMA determineBasalResultMA = determineBasalAdapterMAJS.invoke(); DetermineBasalResultMA determineBasalResultMA = determineBasalAdapterMAJS.invoke();
// Fix bug determine basal // Fix bug determinef basal
if (determineBasalResultMA.rate == 0d && determineBasalResultMA.duration == 0 && !MainApp.getConfigBuilder().isTempBasalInProgress()) if (determineBasalResultMA.rate == 0d && determineBasalResultMA.duration == 0 && !MainApp.getConfigBuilder().isTempBasalInProgress())
determineBasalResultMA.changeRequested = false; determineBasalResultMA.changeRequested = false;
// limit requests on openloop mode // limit requests on openloop mode
@ -229,8 +233,6 @@ public class OpenAPSMAPlugin implements PluginBase, APSInterface {
determineBasalResultMA.iob = iobTotal; determineBasalResultMA.iob = iobTotal;
determineBasalAdapterMAJS.release();
try { try {
determineBasalResultMA.json.put("timestamp", DateUtil.toISOString(now)); determineBasalResultMA.json.put("timestamp", DateUtil.toISOString(now));
} catch (JSONException e) { } catch (JSONException e) {
@ -241,8 +243,6 @@ public class OpenAPSMAPlugin implements PluginBase, APSInterface {
lastAPSResult = determineBasalResultMA; lastAPSResult = determineBasalResultMA;
lastAPSRun = now; lastAPSRun = now;
MainApp.bus().post(new EventOpenAPSUpdateGui()); MainApp.bus().post(new EventOpenAPSUpdateGui());
//deviceStatus.suggested = determineBasalResultMA.json;
} }