duplicate MA -> AMA

This commit is contained in:
Milos Kozak 2017-01-03 21:15:40 +01:00
parent faf8e07248
commit 3dfac49b82
8 changed files with 1333 additions and 0 deletions

View file

@ -0,0 +1,316 @@
/*
Determine Basal
Released under MIT license. See the accompanying LICENSE.txt file for
full terms and conditions
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
var determine_basal = function determine_basal(glucose_status, currenttemp, iob_data, profile, offline, meal_data, setTempBasal) {
var rT = { //short for requestedTemp
};
if (typeof profile === 'undefined' || typeof profile.current_basal === 'undefined') {
rT.error ='Error: could not get current basal rate';
return rT;
}
var bg = glucose_status.glucose;
if (bg < 38) { //Dexcom is in ??? mode or calibrating, do nothing. Asked @benwest for raw data in iter_glucose
rT.error = "CGM is calibrating or in ??? state";
return rT;
}
var max_iob = profile.max_iob; // maximum amount of non-bolus IOB OpenAPS will ever deliver
// if target_bg is set, great. otherwise, if min and max are set, then set target to their average
var target_bg;
if (typeof profile.target_bg !== 'undefined') {
target_bg = profile.target_bg;
} else {
if (typeof profile.min_bg !== 'undefined' && typeof profile.max_bg !== 'undefined') {
target_bg = (profile.min_bg + profile.max_bg) / 2;
} else {
rT.error ='Error: could not determine target_bg';
return rT;
}
}
if (typeof iob_data === 'undefined' ) {
rT.error ='Error: iob_data undefined';
return rT;
}
if (typeof iob_data.activity === 'undefined' || typeof iob_data.iob === 'undefined' || typeof iob_data.activity === 'undefined') {
rT.error ='Error: iob_data missing some property';
return rT;
}
var tick;
if (glucose_status.delta >= 0) {
tick = "+" + glucose_status.delta;
} else {
tick = glucose_status.delta;
}
var minDelta = Math.min(glucose_status.delta, glucose_status.avgdelta);
//var maxDelta = Math.max(glucose_status.delta, glucose_status.avgdelta);
//calculate BG impact: the amount BG "should" be rising or falling based on insulin activity alone
var bgi = Math.round(( -iob_data.activity * profile.sens * 5 )*100)/100;
// project positive deviations for 15 minutes
var deviation = Math.round( 15 / 5 * ( glucose_status.avgdelta - bgi ) );
// project negative deviations for 30 minutes
if (deviation < 0) {
deviation = Math.round( 30 / 5 * ( glucose_status.avgdelta - bgi ) );
}
//console.log("Avg.Delta: " + glucose_status.avgdelta.toFixed(1) + ", BGI: " + bgi.toFixed(1) + " 15m activity projection: " + deviation.toFixed(0));
// calculate the naive (bolus calculator math) eventual BG based on net IOB and sensitivity
var naive_eventualBG = Math.round( bg - (iob_data.iob * profile.sens) );
// and adjust it for the deviation above
var eventualBG = naive_eventualBG + deviation;
// calculate what portion of that is due to bolussnooze
var bolusContrib = iob_data.bolussnooze * profile.sens;
// and add it back in to get snoozeBG, plus another 50% to avoid low-temping at mealtime
var naive_snoozeBG = Math.round( naive_eventualBG + 1.5 * bolusContrib );
// adjust that for deviation like we did eventualBG
var snoozeBG = naive_snoozeBG + deviation;
//console.log("BG: " + bg +"(" + tick + ","+glucose_status.avgdelta.toFixed(1)+")"+ " -> " + eventualBG + "-" + snoozeBG + " (Unadjusted: " + naive_eventualBG + "-" + naive_snoozeBG + "), BGI: " + bgi);
var expectedDelta = Math.round(( bgi + ( target_bg - eventualBG ) / ( profile.dia * 60 / 5 ) )*10)/10;
//console.log("expectedDelta: " + expectedDelta);
if (typeof eventualBG === 'undefined' || isNaN(eventualBG)) {
rT.error ='Error: could not calculate eventualBG';
return rT;
}
// min_bg of 90 -> threshold of 70, 110 -> 80, and 130 -> 90
var threshold = profile.min_bg - 0.5*(profile.min_bg-50);
rT = {
'temp': 'absolute'
, 'bg': bg
, 'tick': tick
, 'eventualBG': eventualBG
, 'snoozeBG': snoozeBG
};
var basaliob;
if (iob_data.basaliob) { basaliob = iob_data.basaliob; }
else { basaliob = iob_data.iob - iob_data.bolussnooze; }
// allow meal assist to run when carbs are just barely covered
if (minDelta > Math.max(3, bgi) && ( (meal_data.carbs > 0 && (1.1 * meal_data.carbs/profile.carb_ratio > meal_data.boluses + basaliob)) || ( deviation > 25 && minDelta > 7 ) ) ) {
// ignore all covered IOB, and just set eventualBG to the current bg
eventualBG = Math.max(bg,eventualBG) + deviation;
rT.eventualBG = eventualBG;
profile.min_bg = 80;
target_bg = (profile.min_bg + profile.max_bg) / 2;
expectedDelta = Math.round(( bgi + ( target_bg - eventualBG ) / ( profile.dia * 60 / 5 ) )*10)/10;
rT.mealAssist = "On: Carbs: " + meal_data.carbs + " Boluses: " + meal_data.boluses + " Target: " + target_bg + " Deviation: " + deviation + " BGI: " + bgi;
} else {
rT.mealAssist = "Off: Carbs: " + meal_data.carbs + " Boluses: " + meal_data.boluses + " Target: " + target_bg + " Deviation: " + deviation + " BGI: " + bgi;
}
if (bg < threshold) { // low glucose suspend mode: BG is < ~80
rT.reason = "BG " + bg + "<" + threshold;
if ((glucose_status.delta <= 0 && glucose_status.avgdelta <= 0) || (glucose_status.delta < expectedDelta && glucose_status.avgdelta < expectedDelta)) {
// BG is still falling / rising slower than predicted
return setTempBasal(0, 30, profile, rT, offline);
}
if (glucose_status.delta > glucose_status.avgdelta) {
rT.reason += ", delta " + glucose_status.delta + ">0";
} else {
rT.reason += ", avg delta " + glucose_status.avgdelta.toFixed(2) + ">0";
}
if (currenttemp.rate > profile.current_basal) { // if a high-temp is running
rT.reason += ", cancel high temp";
return setTempBasal(0, 0, profile, rT, offline); // cancel high temp
} else if (currenttemp.duration && eventualBG > profile.max_bg) { // if low-temped and predicted to go high from negative IOB
rT.reason += ", cancel low temp";
return setTempBasal(0, 0, profile, rT, offline); // cancel low temp
}
rT.reason += "; no high-temp to cancel";
return rT;
}
if (eventualBG < profile.min_bg) { // if eventual BG is below target:
if (rT.mealAssist.indexOf("On") == 0) {
rT.reason = "Meal assist: " + meal_data.carbs + "g, " + meal_data.boluses + "U";
} else {
rT.reason = "Eventual BG " + eventualBG + "<" + profile.min_bg;
// if 5m or 15m avg BG is rising faster than expected delta
if (minDelta > expectedDelta && minDelta > 0) {
if (glucose_status.delta > glucose_status.avgdelta) {
rT.reason += ", but Delta " + tick + " > Exp. Delta " + expectedDelta;
} else {
rT.reason += ", but Avg. Delta " + glucose_status.avgdelta.toFixed(2) + " > Exp. Delta " + expectedDelta;
}
if (currenttemp.duration > 0) { // if there is currently any temp basal running
rT.reason = rT.reason += "; cancel";
return setTempBasal(0, 0, profile, rT, offline); // cancel temp
} else {
rT.reason = rT.reason += "; no temp to cancel";
return rT;
}
}
}
if (eventualBG < profile.min_bg) {
// if this is just due to boluses, we can snooze until the bolus IOB decays (at double speed)
if (snoozeBG > profile.min_bg) { // if adding back in the bolus contribution BG would be above min
// if BG is falling and high-temped, or rising and low-temped, cancel
// compare against zero here, not BGI, because BGI will be highly negative from boluses and no carbs
if (glucose_status.delta < 0 && currenttemp.duration > 0 && currenttemp.rate > profile.current_basal) {
rT.reason += tick + ", and temp " + currenttemp.rate + " > basal " + profile.current_basal;
return setTempBasal(0, 0, profile, rT, offline); // cancel temp
} else if (glucose_status.delta > 0 && currenttemp.duration > 0 && currenttemp.rate < profile.current_basal) {
rT.reason += tick + ", and temp " + currenttemp.rate + " < basal " + profile.current_basal;
return setTempBasal(0, 0, profile, rT, offline); // cancel temp
}
rT.reason += ", bolus snooze: eventual BG range " + eventualBG + "-" + snoozeBG;
return rT;
} else {
// calculate 30m low-temp required to get projected BG up to target
// use snoozeBG to more gradually ramp in any counteraction of the user's boluses
// multiply by 2 to low-temp faster for increased hypo safety
var insulinReq = 2 * Math.min(0, (snoozeBG - target_bg) / profile.sens);
if (minDelta < 0 && minDelta > expectedDelta) {
// if we're barely falling, newinsulinReq should be barely negative
rT.reason += ", Snooze BG " + snoozeBG;
var newinsulinReq = Math.round(( insulinReq * (minDelta / expectedDelta) ) * 100)/100;
//console.log("Increasing insulinReq from " + insulinReq + " to " + newinsulinReq);
insulinReq = newinsulinReq;
}
// rate required to deliver insulinReq less insulin over 30m:
var rate = profile.current_basal + (2 * insulinReq);
rate = Math.round( rate * 1000 ) / 1000;
// if required temp < existing temp basal
var insulinScheduled = currenttemp.duration * (currenttemp.rate - profile.current_basal) / 60;
if (insulinScheduled < insulinReq - 0.2) { // if current temp would deliver >0.2U less than the required insulin, raise the rate
rT.reason = currenttemp.duration + "m@" + (currenttemp.rate - profile.current_basal).toFixed(3) + " = " + insulinScheduled.toFixed(3) + " < req " + insulinReq + "-0.2U";
return setTempBasal(rate, 30, profile, rT, offline);
}
if (typeof currenttemp.rate !== 'undefined' && (currenttemp.duration > 5 && rate > currenttemp.rate - 0.1)) {
rT.reason += ", temp " + currenttemp.rate + " ~< req " + rate + "U/hr";
return rT;
} else {
rT.reason += ", setting " + rate + "U/hr";
return setTempBasal(rate, 30, profile, rT, offline);
}
}
}
}
// if eventual BG is above min but BG is falling faster than expected Delta
if (minDelta < expectedDelta) {
if (glucose_status.delta < glucose_status.avgdelta) {
rT.reason = "Eventual BG " + eventualBG + ">" + profile.min_bg + " but Delta " + tick + " < Exp. Delta " + expectedDelta;
} else {
rT.reason = "Eventual BG " + eventualBG + ">" + profile.min_bg + " but Avg. Delta " + glucose_status.avgdelta.toFixed(2) + " < Exp. Delta " + expectedDelta;
}
if (currenttemp.duration > 0) { // if there is currently any temp basal running
rT.reason = rT.reason += "; cancel";
return setTempBasal(0, 0, profile, rT, offline); // cancel temp
} else {
rT.reason = rT.reason += "; no temp to cancel";
return rT;
}
}
if (eventualBG < profile.max_bg) {
rT.reason = eventualBG + " is in range. No temp required";
if (currenttemp.duration > 0) { // if there is currently any temp basal running
rT.reason = rT.reason += "; cancel";
return setTempBasal(0, 0, profile, rT, offline); // cancel temp
}
if (offline == 'Offline') {
// if no temp is running or required, set the current basal as a temp, so you can see on the pump that the loop is working
if ((!currenttemp.duration || (currenttemp.rate == profile.current_basal)) && !rT.duration) {
rT.reason = rT.reason + "; setting current basal of " + profile.current_basal + " as temp";
return setTempBasal(profile.current_basal, 30, profile, rT, offline);
}
}
return rT;
}
if (snoozeBG < profile.max_bg) {
rT.reason = snoozeBG + " < " + profile.max_bg;
if (currenttemp.duration > 0) { // if there is currently any temp basal running
rT.reason = rT.reason += "; cancel";
return setTempBasal(0, 0, profile, rT, offline); // cancel temp
} else {
rT.reason = rT.reason += "; no temp to cancel";
return rT;
}
}
// eventual BG is at/above target:
// if iob is over max, just cancel any temps
var basaliob;
if (iob_data.basaliob) { basaliob = iob_data.basaliob; }
else { basaliob = iob_data.iob - iob_data.bolussnooze; }
rT.reason = "Eventual BG " + eventualBG + ">=" + profile.max_bg + ", ";
if (basaliob > max_iob) {
rT.reason = "basaliob " + basaliob + " > max_iob " + max_iob;
return setTempBasal(0, 0, profile, rT, offline);
} else { // otherwise, calculate 30m high-temp required to get projected BG down to target
// insulinReq is the additional insulin required to get down to max bg:
// if in meal assist mode, check if snoozeBG is lower, as eventualBG is not dependent on IOB
var insulinReq = (Math.min(snoozeBG,eventualBG) - target_bg) / profile.sens;
if (minDelta < 0 && minDelta > expectedDelta) {
var newinsulinReq = Math.round(( insulinReq * (1 - (minDelta / expectedDelta)) ) * 100)/100;
//console.log("Reducing insulinReq from " + insulinReq + " to " + newinsulinReq);
insulinReq = newinsulinReq;
}
// if that would put us over max_iob, then reduce accordingly
if (insulinReq > max_iob-basaliob) {
rT.reason = "max_iob " + max_iob + ", ";
insulinReq = max_iob-basaliob;
}
// rate required to deliver insulinReq more insulin over 30m:
var rate = profile.current_basal + (2 * insulinReq);
rate = Math.round( rate * 1000 ) / 1000;
var maxSafeBasal = Math.min(profile.max_basal, 3 * profile.max_daily_basal, 4 * profile.current_basal);
if (rate > maxSafeBasal) {
rT.reason += "adj. req. rate:"+rate.toFixed(1) +" to maxSafeBasal:"+maxSafeBasal.toFixed(1)+", ";
rate = maxSafeBasal;
}
var insulinScheduled = currenttemp.duration * (currenttemp.rate - profile.current_basal) / 60;
if (insulinScheduled > insulinReq + 0.2) { // if current temp would deliver >0.2U more than the required insulin, lower the rate
rT.reason = currenttemp.duration + "m@" + (currenttemp.rate - profile.current_basal).toFixed(3) + " = " + insulinScheduled.toFixed(3) + " > req " + insulinReq + "+0.2U";
return setTempBasal(rate, 30, profile, rT, offline);
}
if (typeof currenttemp.duration == 'undefined' || currenttemp.duration == 0) { // no temp is set
rT.reason += "no temp, setting " + rate + "U/hr";
return setTempBasal(rate, 30, profile, rT, offline);
}
if (currenttemp.duration > 5 && rate < currenttemp.rate + 0.1) { // if required temp <~ existing temp basal
rT.reason += "temp " + currenttemp.rate + " >~ req " + rate + "U/hr";
return rT;
}
// required temp > existing temp basal
rT.reason += "temp " + currenttemp.rate + "<" + rate + "U/hr";
return setTempBasal(rate, 30, profile, rT, offline);
}
};
module.exports = determine_basal;

View file

@ -29,6 +29,7 @@ import info.nightscout.androidaps.plugins.Loop.LoopFragment;
import info.nightscout.androidaps.plugins.MDI.MDIFragment;
import info.nightscout.androidaps.plugins.NSProfile.NSProfileFragment;
import info.nightscout.androidaps.plugins.Objectives.ObjectivesFragment;
import info.nightscout.androidaps.plugins.OpenAPSAMA.OpenAPSAMAFragment;
import info.nightscout.androidaps.plugins.OpenAPSMA.OpenAPSMAFragment;
import info.nightscout.androidaps.plugins.Overview.OverviewFragment;
import info.nightscout.androidaps.plugins.SafetyFragment.SafetyFragment;
@ -83,6 +84,7 @@ public class MainApp extends Application {
pluginsList.add(VirtualPumpFragment.getPlugin());
if (Config.LOOPENABLED) pluginsList.add(LoopFragment.getPlugin());
if (Config.OPENAPSENABLED) pluginsList.add(OpenAPSMAFragment.getPlugin());
if (Config.OPENAPSENABLED) pluginsList.add(OpenAPSAMAFragment.getPlugin());
pluginsList.add(NSProfileFragment.getPlugin());
pluginsList.add(SimpleProfileFragment.getPlugin());
pluginsList.add(LocalProfileFragment.getPlugin());

View file

@ -0,0 +1,324 @@
package info.nightscout.androidaps.plugins.OpenAPSAMA;
import android.os.Parcel;
import android.os.Parcelable;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import info.nightscout.androidaps.Config;
import info.nightscout.androidaps.interfaces.PumpInterface;
import info.nightscout.androidaps.db.DatabaseHelper;
import info.nightscout.androidaps.plugins.Loop.ScriptReader;
import info.nightscout.androidaps.plugins.OpenAPSMA.IobTotal;
import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin;
import info.nightscout.client.data.NSProfile;
public class DetermineBasalAdapterAMAJS implements Parcelable {
private static Logger log = LoggerFactory.getLogger(DetermineBasalAdapterAMAJS.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 String storedCurrentTemp = null;
public String storedIobData = null;
private String storedGlucoseStatus = null;
private String storedProfile = null;
private String storedMeal_data = null;
/**
* Parcelable implementation
* result string for display only
**/
protected DetermineBasalAdapterAMAJS(Parcel in) {
storedCurrentTemp = in.readString();
storedIobData = in.readString();
storedGlucoseStatus = in.readString();
storedProfile = in.readString();
storedMeal_data = in.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(storedCurrentTemp);
dest.writeString(storedIobData);
dest.writeString(storedGlucoseStatus);
dest.writeString(storedProfile);
dest.writeString(storedMeal_data);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<DetermineBasalAdapterAMAJS> CREATOR = new Creator<DetermineBasalAdapterAMAJS>() {
@Override
public DetermineBasalAdapterAMAJS createFromParcel(Parcel in) {
return new DetermineBasalAdapterAMAJS(in);
}
@Override
public DetermineBasalAdapterAMAJS[] newArray(int size) {
return new DetermineBasalAdapterAMAJS[size];
}
};
/**
* Main code
*/
public DetermineBasalAdapterAMAJS(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("carbs_hr", 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("carbratio", 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 DetermineBasalResultAMA 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" +
");");
String ret = mV8rt.executeStringScript("JSON.stringify(rT);");
if (Config.logAPSResult)
log.debug("Result: " + ret);
V8Object v8ObjectReuslt = mV8rt.getObject("rT");
DetermineBasalResultAMA result = null;
try {
result = new DetermineBasalResultAMA(v8ObjectReuslt, new JSONObject(ret));
} catch (JSONException e) {
e.printStackTrace();
}
// Store input params for Parcelable
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 + ");");
return result;
}
String getGlucoseStatusParam() {
return storedGlucoseStatus;
}
String getCurrentTempParam() {
return storedCurrentTemp;
}
String getIobDataParam() {
return storedIobData;
}
String getProfileParam() {
return storedProfile;
}
String getMealDataParam() {
return storedMeal_data;
}
private void loadScript() throws IOException {
mV8rt.executeVoidScript(
readFile("OpenAPSAMA/determine-basal.js"),
"OpenAPSAMA/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(NSProfile profile,
double maxIob,
double maxBasal,
double minBg,
double maxBg,
double targetBg,
PumpInterface pump,
IobTotal iobData,
DatabaseHelper.GlucoseStatus glucoseStatus,
TreatmentsPlugin.MealData mealData) {
String units = profile.getUnits();
mProfile.add("max_iob", maxIob);
mProfile.add("carbs_hr", profile.getCarbAbsorbtionRate());
mProfile.add("dia", profile.getDia());
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("carbratio", profile.getIc(profile.secondsFromMidnight()));
mProfile.add("sens", NSProfile.toMgdl(profile.getIsf(NSProfile.secondsFromMidnight()).doubleValue(), units));
mProfile.add("current_basal", pump.getBaseBasalRate());
mCurrentTemp.add("duration", pump.getTempBasalRemainingMinutes());
mCurrentTemp.add("rate", pump.getTempBasalAbsoluteRate());
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);
mGlucoseStatus.add("glucose", glucoseStatus.glucose);
mGlucoseStatus.add("delta", glucoseStatus.delta);
mGlucoseStatus.add("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();
}
public String readFile(String filename) throws IOException {
byte[] bytes = mScriptReader.readFile(filename);
String string = new String(bytes, "UTF-8");
if (string.startsWith("#!/usr/bin/env node")) {
string = string.substring(20);
}
return string;
}
}

View file

@ -0,0 +1,123 @@
package info.nightscout.androidaps.plugins.OpenAPSAMA;
import android.os.Parcel;
import android.os.Parcelable;
import com.eclipsesource.v8.V8Object;
import org.json.JSONException;
import org.json.JSONObject;
import info.nightscout.androidaps.plugins.Loop.APSResult;
import info.nightscout.androidaps.plugins.OpenAPSMA.IobTotal;
public class DetermineBasalResultAMA extends APSResult {
public JSONObject json = new JSONObject();
public double eventualBG;
public double snoozeBG;
public String mealAssist;
public IobTotal iob;
public DetermineBasalResultAMA(V8Object result, JSONObject j) {
json = j;
if (result.contains("error")) {
reason = result.getString("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");
if (rate < 0d) rate = 0d;
changeRequested = true;
} else {
rate = -1;
changeRequested = false;
}
if (result.contains("duration")) {
duration = result.getInteger("duration");
changeRequested = changeRequested;
} else {
duration = -1;
changeRequested = false;
}
if (result.contains("mealAssist")) {
mealAssist = result.getString("mealAssist");
} else mealAssist = "";
}
result.release();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeString(json.toString());
dest.writeDouble(eventualBG);
dest.writeDouble(snoozeBG);
dest.writeString(mealAssist);
}
public final Parcelable.Creator<DetermineBasalResultAMA> CREATOR = new Parcelable.Creator<DetermineBasalResultAMA>() {
public DetermineBasalResultAMA createFromParcel(Parcel in) {
return new DetermineBasalResultAMA(in);
}
public DetermineBasalResultAMA[] newArray(int size) {
return new DetermineBasalResultAMA[size];
}
};
private DetermineBasalResultAMA(Parcel in) {
super(in);
try {
json = new JSONObject(in.readString());
} catch (JSONException e) {
e.printStackTrace();
}
eventualBG = in.readDouble();
snoozeBG = in.readDouble();
mealAssist = in.readString();
}
public DetermineBasalResultAMA() {
}
@Override
public DetermineBasalResultAMA clone() {
DetermineBasalResultAMA newResult = new DetermineBasalResultAMA();
newResult.reason = new String(reason);
newResult.rate = rate;
newResult.duration = duration;
newResult.changeRequested = changeRequested;
newResult.rate = rate;
newResult.duration = duration;
newResult.changeRequested = changeRequested;
try {
newResult.json = new JSONObject(json.toString());
} catch (JSONException e) {
e.printStackTrace();
}
newResult.eventualBG = eventualBG;
newResult.snoozeBG = snoozeBG;
newResult.mealAssist = new String(mealAssist);
return newResult;
}
@Override
public JSONObject json() {
try {
JSONObject ret = new JSONObject(this.json.toString());
return ret;
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
}

View file

@ -0,0 +1,141 @@
package info.nightscout.androidaps.plugins.OpenAPSAMA;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import com.squareup.otto.Subscribe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.interfaces.FragmentBase;
import info.nightscout.androidaps.plugins.OpenAPSMA.events.EventOpenAPSUpdateGui;
import info.nightscout.androidaps.plugins.OpenAPSMA.events.EventOpenAPSUpdateResultGui;
import info.nightscout.utils.JSONFormatter;
public class OpenAPSAMAFragment extends Fragment implements View.OnClickListener, FragmentBase {
private static Logger log = LoggerFactory.getLogger(OpenAPSAMAFragment.class);
private static OpenAPSAMAPlugin openAPSAMAPlugin;
public static OpenAPSAMAPlugin getPlugin() {
if(openAPSAMAPlugin ==null){
openAPSAMAPlugin = new OpenAPSAMAPlugin();
}
return openAPSAMAPlugin;
}
Button run;
TextView lastRunView;
TextView glucoseStatusView;
TextView currentTempView;
TextView iobDataView;
TextView profileView;
TextView mealDataView;
TextView resultView;
TextView requestView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.openapsama_fragment, container, false);
run = (Button) view.findViewById(R.id.openapsma_run);
run.setOnClickListener(this);
lastRunView = (TextView) view.findViewById(R.id.openapsma_lastrun);
glucoseStatusView = (TextView) view.findViewById(R.id.openapsma_glucosestatus);
currentTempView = (TextView) view.findViewById(R.id.openapsma_currenttemp);
iobDataView = (TextView) view.findViewById(R.id.openapsma_iobdata);
profileView = (TextView) view.findViewById(R.id.openapsma_profile);
mealDataView = (TextView) view.findViewById(R.id.openapsma_mealdata);
resultView = (TextView) view.findViewById(R.id.openapsma_result);
requestView = (TextView) view.findViewById(R.id.openapsma_request);
updateGUI();
return view;
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.openapsma_run:
getPlugin().invoke();
break;
}
}
@Override
public void onPause() {
super.onPause();
MainApp.bus().unregister(this);
}
@Override
public void onResume() {
super.onResume();
MainApp.bus().register(this);
}
@Subscribe
public void onStatusEvent(final EventOpenAPSUpdateGui ev) {
updateGUI();
}
@Subscribe
public void onStatusEvent(final EventOpenAPSUpdateResultGui ev) {
updateResultGUI(ev.text);
}
void updateGUI() {
Activity activity = getActivity();
if (activity != null)
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
DetermineBasalResultAMA lastAPSResult = getPlugin().lastAPSResult;
if (lastAPSResult != null) {
resultView.setText(JSONFormatter.format(lastAPSResult.json));
requestView.setText(lastAPSResult.toSpanned());
}
DetermineBasalAdapterAMAJS determineBasalAdapterAMAJS = getPlugin().lastDetermineBasalAdapterAMAJS;
if (determineBasalAdapterAMAJS != null) {
glucoseStatusView.setText(JSONFormatter.format(determineBasalAdapterAMAJS.getGlucoseStatusParam()));
currentTempView.setText(JSONFormatter.format(determineBasalAdapterAMAJS.getCurrentTempParam()));
iobDataView.setText(JSONFormatter.format(determineBasalAdapterAMAJS.getIobDataParam()));
profileView.setText(JSONFormatter.format(determineBasalAdapterAMAJS.getProfileParam()));
mealDataView.setText(JSONFormatter.format(determineBasalAdapterAMAJS.getMealDataParam()));
}
if (getPlugin().lastAPSRun != null) {
lastRunView.setText(getPlugin().lastAPSRun.toLocaleString());
}
}
});
}
void updateResultGUI(final String text) {
Activity activity = getActivity();
if (activity != null)
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
resultView.setText(text);
glucoseStatusView.setText("");
currentTempView.setText("");
iobDataView.setText("");
profileView.setText("");
mealDataView.setText("");
requestView.setText("");
lastRunView.setText("");
}
});
}
}

View file

@ -0,0 +1,241 @@
package info.nightscout.androidaps.plugins.OpenAPSAMA;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import org.json.JSONException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Date;
import info.nightscout.androidaps.Config;
import info.nightscout.androidaps.Constants;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.db.DatabaseHelper;
import info.nightscout.androidaps.interfaces.APSInterface;
import info.nightscout.androidaps.interfaces.PluginBase;
import info.nightscout.androidaps.interfaces.PumpInterface;
import info.nightscout.androidaps.interfaces.TempBasalsInterface;
import info.nightscout.androidaps.interfaces.TreatmentsInterface;
import info.nightscout.androidaps.plugins.Loop.APSResult;
import info.nightscout.androidaps.plugins.Loop.ScriptReader;
import info.nightscout.androidaps.plugins.OpenAPSMA.IobTotal;
import info.nightscout.androidaps.plugins.OpenAPSMA.events.EventOpenAPSUpdateGui;
import info.nightscout.androidaps.plugins.OpenAPSMA.events.EventOpenAPSUpdateResultGui;
import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin;
import info.nightscout.client.data.NSProfile;
import info.nightscout.utils.DateUtil;
import info.nightscout.utils.Round;
import info.nightscout.utils.SafeParse;
import info.nightscout.utils.ToastUtils;
/**
* Created by mike on 05.08.2016.
*/
public class OpenAPSAMAPlugin implements PluginBase, APSInterface {
private static Logger log = LoggerFactory.getLogger(OpenAPSAMAPlugin.class);
// last values
DetermineBasalAdapterAMAJS lastDetermineBasalAdapterAMAJS = null;
Date lastAPSRun = null;
DetermineBasalResultAMA lastAPSResult = null;
boolean fragmentEnabled = false;
boolean fragmentVisible = true;
@Override
public String getName() {
return MainApp.instance().getString(R.string.openapsama);
}
@Override
public boolean isEnabled(int type) {
return type == APS && fragmentEnabled && MainApp.getConfigBuilder().getPumpDescription().isTempBasalCapable;
}
@Override
public boolean isVisibleInTabs(int type) {
return type == APS && fragmentVisible && MainApp.getConfigBuilder().getPumpDescription().isTempBasalCapable;
}
@Override
public boolean canBeHidden(int type) {
return true;
}
@Override
public void setFragmentVisible(int type, boolean fragmentVisible) {
if (type == APS) this.fragmentVisible = fragmentVisible;
}
@Override
public void setFragmentEnabled(int type, boolean fragmentEnabled) {
if (type == APS) this.fragmentEnabled = fragmentEnabled;
}
@Override
public int getType() {
return PluginBase.APS;
}
@Override
public String getFragmentClass() {
return OpenAPSAMAFragment.class.getName();
}
@Override
public APSResult getLastAPSResult() {
return lastAPSResult;
}
@Override
public Date getLastAPSRun() {
return lastAPSRun;
}
@Override
public void invoke() {
lastAPSResult = null;
DetermineBasalAdapterAMAJS determineBasalAdapterAMAJS = null;
try {
determineBasalAdapterAMAJS = new DetermineBasalAdapterAMAJS(new ScriptReader(MainApp.instance().getBaseContext()));
} catch (IOException e) {
log.error(e.getMessage(), e);
return;
}
DatabaseHelper.GlucoseStatus glucoseStatus = MainApp.getDbHelper().getGlucoseStatusData();
NSProfile profile = MainApp.getConfigBuilder().getActiveProfile().getProfile();
PumpInterface pump = MainApp.getConfigBuilder();
if (!isEnabled(PluginBase.APS)) {
MainApp.bus().post(new EventOpenAPSUpdateResultGui(MainApp.instance().getString(R.string.openapsma_disabled)));
if (Config.logAPSResult)
log.debug(MainApp.instance().getString(R.string.openapsma_disabled));
return;
}
if (glucoseStatus == null) {
MainApp.bus().post(new EventOpenAPSUpdateResultGui(MainApp.instance().getString(R.string.openapsma_noglucosedata)));
if (Config.logAPSResult)
log.debug(MainApp.instance().getString(R.string.openapsma_noglucosedata));
return;
}
if (profile == null) {
MainApp.bus().post(new EventOpenAPSUpdateResultGui(MainApp.instance().getString(R.string.openapsma_noprofile)));
if (Config.logAPSResult)
log.debug(MainApp.instance().getString(R.string.openapsma_noprofile));
return;
}
if (pump == null) {
MainApp.bus().post(new EventOpenAPSUpdateResultGui(MainApp.instance().getString(R.string.openapsma_nopump)));
if (Config.logAPSResult)
log.debug(MainApp.instance().getString(R.string.openapsma_nopump));
return;
}
SharedPreferences SP = PreferenceManager.getDefaultSharedPreferences(MainApp.instance().getApplicationContext());
String units = profile.getUnits();
String maxBgDefault = "180";
String minBgDefault = "100";
String targetBgDefault = "150";
if (!units.equals(Constants.MGDL)) {
maxBgDefault = "10";
minBgDefault = "5";
targetBgDefault = "7";
}
Date now = new Date();
double maxIob = SafeParse.stringToDouble(SP.getString("openapsma_max_iob", "1.5"));
double maxBasal = SafeParse.stringToDouble(SP.getString("openapsma_max_basal", "1"));
double minBg = NSProfile.toMgdl(SafeParse.stringToDouble(SP.getString("openapsma_min_bg", minBgDefault)), units);
double maxBg = NSProfile.toMgdl(SafeParse.stringToDouble(SP.getString("openapsma_max_bg", maxBgDefault)), units);
double targetBg = NSProfile.toMgdl(SafeParse.stringToDouble(SP.getString("openapsma_target_bg", targetBgDefault)), units);
minBg = Round.roundTo(minBg, 0.1d);
maxBg = Round.roundTo(maxBg, 0.1d);
TreatmentsInterface treatments = MainApp.getConfigBuilder().getActiveTreatments();
TempBasalsInterface tempBasals = MainApp.getConfigBuilder().getActiveTempBasals();
treatments.updateTotalIOB();
tempBasals.updateTotalIOB();
IobTotal bolusIob = treatments.getLastCalculation();
IobTotal basalIob = tempBasals.getLastCalculation();
IobTotal iobTotal = IobTotal.combine(bolusIob, basalIob).round();
TreatmentsPlugin.MealData mealData = treatments.getMealData();
maxIob = MainApp.getConfigBuilder().applyMaxIOBConstraints(maxIob);
minBg = verifyHardLimits(minBg, "minBg", 72, 180);
maxBg = verifyHardLimits(maxBg, "maxBg", 100, 270);
targetBg = verifyHardLimits(targetBg, "targetBg", 80, 200);
maxIob = verifyHardLimits(maxIob, "maxIob", 0, 7);
maxBasal = verifyHardLimits(maxBasal, "max_basal", 0.1, 10);
if (!checkOnlyHardLimits(profile.getCarbAbsorbtionRate(), "carbs_hr", 4, 100)) return;
if (!checkOnlyHardLimits(profile.getDia(), "dia", 2, 7)) return;
if (!checkOnlyHardLimits(profile.getIc(profile.secondsFromMidnight()), "carbratio", 2, 100)) return;
if (!checkOnlyHardLimits(NSProfile.toMgdl(profile.getIsf(NSProfile.secondsFromMidnight()).doubleValue(), units), "sens", 2, 900)) return;
if (!checkOnlyHardLimits(profile.getMaxDailyBasal(), "max_daily_basal", 0.1, 10)) return;
if (!checkOnlyHardLimits(pump.getBaseBasalRate(), "current_basal", 0.01, 5)) return;
determineBasalAdapterAMAJS.setData(profile, maxIob, maxBasal, minBg, maxBg, targetBg, pump, iobTotal, glucoseStatus, mealData);
DetermineBasalResultAMA determineBasalResultAMA = determineBasalAdapterAMAJS.invoke();
// Fix bug determine basal
if (determineBasalResultAMA.rate == 0d && determineBasalResultAMA.duration == 0 && !MainApp.getConfigBuilder().isTempBasalInProgress())
determineBasalResultAMA.changeRequested = false;
// limit requests on openloop mode
if (!MainApp.getConfigBuilder().isClosedModeEnabled()) {
if (MainApp.getConfigBuilder().isTempBasalInProgress() && Math.abs(determineBasalResultAMA.rate - MainApp.getConfigBuilder().getTempBasalAbsoluteRate()) < 0.1)
determineBasalResultAMA.changeRequested = false;
if (!MainApp.getConfigBuilder().isTempBasalInProgress() && Math.abs(determineBasalResultAMA.rate - MainApp.getConfigBuilder().getBaseBasalRate()) < 0.1)
determineBasalResultAMA.changeRequested = false;
}
determineBasalResultAMA.iob = iobTotal;
determineBasalAdapterAMAJS.release();
try {
determineBasalResultAMA.json.put("timestamp", DateUtil.toISOString(now));
} catch (JSONException e) {
e.printStackTrace();
}
lastDetermineBasalAdapterAMAJS = determineBasalAdapterAMAJS;
lastAPSResult = determineBasalResultAMA;
lastAPSRun = now;
MainApp.bus().post(new EventOpenAPSUpdateGui());
//deviceStatus.suggested = determineBasalResultAMA.json;
}
// safety checks
public static boolean checkOnlyHardLimits(Double value, String valueName, double lowLimit, double highLimit) {
return value.equals(verifyHardLimits(value, valueName, lowLimit, highLimit));
}
public static Double verifyHardLimits(Double value, String valueName, double lowLimit, double highLimit) {
if (value < lowLimit || value > highLimit) {
String msg = String.format(MainApp.sResources.getString(R.string.openapsma_valueoutofrange), valueName);
log.error(msg);
MainApp.getConfigBuilder().uploadError(msg);
ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), msg, R.raw.error);
value = Math.max(value, lowLimit);
value = Math.min(value, highLimit);
}
return value;
}
}

View file

@ -0,0 +1,185 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="info.nightscout.androidaps.plugins.OpenAPSAMA.OpenAPSAMAFragment">
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/openapsma_run"
style="?android:attr/buttonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/openapsma_run" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="10dp"
android:background="@color/linearBlockBackground"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/openapsma_lastrun_label"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/openapsma_lastrun"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@color/linearBlockBackground"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/openapsma_inputparameters_label"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/openapsma_glucosestatus_label"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_marginLeft="5dp" />
<TextView
android:id="@+id/openapsma_glucosestatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:textAppearance="?android:attr/textAppearanceSmall" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/openapsma_currenttemp_label"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_marginLeft="5dp" />
<TextView
android:id="@+id/openapsma_currenttemp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:textAppearance="?android:attr/textAppearanceSmall" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/openapsma_iobdata_label"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_marginLeft="5dp" />
<TextView
android:id="@+id/openapsma_iobdata"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:textAppearance="?android:attr/textAppearanceSmall" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/openapsma_profile_label"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_marginLeft="5dp" />
<TextView
android:id="@+id/openapsma_profile"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:textAppearance="?android:attr/textAppearanceSmall" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/openapsma_mealdata_label"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_marginLeft="5dp" />
<TextView
android:id="@+id/openapsma_mealdata"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="10dp"
android:background="@color/linearBlockBackground"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/openapsma_result_label"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/openapsma_result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="10dp"
android:background="@color/linearBlockBackground"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/openapsma_request_label"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/openapsma_request"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
</LinearLayout>
</ScrollView>
</FrameLayout>

View file

@ -397,4 +397,5 @@
<string name="minago">%dmin ago</string>
<string name="sms_minago">%dmin ago</string>
<string name="localprofile">Local Profile</string>
<string name="openapsama">OpenAPS AMA</string>
</resources>