Merge branch 'dev' into remove-pre-bolus

This commit is contained in:
Johannes Mockenhaupt 2018-03-18 20:48:19 +01:00 committed by GitHub
commit da014daec8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
130 changed files with 6658 additions and 494 deletions

View file

@ -57,7 +57,7 @@ android {
targetSdkVersion 23
multiDexEnabled true
versionCode 1500
version "1.59-dev"
version "1.60-dev"
buildConfigField "String", "VERSION", '"' + version + '"'
buildConfigField "String", "BUILDVERSION", generateGitBuild()
@ -238,6 +238,7 @@ dependencies {
testCompile "org.powermock:powermock-module-junit4:${powermockVersion}"
testCompile "joda-time:joda-time:2.9.4.2"
testCompile "com.google.truth:truth:0.39"
testCompile "org.skyscreamer:jsonassert:1.5.0"
androidTestCompile "org.mockito:mockito-core:2.7.22"
androidTestCompile "com.google.dexmaker:dexmaker:${dexmakerVersion}"

View file

@ -0,0 +1,20 @@
// IRTHandler.aidl
package org.monkey.d.ruffy.ruffy.driver;
// Declare any non-default types here with import statements
import org.monkey.d.ruffy.ruffy.driver.display.Menu;
interface IRTHandler {
void log(String message);
void fail(String message);
void requestBluetooth();
void rtStopped();
void rtStarted();
void rtClearDisplay();
void rtUpdateDisplay(in byte[] quarter, int which);
void rtDisplayHandleMenu(in Menu menu);
void rtDisplayHandleNoMenu();
}

View file

@ -0,0 +1,23 @@
// IRuffyService.aidl
package org.monkey.d.ruffy.ruffy.driver;
// Declare any non-default types here with import statements
import org.monkey.d.ruffy.ruffy.driver.IRTHandler;
interface IRuffyService {
void setHandler(IRTHandler handler);
/** Connect to the pump
*
* @return 0 if successful, -1 otherwise
*/
int doRTConnect();
/** Disconnect from the pump */
void doRTDisconnect();
void rtSendKey(byte keyCode, boolean changed);
void resetPairing();
boolean isConnected();
}

View file

@ -0,0 +1,3 @@
package org.monkey.d.ruffy.ruffy.driver.display;
parcelable Menu;

View file

@ -0,0 +1 @@
//b916a900c0899ef58ad58c7427d1c30d3c8731f4

View file

@ -1,6 +1,6 @@
<configuration>
<!-- Create a file appender for a log in the application's data directory -->
<property name="EXT_FILES_DIR" value="${EXT_DIR:-/sdcard}/Android/data/${PACKAGE_NAME}/files"/>
<property scope="context" name="EXT_FILES_DIR" value="${EXT_DIR:-/sdcard}/Android/data/${PACKAGE_NAME}/files"/>
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${EXT_FILES_DIR}/AndroidAPS.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">

View file

@ -13,7 +13,7 @@ public class Config {
public static final boolean G5UPLOADER = BuildConfig.G5UPLOADER;
public static final boolean PUMPCONTROL = BuildConfig.PUMPCONTROL;
public static final boolean DANAR = BuildConfig.PUMPDRIVERS;
public static final boolean HWPUMPS = BuildConfig.PUMPDRIVERS;
public static final boolean ACTION = !BuildConfig.NSCLIENTOLNY && !BuildConfig.G5UPLOADER;
public static final boolean VIRTUALPUMP = !BuildConfig.NSCLIENTOLNY && !BuildConfig.G5UPLOADER;
@ -44,4 +44,6 @@ public class Config {
public static final boolean logDanaBTComm = true;
public static boolean logDanaMessageDetail = true;
public static final boolean logDanaSerialEngine = true;
public static final boolean enableComboBetaFeatures = false;
}

View file

@ -1,7 +1,5 @@
package info.nightscout.androidaps;
import com.j256.ormlite.stmt.query.In;
/**
* Created by mike on 07.06.2016.
*/

View file

@ -19,6 +19,9 @@ import android.support.v4.view.ViewPager;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.PopupMenu;
import android.text.SpannableString;
import android.text.method.LinkMovementMethod;
import android.text.util.Linkify;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
@ -27,6 +30,7 @@ import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.TextView;
import com.joanzapata.iconify.Iconify;
import com.joanzapata.iconify.fonts.FontAwesomeModule;
@ -400,10 +404,16 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
builder.setIcon(R.mipmap.blueowl);
String message = "Build: " + BuildConfig.BUILDVERSION + "\n";
message += MainApp.sResources.getString(R.string.configbuilder_nightscoutversion_label) + " " + ConfigBuilderPlugin.nightscoutVersionName;
builder.setMessage(message);
if (MainApp.engineeringMode)
message += "\n" + MainApp.gs(R.string.engineering_mode_enabled);
message += getString(R.string.about_link_urls);
final SpannableString messageSpanned = new SpannableString(message);
Linkify.addLinks(messageSpanned, Linkify.WEB_URLS);
builder.setMessage(messageSpanned);
builder.setPositiveButton(MainApp.sResources.getString(R.string.ok), null);
AlertDialog alertDialog = builder.create();
alertDialog.show();
((TextView)alertDialog.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance());
break;
case R.id.nav_exit:
log.debug("Exiting");

View file

@ -20,8 +20,10 @@ import net.danlew.android.joda.JodaTimeAndroid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.ArrayList;
import ch.qos.logback.classic.LoggerContext;
import info.nightscout.androidaps.Services.Intents;
import info.nightscout.androidaps.db.DatabaseHelper;
import info.nightscout.androidaps.interfaces.InsulinInterface;
@ -46,10 +48,13 @@ import info.nightscout.androidaps.plugins.OpenAPSAMA.OpenAPSAMAPlugin;
import info.nightscout.androidaps.plugins.OpenAPSMA.OpenAPSMAPlugin;
import info.nightscout.androidaps.plugins.OpenAPSSMB.OpenAPSSMBPlugin;
import info.nightscout.androidaps.plugins.Overview.OverviewPlugin;
import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification;
import info.nightscout.androidaps.plugins.Overview.notifications.Notification;
import info.nightscout.androidaps.plugins.Persistentnotification.PersistentNotificationPlugin;
import info.nightscout.androidaps.plugins.ProfileLocal.LocalProfilePlugin;
import info.nightscout.androidaps.plugins.ProfileNS.NSProfilePlugin;
import info.nightscout.androidaps.plugins.ProfileSimple.SimpleProfilePlugin;
import info.nightscout.androidaps.plugins.PumpCombo.ComboPlugin;
import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPlugin;
import info.nightscout.androidaps.plugins.PumpDanaRKorean.DanaRKoreanPlugin;
import info.nightscout.androidaps.plugins.PumpDanaRS.DanaRSPlugin;
@ -74,6 +79,7 @@ import info.nightscout.androidaps.receivers.KeepAliveReceiver;
import info.nightscout.androidaps.receivers.NSAlarmReceiver;
import info.nightscout.utils.FabricPrivacy;
import info.nightscout.utils.NSUpload;
import info.nightscout.utils.SP;
import io.fabric.sdk.android.Fabric;
@ -95,6 +101,9 @@ public class MainApp extends Application {
private static AckAlarmReceiver ackAlarmReciever = new AckAlarmReceiver();
private LocalBroadcastManager lbm;
public static boolean devBranch;
public static boolean engineeringMode;
@Override
public void onCreate() {
super.onCreate();
@ -116,6 +125,12 @@ public class MainApp extends Application {
log.info("Version: " + BuildConfig.VERSION_NAME);
log.info("BuildVersion: " + BuildConfig.BUILDVERSION);
String extFilesDir = this.getLogDirectory();
File engineeringModeSemaphore = new File(extFilesDir,"engineering_mode");
engineeringMode = engineeringModeSemaphore.exists() && engineeringModeSemaphore.isFile();
devBranch = BuildConfig.VERSION.contains("dev");
sBus = Config.logEvents ? new LoggingBus(ThreadEnforcer.ANY) : new Bus(ThreadEnforcer.ANY);
registerLocalBroadcastReceiver();
@ -134,12 +149,13 @@ public class MainApp extends Application {
pluginsList.add(SensitivityOref0Plugin.getPlugin());
pluginsList.add(SensitivityAAPSPlugin.getPlugin());
pluginsList.add(SensitivityWeightedAveragePlugin.getPlugin());
if (Config.DANAR) pluginsList.add(DanaRPlugin.getPlugin());
if (Config.DANAR) pluginsList.add(DanaRKoreanPlugin.getPlugin());
if (Config.DANAR) pluginsList.add(DanaRv2Plugin.getPlugin());
if (Config.DANAR) pluginsList.add(DanaRSPlugin.getPlugin());
if (Config.HWPUMPS) pluginsList.add(DanaRPlugin.getPlugin());
if (Config.HWPUMPS) pluginsList.add(DanaRKoreanPlugin.getPlugin());
if (Config.HWPUMPS) pluginsList.add(DanaRv2Plugin.getPlugin());
if (Config.HWPUMPS) pluginsList.add(DanaRSPlugin.getPlugin());
pluginsList.add(CareportalPlugin.getPlugin());
// if (Config.DANAR) pluginsList.add(InsightPumpPlugin.getPlugin()); // <-- Enable Insight plugin here
if (Config.HWPUMPS && engineeringMode) pluginsList.add(InsightPumpPlugin.getPlugin()); // <-- Enable Insight plugin here
if (Config.HWPUMPS && engineeringMode) pluginsList.add(ComboPlugin.getPlugin()); // <-- Enable Combo plugin here
if (Config.MDI) pluginsList.add(MDIPlugin.getPlugin());
if (Config.VIRTUALPUMP) pluginsList.add(VirtualPumpPlugin.getPlugin());
if (Config.APS) pluginsList.add(LoopPlugin.getPlugin());
@ -195,6 +211,10 @@ public class MainApp extends Application {
}
}).start();
if (!isEngineeringModeOrRelease()) {
Notification n = new Notification(Notification.TOAST_ALARM, gs(R.string.closed_loop_disabled_on_dev_branch), Notification.NORMAL);
bus().post(new EventNewNotification(n));
}
}
private void registerLocalBroadcastReceiver() {
@ -361,6 +381,15 @@ public class MainApp extends Application {
return null;
}
public static boolean isEngineeringModeOrRelease() {
return engineeringMode || !devBranch;
}
private String getLogDirectory() {
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
return lc.getProperty("EXT_FILES_DIR");
}
@Override
public void onTerminate() {
super.onTerminate();

View file

@ -1,11 +1,9 @@
package info.nightscout.androidaps;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.MultiSelectListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
@ -155,7 +153,7 @@ public class PreferencesActivity extends PreferenceActivity implements SharedPre
addPreferencesFromResourceIfEnabled(SensitivityWeightedAveragePlugin.getPlugin(), PluginBase.SENSITIVITY);
addPreferencesFromResourceIfEnabled(SensitivityOref0Plugin.getPlugin(), PluginBase.SENSITIVITY);
if (Config.DANAR) {
if (Config.HWPUMPS) {
addPreferencesFromResourceIfEnabled(DanaRPlugin.getPlugin(), PluginBase.PUMP);
addPreferencesFromResourceIfEnabled(DanaRKoreanPlugin.getPlugin(), PluginBase.PUMP);
addPreferencesFromResourceIfEnabled(DanaRv2Plugin.getPlugin(), PluginBase.PUMP);

View file

@ -96,7 +96,7 @@ public class DataService extends IntentService {
dexcomG5Enabled = true;
}
boolean isNSProfile = ConfigBuilderPlugin.getActiveProfileInterface().getClass().equals(NSProfilePlugin.class);
boolean isNSProfile = MainApp.getConfigBuilder().getActiveProfileInterface().getClass().equals(NSProfilePlugin.class);
boolean acceptNSData = !SP.getBoolean(R.string.key_ns_upload_only, false);
Bundle bundles = intent.getExtras();
@ -192,7 +192,7 @@ public class DataService extends IntentService {
bgReading.date = bundle.getLong(Intents.EXTRA_TIMESTAMP);
bgReading.raw = bundle.getDouble(Intents.EXTRA_RAW);
MainApp.getDbHelper().createIfNotExists(bgReading, "XDRIP");
MainApp.getDbHelper().createIfNotExists(bgReading, "XDRIP", xDripEnabled);
}
private void handleNewDataFromGlimp(Intent intent) {
@ -206,7 +206,7 @@ public class DataService extends IntentService {
bgReading.date = bundle.getLong("myTimestamp");
bgReading.raw = 0;
MainApp.getDbHelper().createIfNotExists(bgReading, "GLIMP");
MainApp.getDbHelper().createIfNotExists(bgReading, "GLIMP", glimpEnabled);
}
private void handleNewDataFromDexcomG5(Intent intent) {
@ -229,7 +229,7 @@ public class DataService extends IntentService {
bgReading.direction = json.getString("m_trend");
bgReading.date = json.getLong("m_time") * 1000L;
bgReading.raw = 0;
boolean isNew = MainApp.getDbHelper().createIfNotExists(bgReading, "DexcomG5");
boolean isNew = MainApp.getDbHelper().createIfNotExists(bgReading, "DexcomG5", dexcomG5Enabled);
if (isNew && SP.getBoolean(R.string.key_dexcomg5_nsupload, false)) {
NSUpload.uploadBg(bgReading);
}
@ -268,7 +268,7 @@ public class DataService extends IntentService {
bgReading.date = json_object.getLong("date");
bgReading.raw = json_object.getDouble("sgv");
MainApp.getDbHelper().createIfNotExists(bgReading, "MM640g");
MainApp.getDbHelper().createIfNotExists(bgReading, "MM640g", mm640gEnabled);
break;
default:
log.debug("Unknown entries type: " + type);
@ -368,9 +368,6 @@ public class DataService extends IntentService {
ProfileStore profileStore = new ProfileStore(new JSONObject(profile));
NSProfilePlugin.getPlugin().storeNewProfile(profileStore);
MainApp.bus().post(new EventNSProfileUpdateGUI());
// if there are no profile switches this should lead to profile update
if (MainApp.getConfigBuilder().getProfileSwitchesFromHistory().size() == 0)
MainApp.bus().post(new EventNewBasalProfile());
if (Config.logIncommingData)
log.debug("Received profileStore: " + activeProfile + " " + profile);
} catch (JSONException e) {
@ -428,7 +425,7 @@ public class DataService extends IntentService {
JSONObject sgvJson = new JSONObject(sgvstring);
NSSgv nsSgv = new NSSgv(sgvJson);
BgReading bgReading = new BgReading(nsSgv);
MainApp.getDbHelper().createIfNotExists(bgReading, "NS");
MainApp.getDbHelper().createIfNotExists(bgReading, "NS", nsClientEnabled);
}
if (bundles.containsKey("sgvs")) {
@ -438,7 +435,7 @@ public class DataService extends IntentService {
JSONObject sgvJson = jsonArray.getJSONObject(i);
NSSgv nsSgv = new NSSgv(sgvJson);
BgReading bgReading = new BgReading(nsSgv);
MainApp.getDbHelper().createIfNotExists(bgReading, "NS");
MainApp.getDbHelper().createIfNotExists(bgReading, "NS", nsClientEnabled);
}
}
} catch (Exception e) {

View file

@ -6,10 +6,8 @@ import org.json.JSONObject;
import java.util.Date;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.db.CareportalEvent;
import info.nightscout.androidaps.db.Source;
import info.nightscout.androidaps.interfaces.InsulinInterface;
/**
* Created by mike on 29.05.2017.
@ -31,6 +29,24 @@ public class DetailedBolusInfo {
public boolean isSMB = false; // is a Super-MicroBolus
public long deliverAt = 0; // SMB should be delivered within 1 min from this time
public DetailedBolusInfo copy() {
DetailedBolusInfo copy = new DetailedBolusInfo();
copy.date = this.date;
copy.eventType = this.eventType;
copy.insulin = this.insulin;
copy.carbs = this.carbs;
copy.source = this.source;
copy.isValid = this.isValid;
copy.glucose = this.glucose;
copy.glucoseType = this.glucoseType;
copy.carbTime = this.carbTime;
copy.boluscalc = this.boluscalc;
copy.context = this.context;
copy.pumpId = this.pumpId;
copy.isSMB = this.isSMB;
return copy;
}
@Override
public String toString() {
return new Date(date).toLocaleString() +

View file

@ -2,8 +2,6 @@ package info.nightscout.androidaps.data;
import android.support.v4.util.LongSparseArray;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@ -19,41 +17,43 @@ import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.interfaces.PumpDescription;
import info.nightscout.androidaps.interfaces.PumpInterface;
import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin;
import info.nightscout.androidaps.plugins.Overview.events.EventDismissNotification;
import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification;
import info.nightscout.androidaps.plugins.Overview.notifications.Notification;
import info.nightscout.utils.DateUtil;
import info.nightscout.utils.DecimalFormatter;
import info.nightscout.utils.FabricPrivacy;
import info.nightscout.utils.ToastUtils;
public class Profile {
private static Logger log = LoggerFactory.getLogger(Profile.class);
private JSONObject json;
private String units = null;
private double dia = Constants.defaultDIA;
private TimeZone timeZone = TimeZone.getDefault();
private String units;
private double dia;
private TimeZone timeZone;
private JSONArray isf;
private LongSparseArray<Double> isf_v = null; // oldest at index 0
private LongSparseArray<Double> isf_v; // oldest at index 0
private JSONArray ic;
private LongSparseArray<Double> ic_v = null; // oldest at index 0
private LongSparseArray<Double> ic_v; // oldest at index 0
private JSONArray basal;
private LongSparseArray<Double> basal_v = null; // oldest at index 0
private LongSparseArray<Double> basal_v; // oldest at index 0
private JSONArray targetLow;
private LongSparseArray<Double> targetLow_v = null; // oldest at index 0
private LongSparseArray<Double> targetLow_v; // oldest at index 0
private JSONArray targetHigh;
private LongSparseArray<Double> targetHigh_v = null; // oldest at index 0
private LongSparseArray<Double> targetHigh_v; // oldest at index 0
private int percentage = 100;
private int timeshift = 0;
private int percentage;
private int timeshift;
private boolean isValid = true;
private boolean isValidated = false;
protected boolean isValid;
protected boolean isValidated;
// Default constructor for tests
protected Profile() {
}
// Constructor from profileStore JSON
public Profile(JSONObject json, String units) {
this(json, 100, 0);
init(json, 100, 0);
if (this.units == null) {
if (units != null)
this.units = units;
@ -65,6 +65,22 @@ public class Profile {
}
public Profile(JSONObject json, int percentage, int timeshift) {
init(json, percentage, timeshift);
}
protected void init(JSONObject json, int percentage, int timeshift) {
units = null;
dia = Constants.defaultDIA;
timeZone = TimeZone.getDefault();
isf_v = null;
ic_v = null;
basal_v = null;
targetLow_v = null;
targetHigh_v = null;
isValid = true;
isValidated = false;
this.percentage = percentage;
this.timeshift = timeshift;
this.json = json;
@ -78,53 +94,12 @@ public class Profile {
if (json.has("timezone"))
timeZone = TimeZone.getTimeZone(json.getString("timezone"));
isf = json.getJSONArray("sens");
if (getIsf(0) == null) {
int defaultISF = units.equals(Constants.MGDL) ? 400 : 20;
isf = new JSONArray("[{\"time\":\"00:00\",\"value\":\"" + defaultISF + "\",\"timeAsSeconds\":\"0\"}]");
Notification noisf = new Notification(Notification.ISF_MISSING, MainApp.sResources.getString(R.string.isfmissing), Notification.URGENT);
MainApp.bus().post(new EventNewNotification(noisf));
} else {
MainApp.bus().post(new EventDismissNotification(Notification.ISF_MISSING));
}
ic = json.getJSONArray("carbratio");
if (getIc(0) == null) {
int defaultIC = 25;
ic = new JSONArray("[{\"time\":\"00:00\",\"value\":\"" + defaultIC + "\",\"timeAsSeconds\":\"0\"}]");
Notification noic = new Notification(Notification.IC_MISSING, MainApp.sResources.getString(R.string.icmissing), Notification.URGENT);
MainApp.bus().post(new EventNewNotification(noic));
} else {
MainApp.bus().post(new EventDismissNotification(Notification.IC_MISSING));
}
basal = json.getJSONArray("basal");
if (getBasal(0) == null) {
double defaultBasal = 0.1d;
basal = new JSONArray("[{\"time\":\"00:00\",\"value\":\"" + defaultBasal + "\",\"timeAsSeconds\":\"0\"}]");
Notification nobasal = new Notification(Notification.BASAL_MISSING, MainApp.sResources.getString(R.string.basalmissing), Notification.URGENT);
MainApp.bus().post(new EventNewNotification(nobasal));
} else {
MainApp.bus().post(new EventDismissNotification(Notification.BASAL_MISSING));
}
targetLow = json.getJSONArray("target_low");
if (getTargetLow(0) == null) {
double defaultLow = units.equals(Constants.MGDL) ? 120 : 6;
targetLow = new JSONArray("[{\"time\":\"00:00\",\"value\":\"" + defaultLow + "\",\"timeAsSeconds\":\"0\"}]");
Notification notarget = new Notification(Notification.TARGET_MISSING, MainApp.sResources.getString(R.string.targetmissing), Notification.URGENT);
MainApp.bus().post(new EventNewNotification(notarget));
} else {
MainApp.bus().post(new EventDismissNotification(Notification.TARGET_MISSING));
}
targetHigh = json.getJSONArray("target_high");
if (getTargetHigh(0) == null) {
double defaultHigh = units.equals(Constants.MGDL) ? 160 : 8;
targetHigh = new JSONArray("[{\"time\":\"00:00\",\"value\":\"" + defaultHigh + "\",\"timeAsSeconds\":\"0\"}]");
Notification notarget = new Notification(Notification.TARGET_MISSING, MainApp.sResources.getString(R.string.targetmissing), Notification.URGENT);
MainApp.bus().post(new EventNewNotification(notarget));
} else {
MainApp.bus().post(new EventDismissNotification(Notification.TARGET_MISSING));
}
} catch (JSONException e) {
log.error("Unhandled exception", e);
ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.gs(R.string.invalidprofile));
isValid = false;
isValidated = true;
}
@ -133,7 +108,7 @@ public class Profile {
public String log() {
String ret = "\n";
for (Integer hour = 0; hour < 24; hour++) {
double value = getBasal((Integer) (hour * 60 * 60));
double value = getBasalTimeFromMidnight((Integer) (hour * 60 * 60));
ret += "NS basal value for " + hour + ":00 is " + value + "\n";
}
ret += "NS units: " + getUnits();
@ -155,6 +130,10 @@ public class Profile {
}
// mmol or mg/dl
public void setUnits(String units) {
this.units = units;
}
public String getUnits() {
return units;
}
@ -164,6 +143,11 @@ public class Profile {
}
private LongSparseArray<Double> convertToSparseArray(JSONArray array) {
if (array == null) {
isValid = false;
return new LongSparseArray<>();
}
double multiplier = getMultiplier(array);
LongSparseArray<Double> sparse = new LongSparseArray<>();
@ -235,7 +219,7 @@ public class Profile {
for (int i = 0; i < basal_v.size(); i++) {
if (basal_v.valueAt(i) < description.basalMinimumRate) {
basal_v.setValueAt(i, description.basalMinimumRate);
MainApp.bus().post(new EventNewNotification(new Notification(Notification.MINIMAL_BASAL_VALUE_REPLACED, String.format(MainApp.gs(R.string.minimalbasalvaluereplaced), from), Notification.NORMAL)));
sendBelowMinimumNotification(from);
}
}
} else {
@ -249,6 +233,10 @@ public class Profile {
return isValid;
}
protected void sendBelowMinimumNotification(String from) {
MainApp.bus().post(new EventNewNotification(new Notification(Notification.MINIMAL_BASAL_VALUE_REPLACED, String.format(MainApp.gs(R.string.minimalbasalvaluereplaced), from), Notification.NORMAL)));
}
private void validate(LongSparseArray array) {
if (array.size() == 0) {
isValid = false;
@ -262,6 +250,7 @@ public class Profile {
}
}
/*
private Double getValueToTime(JSONArray array, Integer timeAsSeconds) {
Double lastValue = null;
@ -281,6 +270,7 @@ public class Profile {
}
return lastValue;
}
*/
Integer getShitfTimeSecs(Integer originalTime) {
Integer shiftedTime = originalTime + timeshift * 60 * 60;
@ -322,7 +312,7 @@ public class Profile {
return multiplier;
}
private Double getValueToTime(LongSparseArray<Double> array, Integer timeAsSeconds) {
private double getValueToTime(LongSparseArray<Double> array, Integer timeAsSeconds) {
Double lastValue = null;
for (Integer index = 0; index < array.size(); index++) {
@ -337,7 +327,7 @@ public class Profile {
return lastValue;
}
private String format_HH_MM(Integer timeAsSeconds) {
protected String format_HH_MM(Integer timeAsSeconds) {
String time;
int hour = timeAsSeconds / 60 / 60;
int minutes = (timeAsSeconds - hour * 60 * 60) / 60;
@ -364,51 +354,55 @@ public class Profile {
return retValue;
}
public Double getIsf() {
return getIsf(secondsFromMidnight(System.currentTimeMillis()));
public double getIsf() {
return getIsfTimeFromMidnight(secondsFromMidnight(System.currentTimeMillis()));
}
public Double getIsf(long time) {
return getIsf(secondsFromMidnight(time));
public double getIsf(long time) {
return getIsfTimeFromMidnight(secondsFromMidnight(time));
}
public Double getIsf(Integer timeAsSeconds) {
double getIsfTimeFromMidnight(int timeAsSeconds) {
if (isf_v == null)
isf_v = convertToSparseArray(isf);
return getValueToTime(isf_v, timeAsSeconds);
}
public String getIsfList() {
if (isf_v == null)
isf_v = convertToSparseArray(isf);
return getValuesList(isf_v, null, new DecimalFormat("0.0"), getUnits() + "/U");
}
public Double getIc() {
return getIc(secondsFromMidnight(System.currentTimeMillis()));
public double getIc() {
return getIcTimeFromMidnight(secondsFromMidnight(System.currentTimeMillis()));
}
public Double getIc(long time) {
return getIc(secondsFromMidnight(time));
public double getIc(long time) {
return getIcTimeFromMidnight(secondsFromMidnight(time));
}
public Double getIc(Integer timeAsSeconds) {
public double getIcTimeFromMidnight(int timeAsSeconds) {
if (ic_v == null)
ic_v = convertToSparseArray(ic);
return getValueToTime(ic_v, timeAsSeconds);
}
public String getIcList() {
if (ic_v == null)
ic_v = convertToSparseArray(ic);
return getValuesList(ic_v, null, new DecimalFormat("0.0"), "g/U");
}
public Double getBasal() {
return getBasal(secondsFromMidnight(System.currentTimeMillis()));
public double getBasal() {
return getBasalTimeFromMidnight(secondsFromMidnight(System.currentTimeMillis()));
}
public Double getBasal(long time) {
return getBasal(secondsFromMidnight(time));
public double getBasal(long time) {
return getBasalTimeFromMidnight(secondsFromMidnight(time));
}
public synchronized Double getBasal(Integer timeAsSeconds) {
public synchronized double getBasalTimeFromMidnight(int timeAsSeconds) {
if (basal_v == null) {
basal_v = convertToSparseArray(basal);
}
@ -418,17 +412,17 @@ public class Profile {
public String getBasalList() {
if (basal_v == null)
basal_v = convertToSparseArray(basal);
return getValuesList(basal_v, null, new DecimalFormat("0.00"), "U");
return getValuesList(basal_v, null, new DecimalFormat("0.00"), "U/h");
}
public class BasalValue {
public BasalValue(Integer timeAsSeconds, Double value) {
public BasalValue(int timeAsSeconds, double value) {
this.timeAsSeconds = timeAsSeconds;
this.value = value;
}
public Integer timeAsSeconds;
public Double value;
public int timeAsSeconds;
public double value;
}
public synchronized BasalValue[] getBasalValues() {
@ -438,7 +432,7 @@ public class Profile {
for (Integer index = 0; index < basal_v.size(); index++) {
Integer tas = (int) basal_v.keyAt(index);
Double value = basal_v.valueAt(index);
double value = basal_v.valueAt(index);
ret[index] = new BasalValue(tas, value);
}
return ret;
@ -448,52 +442,56 @@ public class Profile {
return getTarget(secondsFromMidnight(System.currentTimeMillis()));
}
private double getTarget(Integer time) {
return (getTargetLow(time) + getTargetHigh(time))/2;
protected double getTarget(int timeAsSeconds) {
return (getTargetLowTimeFromMidnight(timeAsSeconds) + getTargetHighTimeFromMidnight(timeAsSeconds))/2;
}
public Double getTargetLow() {
return getTargetLow(secondsFromMidnight(System.currentTimeMillis()));
public double getTargetLow() {
return getTargetLowTimeFromMidnight(secondsFromMidnight(System.currentTimeMillis()));
}
public Double getTargetLow(long time) {
return getTargetLow(secondsFromMidnight(time));
public double getTargetLow(long time) {
return getTargetLowTimeFromMidnight(secondsFromMidnight(time));
}
public Double getTargetLow(Integer timeAsSeconds) {
public double getTargetLowTimeFromMidnight(int timeAsSeconds) {
if (targetLow_v == null)
targetLow_v = convertToSparseArray(targetLow);
return getValueToTime(targetLow_v, timeAsSeconds);
}
public Double getTargetHigh() {
return getTargetHigh(secondsFromMidnight(System.currentTimeMillis()));
public double getTargetHigh() {
return getTargetHighTimeFromMidnight(secondsFromMidnight(System.currentTimeMillis()));
}
public Double getTargetHigh(long time) {
return getTargetHigh(secondsFromMidnight(time));
public double getTargetHigh(long time) {
return getTargetHighTimeFromMidnight(secondsFromMidnight(time));
}
public Double getTargetHigh(Integer timeAsSeconds) {
public double getTargetHighTimeFromMidnight(int timeAsSeconds) {
if (targetHigh_v == null)
targetHigh_v = convertToSparseArray(targetHigh);
return getValueToTime(targetHigh_v, timeAsSeconds);
}
public String getTargetList() {
if (targetLow_v == null)
targetLow_v = convertToSparseArray(targetLow);
if (targetHigh_v == null)
targetHigh_v = convertToSparseArray(targetHigh);
return getValuesList(targetLow_v, targetHigh_v, new DecimalFormat("0.0"), getUnits());
}
public double getMaxDailyBasal() {
Double max = 0d;
for (Integer hour = 0; hour < 24; hour++) {
double value = getBasal((Integer) (hour * 60 * 60));
double max = 0d;
for (int hour = 0; hour < 24; hour++) {
double value = getBasalTimeFromMidnight((Integer) (hour * 60 * 60));
if (value > max) max = value;
}
return max;
}
public static Integer secondsFromMidnight() {
public static int secondsFromMidnight() {
Calendar c = Calendar.getInstance();
long now = c.getTimeInMillis();
c.set(Calendar.HOUR_OF_DAY, 0);
@ -504,7 +502,7 @@ public class Profile {
return (int) (passed / 1000);
}
public static Integer secondsFromMidnight(long date) {
public static int secondsFromMidnight(long date) {
Calendar c = Calendar.getInstance();
c.setTimeInMillis(date);
c.set(Calendar.HOUR_OF_DAY, 0);
@ -515,22 +513,22 @@ public class Profile {
return (int) (passed / 1000);
}
public static Double toMgdl(Double value, String units) {
public static double toMgdl(double value, String units) {
if (units.equals(Constants.MGDL)) return value;
else return value * Constants.MMOLL_TO_MGDL;
}
public static Double toMmol(Double value, String units) {
public static double toMmol(double value, String units) {
if (units.equals(Constants.MGDL)) return value * Constants.MGDL_TO_MMOLL;
else return value;
}
public static Double fromMgdlToUnits(Double value, String units) {
public static double fromMgdlToUnits(double value, String units) {
if (units.equals(Constants.MGDL)) return value;
else return value * Constants.MGDL_TO_MMOLL;
}
public static Double toUnits(Double valueInMgdl, Double valueInMmol, String units) {
public static double toUnits(Double valueInMgdl, Double valueInMmol, String units) {
if (units.equals(Constants.MGDL)) return valueInMgdl;
else return valueInMmol;
}
@ -556,7 +554,7 @@ public class Profile {
public double percentageBasalSum() {
double result = 0d;
for (int i = 0; i < 24; i++) {
result += getBasal((Integer) (i * 60 * 60));
result += getBasalTimeFromMidnight(i * 60 * 60);
}
return result;
}
@ -565,7 +563,7 @@ public class Profile {
public double baseBasalSum() {
double result = 0d;
for (int i = 0; i < 24; i++) {
result += getBasal((Integer) (i * 60 * 60)) / getMultiplier(basal_v);
result += getBasalTimeFromMidnight(i * 60 * 60) / getMultiplier(basal_v);
}
return result;
}

View file

@ -3,10 +3,14 @@ package info.nightscout.androidaps.data;
import android.support.annotation.Nullable;
import android.support.v4.util.LongSparseArray;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import info.nightscout.androidaps.interfaces.Interval;
import info.nightscout.utils.DateUtil;
/**
* Created by mike on 09.05.2017.
@ -16,6 +20,7 @@ import info.nightscout.androidaps.interfaces.Interval;
// When no interval match the lastest record without duration is used
public class ProfileIntervals<T extends Interval> {
private static Logger log = LoggerFactory.getLogger(ProfileIntervals.class);
private LongSparseArray<T> rawData = new LongSparseArray<>(); // oldest at index 0
@ -51,6 +56,11 @@ public class ProfileIntervals<T extends Interval> {
public synchronized Interval getValueToTime(long time) {
int index = binarySearch(time);
if (index >= 0) return rawData.valueAt(index);
// if we request data older than first record, use oldest instead
if (rawData.size() > 0) {
log.debug("Requested profile for time: " + DateUtil.dateAndTimeString(time) + ". Providing oldest record: " + rawData.valueAt(0).toString());
return rawData.valueAt(0);
}
return null;
}

View file

@ -13,7 +13,7 @@ import info.nightscout.androidaps.R;
import info.nightscout.utils.DecimalFormatter;
import info.nightscout.utils.Round;
public class PumpEnactResult extends Object {
public class PumpEnactResult {
private static Logger log = LoggerFactory.getLogger(PumpEnactResult.class);
public boolean success = false; // request was processed successfully (but possible no change was needed)
@ -61,6 +61,7 @@ public class PumpEnactResult extends Object {
this.percent = percent;
return this;
}
public PumpEnactResult isPercent(boolean isPercent) {
this.isPercent = isPercent;
return this;

View file

@ -8,7 +8,6 @@ import android.support.annotation.Nullable;
import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper;
import com.j256.ormlite.dao.CloseableIterator;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.stmt.PreparedQuery;
import com.j256.ormlite.stmt.QueryBuilder;
import com.j256.ormlite.stmt.Where;
@ -241,7 +240,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
log.error("Unhandled exception", e);
}
VirtualPumpPlugin.setFakingStatus(true);
scheduleBgChange(null); // trigger refresh
scheduleBgChange(null, false, false); // trigger refresh
scheduleTemporaryBasalChange();
scheduleTreatmentChange(null);
scheduleExtendedBolusChange();
@ -367,14 +366,14 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
}
// ------------------- BgReading handling -----------------------
public boolean createIfNotExists(BgReading bgReading, String from) {
public boolean createIfNotExists(BgReading bgReading, String from, boolean isFromActiveBgSource) {
try {
bgReading.date = roundDateToSec(bgReading.date);
BgReading old = getDaoBgReadings().queryForId(bgReading.date);
if (old == null) {
getDaoBgReadings().create(bgReading);
log.debug("BG: New record from: " + from + " " + bgReading.toString());
scheduleBgChange(bgReading);
scheduleBgChange(bgReading, true, isFromActiveBgSource);
return true;
}
if (!old.isEqual(bgReading)) {
@ -382,7 +381,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
old.copyFrom(bgReading);
getDaoBgReadings().update(old);
log.debug("BG: Updating record from: " + from + " New data: " + old.toString());
scheduleBgChange(bgReading);
scheduleBgChange(bgReading, false, isFromActiveBgSource);
return false;
}
} catch (SQLException e) {
@ -400,11 +399,11 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
}
}
private static void scheduleBgChange(@Nullable final BgReading bgReading) {
private static void scheduleBgChange(@Nullable final BgReading bgReading, boolean isNew, boolean isFromActiveBgSource) {
class PostRunnable implements Runnable {
public void run() {
log.debug("Firing EventNewBg");
MainApp.bus().post(new EventNewBG(bgReading));
MainApp.bus().post(new EventNewBG(bgReading, isNew, isFromActiveBgSource));
scheduledBgPost = null;
}
}
@ -1709,7 +1708,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
if (trJson.has("profileJson"))
profileSwitch.profileJson = trJson.getString("profileJson");
else {
ProfileStore store = ConfigBuilderPlugin.getActiveProfileInterface().getProfile();
ProfileStore store = MainApp.getConfigBuilder().getActiveProfileInterface().getProfile();
Profile profile = store.getSpecificProfile(profileSwitch.profileName);
if (profile != null) {
profileSwitch.profileJson = profile.getData().toString();

View file

@ -10,8 +10,17 @@ import info.nightscout.androidaps.db.BgReading;
public class EventNewBG extends EventLoop {
@Nullable
public final BgReading bgReading;
public final boolean isNew;
public final boolean isFromActiveBgSource;
public EventNewBG(BgReading bgReading) {
/** Whether the BgReading is current (enough to use for treatment decisions). */
public boolean isCurrent() {
return bgReading != null && bgReading.date + 9 * 60 * 1000 > System.currentTimeMillis();
}
public EventNewBG(@Nullable BgReading bgReading, boolean isNew, boolean isFromActiveBgSource) {
this.bgReading = bgReading;
this.isNew = isNew;
this.isFromActiveBgSource = isFromActiveBgSource;
}
}

View file

@ -5,8 +5,8 @@ import org.json.JSONObject;
import java.util.Date;
import info.nightscout.androidaps.data.DetailedBolusInfo;
import info.nightscout.androidaps.data.PumpEnactResult;
import info.nightscout.androidaps.data.Profile;
import info.nightscout.androidaps.data.PumpEnactResult;
/**
* Created by mike on 04.06.2016.

View file

@ -127,12 +127,14 @@ public class ActionsFragment extends SubscriberFragment implements View.OnClickL
return;
}
final PumpInterface pump = ConfigBuilderPlugin.getActivePump();
if (!pump.getPumpDescription().isSetBasalProfileCapable || !pump.isInitialized() || pump.isSuspended())
final boolean basalprofileEnabled = MainApp.isEngineeringModeOrRelease()
&& pump.getPumpDescription().isSetBasalProfileCapable;
if (!basalprofileEnabled || !pump.isInitialized() || pump.isSuspended())
profileSwitch.setVisibility(View.GONE);
else
profileSwitch.setVisibility(View.VISIBLE);
if (!pump.getPumpDescription().isExtendedBolusCapable || !pump.isInitialized() || pump.isSuspended() || pump.isFakingTempsByExtendedBoluses()) {
extendedBolus.setVisibility(View.GONE);
extendedBolusCancel.setVisibility(View.GONE);

View file

@ -96,7 +96,7 @@ public class CareportalFragment extends SubscriberFragment implements View.OnCli
noProfileView = view.findViewById(R.id.profileview_noprofile);
butonsLayout = (LinearLayout) view.findViewById(R.id.careportal_buttons);
ProfileStore profileStore = ConfigBuilderPlugin.getActiveProfileInterface().getProfile();
ProfileStore profileStore = MainApp.getConfigBuilder().getActiveProfileInterface().getProfile();
if (profileStore == null) {
noProfileView.setVisibility(View.VISIBLE);
butonsLayout.setVisibility(View.GONE);

View file

@ -59,6 +59,7 @@ import info.nightscout.utils.FabricPrivacy;
import info.nightscout.utils.DateUtil;
import info.nightscout.utils.NSUpload;
import info.nightscout.utils.NumberPicker;
import info.nightscout.utils.OKDialog;
import info.nightscout.utils.SP;
import info.nightscout.utils.SafeParse;
import info.nightscout.utils.Translator;
@ -73,7 +74,7 @@ public class NewNSTreatmentDialog extends DialogFragment implements View.OnClick
Profile profile;
ProfileStore profileStore;
String units;
String units = Constants.MGDL;
TextView eventTypeText;
LinearLayout layoutPercent;
@ -168,11 +169,16 @@ public class NewNSTreatmentDialog extends DialogFragment implements View.OnClick
// profile
profile = MainApp.getConfigBuilder().getProfile();
profileStore = ConfigBuilderPlugin.getActiveProfileInterface().getProfile();
profileStore = MainApp.getConfigBuilder().getActiveProfileInterface().getProfile();
if (profileStore == null) {
if (options.eventType == R.id.careportal_profileswitch) {
log.error("Profile switch called but plugin doesn't contain valid profile");
}
} else {
ArrayList<CharSequence> profileList;
units = profile != null ? profile.getUnits() : Constants.MGDL;
profileList = profileStore.getProfileList();
ArrayAdapter<CharSequence> adapter = new ArrayAdapter<CharSequence>(getContext(),
ArrayAdapter<CharSequence> adapter = new ArrayAdapter<>(getContext(),
R.layout.spinner_centered, profileList);
profileSpinner.setAdapter(adapter);
// set selected to actual profile
@ -180,7 +186,7 @@ public class NewNSTreatmentDialog extends DialogFragment implements View.OnClick
if (profileList.get(p).equals(MainApp.getConfigBuilder().getProfileName(false)))
profileSpinner.setSelection(p);
}
}
final Double bg = Profile.fromMgdlToUnits(GlucoseStatus.getGlucoseStatusData() != null ? GlucoseStatus.getGlucoseStatusData().glucose : 0d, units);
// temp target
@ -720,7 +726,7 @@ public class NewNSTreatmentDialog extends DialogFragment implements View.OnClick
profileSwitch.source = Source.USER;
profileSwitch.profileName = profileName;
profileSwitch.profileJson = profileStore.getSpecificProfile(profileName).getData().toString();
profileSwitch.profilePlugin = ConfigBuilderPlugin.getActiveProfileInterface().getClass().getName();
profileSwitch.profilePlugin = MainApp.getConfigBuilder().getActiveProfileInterface().getClass().getName();
profileSwitch.durationInMinutes = duration;
profileSwitch.isCPP = percentage != 100 || timeshift != 0;
profileSwitch.timeshift = timeshift;
@ -752,7 +758,7 @@ public class NewNSTreatmentDialog extends DialogFragment implements View.OnClick
profileSwitch.source = Source.USER;
profileSwitch.profileName = MainApp.getConfigBuilder().getProfileName(System.currentTimeMillis(), false);
profileSwitch.profileJson = MainApp.getConfigBuilder().getProfile().getData().toString();
profileSwitch.profilePlugin = ConfigBuilderPlugin.getActiveProfileInterface().getClass().getName();
profileSwitch.profilePlugin = MainApp.getConfigBuilder().getActiveProfileInterface().getClass().getName();
profileSwitch.durationInMinutes = duration;
profileSwitch.isCPP = percentage != 100 || timeshift != 0;
profileSwitch.timeshift = timeshift;

View file

@ -42,6 +42,7 @@ import info.nightscout.androidaps.interfaces.SensitivityInterface;
import info.nightscout.androidaps.interfaces.TreatmentsInterface;
import info.nightscout.androidaps.plugins.Loop.APSResult;
import info.nightscout.androidaps.plugins.Loop.LoopPlugin;
import info.nightscout.androidaps.plugins.Overview.events.EventDismissNotification;
import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification;
import info.nightscout.androidaps.plugins.Overview.notifications.Notification;
import info.nightscout.androidaps.plugins.PumpVirtual.VirtualPumpPlugin;
@ -200,7 +201,7 @@ public class ConfigBuilderPlugin implements PluginBase, ConstraintsInterface, Tr
return activeBgSource;
}
public static ProfileInterface getActiveProfileInterface() {
public ProfileInterface getActiveProfileInterface() {
return activeProfile;
}
@ -738,6 +739,7 @@ public class ConfigBuilderPlugin implements PluginBase, ConstraintsInterface, Tr
@Override
public void addToHistoryProfileSwitch(ProfileSwitch profileSwitch) {
MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_SWITCH_MISSING));
activeTreatments.addToHistoryProfileSwitch(profileSwitch);
NSUpload.uploadProfileSwitch(profileSwitch);
}
@ -760,8 +762,6 @@ public class ConfigBuilderPlugin implements PluginBase, ConstraintsInterface, Tr
}
public String getProfileName(long time, boolean customized) {
boolean ignoreProfileSwitchEvents = SP.getBoolean(R.string.key_do_not_track_profile_switch, false);
if (!ignoreProfileSwitchEvents) {
ProfileSwitch profileSwitch = getProfileSwitchFromHistory(time);
if (profileSwitch != null) {
if (profileSwitch.profileJson != null) {
@ -772,21 +772,11 @@ public class ConfigBuilderPlugin implements PluginBase, ConstraintsInterface, Tr
return profileSwitch.profileName;
}
}
}
// Unable to determine profile, failover to default
String defaultProfile = activeProfile.getProfile().getDefaultProfileName();
if (defaultProfile != null)
return defaultProfile;
// If default from plugin fails .... create empty
return "Default";
return MainApp.gs(R.string.noprofileselected);
}
public boolean isProfileValid(String from) {
return getProfile() != null && getProfile().isValid(from) &&
activeProfile != null &&
activeProfile.getProfile() != null &&
activeProfile.getProfile().getDefaultProfile() != null &&
activeProfile.getProfile().getDefaultProfile().isValid(from);
return getProfile() != null && getProfile().isValid(from);
}
@Nullable
@ -806,8 +796,6 @@ public class ConfigBuilderPlugin implements PluginBase, ConstraintsInterface, Tr
return null; //app not initialized
}
//log.debug("Profile for: " + new Date(time).toLocaleString() + " : " + getProfileName(time));
boolean ignoreProfileSwitchEvents = SP.getBoolean(R.string.key_do_not_track_profile_switch, false);
if (!ignoreProfileSwitchEvents) {
ProfileSwitch profileSwitch = getProfileSwitchFromHistory(time);
if (profileSwitch != null) {
if (profileSwitch.profileJson != null) {
@ -818,29 +806,6 @@ public class ConfigBuilderPlugin implements PluginBase, ConstraintsInterface, Tr
return profile;
}
}
}
// Unable to determine profile, failover to default
if (activeProfile.getProfile() == null) {
log.debug("getProfile activeProfile.getProfile() == null: returning null (activeProfile=" + activeProfile.getClass().getSimpleName() + ")");
return null; //app not initialized
}
Profile defaultProfile = activeProfile.getProfile().getDefaultProfile();
if (defaultProfile != null)
return defaultProfile;
// If default from plugin fails .... create empty
try {
Notification noisf = new Notification(Notification.ISF_MISSING, MainApp.sResources.getString(R.string.isfmissing), Notification.URGENT);
MainApp.bus().post(new EventNewNotification(noisf));
Notification noic = new Notification(Notification.IC_MISSING, MainApp.sResources.getString(R.string.icmissing), Notification.URGENT);
MainApp.bus().post(new EventNewNotification(noic));
Notification nobasal = new Notification(Notification.BASAL_MISSING, MainApp.sResources.getString(R.string.basalmissing), Notification.URGENT);
MainApp.bus().post(new EventNewNotification(nobasal));
Notification notarget = new Notification(Notification.TARGET_MISSING, MainApp.sResources.getString(R.string.targetmissing), Notification.URGENT);
MainApp.bus().post(new EventNewNotification(notarget));
return new Profile(new JSONObject("{\"dia\":\"3\",\"carbratio\":[{\"time\":\"00:00\",\"value\":\"20\"}],\"carbs_hr\":\"20\",\"delay\":\"20\",\"sens\":[{\"time\":\"00:00\",\"value\":\"20\"}],\"timezone\":\"UTC\",\"basal\":[{\"time\":\"00:00\",\"value\":\"0.1\"}],\"target_low\":[{\"time\":\"00:00\",\"value\":\"6\"}],\"target_high\":[{\"time\":\"00:00\",\"value\":\"8\"}],\"startDate\":\"1970-01-01T00:00:00.000Z\",\"units\":\"mmol\"}}"), 100, 0);
} catch (JSONException e) {
log.error("Unhandled exception", e);
}
log.debug("getProfile at the end: returning null");
return null;
}

View file

@ -102,6 +102,7 @@ public class SafetyPlugin implements PluginBase, ConstraintsInterface {
**/
@Override
public boolean isClosedModeEnabled() {
if (!MainApp.isEngineeringModeOrRelease()) return false;
String mode = SP.getString("aps_mode", "open");
return mode.equals("closed") && BuildConfig.CLOSEDLOOP;
}

View file

@ -531,6 +531,8 @@ public class IobCobCalculatorPlugin implements PluginBase {
@Subscribe
public void onEventNewBG(EventNewBG ev) {
if (!ev.isFromActiveBgSource)
return;
if (this != getPlugin()) {
log.debug("Ignoring event for non default instance");
return;

View file

@ -110,11 +110,6 @@ public class IobCobThread extends Thread {
return; // profile not set yet
}
if (profile.getIsf(bgTime) == null) {
log.debug("Aborting calculation thread (no ISF): " + from);
return; // profile not set yet
}
if (Config.logAutosensData)
log.debug("Processing calculation thread: " + from + " (" + i + "/" + bucketed_data.size() + ")");

View file

@ -23,6 +23,7 @@ import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.Profile;
import info.nightscout.androidaps.data.PumpEnactResult;
import info.nightscout.androidaps.db.BgReading;
import info.nightscout.androidaps.events.EventNewBG;
import info.nightscout.androidaps.events.EventTreatmentChange;
import info.nightscout.androidaps.interfaces.APSInterface;
@ -159,8 +160,12 @@ public class LoopPlugin implements PluginBase {
@Subscribe
public void onStatusEvent(final EventAutosensCalculationFinished ev) {
if (ev.cause instanceof EventNewBG) {
invoke(ev.getClass().getSimpleName() + "(" + ev.cause.getClass().getSimpleName() + ")", true);
if (!(ev.cause instanceof EventNewBG))
return;
EventNewBG bgEv = (EventNewBG) ev.cause;
if (bgEv.isNew && bgEv.isFromActiveBgSource && bgEv.isCurrent()) {
invoke("New BG", true);
}
}

View file

@ -204,7 +204,7 @@ public class DetermineBasalAdapterAMAJS {
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("sens", Profile.toMgdl(profile.getIsf(), units));
mProfile.put("max_daily_safety_multiplier", SP.getInt("openapsama_max_daily_safety_multiplier", 3));
mProfile.put("current_basal_safety_multiplier", SP.getDouble("openapsama_current_basal_safety_multiplier", 4d));
mProfile.put("skip_neutral_temps", true);

View file

@ -212,9 +212,9 @@ public class OpenAPSAMAPlugin implements PluginBase, APSInterface {
if (!HardLimits.checkOnlyHardLimits(profile.getDia(), "dia", HardLimits.MINDIA, HardLimits.MAXDIA))
return;
if (!HardLimits.checkOnlyHardLimits(profile.getIc(profile.secondsFromMidnight()), "carbratio", HardLimits.MINIC, HardLimits.MAXIC))
if (!HardLimits.checkOnlyHardLimits(profile.getIcTimeFromMidnight(profile.secondsFromMidnight()), "carbratio", HardLimits.MINIC, HardLimits.MAXIC))
return;
if (!HardLimits.checkOnlyHardLimits(Profile.toMgdl(profile.getIsf().doubleValue(), units), "sens", HardLimits.MINISF, HardLimits.MAXISF))
if (!HardLimits.checkOnlyHardLimits(Profile.toMgdl(profile.getIsf(), units), "sens", HardLimits.MINISF, HardLimits.MAXISF))
return;
if (!HardLimits.checkOnlyHardLimits(profile.getMaxDailyBasal(), "max_daily_basal", 0.05, HardLimits.maxBasal()))
return;

View file

@ -172,7 +172,7 @@ public class DetermineBasalAdapterMAJS {
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("sens", Profile.toMgdl(profile.getIsf(), units));
mProfile.put("current_basal", basalRate);

View file

@ -212,9 +212,9 @@ public class OpenAPSMAPlugin implements PluginBase, APSInterface {
if (!checkOnlyHardLimits(profile.getDia(), "dia", HardLimits.MINDIA, HardLimits.MAXDIA))
return;
if (!checkOnlyHardLimits(profile.getIc(profile.secondsFromMidnight()), "carbratio", HardLimits.MINIC, HardLimits.MAXIC))
if (!checkOnlyHardLimits(profile.getIcTimeFromMidnight(profile.secondsFromMidnight()), "carbratio", HardLimits.MINIC, HardLimits.MAXIC))
return;
if (!checkOnlyHardLimits(Profile.toMgdl(profile.getIsf().doubleValue(), units), "sens", HardLimits.MINISF, HardLimits.MAXISF))
if (!checkOnlyHardLimits(Profile.toMgdl(profile.getIsf(), units), "sens", HardLimits.MINISF, HardLimits.MAXISF))
return;
if (!checkOnlyHardLimits(profile.getMaxDailyBasal(), "max_daily_basal", 0.05, HardLimits.maxBasal()))
return;

View file

@ -228,7 +228,7 @@ public class DetermineBasalAdapterSMBJS {
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("sens", Profile.toMgdl(profile.getIsf(), units));
mProfile.put("max_daily_safety_multiplier", SP.getInt("openapsama_max_daily_safety_multiplier", 3));
mProfile.put("current_basal_safety_multiplier", SP.getDouble("openapsama_current_basal_safety_multiplier", 4d));

View file

@ -216,9 +216,9 @@ public class OpenAPSSMBPlugin implements PluginBase, APSInterface {
maxBasal = verifyHardLimits(maxBasal, "max_basal", 0.1, HardLimits.maxBasal());
if (!checkOnlyHardLimits(profile.getDia(), "dia", HardLimits.MINDIA, HardLimits.MAXDIA)) return;
if (!checkOnlyHardLimits(profile.getIc(profile.secondsFromMidnight()), "carbratio", HardLimits.MINIC, HardLimits.MAXIC))
if (!checkOnlyHardLimits(profile.getIcTimeFromMidnight(profile.secondsFromMidnight()), "carbratio", HardLimits.MINIC, HardLimits.MAXIC))
return;
if (!checkOnlyHardLimits(Profile.toMgdl(profile.getIsf().doubleValue(), units), "sens", HardLimits.MINISF, HardLimits.MAXISF))
if (!checkOnlyHardLimits(Profile.toMgdl(profile.getIsf(), units), "sens", HardLimits.MINISF, HardLimits.MAXISF))
return;
if (!checkOnlyHardLimits(profile.getMaxDailyBasal(), "max_daily_basal", 0.05, HardLimits.maxBasal())) return;
if (!checkOnlyHardLimits(pump.getBaseBasalRate(), "current_basal", 0.01, HardLimits.maxBasal())) return;

View file

@ -142,6 +142,7 @@ public class WizardDialog extends DialogFragment implements OnClickListener, Com
@Subscribe
public void onStatusEvent(final EventNewBG e) {
if (e.isFromActiveBgSource && e.isNew && e.isCurrent()) {
Activity activity = getActivity();
if (activity != null)
activity.runOnUiThread(new Runnable() {
@ -151,6 +152,7 @@ public class WizardDialog extends DialogFragment implements OnClickListener, Com
}
});
}
}
@Subscribe
public void onStatusEvent(final EventAutosensCalculationFinished e) {
@ -400,7 +402,7 @@ public class WizardDialog extends DialogFragment implements OnClickListener, Com
private void initDialog() {
Profile profile = MainApp.getConfigBuilder().getProfile();
ProfileStore profileStore = ConfigBuilderPlugin.getActiveProfileInterface().getProfile();
ProfileStore profileStore = MainApp.getConfigBuilder().getActiveProfileInterface().getProfile();
if (profile == null) {
ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.noprofile));
@ -444,7 +446,7 @@ public class WizardDialog extends DialogFragment implements OnClickListener, Com
}
private void calculateInsulin() {
ProfileStore profileStore = ConfigBuilderPlugin.getActiveProfileInterface().getProfile();
ProfileStore profileStore = MainApp.getConfigBuilder().getActiveProfileInterface().getProfile();
if (profileSpinner == null || profileSpinner.getSelectedItem() == null || profileStore == null)
return; // not initialized yet
String selectedAlternativeProfile = profileSpinner.getSelectedItem().toString();

View file

@ -188,6 +188,7 @@ public class OverviewFragment extends Fragment implements View.OnClickListener,
final Object updateSync = new Object();
public enum CHARTTYPE {PRE, BAS, IOB, COB, DEV, SEN};
private static final ScheduledExecutorService worker = Executors.newSingleThreadScheduledExecutor();
private static ScheduledFuture<?> scheduledUpdate = null;
@ -464,9 +465,11 @@ public class OverviewFragment extends Fragment implements View.OnClickListener,
} else if (v == activeProfileView) {
menu.setHeaderTitle(MainApp.sResources.getString(R.string.profile));
menu.add(MainApp.sResources.getString(R.string.danar_viewprofile));
if (MainApp.getConfigBuilder().getActiveProfileInterface().getProfile() != null) {
menu.add(MainApp.sResources.getString(R.string.careportal_profileswitch));
}
}
}
@Override
public boolean onContextItemSelected(MenuItem item) {
@ -966,7 +969,7 @@ public class OverviewFragment extends Fragment implements View.OnClickListener,
if (timeView != null) { //must not exists
timeView.setText(DateUtil.timeString(new Date()));
}
if (!MainApp.getConfigBuilder().isProfileValid("Overview")) {// app not initialized yet
if (!MainApp.getConfigBuilder().isProfileValid("Overview")) {
pumpStatusView.setText(R.string.noprofileset);
pumpStatusLayout.setVisibility(View.VISIBLE);
loopStatusLayout.setVisibility(View.GONE);
@ -983,12 +986,6 @@ public class OverviewFragment extends Fragment implements View.OnClickListener,
final PumpInterface pump = ConfigBuilderPlugin.getActivePump();
final Profile profile = MainApp.getConfigBuilder().getProfile();
if (profile == null) {
pumpStatusView.setText(R.string.noprofileset);
pumpStatusLayout.setVisibility(View.VISIBLE);
loopStatusLayout.setVisibility(View.GONE);
return;
}
final String units = profile.getUnits();
final double lowLine = OverviewPlugin.getPlugin().determineLowLine(units);
@ -1244,9 +1241,7 @@ public class OverviewFragment extends Fragment implements View.OnClickListener,
flag &= ~Paint.STRIKE_THRU_TEXT_FLAG;
bgView.setPaintFlags(flag);
Long agoMsec = System.currentTimeMillis() - lastBG.date;
int agoMin = (int) (agoMsec / 60d / 1000d);
timeAgoView.setText(String.format(MainApp.sResources.getString(R.string.minago), agoMin));
timeAgoView.setText(DateUtil.minAgo(lastBG.date));
// iob
MainApp.getConfigBuilder().updateTotalIOBTreatments();

View file

@ -42,10 +42,7 @@ public class Notification {
public static final int APPROACHING_DAILY_LIMIT = 11;
public static final int NSCLIENT_NO_WRITE_PERMISSION = 12;
public static final int MISSING_SMS_PERMISSION = 13;
public static final int ISF_MISSING = 14;
public static final int IC_MISSING = 15;
public static final int BASAL_MISSING = 16;
public static final int TARGET_MISSING = 17;
public static final int NSANNOUNCEMENT = 18;
public static final int NSALARM = 19;
public static final int NSURGENTALARM = 20;
@ -53,12 +50,15 @@ public class Notification {
public static final int TOAST_ALARM = 22;
public static final int WRONGBASALSTEP = 23;
public static final int WRONG_DRIVER = 24;
public static final int COMBO_PUMP_ALARM = 25;
public static final int PUMP_UNREACHABLE = 26;
public static final int BG_READINGS_MISSED = 27;
public static final int UNSUPPORTED_FIRMWARE = 28;
public static final int MINIMAL_BASAL_VALUE_REPLACED = 29;
public static final int BASAL_PROFILE_NOT_ALIGNED_TO_HOURS = 30;
public static final int ZERO_VALUE_IN_PROFILE = 31;
public static final int PROFILE_SWITCH_MISSING = 32;
public static final int NOT_ENG_MODE_OR_RELEASE = 33;
public int id;
public Date date;

View file

@ -255,6 +255,7 @@ public class PersistentNotificationPlugin implements PluginBase {
@Subscribe
public void onStatusEvent(final EventNewBG ev) {
if (ev.isFromActiveBgSource && ev.isNew && ev.isCurrent())
updateNotification();
}

View file

@ -14,47 +14,55 @@ import com.squareup.otto.Subscribe;
import java.util.ArrayList;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnItemSelected;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.Profile;
import info.nightscout.androidaps.data.ProfileStore;
import info.nightscout.androidaps.plugins.Common.SubscriberFragment;
import info.nightscout.androidaps.plugins.ProfileNS.events.EventNSProfileUpdateGUI;
import info.nightscout.androidaps.plugins.Treatments.fragments.ProfileGraph;
import info.nightscout.utils.DecimalFormatter;
import info.nightscout.utils.FabricPrivacy;
import static butterknife.OnItemSelected.Callback.NOTHING_SELECTED;
public class NSProfileFragment extends SubscriberFragment implements AdapterView.OnItemSelectedListener {
private Spinner profileSpinner;
private TextView noProfile;
private TextView units;
private TextView dia;
private TextView activeProfile;
private TextView ic;
private TextView isf;
private TextView basal;
private TextView target;
public class NSProfileFragment extends SubscriberFragment {
@BindView(R.id.nsprofile_spinner)
Spinner profileSpinner;
@BindView(R.id.profileview_noprofile)
TextView noProfile;
@BindView(R.id.profileview_invalidprofile)
TextView invalidProfile;
@BindView(R.id.profileview_units)
TextView units;
@BindView(R.id.profileview_dia)
TextView dia;
@BindView(R.id.profileview_activeprofile)
TextView activeProfile;
@BindView(R.id.profileview_ic)
TextView ic;
@BindView(R.id.profileview_isf)
TextView isf;
@BindView(R.id.profileview_basal)
TextView basal;
@BindView(R.id.profileview_target)
TextView target;
@BindView(R.id.basal_graph)
ProfileGraph basalGraph;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
try {
View layout = inflater.inflate(R.layout.nsprofile_fragment, container, false);
profileSpinner = (Spinner) layout.findViewById(R.id.nsprofile_spinner);
noProfile = (TextView) layout.findViewById(R.id.profileview_noprofile);
units = (TextView) layout.findViewById(R.id.profileview_units);
dia = (TextView) layout.findViewById(R.id.profileview_dia);
activeProfile = (TextView) layout.findViewById(R.id.profileview_activeprofile);
ic = (TextView) layout.findViewById(R.id.profileview_ic);
isf = (TextView) layout.findViewById(R.id.profileview_isf);
basal = (TextView) layout.findViewById(R.id.profileview_basal);
target = (TextView) layout.findViewById(R.id.profileview_target);
profileSpinner.setOnItemSelectedListener(this);
View view = inflater.inflate(R.layout.nsprofile_fragment, container, false);
unbinder = ButterKnife.bind(this, view);
updateGUI();
return layout;
return view;
} catch (Exception e) {
FabricPrivacy.logException(e);
}
@ -66,12 +74,7 @@ public class NSProfileFragment extends SubscriberFragment implements AdapterView
public void onStatusEvent(final EventNSProfileUpdateGUI ev) {
Activity activity = getActivity();
if (activity != null)
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
updateGUI();
}
});
activity.runOnUiThread(() -> updateGUI());
}
@Override
@ -97,9 +100,9 @@ public class NSProfileFragment extends SubscriberFragment implements AdapterView
}
}
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String name = parent.getItemAtPosition(position).toString();
@OnItemSelected(R.id.nsprofile_spinner)
public void onItemSelected(Spinner spinner, int position) {
String name = spinner.getItemAtPosition(position).toString();
ProfileStore store = NSProfilePlugin.getPlugin().getProfile();
if (store != null) {
@ -112,12 +115,18 @@ public class NSProfileFragment extends SubscriberFragment implements AdapterView
isf.setText(profile.getIsfList());
basal.setText(profile.getBasalList());
target.setText(profile.getTargetList());
basalGraph.show(profile);
}
if (profile.isValid("NSProfileFragment"))
invalidProfile.setVisibility(View.GONE);
else
invalidProfile.setVisibility(View.VISIBLE);
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
@OnItemSelected(value = R.id.nsprofile_spinner, callback = NOTHING_SELECTED)
public void onNothingSelected() {
invalidProfile.setVisibility(View.VISIBLE);
noProfile.setVisibility(View.VISIBLE);
units.setText("");
dia.setText("");

View file

@ -120,19 +120,6 @@ public class NSProfilePlugin implements PluginBase, ProfileInterface {
profile = new ProfileStore(newProfile.getData());
storeNSProfile();
MainApp.bus().post(new EventNSProfileUpdateGUI());
if (MainApp.getConfigBuilder().isProfileValid("storeNewProfile")) {
ConfigBuilderPlugin.getCommandQueue().setProfile(MainApp.getConfigBuilder().getProfile(), new Callback() {
@Override
public void run() {
if (result.enacted) {
SmsCommunicatorPlugin smsCommunicatorPlugin = MainApp.getSpecificPlugin(SmsCommunicatorPlugin.class);
if (smsCommunicatorPlugin != null && smsCommunicatorPlugin.isEnabled(PluginBase.GENERAL)) {
smsCommunicatorPlugin.sendNotificationToAllNumbers(MainApp.sResources.getString(R.string.profile_set_ok));
}
}
}
});
}
}
private void storeNSProfile() {

View file

@ -0,0 +1,52 @@
package info.nightscout.androidaps.plugins.PumpCombo;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.text.DateFormat;
import java.util.List;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.PumpAlert;
import info.nightscout.androidaps.R;
public class ComboAlertHistoryDialog extends DialogFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.combo_alert_history_fragment, container, false);
TextView text = (TextView) layout.findViewById(R.id.combo_error_history_text);
List<PumpAlert> errors = ComboPlugin.getPlugin().getPump().errorHistory;
if (errors.isEmpty()) {
text.setText(R.string.combo_no_alert_data_note);
} else {
StringBuilder sb = new StringBuilder();
DateFormat dateTimeFormatter = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
boolean first = true;
for (PumpAlert error : errors) {
if (first) {
first = false;
} else {
sb.append("\n");
}
sb.append(dateTimeFormatter.format(error.timestamp));
sb.append(" ");
sb.append(error.message);
if (error.warningCode != null) {
sb.append(" (W");
sb.append(error.warningCode);
sb.append(")");
}
if (error.errorCode != null) {
sb.append(" (E");
sb.append(error.errorCode);
sb.append(")");
}
}
text.setText(sb.toString());
}
return layout;
}
}

View file

@ -0,0 +1,312 @@
package info.nightscout.androidaps.plugins.PumpCombo;
import android.app.Activity;
import android.app.AlertDialog;
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Bundle;
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.apache.commons.lang3.StringUtils;
import info.nightscout.androidaps.Config;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.PumpState;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.Bolus;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.plugins.Common.SubscriberFragment;
import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin;
import info.nightscout.androidaps.plugins.PumpCombo.events.EventComboPumpUpdateGUI;
import info.nightscout.androidaps.queue.Callback;
import info.nightscout.androidaps.queue.events.EventQueueChanged;
import info.nightscout.utils.DateUtil;
import info.nightscout.utils.SP;
public class ComboFragment extends SubscriberFragment implements View.OnClickListener, View.OnLongClickListener {
private TextView stateView;
private TextView activityView;
private TextView batteryView;
private TextView reservoirView;
private TextView lastConnectionView;
private TextView lastBolusView;
private TextView baseBasalRate;
private TextView tempBasalText;
private Button refreshButton;
private Button alertsButton;
private Button tddsButton;
private TextView bolusCount;
private TextView tbrCount;
private Button fullHistoryButton;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.combopump_fragment, container, false);
stateView = (TextView) view.findViewById(R.id.combo_state);
activityView = (TextView) view.findViewById(R.id.combo_activity);
batteryView = (TextView) view.findViewById(R.id.combo_pumpstate_battery);
reservoirView = (TextView) view.findViewById(R.id.combo_insulinstate);
lastBolusView = (TextView) view.findViewById(R.id.combo_last_bolus);
lastConnectionView = (TextView) view.findViewById(R.id.combo_lastconnection);
baseBasalRate = (TextView) view.findViewById(R.id.combo_base_basal_rate);
tempBasalText = (TextView) view.findViewById(R.id.combo_temp_basal);
bolusCount = (TextView) view.findViewById(R.id.combo_bolus_count);
tbrCount = (TextView) view.findViewById(R.id.combo_tbr_count);
refreshButton = (Button) view.findViewById(R.id.combo_refresh_button);
refreshButton.setOnClickListener(this);
alertsButton = (Button) view.findViewById(R.id.combo_alerts_button);
alertsButton.setOnClickListener(this);
alertsButton.setOnLongClickListener(this);
tddsButton = (Button) view.findViewById(R.id.combo_tdds_button);
tddsButton.setOnClickListener(this);
tddsButton.setOnLongClickListener(this);
fullHistoryButton = (Button) view.findViewById(R.id.combo_full_history_button);
fullHistoryButton.setOnClickListener(this);
fullHistoryButton.setOnLongClickListener(this);
updateGUI();
return view;
}
private void runOnUiThread(Runnable action) {
Activity activity = getActivity();
if (activity != null) {
activity.runOnUiThread(action);
}
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.combo_refresh_button:
refreshButton.setEnabled(false);
ConfigBuilderPlugin.getCommandQueue().readStatus("User request", new Callback() {
@Override
public void run() {
runOnUiThread(() -> refreshButton.setEnabled(true));
}
});
break;
case R.id.combo_alerts_button:
ComboAlertHistoryDialog ehd = new ComboAlertHistoryDialog();
ehd.show(getFragmentManager(), ComboAlertHistoryDialog.class.getSimpleName());
break;
case R.id.combo_tdds_button:
ComboTddHistoryDialog thd = new ComboTddHistoryDialog();
thd.show(getFragmentManager(), ComboTddHistoryDialog.class.getSimpleName());
break;
case R.id.combo_full_history_button:
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setMessage(R.string.combo_read_full_history_info);
builder.show();
break;
}
}
// TODO clean up when when queuing
@Override
public boolean onLongClick(View view) {
switch (view.getId()) {
case R.id.combo_alerts_button:
alertsButton.setEnabled(false);
tddsButton.setEnabled(false);
fullHistoryButton.setEnabled(false);
new Thread(() -> ComboPlugin.getPlugin().readAlertData(new Callback() {
@Override
public void run() {
runOnUiThread(() -> {
alertsButton.setEnabled(true);
tddsButton.setEnabled(true);
fullHistoryButton.setEnabled(true);
});
}
})).start();
return true;
case R.id.combo_tdds_button:
alertsButton.setEnabled(false);
tddsButton.setEnabled(false);
fullHistoryButton.setEnabled(false);
new Thread(() -> ComboPlugin.getPlugin().readTddData(new Callback() {
@Override
public void run() {
runOnUiThread(() -> {
alertsButton.setEnabled(true);
tddsButton.setEnabled(true);
fullHistoryButton.setEnabled(true);
});
}
})).start();
return true;
case R.id.combo_full_history_button:
alertsButton.setEnabled(false);
tddsButton.setEnabled(false);
fullHistoryButton.setEnabled(false);
new Thread(() -> ComboPlugin.getPlugin().readAllPumpData(new Callback() {
@Override
public void run() {
runOnUiThread(() -> {
alertsButton.setEnabled(true);
tddsButton.setEnabled(true);
fullHistoryButton.setEnabled(true);
});
}
})).start();
return true;
}
return false;
}
@Subscribe
public void onStatusEvent(final EventComboPumpUpdateGUI ignored) {
updateGUI();
}
@Subscribe
public void onStatusEvent(final EventQueueChanged ignored) {
updateGUI();
}
public void updateGUI() {
runOnUiThread(() -> {
ComboPlugin plugin = ComboPlugin.getPlugin();
// state
stateView.setText(plugin.getStateSummary());
PumpState ps = plugin.getPump().state;
if (ps.insulinState == PumpState.EMPTY || ps.batteryState == PumpState.EMPTY
|| ps.activeAlert != null && ps.activeAlert.errorCode != null) {
stateView.setTextColor(Color.RED);
stateView.setTypeface(null, Typeface.BOLD);
} else if (plugin.getPump().state.suspended
|| ps.activeAlert != null && ps.activeAlert.warningCode != null) {
stateView.setTextColor(Color.YELLOW);
stateView.setTypeface(null, Typeface.BOLD);
} else {
stateView.setTextColor(Color.WHITE);
stateView.setTypeface(null, Typeface.NORMAL);
}
// activity
String activity = plugin.getPump().activity;
if (StringUtils.isNotEmpty(activity)) {
activityView.setTextSize(14);
activityView.setText(activity);
} else {
activityView.setTextSize(20);
activityView.setText("{fa-bed}");
}
if (plugin.isInitialized()) {
refreshButton.setVisibility(View.VISIBLE);
if (Config.enableComboBetaFeatures) {
alertsButton.setVisibility(View.VISIBLE);
tddsButton.setVisibility(View.VISIBLE);
}
fullHistoryButton.setVisibility(View.VISIBLE);
// battery
batteryView.setTextSize(20);
if (ps.batteryState == PumpState.EMPTY) {
batteryView.setText("{fa-battery-empty}");
batteryView.setTextColor(Color.RED);
} else if (ps.batteryState == PumpState.LOW) {
batteryView.setText("{fa-battery-quarter}");
batteryView.setTextColor(Color.YELLOW);
} else {
batteryView.setText("{fa-battery-full}");
batteryView.setTextColor(Color.WHITE);
}
// reservoir
int reservoirLevel = plugin.getPump().reservoirLevel;
if (reservoirLevel != -1) {
reservoirView.setText(reservoirLevel + " " + MainApp.sResources.getString(R.string.treatments_wizard_unit_label));
} else if (ps.insulinState == PumpState.LOW) {
reservoirView.setText(MainApp.gs(R.string.combo_reservoir_low));
} else if (ps.insulinState == PumpState.EMPTY) {
reservoirView.setText(MainApp.gs(R.string.combo_reservoir_empty));
} else {
reservoirView.setText(MainApp.gs(R.string.combo_reservoir_normal));
}
if (ps.insulinState == PumpState.UNKNOWN) {
reservoirView.setTextColor(Color.WHITE);
reservoirView.setTypeface(null, Typeface.NORMAL);
} else if (ps.insulinState == PumpState.LOW) {
reservoirView.setTextColor(Color.YELLOW);
reservoirView.setTypeface(null, Typeface.BOLD);
} else if (ps.insulinState == PumpState.EMPTY) {
reservoirView.setTextColor(Color.RED);
reservoirView.setTypeface(null, Typeface.BOLD);
} else {
reservoirView.setTextColor(Color.WHITE);
reservoirView.setTypeface(null, Typeface.NORMAL);
}
// last connection
String minAgo = DateUtil.minAgo(plugin.getPump().lastSuccessfulCmdTime);
long min = (System.currentTimeMillis() - plugin.getPump().lastSuccessfulCmdTime) / 1000 / 60;
if (plugin.getPump().lastSuccessfulCmdTime + 60 * 1000 > System.currentTimeMillis()) {
lastConnectionView.setText(R.string.combo_pump_connected_now);
lastConnectionView.setTextColor(Color.WHITE);
} else if (plugin.getPump().lastSuccessfulCmdTime + 30 * 60 * 1000 < System.currentTimeMillis()) {
lastConnectionView.setText(MainApp.gs(R.string.combo_no_pump_connection, min));
lastConnectionView.setTextColor(Color.RED);
} else {
lastConnectionView.setText(minAgo);
lastConnectionView.setTextColor(Color.WHITE);
}
// last bolus
Bolus bolus = plugin.getPump().lastBolus;
if (bolus != null) {
long agoMsc = System.currentTimeMillis() - bolus.timestamp;
double bolusMinAgo = agoMsc / 60d / 1000d;
String unit = MainApp.gs(R.string.treatments_wizard_unit_label);
String ago;
if ((agoMsc < 60 * 1000)) {
ago = MainApp.gs(R.string.combo_pump_connected_now);
} else if (bolusMinAgo < 60) {
ago = DateUtil.minAgo(bolus.timestamp);
} else {
ago = DateUtil.hourAgo(bolus.timestamp);
}
lastBolusView.setText(MainApp.gs(R.string.combo_last_bolus, bolus.amount, unit, ago));
} else {
lastBolusView.setText("");
}
// base basal rate
baseBasalRate.setText(MainApp.gs(R.string.pump_basebasalrate, plugin.getBaseBasalRate()));
// TBR
String tbrStr = "";
if (ps.tbrPercent != -1 && ps.tbrPercent != 100) {
long minSinceRead = (System.currentTimeMillis() - plugin.getPump().state.timestamp) / 1000 / 60;
long remaining = ps.tbrRemainingDuration - minSinceRead;
if (remaining >= 0) {
tbrStr = MainApp.gs(R.string.combo_tbr_remaining, ps.tbrPercent, remaining);
}
}
tempBasalText.setText(tbrStr);
// stats
bolusCount.setText(String.valueOf(SP.getLong(ComboPlugin.COMBO_BOLUSES_DELIVERED, 0L)));
tbrCount.setText(String.valueOf(SP.getLong(ComboPlugin.COMBO_TBRS_SET, 0L)));
}
});
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,31 @@
package info.nightscout.androidaps.plugins.PumpCombo;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.BasalProfile;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.PumpState;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.Bolus;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.PumpAlert;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.Tdd;
class ComboPump {
boolean initialized = false;
volatile long lastSuccessfulCmdTime;
public volatile String activity;
@NonNull
volatile PumpState state = new PumpState();
volatile int reservoirLevel = -1;
@NonNull
volatile BasalProfile basalProfile = new BasalProfile();
@Nullable
volatile Bolus lastBolus;
// Alert and TDD histories are not stored in DB, but are read on demand and just cached here
List<PumpAlert> errorHistory = new ArrayList<>(0);
List<Tdd> tddHistory = new ArrayList<>(0);
}

View file

@ -0,0 +1,58 @@
package info.nightscout.androidaps.plugins.PumpCombo;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.text.DateFormat;
import java.util.List;
import java.util.Locale;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.Tdd;
import info.nightscout.androidaps.R;
public class ComboTddHistoryDialog extends DialogFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.combo_tdd_history_fragment, container, false);
TextView text = (TextView) layout.findViewById(R.id.combo_tdd_history_text);
List<Tdd> tdds = ComboPlugin.getPlugin().getPump().tddHistory;
if (tdds.isEmpty()) {
text.setText(R.string.combo_no_tdd_data_note);
} else {
StringBuilder sb = new StringBuilder();
DateFormat dateFormatter = DateFormat.getDateInstance();
double avg = 0;
double min = 999;
double max = 0;
int count = 0;
for (Tdd tdd : tdds) {
if (tdd.total > 0) {
avg += tdd.total;
count++;
}
if (tdd.total < min) min = tdd.total;
if (tdd.total > max) max = tdd.total;
}
avg = avg / count;
sb.append(String.format(Locale.getDefault(), getString(R.string.combo_tdd_minimum), min));
sb.append("\n");
sb.append(String.format(Locale.getDefault(), getString(R.string.combo_tdd_average), avg));
sb.append("\n");
sb.append(String.format(Locale.getDefault(), getString(R.string.combo_tdd_maximum), max));
sb.append("\n");
for (Tdd tdd : tdds) {
sb.append("\n");
sb.append(dateFormatter.format(tdd.timestamp));
sb.append(" ");
sb.append(String.format(Locale.getDefault(), "%3.1f", tdd.total));
sb.append(" U");
}
text.setText(sb.toString());
}
return layout;
}
}

View file

@ -0,0 +1,8 @@
package info.nightscout.androidaps.plugins.PumpCombo.events;
/**
* Created by mike on 24.05.2017.
*/
public class EventComboPumpUpdateGUI {
}

View file

@ -0,0 +1,42 @@
package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter;
import java.util.Arrays;
public class BasalProfile {
public final double[] hourlyRates;
public BasalProfile() {
this.hourlyRates = new double[24];
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BasalProfile that = (BasalProfile) o;
for(int i = 0; i < 24; i++) {
if (Math.abs(hourlyRates[i] - that.hourlyRates[i]) > 0.001) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
return Arrays.hashCode(hourlyRates);
}
@Override
public String toString() {
double total = 0d;
for(int i = 0; i < 24; i++) {
total += hourlyRates[i];
}
return "BasalProfile{" +
"hourlyRates=" + Arrays.toString(hourlyRates) + ", total " + total + " U" +
'}';
}
}

View file

@ -0,0 +1,14 @@
package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter;
public interface BolusProgressReporter {
enum State {
PROGRAMMING,
DELIVERING,
DELIVERED,
STOPPING,
STOPPED,
RECOVERING
}
void report(State state, int percent, double delivered);
}

View file

@ -0,0 +1,58 @@
package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter;
import android.support.annotation.Nullable;
import java.util.LinkedList;
import java.util.List;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.Bolus;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.PumpHistory;
public class CommandResult {
/** Whether the command was executed successfully. */
public boolean success;
/** State of the pump *after* command execution. */
public PumpState state;
/** History if requested by the command. */
@Nullable
public PumpHistory history;
/** Basal rate profile if requested. */
public BasalProfile basalProfile;
/** Warnings raised on the pump that are forwarded to AAPS to be turned into AAPS
* notifications. */
public List<Integer> forwardedWarnings = new LinkedList<>();
public int reservoirLevel = -1;
public CommandResult success(boolean success) {
this.success = success;
return this;
}
public CommandResult state(PumpState state) {
this.state = state;
return this;
}
public CommandResult history(PumpHistory history) {
this.history = history;
return this;
}
public CommandResult basalProfile(BasalProfile basalProfile) {
this.basalProfile = basalProfile;
return this;
}
@Override
public String toString() {
return "CommandResult{" +
"success=" + success +
", state=" + state +
", history=" + history +
", basalProfile=" + basalProfile +
", forwardedWarnings='" + forwardedWarnings + '\'' +
'}';
}
}

View file

@ -0,0 +1,17 @@
package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter;
public class PumpErrorCodes {
public static final int CARTRIDGE_EMPTY = 1;
public static final int BATTERY_EMPTY = 2;
public static final int AUTOMATIC_OFF = 3;
public static final int OCCLUSION = 4;
public static final int END_OF_OPERATION_BACKUP_PUMP = 5;
public static final int MECHANICAL_ERROR = 6;
public static final int ELECTRONIC_ERROR = 7;
public static final int POWER_INTERRUPT = 8;
public static final int END_OF_OPERATION_LOAN_PUMP = 9;
public static final int CARTRIDGE_ERROR = 10;
public static final int SET_NOT_PRIMED = 11;
public static final int DATA_INTERRUPTED = 12;
public static final int LANGUAGE_ERROR = 13;
}

View file

@ -0,0 +1,103 @@
package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter;
/** State displayed on the main screen of the pump. */
public class PumpState {
/** Time the state was captured. */
public long timestamp;
/** Pump time. Note that this is derived from the time displayed on the main menu and assumes
* the date is set correctly */
public long pumpTime;
public String menu = null;
public boolean suspended;
public boolean tbrActive = false;
/** TBR percentage. 100% means no TBR active, just the normal basal rate running. */
public int tbrPercent = -1;
/** The absolute rate the pump is running (regular basal rate or TBR), e.g. 0.80U/h. */
public double basalRate = -1;
/** Remaining time of an active TBR. Note that 0:01 is te lowest displayed, the pump
* jumps from that to TBR end, skipping 0:00(xx). */
public int tbrRemainingDuration = -1;
/** Warning or error code displayed if a warning or alert alert is active,
* see {@link PumpWarningCodes}, {@link PumpErrorCodes} */
public WarningOrErrorCode activeAlert;
public static final int UNKNOWN = -1;
public static final int LOW = 1;
public static final int EMPTY = 2;
public int batteryState = UNKNOWN;
public int insulinState = UNKNOWN;
public int activeBasalProfileNumber;
public static final int SAFE_USAGE = 0;
public static final int UNSUPPORTED_BOLUS_TYPE = 1;
public static final int UNSUPPORTED_BASAL_RATE_PROFILE = 2;
/** True if use of an extended or multiwave bolus has been detected */
public int unsafeUsageDetected = SAFE_USAGE;
public PumpState menu(String menu) {
this.menu = menu;
return this;
}
public PumpState tbrActive(boolean tbrActive) {
this.tbrActive = tbrActive;
return this;
}
public PumpState tbrPercent(int tbrPercent) {
this.tbrPercent = tbrPercent;
return this;
}
public PumpState basalRate(double basalRate) {
this.basalRate = basalRate;
return this;
}
public PumpState tbrRemainingDuration(int tbrRemainingDuration) {
this.tbrRemainingDuration = tbrRemainingDuration;
return this;
}
public PumpState suspended(boolean suspended) {
this.suspended = suspended;
return this;
}
public PumpState batteryState(int batteryState) {
this.batteryState = batteryState;
return this;
}
public PumpState insulinState(int insulinState) {
this.insulinState = insulinState;
return this;
}
public PumpState activeBasalProfileNumber(int activeBasalProfileNumber) {
this.activeBasalProfileNumber = activeBasalProfileNumber;
return this;
}
@Override
public String toString() {
return "PumpState{" +
"timestamp=" + timestamp +
", pumpTime=" + pumpTime +
", menu='" + menu + '\'' +
", suspended=" + suspended +
", tbrActive=" + tbrActive +
", tbrPercent=" + tbrPercent +
", basalRate=" + basalRate +
", tbrRemainingDuration=" + tbrRemainingDuration +
", activeAlert=" + activeAlert +
", batteryState=" + batteryState +
", insulinState=" + insulinState +
", activeBasalProfileNumber=" + activeBasalProfileNumber +
", unsafeUsageDetected=" + unsafeUsageDetected +
'}';
}
}

View file

@ -0,0 +1,14 @@
package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter;
public class PumpWarningCodes {
public static final int CARTRIDGE_LOW = 1;
public static final int BATTERY_LOW = 2;
public static final int REVIEW_TIME = 3;
public static final int CALL_FOR_UPDATE = 4;
public static final int PUMP_TIMER = 5;
public static final int TBR_CANCELLED = 6;
public static final int TBR_OVER = 7;
public static final int BOLUS_CANCELLED = 8;
public static final int LOANTIME_WARNING = 9;
public static final int BLUETOOTH_FAULT = 10;
}

View file

@ -0,0 +1,49 @@
package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.PumpHistoryRequest;
public interface RuffyCommands {
/** Issues a bolus issues updates on progress through via {@link BolusProgressReporter}. */
CommandResult deliverBolus(double amount, BolusProgressReporter bolusProgressReporter);
/** Requests cancellation of an active bolus if possible. */
void cancelBolus();
CommandResult setTbr(int percent, int duration);
CommandResult cancelTbr();
/** Confirms an active warning alert on the pump.
* @see PumpWarningCodes */
CommandResult confirmAlert(int warningCode);
/** Indicate if the pump is ready to receive commands. */
boolean isPumpAvailable();
/** Indicate of the pump is busy processing a command. */
boolean isPumpBusy();
/** Whether there's usable connection to the pump. */
boolean isConnected();
void disconnect();
/** Read the state of the pump, which encompasses all information displayed on the main menu. */
CommandResult readPumpState();
/** Read reservoir level and last bolus via Quick Info */
CommandResult readQuickInfo(int numberOfBolusRecordsToRetrieve);
/** Reads pump history via the My Data menu. The {@link PumpHistoryRequest} specifies
* what types of data and how far back data is returned. */
CommandResult readHistory(PumpHistoryRequest request);
CommandResult readBasalProfile();
CommandResult setBasalProfile(BasalProfile basalProfile);
CommandResult getDateAndTime();
CommandResult setDateAndTime();
}

View file

@ -0,0 +1,943 @@
package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.crashlytics.android.answers.Answers;
import com.crashlytics.android.answers.CustomEvent;
import com.google.common.base.Joiner;
import org.monkey.d.ruffy.ruffy.driver.IRTHandler;
import org.monkey.d.ruffy.ruffy.driver.IRuffyService;
import org.monkey.d.ruffy.ruffy.driver.display.Menu;
import org.monkey.d.ruffy.ruffy.driver.display.MenuAttribute;
import org.monkey.d.ruffy.ruffy.driver.display.MenuType;
import org.monkey.d.ruffy.ruffy.driver.display.menu.BolusType;
import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands.ReadQuickInfoCommand;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.PumpHistoryRequest;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands.BolusCommand;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands.CancelTbrCommand;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands.Command;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands.CommandException;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands.ConfirmAlertCommand;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands.ReadBasalProfileCommand;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands.ReadHistoryCommand;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands.ReadPumpStateCommand;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands.SetBasalProfileCommand;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands.SetTbrCommand;
import info.nightscout.androidaps.BuildConfig;
import info.nightscout.utils.FabricPrivacy;
/**
* Provides scripting 'runtime' and operations. consider moving operations into a separate
* class and inject that into executing commands, so that commands operately solely on
* operations and are cleanly separated from the thread management, connection management etc
*/
public class RuffyScripter implements RuffyCommands {
private static final Logger log = LoggerFactory.getLogger(RuffyScripter.class);
private IRuffyService ruffyService;
@Nullable
private volatile Menu currentMenu;
private volatile long menuLastUpdated = 0;
private volatile boolean unparsableMenuEncountered;
private String previousCommand = "<none>";
private volatile Command activeCmd = null;
private boolean started = false;
private final Object screenlock = new Object();
private IRTHandler mHandler = new IRTHandler.Stub() {
@Override
public void log(String message) throws RemoteException {
if (log.isTraceEnabled()) {
log.trace("Ruffy says: " + message);
}
}
@Override
public void fail(String message) throws RemoteException {
log.warn("Ruffy warns: " + message);
if (message.startsWith("no connection possible"))
FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboRuffyWarning").putCustomAttribute("message", "no connection possible"));
else if (message.startsWith("Error sending keep alive while rtModeRunning is still true"))
FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboRuffyWarning").putCustomAttribute("message", "Error sending keep alive while rtModeRunning is still true"));
else if (message.startsWith("Error sending keep alive. rtModeRunning is false, so this is most likely a race condition during disconnect"))
FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboRuffyWarning").putCustomAttribute("message", "Error sending keep alive. rtModeRunning is false, so this is most likely a race condition during disconnect"));
else
FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboRuffyWarning").putCustomAttribute("message", message.substring(0, 98)));
}
@Override
public void requestBluetooth() throws RemoteException {
log.trace("Ruffy invoked requestBluetooth callback");
}
@Override
public void rtStopped() throws RemoteException {
log.debug("rtStopped callback invoked");
currentMenu = null;
}
@Override
public void rtStarted() throws RemoteException {
log.debug("rtStarted callback invoked");
}
@Override
public void rtClearDisplay() throws RemoteException {
}
@Override
public void rtUpdateDisplay(byte[] quarter, int which) throws RemoteException {
}
@Override
public void rtDisplayHandleMenu(Menu menu) throws RemoteException {
// method is called every ~500ms
log.debug("rtDisplayHandleMenu: " + menu);
currentMenu = menu;
menuLastUpdated = System.currentTimeMillis();
synchronized (screenlock) {
screenlock.notifyAll();
}
}
@Override
public void rtDisplayHandleNoMenu() throws RemoteException {
log.warn("rtDisplayHandleNoMenu callback invoked");
unparsableMenuEncountered = true;
}
};
public RuffyScripter(Context context) {
boolean boundSucceeded = false;
try {
Intent intent = new Intent()
.setComponent(new ComponentName(
// this must be the base package of the app (check package attribute in
// manifest element in the manifest file of the providing app)
"org.monkey.d.ruffy.ruffy",
// full path to the driver;
// in the logs this service is mentioned as (note the slash)
// "org.monkey.d.ruffy.ruffy/.driver.Ruffy";
// org.monkey.d.ruffy.ruffy is the base package identifier
// and /.driver.Ruffy the service within the package
"org.monkey.d.ruffy.ruffy.driver.Ruffy"
));
context.startService(intent);
ServiceConnection mRuffyServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
log.debug("ruffy service connected");
ruffyService = IRuffyService.Stub.asInterface(service);
try {
ruffyService.setHandler(mHandler);
} catch (Exception e) {
log.error("Ruffy handler has issues", e);
}
started = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
log.debug("ruffy service disconnected");
}
};
boundSucceeded = context.bindService(intent, mRuffyServiceConnection, Context.BIND_AUTO_CREATE);
} catch (Exception e) {
log.error("Binding to ruffy service failed", e);
}
if (!boundSucceeded) {
log.error("No connection to ruffy. Pump control unavailable.");
} else {
FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboScripterInit")
.putCustomAttribute("buildversion", BuildConfig.BUILDVERSION)
.putCustomAttribute("version", BuildConfig.VERSION));
}
}
@Override
public boolean isPumpAvailable() {
return started;
}
@Override
public boolean isPumpBusy() {
return activeCmd != null;
}
@Override
public boolean isConnected() {
if (ruffyService == null) {
return false;
}
try {
if (!ruffyService.isConnected()) {
return false;
}
return ruffyService.isConnected() && System.currentTimeMillis() - menuLastUpdated < 10 * 1000;
} catch (RemoteException e) {
return false;
}
}
@Override
public synchronized void disconnect() {
if (ruffyService == null) {
return;
}
try {
log.debug("Disconnecting");
ruffyService.doRTDisconnect();
} catch (RemoteException e) {
// ignore
} catch (Exception e) {
log.warn("Disconnect not happy", e);
}
}
@Override
public CommandResult readPumpState() {
return runCommand(new ReadPumpStateCommand());
}
@Override
public CommandResult readQuickInfo(int numberOfBolusRecordsToRetrieve) {
FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboReadQuickInfoCmd")
.putCustomAttribute("buildversion", BuildConfig.BUILDVERSION)
.putCustomAttribute("version", BuildConfig.VERSION));
return runCommand(new ReadQuickInfoCommand(numberOfBolusRecordsToRetrieve));
}
public void returnToRootMenu() {
// returning to main menu using the 'back' key does not cause a vibration
MenuType menuType = getCurrentMenu().getType();
while (menuType != MenuType.MAIN_MENU && menuType != MenuType.STOP && menuType != MenuType.WARNING_OR_ERROR) {
log.debug("Going back to main menu, currently at " + menuType);
pressBackKey();
while (getCurrentMenu().getType() == menuType) {
waitForScreenUpdate();
}
menuType = getCurrentMenu().getType();
}
}
/**
* Always returns a CommandResult, never throws
*/
private CommandResult runCommand(final Command cmd) {
log.debug("Attempting to run cmd: " + cmd);
List<String> violations = cmd.validateArguments();
if (!violations.isEmpty()) {
log.error("Command argument violations: " + Joiner.on(", ").join(violations));
return new CommandResult().success(false).state(new PumpState());
}
synchronized (RuffyScripter.class) {
Thread cmdThread = null;
try {
activeCmd = cmd;
long connectStart = System.currentTimeMillis();
ensureConnected();
log.debug("Connection ready to execute cmd " + cmd);
cmdThread = new Thread(() -> {
try {
if (!runPreCommandChecks(cmd)) {
return;
}
PumpState pumpState = readPumpStateInternal();
log.debug("Pump state before running command: " + pumpState);
// execute the command
cmd.setScripter(RuffyScripter.this);
long cmdStartTime = System.currentTimeMillis();
cmd.execute();
long cmdEndTime = System.currentTimeMillis();
log.debug("Executing " + cmd + " took " + (cmdEndTime - cmdStartTime) + "ms");
} catch (CommandException e) {
log.error("CommandException running command", e);
activeCmd.getResult().success = false;
} catch (Exception e) {
log.error("Unexpected exception running cmd", e);
activeCmd.getResult().success = false;
}
}, cmd.getClass().getSimpleName());
long executionStart = System.currentTimeMillis();
cmdThread.start();
long overallTimeout = System.currentTimeMillis() + 10 * 60 * 1000;
while (cmdThread.isAlive()) {
if (!isConnected()) {
// on connection loss try to reconnect, confirm warning alerts caused by
// the disconnected and then return the command as failed (the caller
// can retry if needed).
log.debug("Connection unusable (ruffy connection: " + ruffyService.isConnected() + ", "
+ "time since last menu update: " + (System.currentTimeMillis() - menuLastUpdated) + " ms, "
+ "aborting command and attempting reconnect ...");
cmdThread.interrupt();
activeCmd.getResult().success = false;
// the BT connection might be still there, but we might not be receiving
// menu updates, so force a disconnect before connecting again
disconnect();
SystemClock.sleep(500);
for (int attempts = 2; attempts > 0; attempts--) {
boolean reconnected = recoverFromConnectionLoss();
if (reconnected) {
break;
}
// connect attempt times out after 90s, shortly wait and then retry;
// (90s timeout + 5s wait) * 2 attempts = 190s
SystemClock.sleep(5 * 1000);
}
break;
}
if (System.currentTimeMillis() > overallTimeout) {
log.error("Command " + cmd + " timed out");
cmdThread.interrupt();
activeCmd.getResult().success = false;
break;
}
if (unparsableMenuEncountered) {
log.error("UnparsableMenuEncountered flagged, aborting command");
cmdThread.interrupt();
activeCmd.getResult().success = false;
}
log.trace("Waiting for running command to complete");
SystemClock.sleep(500);
}
activeCmd.getResult().state = readPumpStateInternal();
CommandResult result = activeCmd.getResult();
if (log.isDebugEnabled()) {
long connectDurationSec = (executionStart - connectStart) / 1000;
long executionDurationSec = (System.currentTimeMillis() - executionStart) / 1000;
log.debug("Command result: " + result);
log.debug("Connect: " + connectDurationSec + "s, execution: " + executionDurationSec + "s");
}
return result;
} catch (CommandException e) {
log.error("CommandException while executing command", e);
PumpState pumpState = recoverFromCommandFailure();
return activeCmd.getResult().success(false).state(pumpState);
} catch (Exception e) {
log.error("Unexpected exception communication with ruffy", e);
PumpState pumpState = recoverFromCommandFailure();
return activeCmd.getResult().success(false).state(pumpState);
} finally {
Menu menu = this.currentMenu;
if (activeCmd.getResult().success && menu != null && menu.getType() != MenuType.MAIN_MENU) {
log.warn("Command " + activeCmd + " successful, but finished leaving pump on menu " + getCurrentMenuName());
}
if (cmdThread != null) {
try {
// let command thread finish updating activeCmd var
cmdThread.join(1000);
} catch (InterruptedException e) {
// ignore
}
}
previousCommand = "" + activeCmd;
activeCmd = null;
}
}
}
private boolean runPreCommandChecks(Command cmd) {
if (cmd instanceof ReadPumpStateCommand) {
// always allowed, state is set at the end of runCommand method
activeCmd.getResult().success = true;
} else if (getCurrentMenu().getType() == MenuType.STOP) {
if (cmd.needsRunMode()) {
log.error("Requested command requires run mode, but pump is suspended");
activeCmd.getResult().success = false;
return false;
}
} else if (getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) {
if (!(cmd instanceof ConfirmAlertCommand)) {
log.warn("Warning/alert active on pump, but requested command is not ConfirmAlertCommand");
activeCmd.getResult().success = false;
return false;
}
} else if (getCurrentMenu().getType() != MenuType.MAIN_MENU) {
log.debug("Pump is unexpectedly not on main menu but " + getCurrentMenuName() + ", trying to recover");
try {
recoverFromCommandFailure();
} catch (Exception e) {
activeCmd.getResult().success = false;
return false;
}
if (getCurrentMenu().getType() != MenuType.MAIN_MENU) {
activeCmd.getResult().success = false;
return false;
}
}
return true;
}
/**
* On connection loss the pump raises an alert immediately (when setting a TBR or giving a bolus) -
* there's no timeout before that happens. But: a reconnect is still possible which can then
* confirm the alert.
*
* @return whether the reconnect and return to main menu was successful
*/
private boolean recoverFromConnectionLoss() {
log.debug("Connection was lost, trying to reconnect");
ensureConnected();
if (getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) {
WarningOrErrorCode warningOrErrorCode = readWarningOrErrorCode();
if (Objects.equals(activeCmd.getReconnectWarningId(), warningOrErrorCode.warningCode)) {
log.debug("Confirming warning caused by disconnect: #" + warningOrErrorCode.warningCode);
// confirm alert
verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR);
pressCheckKey();
// dismiss alert
verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR);
pressCheckKey();
}
}
boolean connected = isConnected();
if (connected) {
MenuType menuType = getCurrentMenu().getType();
if (menuType != MenuType.MAIN_MENU && menuType != MenuType.WARNING_OR_ERROR) {
returnToRootMenu();
}
}
log.debug("Recovery from connection loss " + (connected ? "succeeded" : "failed"));
FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboRecoveryFromConnectionLoss")
.putCustomAttribute("buildversion", BuildConfig.BUILDVERSION)
.putCustomAttribute("version", BuildConfig.VERSION)
.putCustomAttribute("activeCommand", "" + (activeCmd != null ? activeCmd.getClass().getSimpleName() : ""))
.putCustomAttribute("success", connected ? "true" : "else"));
return connected;
}
/**
* Returns to the main menu (if possible) after a command failure, so that subsequent commands
* reusing the connection won't fail and returns the current PumpState (empty if unreadable).
*/
private PumpState recoverFromCommandFailure() {
Menu menu = this.currentMenu;
if (menu == null) {
FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboRecoveryFromCommandFailure")
.putCustomAttribute("buildversion", BuildConfig.BUILDVERSION)
.putCustomAttribute("version", BuildConfig.VERSION)
.putCustomAttribute("activeCommand", "" + (activeCmd != null ? activeCmd.getClass().getSimpleName() : ""))
.putCustomAttribute("exit", "1")
.putCustomAttribute("success", "false"));
return new PumpState();
}
MenuType type = menu.getType();
if (type != MenuType.WARNING_OR_ERROR && type != MenuType.MAIN_MENU) {
try {
log.debug("Command execution yielded an error, returning to main menu");
returnToRootMenu();
} catch (Exception e) {
log.warn("Error returning to main menu, when trying to recover from command failure", e);
}
}
try {
PumpState pumpState = readPumpStateInternal();
FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboRecoveryFromCommandFailure")
.putCustomAttribute("buildversion", BuildConfig.BUILDVERSION)
.putCustomAttribute("version", BuildConfig.VERSION)
.putCustomAttribute("activeCommand", "" + (activeCmd != null ? activeCmd.getClass().getSimpleName() : ""))
.putCustomAttribute("exit", "2")
.putCustomAttribute("success", "true"));
return pumpState;
} catch (Exception e) {
FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboRecoveryFromCommandFailure")
.putCustomAttribute("buildversion", BuildConfig.BUILDVERSION)
.putCustomAttribute("version", BuildConfig.VERSION)
.putCustomAttribute("exit", "3")
.putCustomAttribute("activeCommand", "" + (activeCmd != null ? activeCmd.getClass().getSimpleName() : ""))
.putCustomAttribute("success", "false"));
log.debug("Reading pump state during recovery failed", e);
return new PumpState();
}
}
/**
* If there's an issue, this times out eventually and throws a CommandException
*/
private void ensureConnected() {
try {
if (isConnected()) {
return;
}
boolean connectInitSuccessful = ruffyService.doRTConnect() == 0;
log.debug("Connect init successful: " + connectInitSuccessful);
log.debug("Waiting for first menu update to be sent");
long timeoutExpired = System.currentTimeMillis() + 90 * 1000;
long initialUpdateTime = menuLastUpdated;
while (initialUpdateTime == menuLastUpdated) {
if (System.currentTimeMillis() > timeoutExpired) {
FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboConnectTimeout")
.putCustomAttribute("buildversion", BuildConfig.BUILDVERSION)
.putCustomAttribute("version", BuildConfig.VERSION)
.putCustomAttribute("activeCommand", "" + (activeCmd != null ? activeCmd.getClass().getSimpleName() : ""))
.putCustomAttribute("previousCommand", previousCommand));
throw new CommandException("Timeout connecting to pump");
}
SystemClock.sleep(50);
}
} catch (CommandException e) {
try {
ruffyService.doRTDisconnect();
} catch (RemoteException e1) {
log.warn("Disconnect after connect failure failed", e1);
}
throw e;
} catch (Exception e) {
try {
ruffyService.doRTDisconnect();
} catch (RemoteException e1) {
log.warn("Disconnect after connect failure failed", e1);
}
throw new CommandException("Unexpected exception while initiating/restoring pump connection", e);
}
}
/**
* This reads the state of the pump, which is whatever is currently displayed on the display,
* no actions are performed.
*/
public PumpState readPumpStateInternal() {
PumpState state = new PumpState();
state.timestamp = System.currentTimeMillis();
Menu menu = currentMenu;
if (menu == null) {
log.debug("Returning empty PumpState, menu is unavailable");
return state;
}
log.debug("Parsing menu: " + menu);
MenuType menuType = menu.getType();
state.menu = menuType.name();
if (menuType == MenuType.MAIN_MENU) {
Double tbrPercentage = (Double) menu.getAttribute(MenuAttribute.TBR);
BolusType bolusType = (BolusType) menu.getAttribute(MenuAttribute.BOLUS_TYPE);
Integer activeBasalRate = (Integer) menu.getAttribute(MenuAttribute.BASAL_SELECTED);
if (!activeBasalRate.equals(1)) {
state.unsafeUsageDetected = PumpState.UNSUPPORTED_BASAL_RATE_PROFILE;
} else if (bolusType != null && bolusType != BolusType.NORMAL) {
state.unsafeUsageDetected = PumpState.UNSUPPORTED_BOLUS_TYPE;
} else if (tbrPercentage != null && tbrPercentage != 100) {
state.tbrActive = true;
Double displayedTbr = (Double) menu.getAttribute(MenuAttribute.TBR);
state.tbrPercent = displayedTbr.intValue();
MenuTime durationMenuTime = ((MenuTime) menu.getAttribute(MenuAttribute.RUNTIME));
state.tbrRemainingDuration = durationMenuTime.getHour() * 60 + durationMenuTime.getMinute();
}
if (menu.attributes().contains(MenuAttribute.BASAL_RATE)) {
state.basalRate = ((double) menu.getAttribute(MenuAttribute.BASAL_RATE));
}
if (menu.attributes().contains(MenuAttribute.BATTERY_STATE)) {
state.batteryState = ((int) menu.getAttribute(MenuAttribute.BATTERY_STATE));
}
if (menu.attributes().contains(MenuAttribute.INSULIN_STATE)) {
state.insulinState = ((int) menu.getAttribute(MenuAttribute.INSULIN_STATE));
}
if (menu.attributes().contains(MenuAttribute.TIME)) {
MenuTime pumpTime = (MenuTime) menu.getAttribute(MenuAttribute.TIME);
Date date = new Date();
// infer yesterday as the pump's date if midnight just passed, but the pump is
// a bit behind
if (date.getHours() == 0 && date.getMinutes() <= 5
&& pumpTime.getHour() == 23 && pumpTime.getMinute() >= 55) {
date.setTime(date.getTime() - 24 * 60 * 60 * 1000);
}
date.setHours(pumpTime.getHour());
date.setMinutes(pumpTime.getMinute());
date.setSeconds(0);
state.pumpTime = date.getTime() - date.getTime() % 1000;
}
} else if (menuType == MenuType.WARNING_OR_ERROR) {
state.activeAlert = readWarningOrErrorCode();
} else if (menuType == MenuType.STOP) {
state.suspended = true;
if (menu.attributes().contains(MenuAttribute.BATTERY_STATE)) {
state.batteryState = ((int) menu.getAttribute(MenuAttribute.BATTERY_STATE));
}
if (menu.attributes().contains(MenuAttribute.INSULIN_STATE)) {
state.insulinState = ((int) menu.getAttribute(MenuAttribute.INSULIN_STATE));
}
if (menu.attributes().contains(MenuAttribute.TIME)) {
MenuTime time = (MenuTime) menu.getAttribute(MenuAttribute.TIME);
Date date = new Date();
date.setHours(time.getHour());
date.setMinutes(time.getMinute());
date.setSeconds(0);
state.pumpTime = date.getTime() - date.getTime() % 1000;
}
}
log.debug("State read: " + state);
return state;
}
@NonNull
public WarningOrErrorCode readWarningOrErrorCode() {
if (currentMenu == null || getCurrentMenu().getType() != MenuType.WARNING_OR_ERROR) {
return new WarningOrErrorCode(null, null, null);
}
Integer warningCode = (Integer) getCurrentMenu().getAttribute(MenuAttribute.WARNING);
Integer errorCode = (Integer) getCurrentMenu().getAttribute(MenuAttribute.ERROR);
int retries = 5;
while (warningCode == null && errorCode == null && retries > 0) {
waitForScreenUpdate();
warningCode = (Integer) getCurrentMenu().getAttribute(MenuAttribute.WARNING);
errorCode = (Integer) getCurrentMenu().getAttribute(MenuAttribute.ERROR);
retries--;
}
String message = (String) getCurrentMenu().getAttribute(MenuAttribute.MESSAGE);
return new WarningOrErrorCode(warningCode, errorCode, message);
}
public static class Key {
public static byte NO_KEY = (byte) 0x00;
public static byte MENU = (byte) 0x03;
public static byte CHECK = (byte) 0x0C;
public static byte UP = (byte) 0x30;
public static byte DOWN = (byte) 0xC0;
public static byte BACK = (byte) 0x33;
}
// === pump ops ===
@NonNull
public Menu getCurrentMenu() {
if (Thread.currentThread().isInterrupted())
throw new CommandException("Interrupted");
Menu menu = this.currentMenu;
if (menu == null) {
log.error("currentMenu == null, bailing");
throw new CommandException("Unable to read current menu");
}
return menu;
}
@Nullable
private String getCurrentMenuName() {
Menu menu = this.currentMenu;
return menu != null ? menu.getType().toString() : "<none>";
}
public void pressUpKey() {
log.debug("Pressing up key");
pressKey(Key.UP);
log.debug("Releasing up key");
}
public void pressDownKey() {
log.debug("Pressing down key");
pressKey(Key.DOWN);
log.debug("Releasing down key");
}
public void pressCheckKey() {
log.debug("Pressing check key");
pressKey(Key.CHECK);
log.debug("Releasing check key");
}
public void pressMenuKey() {
log.debug("Pressing menu key");
pressKey(Key.MENU);
log.debug("Releasing menu key");
}
private void pressBackKey() {
log.debug("Pressing back key");
pressKey(Key.BACK);
log.debug("Releasing back key");
}
public void pressKeyMs(final byte key, long ms) {
long stepMs = 100;
try {
log.debug("Scroll: Pressing key for " + ms + " ms with step " + stepMs + " ms");
ruffyService.rtSendKey(key, true);
ruffyService.rtSendKey(key, false);
while (ms > stepMs) {
SystemClock.sleep(stepMs);
ruffyService.rtSendKey(key, false);
ms -= stepMs;
}
SystemClock.sleep(ms);
ruffyService.rtSendKey(Key.NO_KEY, true);
log.debug("Releasing key");
} catch (Exception e) {
throw new CommandException("Error while pressing buttons");
}
}
/**
* Wait until the menu is updated
*/
public void waitForScreenUpdate() {
if (Thread.currentThread().isInterrupted())
throw new CommandException("Interrupted");
synchronized (screenlock) {
try {
// updates usually come in every ~500, occasionally up to 1100ms
screenlock.wait((long) 2000);
} catch (InterruptedException e) {
throw new CommandException("Interrupted");
}
}
}
private void pressKey(final byte key) {
if (Thread.currentThread().isInterrupted())
throw new CommandException("Interrupted");
try {
ruffyService.rtSendKey(key, true);
SystemClock.sleep(150);
ruffyService.rtSendKey(Key.NO_KEY, true);
} catch (Exception e) {
throw new CommandException("Error while pressing buttons");
}
}
public void navigateToMenu(MenuType desiredMenu) {
verifyMenuIsDisplayed(MenuType.MAIN_MENU);
int moves = 20;
MenuType lastSeenMenu = getCurrentMenu().getType();
while (lastSeenMenu != desiredMenu) {
log.debug("Navigating to menu " + desiredMenu + ", current menu: " + lastSeenMenu);
moves--;
if (moves == 0) {
throw new CommandException("Menu not found searching for " + desiredMenu
+ ". Check menu settings on your pump to ensure it's not hidden.");
}
MenuType next = getCurrentMenu().getType();
pressMenuKey();
// sometimes the pump takes a bit longer (more than one screen refresh) to advance
// to the next menu. wait until we actually see the change to avoid overshoots.
while (next == lastSeenMenu) {
waitForScreenUpdate();
next = getCurrentMenu().getType();
}
lastSeenMenu = getCurrentMenu().getType();
}
}
/**
* Wait till a menu changed has completed, "away" from the menu provided as argument.
*/
public void waitForMenuToBeLeft(MenuType menuType) {
long timeout = System.currentTimeMillis() + 10 * 1000;
while (getCurrentMenu().getType() == menuType) {
if (System.currentTimeMillis() > timeout) {
throw new CommandException("Timeout waiting for menu " + menuType + " to be left");
}
waitForScreenUpdate();
}
}
public void verifyMenuIsDisplayed(MenuType expectedMenu) {
verifyMenuIsDisplayed(expectedMenu, null);
}
public void verifyMenuIsDisplayed(MenuType expectedMenu, String failureMessage) {
int attempts = 5;
while (getCurrentMenu().getType() != expectedMenu) {
attempts -= 1;
if (attempts > 0) {
waitForScreenUpdate();
} else {
if (failureMessage == null) {
failureMessage = "Invalid pump state, expected to be in menu " + expectedMenu + ", but current menu is " + getCurrentMenuName();
}
throw new CommandException(failureMessage);
}
}
}
public void verifyRootMenuIsDisplayed() {
int retries = 600;
while (getCurrentMenu().getType() != MenuType.MAIN_MENU && getCurrentMenu().getType() != MenuType.STOP) {
if (retries > 0) {
SystemClock.sleep(100);
retries = retries - 1;
} else {
throw new CommandException("Invalid pump state, expected to be in menu MAIN or STOP but current menu is " + getCurrentMenuName());
}
}
}
@SuppressWarnings("unchecked")
public <T> T readBlinkingValue(Class<T> expectedType, MenuAttribute attribute) {
int retries = 5;
Object value = getCurrentMenu().getAttribute(attribute);
while (!expectedType.isInstance(value)) {
value = getCurrentMenu().getAttribute(attribute);
waitForScreenUpdate();
retries--;
if (retries == 0) {
throw new CommandException("Failed to read blinkng value: " + attribute + "=" + value + " type=" + value);
}
}
return (T) value;
}
@Override
public CommandResult deliverBolus(double amount, BolusProgressReporter bolusProgressReporter) {
FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboBolusCmd")
.putCustomAttribute("buildversion", BuildConfig.BUILDVERSION)
.putCustomAttribute("version", BuildConfig.VERSION));
return runCommand(new BolusCommand(amount, bolusProgressReporter));
}
@Override
public void cancelBolus() {
if (activeCmd instanceof BolusCommand) {
FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboBolusCmdCancel")
.putCustomAttribute("buildversion", BuildConfig.BUILDVERSION)
.putCustomAttribute("version", BuildConfig.VERSION));
((BolusCommand) activeCmd).requestCancellation();
} else {
log.error("cancelBolus called, but active command is not a bolus:" + activeCmd);
}
}
@Override
public CommandResult setTbr(int percent, int duration) {
FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboSetTbrCmd")
.putCustomAttribute("buildversion", BuildConfig.BUILDVERSION)
.putCustomAttribute("version", BuildConfig.VERSION));
return runCommand(new SetTbrCommand(percent, duration));
}
@Override
public CommandResult cancelTbr() {
FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboCancelTbrCmd")
.putCustomAttribute("buildversion", BuildConfig.BUILDVERSION)
.putCustomAttribute("version", BuildConfig.VERSION));
return runCommand(new CancelTbrCommand());
}
@Override
public CommandResult confirmAlert(int warningCode) {
FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboConfirmAlertCmd")
.putCustomAttribute("buildversion", BuildConfig.BUILDVERSION)
.putCustomAttribute("version", BuildConfig.VERSION));
return runCommand(new ConfirmAlertCommand(warningCode));
}
@Override
public CommandResult readHistory(PumpHistoryRequest request) {
FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboReadHistoryCmd")
.putCustomAttribute("buildversion", BuildConfig.BUILDVERSION)
.putCustomAttribute("version", BuildConfig.VERSION));
return runCommand(new ReadHistoryCommand(request));
}
@Override
public CommandResult readBasalProfile() {
FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboReadBasalProfileCmd")
.putCustomAttribute("buildversion", BuildConfig.BUILDVERSION)
.putCustomAttribute("version", BuildConfig.VERSION));
return runCommand(new ReadBasalProfileCommand());
}
@Override
public CommandResult setBasalProfile(BasalProfile basalProfile) {
FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboSetBasalProfileCmd")
.putCustomAttribute("buildversion", BuildConfig.BUILDVERSION)
.putCustomAttribute("version", BuildConfig.VERSION));
return runCommand(new SetBasalProfileCommand(basalProfile));
}
@Override
public CommandResult getDateAndTime() {
throw new RuntimeException("Not supported");
}
@Override
public CommandResult setDateAndTime() {
throw new RuntimeException("Not supported");
}
/**
* Confirms and dismisses the given alert if it's raised before the timeout
*/
public boolean confirmAlert(@NonNull Integer warningCode, int maxWaitMs) {
long timeout = System.currentTimeMillis() + maxWaitMs;
while (System.currentTimeMillis() < timeout) {
if (getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) {
WarningOrErrorCode warningOrErrorCode = readWarningOrErrorCode();
if (warningOrErrorCode.errorCode != null) {
throw new CommandException("Pump is in error state");
}
Integer displayedWarningCode = warningOrErrorCode.warningCode;
String errorMsg = null;
try {
errorMsg = (String) getCurrentMenu().getAttribute(MenuAttribute.MESSAGE);
} catch (Exception e) {
// ignore
}
if (!Objects.equals(displayedWarningCode, warningCode)) {
throw new CommandException("An alert other than the expected warning " + warningCode + " was raised by the pump: "
+ displayedWarningCode + "(" + errorMsg + "). Please check the pump.");
}
// confirm alert
verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR);
pressCheckKey();
// dismiss alert
// if the user has confirmed the alert we have dismissed it with the button press
// above already, so only do that if an alert is still displayed
waitForScreenUpdate();
if (getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) {
pressCheckKey();
}
// wait till the pump has processed the alarm, otherwise it might still be showing
// when a command returns
WarningOrErrorCode displayedWarning = readWarningOrErrorCode();
while (Objects.equals(displayedWarning.warningCode, warningCode)) {
waitForScreenUpdate();
displayedWarning = readWarningOrErrorCode();
}
return true;
}
SystemClock.sleep(10);
}
return false;
}
}

View file

@ -0,0 +1,27 @@
package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter;
import android.support.annotation.Nullable;
public class WarningOrErrorCode {
@Nullable
public final Integer warningCode;
@Nullable
public final Integer errorCode;
@Nullable
public String message;
public WarningOrErrorCode(@Nullable Integer warningCode, @Nullable Integer errorCode, @Nullable String message) {
this.warningCode = warningCode;
this.errorCode = errorCode;
this.message = message;
}
@Override
public String toString() {
return "WarningOrErrorCode{" +
"warningCode=" + warningCode +
", errorCode=" + errorCode +
", message=" + message +
'}';
}
}

View file

@ -0,0 +1,85 @@
package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands;
import android.support.annotation.NonNull;
import org.monkey.d.ruffy.ruffy.driver.display.MenuAttribute;
import org.monkey.d.ruffy.ruffy.driver.display.MenuType;
import org.monkey.d.ruffy.ruffy.driver.display.menu.BolusType;
import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuDate;
import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuTime;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.CommandResult;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.PumpWarningCodes;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.RuffyScripter;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.Bolus;
public abstract class BaseCommand implements Command {
// RS will inject itself here
protected RuffyScripter scripter;
protected CommandResult result;
public BaseCommand() {
result = new CommandResult();
}
@Override
public void setScripter(RuffyScripter scripter) {
this.scripter = scripter;
}
@Override
public boolean needsRunMode() {
return true;
}
/**
* A warning id (or null) caused by a disconnect we can safely confirm on reconnect,
* knowing it's not severe as it was caused by this command.
* @see PumpWarningCodes
*/
@Override
public Integer getReconnectWarningId() {
return null;
}
@Override
public List<String> validateArguments() {
return Collections.emptyList();
}
@Override
public CommandResult getResult() {
return result;
}
@NonNull
protected Bolus readBolusRecord() {
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_DATA);
BolusType bolusType = (BolusType) scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS_TYPE);
boolean isValid = bolusType == BolusType.NORMAL;
Double bolus = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS);
long recordDate = readRecordDate();
return new Bolus(recordDate, bolus, isValid);
}
protected long readRecordDate() {
MenuDate date = (MenuDate) scripter.getCurrentMenu().getAttribute(MenuAttribute.DATE);
MenuTime time = (MenuTime) scripter.getCurrentMenu().getAttribute(MenuAttribute.TIME);
int year = Calendar.getInstance().get(Calendar.YEAR);
if (date.getMonth() > Calendar.getInstance().get(Calendar.MONTH) + 1) {
year -= 1;
}
Calendar calendar = Calendar.getInstance();
calendar.set(year, date.getMonth() - 1, date.getDay(), time.getHour(), time.getMinute(), 0);
// round to second
return calendar.getTimeInMillis() - calendar.getTimeInMillis() % 1000;
}
}

View file

@ -0,0 +1,254 @@
package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands;
import android.os.SystemClock;
import org.monkey.d.ruffy.ruffy.driver.display.MenuAttribute;
import org.monkey.d.ruffy.ruffy.driver.display.MenuType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.BolusProgressReporter;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.CommandResult;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.PumpWarningCodes;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.WarningOrErrorCode;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.Bolus;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.RuffyScripter;
import static info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.BolusProgressReporter.State.DELIVERED;
import static info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.BolusProgressReporter.State.DELIVERING;
import static info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.BolusProgressReporter.State.PROGRAMMING;
import static info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.BolusProgressReporter.State.STOPPED;
import static info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.BolusProgressReporter.State.STOPPING;
public class BolusCommand extends BaseCommand {
private static final Logger log = LoggerFactory.getLogger(BolusCommand.class);
protected final double bolus;
private final BolusProgressReporter bolusProgressReporter;
private volatile boolean cancelRequested;
public BolusCommand(double bolus, BolusProgressReporter bolusProgressReporter) {
this.bolus = bolus;
this.bolusProgressReporter = bolusProgressReporter;
}
@Override
public List<String> validateArguments() {
List<String> violations = new ArrayList<>();
if (bolus <= 0) {
violations.add("Requested bolus non-positive: " + bolus);
}
return violations;
}
@Override
public Integer getReconnectWarningId() {
return PumpWarningCodes.BOLUS_CANCELLED;
}
@Override
public void execute() {
if (cancelRequested) {
bolusProgressReporter.report(STOPPED, 0, 0);
result.success = true;
log.debug("Stage 0: cancelled bolus before programming");
return;
}
bolusProgressReporter.report(PROGRAMMING, 0, 0);
enterBolusMenu();
inputBolusAmount();
verifyDisplayedBolusAmount();
// last chance to abort before confirming the bolus
if (cancelRequested) {
bolusProgressReporter.report(STOPPING, 0, 0);
scripter.returnToRootMenu();
bolusProgressReporter.report(STOPPED, 0, 0);
result.success = true;
log.debug("Stage 1: cancelled bolus after programming");
return;
}
// confirm bolus
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
scripter.pressCheckKey();
log.debug("Stage 2: bolus confirmed");
// the pump displays the entered bolus and waits a few seconds to let user check and cancel
while (scripter.getCurrentMenu().getType() == MenuType.BOLUS_ENTER) {
if (cancelRequested) {
log.debug("Stage 2: cancelling during confirmation wait");
bolusProgressReporter.report(STOPPING, 0, 0);
scripter.pressUpKey();
// wait up to 1s for a BOLUS_CANCELLED alert, if it doesn't happen we missed
// the window, simply continue and let the next cancel attempt try its luck
boolean alertWasCancelled = scripter.confirmAlert(PumpWarningCodes.BOLUS_CANCELLED, 1000);
if (alertWasCancelled) {
log.debug("Stage 2: successfully cancelled during confirmation wait");
bolusProgressReporter.report(STOPPED, 0, 0);
result.success = true;
return;
}
}
SystemClock.sleep(10);
}
// the bolus progress is displayed on the main menu
scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU,
"Pump did not return to MAIN_MEU from BOLUS_ENTER to deliver bolus. "
+ "Check pump manually, the bolus might not have been delivered.");
bolusProgressReporter.report(DELIVERING, 0, 0);
// wait for bolus delivery to complete; the remaining units to deliver are counted down
boolean cancelInProgress = false;
Double lastBolusReported = 0d;
Double bolusRemaining = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS_REMAINING);
Thread cancellationThread = null;
while (bolusRemaining != null || scripter.getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) {
if (cancelRequested && !cancelInProgress) {
log.debug("Stage 3: cancellation while delivering bolus");
bolusProgressReporter.report(STOPPING, 0, 0);
cancelInProgress = true;
cancellationThread = new Thread(() ->
scripter.pressKeyMs(RuffyScripter.Key.UP, 3000), "bolus-canceller");
cancellationThread.start();
}
if (scripter.getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) {
// confirm warning alert and update the result to indicate alerts occurred
WarningOrErrorCode warningOrErrorCode = scripter.readWarningOrErrorCode();
if (warningOrErrorCode.errorCode != null) {
throw new CommandException("Pump is in error state");
}
Integer warningCode = warningOrErrorCode.warningCode;
if (Objects.equals(warningCode, PumpWarningCodes.BOLUS_CANCELLED)) {
// wait until cancellation thread releases the up button, otherwise we won't
// be able to confirm anything
if (cancellationThread != null) {
try {
cancellationThread.join(3500);
} catch (InterruptedException e) {
// ignore
}
}
scripter.confirmAlert(PumpWarningCodes.BOLUS_CANCELLED, 2000);
bolusProgressReporter.report(STOPPED, 0, 0);
log.debug("Stage 3: confirmed BOLUS CANCELLED after cancelling bolus during delivery");
} else if (Objects.equals(warningCode, PumpWarningCodes.CARTRIDGE_LOW)) {
scripter.confirmAlert(PumpWarningCodes.CARTRIDGE_LOW, 2000);
result.forwardedWarnings.add(PumpWarningCodes.CARTRIDGE_LOW);
log.debug("Stage 3: confirmed low cartridge alert and forwarding to AAPS");
} else if (Objects.equals(warningCode, PumpWarningCodes.BATTERY_LOW)) {
scripter.confirmAlert(PumpWarningCodes.BATTERY_LOW, 2000);
result.forwardedWarnings.add(PumpWarningCodes.BATTERY_LOW);
log.debug("Stage 3: confirmed low battery alert and forwarding to AAPS");
} else {
// all other warnings or errors;
// An occlusion error can also occur during bolus. To read the partially delivered
// bolus, we'd have to first confirm the error. But an (occlusion) **error** shall not
// be confirmed and potentially be swallowed by a bug or shaky comms, so we let
// the pump be noisy (which the user will have to interact with anyway).
// Thus, this method will terminate with an exception and display an error message.
// Ideally, sometime after the user has dealt with the situation, the partially
// delivered bolus should be read. However, ready history is tricky at this point.
// Also: with an occlusion, the amount of insulin active is in question.
// It would be safer to assume the delivered bolus results in IOB, but there's
// only so much we can do at this point, so the user shall take over here and
// add a bolus record as and if needed.
throw new CommandException("Pump is showing exotic warning/error: " + warningOrErrorCode);
}
}
if (bolusRemaining != null && !Objects.equals(bolusRemaining, lastBolusReported)) {
log.debug("Delivering bolus, remaining: " + bolusRemaining);
int percentDelivered = (int) (100 - (bolusRemaining / bolus * 100));
bolusProgressReporter.report(DELIVERING, percentDelivered, bolus - bolusRemaining);
lastBolusReported = bolusRemaining;
}
SystemClock.sleep(50);
bolusRemaining = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS_REMAINING);
}
// if a cancellation was started by pressing up for 3 seconds but the bolus has finished during those
// three seconds, must wait until the button is unpressed again so that follow up commands
// work properly.
if (cancellationThread != null) {
try {
cancellationThread.join();
} catch (InterruptedException e) {
// ignore
}
}
if (cancelInProgress) {
log.debug("Stage 4: bolus was cancelled, with unknown amount delivered");
} else {
log.debug("Stage 4: full bolus of " + bolus + " U was successfully delivered");
bolusProgressReporter.report(DELIVERED, 100, bolus);
}
result.success = true;
}
private void enterBolusMenu() {
scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU);
scripter.navigateToMenu(MenuType.BOLUS_MENU);
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_MENU);
scripter.pressCheckKey();
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
}
private void inputBolusAmount() {
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
// press 'up' once for each 0.1 U increment
long steps = Math.round(bolus * 10);
for (int i = 0; i < steps; i++) {
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
scripter.pressUpKey();
SystemClock.sleep(50);
}
}
private void verifyDisplayedBolusAmount() {
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
// wait up to 10s for any scrolling to finish
double displayedBolus = scripter.readBlinkingValue(Double.class, MenuAttribute.BOLUS);
long timeout = System.currentTimeMillis() + 10 * 1000;
while (timeout > System.currentTimeMillis() && bolus - displayedBolus > 0.05) {
log.debug("Waiting for pump to process scrolling input for amount, current: " + displayedBolus + ", desired: " + bolus);
SystemClock.sleep(50);
displayedBolus = scripter.readBlinkingValue(Double.class, MenuAttribute.BOLUS);
}
log.debug("Final bolus: " + displayedBolus);
if (Math.abs(displayedBolus - bolus) > 0.01) {
throw new CommandException("Failed to set correct bolus. Expected: " + bolus + ", actual: " + displayedBolus);
}
// check again to ensure the displayed value hasn't change due to due scrolling taking extremely long
SystemClock.sleep(1000);
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
double refreshedDisplayedBolus = scripter.readBlinkingValue(Double.class, MenuAttribute.BOLUS);
if (Math.abs(displayedBolus - refreshedDisplayedBolus) > 0.01) {
throw new CommandException("Failed to set bolus: bolus changed after input stopped from "
+ displayedBolus + " -> " + refreshedDisplayedBolus);
}
}
public void requestCancellation() {
log.debug("Bolus cancellation requested");
cancelRequested = true;
bolusProgressReporter.report(STOPPING, 0, 0);
}
@Override
public String toString() {
return "BolusCommand{" +
"bolus=" + bolus +
'}';
}
}

View file

@ -0,0 +1,41 @@
package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands;
import org.monkey.d.ruffy.ruffy.driver.display.MenuType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.PumpState;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.PumpWarningCodes;
public class CancelTbrCommand extends BaseCommand {
private static final Logger log = LoggerFactory.getLogger(CancelTbrCommand.class);
@Override
public Integer getReconnectWarningId() {
return PumpWarningCodes.TBR_CANCELLED;
}
@Override
public void execute() {
scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU);
PumpState pumpState = scripter.readPumpStateInternal();
if (!pumpState.tbrActive) {
// This is non-critical; when cancelling a TBR and the connection was interrupted
// the TBR was cancelled by that. In that case not cancelling anything is fine.
result.success = true;
return;
}
log.debug("Cancelling active TBR of " + pumpState.tbrPercent
+ "% with " + pumpState.tbrRemainingDuration + " min remaining");
SetTbrCommand setTbrCommand = new SetTbrCommand(100, 0);
setTbrCommand.setScripter(scripter);
setTbrCommand.execute();
result = setTbrCommand.result;
}
@Override
public String toString() {
return "CancelTbrCommand{}";
}
}

View file

@ -0,0 +1,22 @@
package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands;
import java.util.List;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.RuffyScripter;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.CommandResult;
/**
* Interface for all commands to be executed by the pump.
* <p>
* Note on cammond methods and timing: a method shall wait before and after executing
* as necessary to not cause timing issues, so the caller can just call methods in
* sequence, letting the methods take care of waits.
*/
public interface Command {
void setScripter(RuffyScripter scripter);
List<String> validateArguments();
boolean needsRunMode();
void execute();
CommandResult getResult();
Integer getReconnectWarningId();
}

View file

@ -0,0 +1,11 @@
package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands;
public class CommandException extends RuntimeException {
public CommandException(String message) {
super(message);
}
public CommandException(String message, Exception exception) {
super(message, exception);
}
}

View file

@ -0,0 +1,26 @@
package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands;
public class ConfirmAlertCommand extends BaseCommand {
private final int warningCode;
public ConfirmAlertCommand(int warningCode) {
this.warningCode = warningCode;
}
@Override
public void execute() {
result.success(scripter.confirmAlert(warningCode, 5000));
}
@Override
public boolean needsRunMode() {
return false;
}
@Override
public String toString() {
return "ConfirmAlertCommand{" +
"warningCode=" + warningCode +
'}';
}
}

View file

@ -0,0 +1,62 @@
package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands;
import org.monkey.d.ruffy.ruffy.driver.display.Menu;
import org.monkey.d.ruffy.ruffy.driver.display.MenuAttribute;
import org.monkey.d.ruffy.ruffy.driver.display.MenuType;
import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.BasalProfile;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.PumpState;
public class ReadBasalProfileCommand extends BaseCommand {
private static final Logger log = LoggerFactory.getLogger(ReadBasalProfileCommand.class);
@Override
public void execute() {
scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU);
if (scripter.readPumpStateInternal().unsafeUsageDetected == PumpState.UNSUPPORTED_BASAL_RATE_PROFILE) {
throw new CommandException("Active basal rate profile != 1");
}
scripter.navigateToMenu(MenuType.BASAL_1_MENU);
scripter.verifyMenuIsDisplayed(MenuType.BASAL_1_MENU);
scripter.pressCheckKey();
BasalProfile basalProfile = new BasalProfile();
// summary screen is shown; press menu to page through hours, wraps around to summary;
scripter.verifyMenuIsDisplayed(MenuType.BASAL_TOTAL);
for (int i = 0; i < 24; i++) {
scripter.pressMenuKey();
Menu menu = scripter.getCurrentMenu();
while (menu.getType() != MenuType.BASAL_SET
|| ((MenuTime) scripter.getCurrentMenu().getAttribute(MenuAttribute.BASAL_START)).getHour() != i) {
scripter.waitForScreenUpdate();
menu = scripter.getCurrentMenu();
}
scripter.verifyMenuIsDisplayed(MenuType.BASAL_SET);
MenuTime startTime = (MenuTime) scripter.getCurrentMenu().getAttribute(MenuAttribute.BASAL_START);
if (i != startTime.getHour()) {
throw new CommandException("Attempting to read basal rate for hour " + i + ", but hour " + startTime.getHour() + " is displayed");
}
basalProfile.hourlyRates[i] = scripter.readBlinkingValue(Double.class, MenuAttribute.BASAL_RATE);
log.debug("Read basal profile, hour " + i + ": " + basalProfile.hourlyRates[i]);
}
log.debug("Basal profile read: " + Arrays.toString(basalProfile.hourlyRates));
scripter.returnToRootMenu();
scripter.verifyRootMenuIsDisplayed();
result.success(true).basalProfile(basalProfile);
}
@Override
public String toString() {
return "ReadBasalProfileCommand{}";
}
}

View file

@ -0,0 +1,274 @@
package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands;
import android.support.annotation.NonNull;
import org.monkey.d.ruffy.ruffy.driver.display.MenuAttribute;
import org.monkey.d.ruffy.ruffy.driver.display.MenuType;
import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuDate;
import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Calendar;
import java.util.Date;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.Bolus;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.PumpAlert;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.PumpHistory;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.PumpHistoryRequest;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.Tbr;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.Tdd;
public class ReadHistoryCommand extends BaseCommand {
private static Logger log = LoggerFactory.getLogger(ReadHistoryCommand.class);
private final PumpHistoryRequest request;
private final PumpHistory history = new PumpHistory();
public ReadHistoryCommand(PumpHistoryRequest request) {
this.request = request;
}
@Override
public void execute() {
if (request.bolusHistory == PumpHistoryRequest.SKIP
&& request.tbrHistory == PumpHistoryRequest.SKIP
&& request.pumpErrorHistory == PumpHistoryRequest.SKIP
&& request.tddHistory == PumpHistoryRequest.SKIP) {
throw new CommandException("History request but all data types are skipped");
}
scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU);
scripter.navigateToMenu(MenuType.MY_DATA_MENU);
scripter.verifyMenuIsDisplayed(MenuType.MY_DATA_MENU);
scripter.pressCheckKey();
// bolus history
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_DATA);
if (request.bolusHistory != PumpHistoryRequest.SKIP) {
int totalRecords = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.TOTAL_RECORD);
if (totalRecords > 0) {
if (request.bolusHistory == PumpHistoryRequest.LAST) {
Bolus bolus = readBolusRecord();
history.bolusHistory.add(bolus);
} else {
readBolusRecords(request.bolusHistory);
}
}
}
if (request.pumpErrorHistory != PumpHistoryRequest.SKIP
|| request.tddHistory != PumpHistoryRequest.SKIP
|| request.tbrHistory != PumpHistoryRequest.SKIP) {
// error history
scripter.pressMenuKey();
scripter.verifyMenuIsDisplayed(MenuType.ERROR_DATA);
if (request.pumpErrorHistory != PumpHistoryRequest.SKIP) {
int totalRecords = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.TOTAL_RECORD);
if (totalRecords > 0) {
if (request.pumpErrorHistory == PumpHistoryRequest.LAST) {
PumpAlert error = readAlertRecord();
history.pumpAlertHistory.add(error);
} else {
readAlertRecords(request.pumpErrorHistory);
}
}
}
// tdd history (TBRs are added to history only after they've completed running)
scripter.pressMenuKey();
scripter.verifyMenuIsDisplayed(MenuType.DAILY_DATA);
if (request.tddHistory != PumpHistoryRequest.SKIP) {
int totalRecords = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.TOTAL_RECORD);
if (totalRecords > 0) {
if (request.tddHistory == PumpHistoryRequest.LAST) {
Tdd tdd = readTddRecord();
history.tddHistory.add(tdd);
} else {
readTddRecords(request.tbrHistory);
}
}
}
// tbr history
scripter.pressMenuKey();
scripter.verifyMenuIsDisplayed(MenuType.TBR_DATA);
if (request.tbrHistory != PumpHistoryRequest.SKIP) {
int totalRecords = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.TOTAL_RECORD);
if (totalRecords > 0) {
if (request.tbrHistory == PumpHistoryRequest.LAST) {
Tbr tbr = readTbrRecord();
history.tbrHistory.add(tbr);
} else {
readTbrRecords(request.tbrHistory);
}
}
}
}
if (log.isDebugEnabled()) {
if (!history.bolusHistory.isEmpty()) {
log.debug("Read bolus history (" + history.bolusHistory.size() + "):");
for (Bolus bolus : history.bolusHistory) {
log.debug(new Date(bolus.timestamp) + ": " + bolus.toString());
}
}
if (!history.pumpAlertHistory.isEmpty()) {
log.debug("Read error history (" + history.pumpAlertHistory.size() + "):");
for (PumpAlert pumpAlert : history.pumpAlertHistory) {
log.debug(new Date(pumpAlert.timestamp) + ": " + pumpAlert.toString());
}
}
if (!history.tddHistory.isEmpty()) {
log.debug("Read TDD history (" + history.tddHistory.size() + "):");
for (Tdd tdd : history.tddHistory) {
log.debug(new Date(tdd.timestamp) + ": " + tdd.toString());
}
}
if (!history.tbrHistory.isEmpty()) {
log.debug("Read TBR history (" + history.tbrHistory.size() + "):");
for (Tbr tbr : history.tbrHistory) {
log.debug(new Date(tbr.timestamp) + ": " + tbr.toString());
}
}
}
scripter.returnToRootMenu();
scripter.verifyRootMenuIsDisplayed();
result.success(true).history(history);
}
private void readTddRecords(long requestedTime) {
int record = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD);
int totalRecords = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.TOTAL_RECORD);
while (true) {
Tdd tdd = readTddRecord();
if (requestedTime != PumpHistoryRequest.FULL && tdd.timestamp < requestedTime) {
break;
}
log.debug("Read TDD record #" + record + "/" + totalRecords);
history.tddHistory.add(tdd);
log.debug("Parsed " + scripter.getCurrentMenu().toString() + " => " + tdd);
if (record == totalRecords) {
break;
}
scripter.pressDownKey();
while (record == (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD)) {
scripter.waitForScreenUpdate();
}
record = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD);
}
}
@NonNull
private Tdd readTddRecord() {
scripter.verifyMenuIsDisplayed(MenuType.DAILY_DATA);
Double dailyTotal = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.DAILY_TOTAL);
MenuDate date = (MenuDate) scripter.getCurrentMenu().getAttribute(MenuAttribute.DATE);
int year = Calendar.getInstance().get(Calendar.YEAR);
if (date.getMonth() > Calendar.getInstance().get(Calendar.MONTH) + 1) {
year -= 1;
}
Calendar calendar = Calendar.getInstance();
calendar.set(year, date.getMonth() - 1, date.getDay(), 0, 0, 0);
return new Tdd(calendar.getTimeInMillis(), dailyTotal);
}
private void readTbrRecords(long requestedTime) {
int record = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD);
int totalRecords = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.TOTAL_RECORD);
while (true) {
Tbr tbr = readTbrRecord();
if (requestedTime != PumpHistoryRequest.FULL && tbr.timestamp < requestedTime) {
break;
}
log.debug("Read TBR record #" + record + "/" + totalRecords);
history.tbrHistory.add(tbr);
log.debug("Parsed " + scripter.getCurrentMenu().toString() + " => " + tbr);
if (record == totalRecords) {
break;
}
scripter.pressDownKey();
while (record == (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD)) {
scripter.waitForScreenUpdate();
}
record = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD);
}
}
@NonNull
private Tbr readTbrRecord() {
scripter.verifyMenuIsDisplayed(MenuType.TBR_DATA);
Double percentage = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.TBR);
MenuTime durationTime = (MenuTime) scripter.getCurrentMenu().getAttribute(MenuAttribute.RUNTIME);
int duration = durationTime.getHour() * 60 + durationTime.getMinute();
long tbrStartDate = readRecordDate() - duration * 60 * 1000;
return new Tbr(tbrStartDate, duration, percentage.intValue());
}
private void readBolusRecords(long requestedTime) {
int record = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD);
int totalRecords = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.TOTAL_RECORD);
while (true) {
Bolus bolus = readBolusRecord();
if (requestedTime != PumpHistoryRequest.FULL && bolus.timestamp < requestedTime) {
break;
}
log.debug("Read bolus record #" + record + "/" + totalRecords);
history.bolusHistory.add(bolus);
log.debug("Parsed " + scripter.getCurrentMenu().toString() + " => " + bolus);
if (record == totalRecords) {
break;
}
scripter.pressDownKey();
while (record == (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD)) {
scripter.waitForScreenUpdate();
}
record = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD);
}
}
private void readAlertRecords(long requestedTime) {
int record = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD);
int totalRecords = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.TOTAL_RECORD);
while (true) {
PumpAlert error = readAlertRecord();
if (requestedTime != PumpHistoryRequest.FULL && error.timestamp < requestedTime) {
break;
}
log.debug("Read alert record #" + record + "/" + totalRecords);
history.pumpAlertHistory.add(error);
log.debug("Parsed " + scripter.getCurrentMenu().toString() + " => " + error);
if (record == totalRecords) {
break;
}
scripter.pressDownKey();
while (record == (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD)) {
scripter.waitForScreenUpdate();
}
record = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD);
}
}
@NonNull
private PumpAlert readAlertRecord() {
scripter.verifyMenuIsDisplayed(MenuType.ERROR_DATA);
Integer warningCode = (Integer) scripter.getCurrentMenu().getAttribute(MenuAttribute.WARNING);
Integer errorCode = (Integer) scripter.getCurrentMenu().getAttribute(MenuAttribute.ERROR);
String message = (String) scripter.getCurrentMenu().getAttribute(MenuAttribute.MESSAGE);
long recordDate = readRecordDate();
return new PumpAlert(recordDate, warningCode, errorCode, message);
}
@Override
public String toString() {
return "ReadHistoryCommand{" +
"request=" + request +
", history=" + history +
'}';
}
}

View file

@ -0,0 +1,19 @@
package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands;
public class ReadPumpStateCommand extends BaseCommand {
@Override
public void execute() {
// nothing to do, scripter adds state to all command results
result.success = true;
}
@Override
public String toString() {
return "ReadPumpStateCommand{}";
}
@Override
public boolean needsRunMode() {
return false;
}
}

View file

@ -0,0 +1,76 @@
package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands;
import org.monkey.d.ruffy.ruffy.driver.display.MenuAttribute;
import org.monkey.d.ruffy.ruffy.driver.display.MenuType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.Bolus;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.PumpHistory;
public class ReadQuickInfoCommand extends BaseCommand {
private static final Logger log = LoggerFactory.getLogger(ReadQuickInfoCommand.class);
private final int numberOfBolusRecordsToRetrieve;
public ReadQuickInfoCommand(int numberOfBolusRecordsToRetrieve) {
this.numberOfBolusRecordsToRetrieve = numberOfBolusRecordsToRetrieve;
}
@Override
public void execute() {
scripter.verifyRootMenuIsDisplayed();
// navigate to reservoir menu
scripter.pressCheckKey();
scripter.waitForMenuToBeLeft(MenuType.MAIN_MENU);
scripter.waitForMenuToBeLeft(MenuType.STOP);
scripter.verifyMenuIsDisplayed(MenuType.QUICK_INFO);
result.reservoirLevel = ((Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.REMAINING_INSULIN)).intValue();
if (numberOfBolusRecordsToRetrieve > 0) {
// navigate to bolus data menu
scripter.pressCheckKey();
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_DATA);
List<Bolus> bolusHistory = new ArrayList<>(numberOfBolusRecordsToRetrieve);
result.history = new PumpHistory().bolusHistory(bolusHistory);
// read bolus records
int totalRecords = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.TOTAL_RECORD);
int record = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD);
while (true) {
bolusHistory.add(readBolusRecord());
if (bolusHistory.size() == numberOfBolusRecordsToRetrieve || record == totalRecords) {
break;
}
// advance to next record
scripter.pressDownKey();
while (record == (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD)) {
scripter.waitForScreenUpdate();
}
record = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD);
}
if (log.isDebugEnabled()) {
if (!result.history.bolusHistory.isEmpty()) {
log.debug("Read bolus history (" + result.history.bolusHistory.size() + "):");
for (Bolus bolus : result.history.bolusHistory) {
log.debug(new Date(bolus.timestamp) + ": " + bolus.toString());
}
}
}
}
scripter.returnToRootMenu();
result.success = true;
}
@Override
public boolean needsRunMode() {
return false;
}
@Override
public String toString() {
return "ReadQuickInfoCommand{}";
}
}

View file

@ -0,0 +1,162 @@
package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands;
import android.os.SystemClock;
import org.monkey.d.ruffy.ruffy.driver.display.Menu;
import org.monkey.d.ruffy.ruffy.driver.display.MenuAttribute;
import org.monkey.d.ruffy.ruffy.driver.display.MenuType;
import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.BasalProfile;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.PumpState;
public class SetBasalProfileCommand extends BaseCommand {
private static final Logger log = LoggerFactory.getLogger(SetBasalProfileCommand.class);
private final BasalProfile basalProfile;
public SetBasalProfileCommand(BasalProfile basalProfile) {
this.basalProfile = basalProfile;
}
@Override
public void execute() {
scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU);
if (scripter.readPumpStateInternal().unsafeUsageDetected == PumpState.UNSUPPORTED_BASAL_RATE_PROFILE) {
throw new CommandException("Active basal rate profile != 1");
}
scripter.navigateToMenu(MenuType.BASAL_1_MENU);
scripter.verifyMenuIsDisplayed(MenuType.BASAL_1_MENU);
scripter.pressCheckKey();
// summary screen is shown; press menu to page through hours, wraps around to summary;
scripter.verifyMenuIsDisplayed(MenuType.BASAL_TOTAL);
for (int i = 0; i < 24; i++) {
scripter.pressMenuKey();
Menu menu = scripter.getCurrentMenu();
while (menu.getType() != MenuType.BASAL_SET
|| ((MenuTime) scripter.getCurrentMenu().getAttribute(MenuAttribute.BASAL_START)).getHour() != i) {
scripter.waitForScreenUpdate();
menu = scripter.getCurrentMenu();
}
scripter.verifyMenuIsDisplayed(MenuType.BASAL_SET);
double requestedRate = basalProfile.hourlyRates[i];
long change = inputBasalRate(requestedRate);
if (change != 0) {
verifyDisplayedRate(requestedRate, change);
}
log.debug("Set basal profile, hour " + i + ": " + requestedRate);
}
// move from hourly values to basal total
scripter.pressCheckKey();
scripter.verifyMenuIsDisplayed(MenuType.BASAL_TOTAL);
// check total basal total on pump matches requested total
Double pumpTotal = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.BASAL_TOTAL);
Double requestedTotal = 0d;
for (int i = 0; i < 24; i++) {
requestedTotal += basalProfile.hourlyRates[i];
}
if (Math.abs(pumpTotal - requestedTotal) > 0.001) {
throw new CommandException("Basal total of " + pumpTotal + " differs from requested total of " + requestedTotal);
}
// confirm entered basal rate
scripter.pressCheckKey();
scripter.verifyRootMenuIsDisplayed();
result.success(true).basalProfile(basalProfile);
}
private long inputBasalRate(double requestedRate) {
double currentRate = scripter.readBlinkingValue(Double.class, MenuAttribute.BASAL_RATE);
log.debug("Current rate: " + currentRate + ", requested: " + requestedRate);
// the pump changes steps size from 0.01 to 0.05 when crossing 1.00 U
long steps = 0;
if (currentRate == 0) {
// edge case of starting from 0.00;
steps = stepsToOne(0.05) - stepsToOne(requestedRate) + 1;
} else {
steps = stepsToOne(currentRate) - stepsToOne(requestedRate);
}
if (steps == 0) {
return 0;
}
log.debug("Pressing " + (steps > 0 ? "up" : "down") + " " + Math.abs(steps) + " times");
for (int i = 0; i < Math.abs(steps); i++) {
scripter.verifyMenuIsDisplayed(MenuType.BASAL_SET);
log.debug("Push #" + (i + 1) + "/" + Math.abs(steps));
if (steps > 0) scripter.pressUpKey();
else scripter.pressDownKey();
SystemClock.sleep(50);
}
return steps;
}
/**
* Steps required to go up to 1.0 (positive return value),
* or down to 1.0 (negative return value).
*/
private long stepsToOne(double rate) {
double change = (1.0 - rate);
if (rate > 1) return Math.round(change / 0.05);
return Math.round(change / 0.01);
}
private void verifyDisplayedRate(double requestedRate, long change) {
scripter.verifyMenuIsDisplayed(MenuType.BASAL_SET);
// wait up to 5s for any scrolling to finish
double displayedRate = scripter.readBlinkingValue(Double.class, MenuAttribute.BASAL_RATE);
long timeout = System.currentTimeMillis() + 10 * 1000;
while (timeout > System.currentTimeMillis()
&& ((change > 0 && requestedRate - displayedRate > 0.001) // displayedRate < requestedRate)
|| (change < 0 && displayedRate - requestedRate > 0.001))) { //displayedRate > requestedRate))) {
log.debug("Waiting for pump to process scrolling input for rate, current: "
+ displayedRate + ", desired: " + requestedRate + ", scrolling "
+ (change > 0 ? "up" : "down"));
scripter.waitForScreenUpdate();
displayedRate = scripter.readBlinkingValue(Double.class, MenuAttribute.BASAL_RATE);
}
log.debug("Final displayed basal rate: " + displayedRate);
if (Math.abs(displayedRate - requestedRate) > 0.001) {
throw new CommandException("Failed to set basal rate, requested: "
+ requestedRate + ", actual: " + displayedRate);
}
// check again to ensure the displayed value hasn't change and scrolled past the desired
// value due to due scrolling taking extremely long
SystemClock.sleep(1000);
scripter.verifyMenuIsDisplayed(MenuType.BASAL_SET);
double refreshedDisplayedRate = scripter.readBlinkingValue(Double.class, MenuAttribute.BASAL_RATE);
if (Math.abs(displayedRate - refreshedDisplayedRate) > 0.001) {
throw new CommandException("Failed to set basal rate: " +
"percentage changed after input stopped from "
+ displayedRate + " -> " + refreshedDisplayedRate);
}
}
@Override
public List<String> validateArguments() {
ArrayList<String> violations = new ArrayList<>();
if (basalProfile == null) {
violations.add("No basal profile supplied");
}
return violations;
}
@Override
public String toString() {
return "SetBasalProfileCommand{" +
"basalProfile=" + basalProfile +
'}';
}
}

View file

@ -0,0 +1,286 @@
package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands;
import android.os.SystemClock;
import org.monkey.d.ruffy.ruffy.driver.display.MenuAttribute;
import org.monkey.d.ruffy.ruffy.driver.display.MenuType;
import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.PumpState;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.PumpWarningCodes;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.WarningOrErrorCode;
public class SetTbrCommand extends BaseCommand {
private static final Logger log = LoggerFactory.getLogger(SetTbrCommand.class);
private final long percentage;
private final long duration;
public SetTbrCommand(long percentage, long duration) {
this.percentage = percentage;
this.duration = duration;
}
@Override
public List<String> validateArguments() {
List<String> violations = new ArrayList<>();
if (percentage % 10 != 0) {
violations.add("TBR percentage must be set in 10% steps");
}
if (percentage < 0 || percentage > 500) {
violations.add("TBR percentage must be within 0-500%");
}
if (percentage != 100) {
if (duration % 15 != 0) {
violations.add("TBR duration can only be set in 15 minute steps");
}
if (duration > 60 * 24) {
violations.add("Maximum TBR duration is 24 hours");
}
}
return violations;
}
@Override
public Integer getReconnectWarningId() {
return PumpWarningCodes.TBR_CANCELLED;
}
@Override
public void execute() {
try {
if (checkAndWaitIfExistingTbrIsAboutToEnd()) {
return;
}
enterTbrMenu();
boolean increasingPercentage = inputTbrPercentage();
verifyDisplayedTbrPercentage(increasingPercentage);
if (percentage == 100) {
cancelTbrAndConfirmCancellationWarning();
} else {
// switch to TBR_DURATION menu by pressing menu key
scripter.verifyMenuIsDisplayed(MenuType.TBR_SET);
scripter.pressMenuKey();
scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION);
boolean increasingDuration = inputTbrDuration();
verifyDisplayedTbrDuration(increasingDuration);
// confirm TBR
scripter.pressCheckKey();
scripter.waitForMenuToBeLeft(MenuType.TBR_DURATION);
}
} catch (CommandException e) {
if (scripter.getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) {
// The pump raises a TBR CANCELLED warning when a running TBR finishes while we're
// programming a new one (TBR remaining time was last displayed as 0:01, the pump
// rounds seconds up to full minutes). In that case confirm the alert since we know
// we caused it (in a way), but still fail the command so the usual cleanups of returning
// to main menu etc are performed, after which this command can simply be retried.
// Note that this situation should have been dealt with in
// #checkAndWaitIfExistingTbrIsAboutToEnd, but still occur if that method runs
// into a timeout or some other freaky thing happens, so we'll leave it here.
WarningOrErrorCode warningOrErrorCode = scripter.readWarningOrErrorCode();
if (Objects.equals(warningOrErrorCode.warningCode, PumpWarningCodes.TBR_CANCELLED)) {
scripter.confirmAlert(PumpWarningCodes.TBR_CANCELLED);
}
}
throw e;
}
result.success = true;
}
/**
* When programming a new TBR while an existing TBR runs out, a TBR CANCELLED
* alert is raised (failing the command, requiring a reconnect and confirming alert
* and all). To avoid this, wait until the active TBR runs out if the active TBR
* is about to end.
*
* @return true if we waited till the TBR ended and cancellation was request so all work is done.
*/
private boolean checkAndWaitIfExistingTbrIsAboutToEnd() {
scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU);
long timeout = System.currentTimeMillis() + 65 * 1000;
PumpState state = scripter.readPumpStateInternal();
if (state.tbrRemainingDuration == 1) {
while (state.tbrActive && System.currentTimeMillis() < timeout) {
log.debug("Waiting for existing TBR to run out to avoid alert while setting TBR");
scripter.waitForScreenUpdate();
state = scripter.readPumpStateInternal();
}
// if we waited above and a cancellation was requested, we already completed the request
if (!state.tbrActive && percentage == 100) {
result.success = true;
return true;
}
}
return false;
}
private void enterTbrMenu() {
scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU);
scripter.navigateToMenu(MenuType.TBR_MENU);
scripter.verifyMenuIsDisplayed(MenuType.TBR_MENU);
scripter.pressCheckKey();
scripter.verifyMenuIsDisplayed(MenuType.TBR_SET);
}
private boolean inputTbrPercentage() {
scripter.verifyMenuIsDisplayed(MenuType.TBR_SET);
long currentPercent = readDisplayedPercentage();
log.debug("Current TBR %: " + currentPercent);
long percentageChange = percentage - currentPercent;
long percentageSteps = percentageChange / 10;
boolean increasePercentage = percentageSteps > 0;
log.debug("Pressing " + (increasePercentage ? "up" : "down") + " " + percentageSteps + " times");
for (int i = 0; i < Math.abs(percentageSteps); i++) {
scripter.verifyMenuIsDisplayed(MenuType.TBR_SET);
log.debug("Push #" + (i + 1) + "/" + Math.abs(percentageSteps));
if (increasePercentage) scripter.pressUpKey();
else scripter.pressDownKey();
SystemClock.sleep(50);
}
return increasePercentage;
}
private void verifyDisplayedTbrPercentage(boolean increasingPercentage) {
scripter.verifyMenuIsDisplayed(MenuType.TBR_SET);
// wait up to 5s for any scrolling to finish
long displayedPercentage = readDisplayedPercentage();
long timeout = System.currentTimeMillis() + 10 * 1000;
while (timeout > System.currentTimeMillis()
&& ((increasingPercentage && displayedPercentage < percentage)
|| (!increasingPercentage && displayedPercentage > percentage))) {
log.debug("Waiting for pump to process scrolling input for percentage, current: "
+ displayedPercentage + ", desired: " + percentage + ", scrolling "
+ (increasingPercentage ? "up" : "down"));
SystemClock.sleep(50);
displayedPercentage = readDisplayedPercentage();
}
log.debug("Final displayed TBR percentage: " + displayedPercentage);
if (displayedPercentage != percentage) {
throw new CommandException("Failed to set TBR percentage, requested: "
+ percentage + ", actual: " + displayedPercentage);
}
// check again to ensure the displayed value hasn't change and scrolled past the desired
// value due to due scrolling taking extremely long
SystemClock.sleep(1000);
scripter.verifyMenuIsDisplayed(MenuType.TBR_SET);
long refreshedDisplayedTbrPecentage = readDisplayedPercentage();
if (displayedPercentage != refreshedDisplayedTbrPecentage) {
throw new CommandException("Failed to set TBR percentage: " +
"percentage changed after input stopped from "
+ displayedPercentage + " -> " + refreshedDisplayedTbrPecentage);
}
}
private boolean inputTbrDuration() {
scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION);
long durationSteps = calculateDurationSteps();
boolean increaseDuration = durationSteps > 0;
log.debug("Pressing " + (increaseDuration ? "up" : "down") + " " + durationSteps + " times");
for (int i = 0; i < Math.abs(durationSteps); i++) {
scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION);
log.debug("Push #" + (i + 1) + "/" + Math.abs(durationSteps));
if (increaseDuration) scripter.pressUpKey();
else scripter.pressDownKey();
SystemClock.sleep(50);
}
return increaseDuration;
}
private long calculateDurationSteps() {
long currentDuration = readDisplayedDuration();
log.debug("Initial TBR duration: " + currentDuration);
long difference = duration - currentDuration;
long durationSteps = difference / 15;
long durationAfterInitialSteps = currentDuration + (durationSteps * 15);
if (durationAfterInitialSteps < duration) return durationSteps + 1;
else if (durationAfterInitialSteps > duration) return durationSteps - 1;
else return durationSteps;
}
private void verifyDisplayedTbrDuration(boolean increasingPercentage) {
scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION);
// wait up to 5s for any scrolling to finish
long displayedDuration = readDisplayedDuration();
long timeout = System.currentTimeMillis() + 10 * 1000;
while (timeout > System.currentTimeMillis()
&& ((increasingPercentage && displayedDuration < duration)
|| (!increasingPercentage && displayedDuration > duration))) {
log.debug("Waiting for pump to process scrolling input for duration, current: "
+ displayedDuration + ", desired: " + duration
+ ", scrolling " + (increasingPercentage ? "up" : "down"));
SystemClock.sleep(50);
displayedDuration = readDisplayedDuration();
}
log.debug("Final displayed TBR duration: " + displayedDuration);
if (displayedDuration != duration) {
throw new CommandException("Failed to set TBR duration, requested: "
+ duration + ", actual: " + displayedDuration);
}
// check again to ensure the displayed value hasn't change and scrolled past the desired
// value due to due scrolling taking extremely long
SystemClock.sleep(1000);
scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION);
long refreshedDisplayedTbrDuration = readDisplayedDuration();
if (displayedDuration != refreshedDisplayedTbrDuration) {
throw new CommandException("Failed to set TBR duration: " +
"duration changed after input stopped from "
+ displayedDuration + " -> " + refreshedDisplayedTbrDuration);
}
}
private void cancelTbrAndConfirmCancellationWarning() {
// confirm entered TBR
scripter.verifyMenuIsDisplayed(MenuType.TBR_SET);
scripter.pressCheckKey();
// A "TBR CANCELLED alert" is only raised by the pump when the remaining time is
// greater than 60s (displayed as 0:01, the pump goes from there to finished.
// We could read the remaining duration from MAIN_MENU, but by the time we're here,
// the pump could have moved from 0:02 to 0:01, so instead, check if a "TBR CANCELLED" alert
// is raised and if so dismiss it
scripter.confirmAlert(PumpWarningCodes.TBR_CANCELLED, 2000);
}
private long readDisplayedDuration() {
MenuTime duration = scripter.readBlinkingValue(MenuTime.class, MenuAttribute.RUNTIME);
return duration.getHour() * 60 + duration.getMinute();
}
private long readDisplayedPercentage() {
return scripter.readBlinkingValue(Double.class, MenuAttribute.BASAL_RATE).longValue();
}
@Override
public boolean needsRunMode() {
return true;
}
@Override
public String toString() {
return "SetTbrCommand{" +
"percentage=" + percentage +
", duration=" + duration +
'}';
}
}

View file

@ -0,0 +1,46 @@
package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history;
import java.util.Date;
public class Bolus extends HistoryRecord {
public final double amount;
public final boolean isValid;
public Bolus(long timestamp, double amount, boolean isValid) {
super(timestamp);
this.amount = amount;
this.isValid = isValid;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Bolus bolus = (Bolus) o;
if (timestamp != bolus.timestamp) return false;
if (isValid != bolus.isValid) return false;
return Math.abs(bolus.amount - amount) <= 0.01;
}
@Override
public int hashCode() {
int result;
long temp;
result = (int) (timestamp ^ (timestamp >>> 32));
temp = Double.doubleToLongBits(amount);
result = result + (isValid ? 1 : 0);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public String toString() {
return "Bolus{" +
"timestamp=" + timestamp + " (" + new Date(timestamp) + ")" +
", amount=" + amount +
'}';
}
}

View file

@ -0,0 +1,9 @@
package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history;
public abstract class HistoryRecord {
public final long timestamp;
protected HistoryRecord(long timestamp) {
this.timestamp = timestamp;
}
}

View file

@ -0,0 +1,51 @@
package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history;
import java.util.Date;
public class PumpAlert extends HistoryRecord {
public final Integer warningCode;
public final Integer errorCode;
/** Error message, in the language configured on the pump. */
public final String message;
public PumpAlert(long timestamp, Integer warningCode, Integer errorCode, String message) {
super(timestamp);
this.warningCode = warningCode;
this.errorCode = errorCode;
this.message = message;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PumpAlert pumpAlert = (PumpAlert) o;
if (timestamp != pumpAlert.timestamp) return false;
if (warningCode != null ? !warningCode.equals(pumpAlert.warningCode) : pumpAlert.warningCode != null)
return false;
if (errorCode != null ? !errorCode.equals(pumpAlert.errorCode) : pumpAlert.errorCode != null)
return false;
return message != null ? message.equals(pumpAlert.message) : pumpAlert.message == null;
}
@Override
public int hashCode() {
int result = (int) (timestamp ^ (timestamp >>> 32));
result = 31 * result + (warningCode != null ? warningCode.hashCode() : 0);
result = 31 * result + (errorCode != null ? errorCode.hashCode() : 0);
result = 31 * result + (message != null ? message.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "PumpAlert{" +
"timestamp=" + timestamp + "(" + new Date(timestamp) + ")" +
", warningCode=" + warningCode +
", errorCode=" + errorCode +
", message='" + message + '\'' +
'}';
}
}

View file

@ -0,0 +1,50 @@
package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history;
import android.support.annotation.NonNull;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/** History data as read from the pump's My Data menu.
* Records are ordered from newest to oldest, so the first record is always the newest. */
public class PumpHistory {
@NonNull
public List<Bolus> bolusHistory = new ArrayList<>();
@NonNull
public List<Tbr> tbrHistory = new ArrayList<>();
@NonNull
public List<PumpAlert> pumpAlertHistory = new LinkedList<>();
@NonNull
public List<Tdd> tddHistory = new ArrayList<>();
public PumpHistory bolusHistory(List<Bolus> bolusHistory) {
this.bolusHistory = bolusHistory;
return this;
}
public PumpHistory tbrHistory(List<Tbr> tbrHistory) {
this.tbrHistory = tbrHistory;
return this;
}
public PumpHistory pumpErrorHistory(List<PumpAlert> pumpAlertHistory) {
this.pumpAlertHistory = pumpAlertHistory;
return this;
}
public PumpHistory tddHistory(List<Tdd> tddHistory) {
this.tddHistory = tddHistory;
return this;
}
@Override
public String toString() {
return "PumpHistory{" +
"bolusHistory=" + bolusHistory.size() +
", tbrHistory=" + tbrHistory.size() +
", pumpAlertHistory=" + pumpAlertHistory.size() +
", tddHistory=" + tddHistory.size() +
'}';
}
}

View file

@ -0,0 +1,51 @@
package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history;
import java.util.Date;
/** What data a 'read history' request should return. */
public class PumpHistoryRequest {
/* History to read:
Either the timestamp of the last known record or one of the constants to read no history
or all of it. When a timestamp is provided all newer records and records matching the
timestamp are returned. Returning all records equal to the timestamp ensures a record
with a duplicate timestamp is also detected as a new record.
*/
public static final long LAST = -2;
public static final long SKIP = -1;
public static final long FULL = 0;
public long bolusHistory = SKIP;
public long tbrHistory = SKIP;
public long pumpErrorHistory = SKIP;
public long tddHistory = SKIP;
public PumpHistoryRequest bolusHistory(long bolusHistory) {
this.bolusHistory = bolusHistory;
return this;
}
public PumpHistoryRequest tbrHistory(long tbrHistory) {
this.tbrHistory = tbrHistory;
return this;
}
public PumpHistoryRequest pumpErrorHistory(long pumpErrorHistory) {
this.pumpErrorHistory = pumpErrorHistory;
return this;
}
public PumpHistoryRequest tddHistory(long tddHistory) {
this.tddHistory = tddHistory;
return this;
}
@Override
public String toString() {
return "PumpHistoryRequest{" +
"bolusHistory=" + bolusHistory + (bolusHistory > 0 ? ("(" + new Date(bolusHistory) + ")") : "") +
", tbrHistory=" + tbrHistory + (tbrHistory > 0 ? ("(" + new Date(tbrHistory) + ")") : "") +
", pumpAlertHistory=" + pumpErrorHistory + (pumpErrorHistory > 0 ? ("(" + new Date(pumpErrorHistory) + ")") : "") +
", tddHistory=" + tddHistory + (tddHistory > 0 ? ("(" + new Date(tddHistory) + ")") : "") +
'}';
}
}

View file

@ -0,0 +1,44 @@
package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history;
import java.util.Date;
public class Tbr extends HistoryRecord {
/** Duration in minutes */
public final int duration;
public final int percent;
public Tbr(long timestamp, int duration, int percent) {
super(timestamp);
this.duration = duration;
this.percent = percent;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Tbr tbr = (Tbr) o;
if (timestamp != tbr.timestamp) return false;
if (duration != tbr.duration) return false;
return percent == tbr.percent;
}
@Override
public int hashCode() {
int result = (int) (timestamp ^ (timestamp >>> 32));
result = 31 * result + duration;
result = 31 * result + percent;
return result;
}
@Override
public String toString() {
return "Tbr{" +
"timestamp=" + timestamp + "(" + new Date(timestamp) + ")" +
", duration=" + duration +
", percent=" + percent +
'}';
}
}

View file

@ -0,0 +1,42 @@
package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history;
import java.util.Date;
/** Total daily dosage; amount of insulin delivered over a full day. */
public class Tdd extends HistoryRecord {
public final double total;
public Tdd(long timestamp, double total) {
super(timestamp);
this.total = total;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Tdd tdd = (Tdd) o;
if (timestamp != tdd.timestamp) return false;
return tdd.total != total;
}
@Override
public int hashCode() {
int result;
long temp;
result = (int) (timestamp ^ (timestamp >>> 32));
temp = Double.doubleToLongBits(total);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public String toString() {
return "Tdd{" +
"timestamp=" + timestamp + "(" + new Date(timestamp) + ")" +
", total=" + total +
'}';
}
}

View file

@ -184,7 +184,7 @@ public abstract class AbstractDanaRPlugin implements PluginBase, PumpInterface,
int basalIncrement = pump.basal48Enable ? 30 * 60 : 60 * 60;
for (int h = 0; h < basalValues; h++) {
Double pumpValue = pump.pumpProfiles[pump.activeProfile][h];
Double profileValue = profile.getBasal((Integer) (h * basalIncrement));
Double profileValue = profile.getBasalTimeFromMidnight((Integer) (h * basalIncrement));
if (profileValue == null) return true;
if (Math.abs(pumpValue - profileValue) > getPumpDescription().basalStep) {
log.debug("Diff found. Hour: " + h + " Pump: " + pumpValue + " Profile: " + profileValue);

View file

@ -241,7 +241,7 @@ public class DanaRPump {
for (Integer hour = 0; hour < 24; hour++) {
//Some values get truncated to the next lower one.
// -> round them to two decimals and make sure we are a small delta larger (that will get truncated)
double value = Math.round(100d * nsProfile.getBasal((Integer) (hour * 60 * 60)))/100d + 0.00001;
double value = Math.round(100d * nsProfile.getBasalTimeFromMidnight((Integer) (hour * 60 * 60)))/100d + 0.00001;
if (Config.logDanaMessageDetail)
log.debug("NS basal value for " + hour + ":00 is " + value);
record[hour] = value;

View file

@ -428,7 +428,7 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface,
int basalIncrement = pump.basal48Enable ? 30 * 60 : 60 * 60;
for (int h = 0; h < basalValues; h++) {
Double pumpValue = pump.pumpProfiles[pump.activeProfile][h];
Double profileValue = profile.getBasal((Integer) (h * basalIncrement));
Double profileValue = profile.getBasalTimeFromMidnight((Integer) (h * basalIncrement));
if (profileValue == null) return true;
if (Math.abs(pumpValue - profileValue) > getPumpDescription().basalStep) {
log.debug("Diff found. Hour: " + h + " Pump: " + pumpValue + " Profile: " + profileValue);

View file

@ -52,12 +52,12 @@ public class VirtualPumpPlugin implements PluginBase, PumpInterface {
private PumpDescription pumpDescription = new PumpDescription();
private static void loadFakingStatus() {
fromNSAreCommingFakedExtendedBoluses = SP.getBoolean("fromNSAreCommingFakedExtendedBoluses", false);
fromNSAreCommingFakedExtendedBoluses = SP.getBoolean(R.string.key_fromNSAreCommingFakedExtendedBoluses, false);
}
public static void setFakingStatus(boolean newStatus) {
fromNSAreCommingFakedExtendedBoluses = newStatus;
SP.putBoolean("fromNSAreCommingFakedExtendedBoluses", fromNSAreCommingFakedExtendedBoluses);
SP.putBoolean(R.string.key_fromNSAreCommingFakedExtendedBoluses, fromNSAreCommingFakedExtendedBoluses);
}
public static boolean getFakingStatus() {
@ -73,7 +73,7 @@ public class VirtualPumpPlugin implements PluginBase, PumpInterface {
return plugin;
}
private VirtualPumpPlugin() {
public VirtualPumpPlugin() {
pumpDescription.isBolusCapable = true;
pumpDescription.bolusStep = 0.1d;
@ -245,7 +245,7 @@ public class VirtualPumpPlugin implements PluginBase, PumpInterface {
public double getBaseBasalRate() {
Profile profile = MainApp.getConfigBuilder().getProfile();
if (profile != null)
return profile.getBasal() != null ? profile.getBasal() : 0d;
return profile.getBasal();
else
return 0d;
}

View file

@ -59,7 +59,7 @@ public class BGSourceFragment extends SubscriberFragment {
RecyclerViewAdapter adapter = new RecyclerViewAdapter(MainApp.getDbHelper().getAllBgreadingsDataFromTime(now - MILLS_TO_THE_PAST, false));
recyclerView.setAdapter(adapter);
profile = ConfigBuilderPlugin.getActiveProfileInterface().getProfile().getDefaultProfile();
profile = MainApp.getConfigBuilder().getActiveProfileInterface().getProfile().getDefaultProfile();
return view;
} catch (Exception e) {

View file

@ -33,8 +33,8 @@ public class ProfileGraph extends GraphView {
List<DataPoint> basalArray = new ArrayList<>();
for (int hour = 0; hour < 24; hour++) {
basalArray.add(new DataPoint(hour, profile.getBasal(new Integer(hour*60*60))));
basalArray.add(new DataPoint(hour+1, profile.getBasal(new Integer(hour*60*60))));
basalArray.add(new DataPoint(hour, profile.getBasalTimeFromMidnight(new Integer(hour*60*60))));
basalArray.add(new DataPoint(hour+1, profile.getBasalTimeFromMidnight(new Integer(hour*60*60))));
}
DataPoint[] basalDataPoints = new DataPoint[basalArray.size()];
basalDataPoints = basalArray.toArray(basalDataPoints);

View file

@ -12,6 +12,9 @@ import android.widget.TextView;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.Profile;
@ -30,19 +33,36 @@ public class ProfileViewerDialog extends DialogFragment {
private static Logger log = LoggerFactory.getLogger(ProfileViewDialog.class);
private TextView noProfile;
private TextView units;
private TextView dia;
private TextView activeProfile;
private TextView ic;
private TextView isf;
private TextView basal;
private TextView target;
private View dateDelimiter;
private LinearLayout dateLayout;
private TextView dateTextView;
private Button refreshButton;
private ProfileGraph basalGraph;
@BindView(R.id.profileview_noprofile)
TextView noProfile;
@BindView(R.id.profileview_invalidprofile)
TextView invalidProfile;
@BindView(R.id.profileview_units)
TextView units;
@BindView(R.id.profileview_dia)
TextView dia;
@BindView(R.id.profileview_activeprofile)
TextView activeProfile;
@BindView(R.id.profileview_ic)
TextView ic;
@BindView(R.id.profileview_isf)
TextView isf;
@BindView(R.id.profileview_basal)
TextView basal;
@BindView(R.id.profileview_target)
TextView target;
@BindView(R.id.profileview_datedelimiter)
View dateDelimiter;
@BindView(R.id.profileview_datelayout)
LinearLayout dateLayout;
@BindView(R.id.profileview_date)
TextView dateTextView;
@BindView(R.id.profileview_reload)
Button refreshButton;
@BindView(R.id.basal_graph)
ProfileGraph basalGraph;
private Unbinder unbinder;
public static ProfileViewerDialog newInstance(long time) {
ProfileViewerDialog dialog = new ProfileViewerDialog();
@ -61,31 +81,26 @@ public class ProfileViewerDialog extends DialogFragment {
time = getArguments().getLong("time");
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (unbinder != null)
unbinder.unbind();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.profileviewer_fragment, container, false);
View view = inflater.inflate(R.layout.profileviewer_fragment, container, false);
unbinder = ButterKnife.bind(this, view);
noProfile = (TextView) layout.findViewById(R.id.profileview_noprofile);
units = (TextView) layout.findViewById(R.id.profileview_units);
dia = (TextView) layout.findViewById(R.id.profileview_dia);
activeProfile = (TextView) layout.findViewById(R.id.profileview_activeprofile);
ic = (TextView) layout.findViewById(R.id.profileview_ic);
isf = (TextView) layout.findViewById(R.id.profileview_isf);
basal = (TextView) layout.findViewById(R.id.profileview_basal);
target = (TextView) layout.findViewById(R.id.profileview_target);
refreshButton = (Button) layout.findViewById(R.id.profileview_reload);
refreshButton.setVisibility(View.GONE);
dateDelimiter = layout.findViewById(R.id.profileview_datedelimiter);
dateDelimiter.setVisibility(View.VISIBLE);
dateLayout = (LinearLayout) layout.findViewById(R.id.profileview_datelayout);
dateLayout.setVisibility(View.VISIBLE);
dateTextView = (TextView) layout.findViewById(R.id.profileview_date);
basalGraph = (ProfileGraph) layout.findViewById(R.id.basal_graph);
setContent();
return layout;
return view;
}
@Override
@ -114,6 +129,11 @@ public class ProfileViewerDialog extends DialogFragment {
basal.setText(profile.getBasalList());
target.setText(profile.getTargetList());
basalGraph.show(profile);
if (profile.isValid("ProfileViewDialog"))
invalidProfile.setVisibility(View.GONE);
else
invalidProfile.setVisibility(View.VISIBLE);
} else {
noProfile.setVisibility(View.VISIBLE);
}

View file

@ -586,7 +586,7 @@ public class ActionStringHandler {
}
final Profile profile = MainApp.getConfigBuilder().getProfile();
if (profile == null || profile.getBasal() == null) {
if (profile == null) {
msg += MainApp.sResources.getString(R.string.notloadedplugins) + "\n";
}
if (!"".equals(msg)) {

View file

@ -31,8 +31,8 @@ import info.nightscout.utils.SP;
public class WearPlugin implements PluginBase {
private static boolean fragmentEnabled = true;
private boolean fragmentVisible = true;
private static boolean fragmentEnabled = false;
private boolean fragmentVisible = false;
private static WatchUpdaterService watchUS;
private final Context ctx;
@ -182,6 +182,7 @@ public class WearPlugin implements PluginBase {
@Subscribe
public void onStatusEvent(final EventNewBG ev) {
if (ev.isFromActiveBgSource)
sendDataToWatch(true, true, true);
}

View file

@ -235,6 +235,7 @@ public class StatuslinePlugin implements PluginBase {
@Subscribe
public void onStatusEvent(final EventNewBG ev) {
if (ev.isFromActiveBgSource && ev.isNew && ev.isCurrent())
sendStatus();
}

View file

@ -2,6 +2,7 @@ package info.nightscout.androidaps.queue;
import android.content.Context;
import android.content.Intent;
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.text.Html;
import android.text.Spanned;
@ -81,7 +82,7 @@ public class CommandQueue {
return new PumpEnactResult().success(false).enacted(false).comment(MainApp.sResources.getString(R.string.executingrightnow));
}
public boolean isRunning(Command.CommandType type) {
private boolean isRunning(Command.CommandType type) {
if (performing != null && performing.commandType == type)
return true;
return false;
@ -293,12 +294,35 @@ public class CommandQueue {
return false;
}
// Check that there is a valid profileSwitch NOW
if (MainApp.getConfigBuilder().getProfileSwitchFromHistory(System.currentTimeMillis())==null) {
// wait for DatabaseHelper.scheduleProfiSwitch() to do the profile switch // TODO clean this crap up
SystemClock.sleep(5000);
if (MainApp.getConfigBuilder().getProfileSwitchFromHistory(System.currentTimeMillis())==null) {
Notification noProfileSwitchNotif = new Notification(Notification.PROFILE_SWITCH_MISSING, MainApp.gs(R.string.profileswitch_ismissing), Notification.NORMAL);
MainApp.bus().post(new EventNewNotification(noProfileSwitchNotif));
if (callback != null) {
PumpEnactResult result = new PumpEnactResult().success(false).enacted(false).comment("Refuse to send profile to pump! No ProfileSwitch!");
callback.result(result).run();
}
return false;
}
}
if (!MainApp.isEngineeringModeOrRelease()) {
Notification notification = new Notification(Notification.NOT_ENG_MODE_OR_RELEASE, MainApp.sResources.getString(R.string.not_eng_mode_or_release), Notification.URGENT);
MainApp.bus().post(new EventNewNotification(notification));
if (callback != null)
callback.result(new PumpEnactResult().success(false).comment(MainApp.sResources.getString(R.string.not_eng_mode_or_release))).run();
return false;
}
// Compare with pump limits
Profile.BasalValue[] basalValues = profile.getBasalValues();
PumpInterface pump = ConfigBuilderPlugin.getActivePump();
for (int index = 0; index < basalValues.length; index++) {
if (basalValues[index].value < pump.getPumpDescription().basalMinimumRate) {
for (Profile.BasalValue basalValue : basalValues) {
if (basalValue.value < pump.getPumpDescription().basalMinimumRate) {
Notification notification = new Notification(Notification.BASAL_VALUE_BELOW_MINIMUM, MainApp.sResources.getString(R.string.basalvaluebelowminimum), Notification.URGENT);
MainApp.bus().post(new EventNewNotification(notification));
if (callback != null)

View file

@ -28,9 +28,8 @@ import info.nightscout.utils.SP;
public class QueueThread extends Thread {
private static Logger log = LoggerFactory.getLogger(QueueThread.class);
CommandQueue queue;
private CommandQueue queue;
private long connectionStartTime = 0;
private long lastCommandTime = 0;
private boolean connectLogged = false;
@ -48,7 +47,7 @@ public class QueueThread extends Thread {
public final void run() {
mWakeLock.acquire();
MainApp.bus().post(new EventQueueChanged());
connectionStartTime = lastCommandTime = System.currentTimeMillis();
long connectionStartTime = lastCommandTime = System.currentTimeMillis();
try {
while (true) {

View file

@ -1,9 +1,14 @@
package info.nightscout.androidaps.queue.commands;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.Profile;
import info.nightscout.androidaps.data.PumpEnactResult;
import info.nightscout.androidaps.db.ProfileSwitch;
import info.nightscout.androidaps.db.Source;
import info.nightscout.androidaps.interfaces.PluginBase;
import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin;
import info.nightscout.androidaps.plugins.SmsCommunicator.SmsCommunicatorPlugin;
import info.nightscout.androidaps.queue.Callback;
/**
@ -11,7 +16,7 @@ import info.nightscout.androidaps.queue.Callback;
*/
public class CommandSetProfile extends Command {
Profile profile;
private Profile profile;
public CommandSetProfile(Profile profile, Callback callback) {
commandType = CommandType.BASALPROFILE;
@ -24,6 +29,15 @@ public class CommandSetProfile extends Command {
PumpEnactResult r = ConfigBuilderPlugin.getActivePump().setNewBasalProfile(profile);
if (callback != null)
callback.result(r).run();
// Send SMS notification if ProfileSwitch is comming from NS
ProfileSwitch profileSwitch = MainApp.getConfigBuilder().getProfileSwitchFromHistory(System.currentTimeMillis());
if (r.enacted && profileSwitch.source == Source.NIGHTSCOUT) {
SmsCommunicatorPlugin smsCommunicatorPlugin = MainApp.getSpecificPlugin(SmsCommunicatorPlugin.class);
if (smsCommunicatorPlugin != null && smsCommunicatorPlugin.isEnabled(PluginBase.GENERAL)) {
smsCommunicatorPlugin.sendNotificationToAllNumbers(MainApp.sResources.getString(R.string.profile_set_ok));
}
}
}
@Override

View file

@ -52,7 +52,7 @@ public class KeepAliveReceiver extends BroadcastReceiver {
private void checkPump() {
final PumpInterface pump = ConfigBuilderPlugin.getActivePump();
final Profile profile = MainApp.getConfigBuilder().getProfile();
if (pump != null && profile != null && profile.getBasal() != null) {
if (pump != null && profile != null) {
Date lastConnection = pump.lastDataTime();
boolean isStatusOutdated = lastConnection.getTime() + STATUS_UPDATE_FREQUENCY < System.currentTimeMillis();
boolean isBasalOutdated = Math.abs(profile.getBasal() - pump.getBaseBasalRate()) > pump.getPumpDescription().basalStep;

View file

@ -124,7 +124,12 @@ public class DateUtil {
public static String minAgo(long time) {
int mins = (int) ((System.currentTimeMillis() - time) / 1000 / 60);
return String.format(MainApp.sResources.getString(R.string.minago), mins);
return MainApp.gs(R.string.minago, mins);
}
public static String hourAgo(long time) {
double hours = (System.currentTimeMillis() - time) / 1000d / 60 / 60;
return MainApp.gs(R.string.hoursago, hours);
}
private static LongSparseArray<String> timeStrings = new LongSparseArray<>();

View file

@ -15,6 +15,8 @@ import info.nightscout.androidaps.interfaces.PumpInterface;
import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin;
import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification;
import info.nightscout.androidaps.plugins.Overview.notifications.Notification;
import info.nightscout.androidaps.receivers.KeepAliveReceiver;
import info.nightscout.utils.NSUpload;
/**
* Created by adrian on 17/12/17.
@ -76,7 +78,7 @@ public class LocalAlertUtils {
final PumpInterface pump = ConfigBuilderPlugin.getActivePump();
final Profile profile = MainApp.getConfigBuilder().getProfile();
if (pump != null && profile != null && profile.getBasal() != null) {
if (pump != null && profile != null) {
Date lastConnection = pump.lastDataTime();
long earliestAlarmTime = lastConnection.getTime() + pumpUnreachableThreshold();
if (SP.getLong("nextPumpDisconnectedAlarm", 0l) < earliestAlarmTime) {

View file

@ -0,0 +1,142 @@
package org.monkey.d.ruffy.ruffy.driver.display;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import org.monkey.d.ruffy.ruffy.driver.display.menu.BolusType;
import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuBlink;
import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuDate;
import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuTime;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* Created by fishermen21 on 20.05.17.
*/
public class Menu implements Parcelable{
private MenuType type;
private Map<MenuAttribute,Object> attributes = new HashMap<>();
public Menu(MenuType type)
{
this.type = type;
}
public Menu(Parcel in) {
this.type = MenuType.valueOf(in.readString());
while(in.dataAvail()>0) {
try {
String attr = in.readString();
String clas = in.readString();
String value = in.readString();
if(attr!=null && clas!=null && value!=null) {
MenuAttribute a = MenuAttribute.valueOf(attr);
Object o = null;
if (Integer.class.toString().equals(clas)) {
o = new Integer(value);
} else if (Double.class.toString().equals(clas)) {
o = new Double(value);
} else if (Boolean.class.toString().equals(clas)) {
o = new Boolean(value);
} else if (MenuDate.class.toString().equals(clas)) {
o = new MenuDate(value);
} else if (MenuTime.class.toString().equals(clas)) {
o = new MenuTime(value);
} else if (MenuBlink.class.toString().equals(clas)) {
o = new MenuBlink();
} else if (BolusType.class.toString().equals(clas)) {
o = BolusType.valueOf(value);
} else if (String.class.toString().equals(clas)) {
o = new String(value);
}
if (o != null) {
attributes.put(a, o);
} else {
Log.e("MenuIn", "failed to parse: " + attr + " / " + clas + " / " + value);
}
}
}catch(Exception e)
{
Log.e("MenuIn","Exception in read",e);
}
}
}
public void setAttribute(MenuAttribute key, Object value)
{
attributes.put(key,value);
}
public List<MenuAttribute> attributes()
{
return new LinkedList<MenuAttribute>(attributes.keySet());
}
public Object getAttribute(MenuAttribute key)
{
return attributes.get(key);
}
public MenuType getType() {
return type;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(type.toString());
for(MenuAttribute a : attributes.keySet())
{
try
{
String atr = a.toString();
Object o = attributes.get(a);
String clas = o.getClass().toString();
String v = o.toString();
if(atr != null && o != null && v != null) {
dest.writeString(atr);
dest.writeString(clas);
dest.writeString(v);
}
else
{
Log.e("Menu","null in write :/");
}
}catch(Exception e)
{
Log.v("MenuOut","error in write",e);
}
}
}
public static final Parcelable.Creator<Menu> CREATOR = new
Parcelable.Creator<Menu>() {
public Menu createFromParcel(Parcel in) {
return new Menu(in);
}
public Menu[] newArray(int size) {
return new Menu[size];
}
};
@Override
public String toString() {
return "Menu{" +
"type=" + type +
", attributes=" + attributes +
'}';
}
}

View file

@ -0,0 +1,34 @@
package org.monkey.d.ruffy.ruffy.driver.display;
/**
* Created by fishermen21 on 22.05.17.
*/
public enum MenuAttribute {
RUNTIME,//runtime of current operation, remaining time on main menu
BOLUS,//double units
BOLUS_REMAINING,//double units remain from current bolus
TBR,//double 0-500%
BASAL_RATE,//double units/h
BASAL_SELECTED,//int selected basal profile
BATTERY_STATE,//int, like insulin state
INSULIN_STATE,//int insulin warning 0 == no warning, 1== low, 2 == empty
LOCK_STATE,//int keylock state 0==no lock, 1==unlocked, 2==locked
MULTIWAVE_BOLUS,//double immediate bolus on multiwave
BOLUS_TYPE,//BolusType, only history uses MULTIWAVE
TIME,//time MenuTime
REMAINING_INSULIN,//double units
DATE,//date MenuDate
CURRENT_RECORD,//int current record
TOTAL_RECORD, //int total num record
ERROR, //int errorcode
WARNING, //int errorcode
MESSAGE, //string errormessage
DAILY_TOTAL, //double units
BASAL_TOTAL, //double total basal
BASAL_START, //time MenuTime the basalrate starts
BASAL_END, // time MenuTime the basalrate ends
DEBUG_TIMING, //double with timing infos
WARANTY, //boolean true if out of waranty
ERROR_OR_WARNING, // set if menu in blink during error/warning
}

View file

@ -0,0 +1,42 @@
package org.monkey.d.ruffy.ruffy.driver.display;
/**
* Created by fishermen21 on 22.05.17.
*/
public enum MenuType {
MAIN_MENU,
STOP_MENU,
BOLUS_MENU,
BOLUS_ENTER,
EXTENDED_BOLUS_MENU,
BOLUS_DURATION,
MULTIWAVE_BOLUS_MENU,
IMMEDIATE_BOLUS,
TBR_MENU,
MY_DATA_MENU,
BASAL_MENU,
BASAL_1_MENU,
BASAL_2_MENU,
BASAL_3_MENU,
BASAL_4_MENU,
BASAL_5_MENU,
DATE_AND_TIME_MENU,
ALARM_MENU,
MENU_SETTINGS_MENU,
BLUETOOTH_MENU,
THERAPY_MENU,
PUMP_MENU,
QUICK_INFO,
BOLUS_DATA,
DAILY_DATA,
TBR_DATA,
ERROR_DATA,
TBR_SET,
TBR_DURATION,
STOP,
START_MENU,
BASAL_TOTAL,
BASAL_SET,
WARNING_OR_ERROR,
}

View file

@ -0,0 +1,13 @@
package org.monkey.d.ruffy.ruffy.driver.display.menu;
/**
* Created by fishermen21 on 22.05.17.
*/
public enum BolusType{
NORMAL,
EXTENDED,
MULTIWAVE,
MULTIWAVE_BOLUS,
MULTIWAVE_EXTENDED,
}

View file

@ -0,0 +1,12 @@
package org.monkey.d.ruffy.ruffy.driver.display.menu;
/**
* Created by fishermen21 on 22.05.17.
*/
public class MenuBlink {
@Override
public String toString() {
return "BLINK";
}
}

View file

@ -0,0 +1,35 @@
package org.monkey.d.ruffy.ruffy.driver.display.menu;
/**
* Created by fishermen21 on 24.05.17.
*/
public class MenuDate {
private final int day;
private final int month;
public MenuDate(int day, int month) {
this.day = day;
this.month = month;
}
public MenuDate(String value) {
String[] p = value.split("\\.");
day = Integer.parseInt(p[0]);
month = Integer.parseInt(p[1]);
}
public int getDay() {
return day;
}
public int getMonth() {
return month;
}
@Override
public String toString() {
return day+"."+String.format("%02d",month)+".";
}
}

View file

@ -0,0 +1,36 @@
package org.monkey.d.ruffy.ruffy.driver.display.menu;
/**
* Created by fishermen21 on 22.05.17.
*/
public class MenuTime {
private final int hour;
private final int minute;
public MenuTime(int hour, int minute)
{
this.hour = hour;
this.minute = minute;
}
public MenuTime(String value) {
String[] p = value.split(":");
hour = Integer.parseInt(p[0]);
minute = Integer.parseInt(p[1]);
}
public int getHour() {
return hour;
}
public int getMinute() {
return minute;
}
@Override
public String toString() {
return hour+":"+String.format("%02d",minute);
}
}

View file

@ -0,0 +1,53 @@
<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=".plugins.PumpCombo.ComboFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/textView3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center_horizontal"
android:layout_marginTop="5dp"
android:text="Alerts"
android:textStyle="bold" />
<View
android:id="@+id/profileview_datedelimiter"
android:layout_width="match_parent"
android:layout_height="2dip"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="5dp"
android:background="@color/listdelimiter" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/combo_error_history_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="textStart"
android:padding="10dp"
android:gravity="start"/>
</LinearLayout>
</ScrollView>
</LinearLayout>
</FrameLayout>

View file

@ -0,0 +1,54 @@
<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=".plugins.PumpCombo.ComboFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/textView3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center_horizontal"
android:layout_marginTop="5dp"
android:text="@string/combo_tdds"
android:textStyle="bold" />
<View
android:id="@+id/profileview_datedelimiter"
android:layout_width="match_parent"
android:layout_height="2dip"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="5dp"
android:background="@color/listdelimiter" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/combo_tdd_history_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start"
android:padding="10dp"
android:textAlignment="textStart" />
</LinearLayout>
</ScrollView>
</LinearLayout>
</FrameLayout>

View file

@ -0,0 +1,548 @@
<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=".plugins.PumpCombo.ComboFragment">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1.5"
android:gravity="end"
android:paddingRight="5dp"
android:text="@string/combo_pump_state_label"
android:textSize="14sp" />
<TextView
android:layout_width="5dp"
android:layout_height="wrap_content"
android:layout_weight="0"
android:gravity="center_horizontal"
android:paddingEnd="2dp"
android:paddingStart="2dp"
android:text=":"
android:textSize="14sp" />
<TextView
android:id="@+id/combo_state"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:paddingLeft="5dp"
android:textColor="@android:color/white"
android:textSize="14sp" />
</LinearLayout>
<View
android:layout_width="fill_parent"
android:layout_height="2dip"
android:layout_marginBottom="5dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="5dp"
android:background="@color/listdelimiter" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1.5"
android:gravity="end"
android:paddingRight="5dp"
android:text="@string/combo_pump_activity_label"
android:textSize="14sp" />
<TextView
android:layout_width="5dp"
android:layout_height="wrap_content"
android:layout_weight="0"
android:gravity="center_horizontal"
android:paddingEnd="2dp"
android:paddingStart="2dp"
android:text=":"
android:textSize="14sp" />
<com.joanzapata.iconify.widget.IconTextView
android:id="@+id/combo_activity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:paddingLeft="5dp"
android:textColor="@android:color/white"
android:textSize="14sp" />
</LinearLayout>
<View
android:layout_width="fill_parent"
android:layout_height="2dip"
android:layout_marginBottom="5dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="5dp"
android:background="@color/listdelimiter" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1.5"
android:gravity="end"
android:paddingRight="5dp"
android:text="@string/pump_battery_label"
android:textSize="14sp" />
<TextView
android:layout_width="5dp"
android:layout_height="wrap_content"
android:layout_weight="0"
android:gravity="center_horizontal"
android:paddingEnd="2dp"
android:paddingStart="2dp"
android:text=":"
android:textSize="14sp" />
<com.joanzapata.iconify.widget.IconTextView
android:id="@+id/combo_pumpstate_battery"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:paddingLeft="5dp"
android:textColor="@android:color/white"
android:text=""
android:textSize="14sp" />
</LinearLayout>
<View
android:layout_width="fill_parent"
android:layout_height="2dip"
android:layout_marginBottom="5dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="5dp"
android:background="@color/listdelimiter" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1.5"
android:gravity="end"
android:paddingRight="5dp"
android:text="@string/pump_reservoir_label"
android:textSize="14sp" />
<TextView
android:layout_width="5dp"
android:layout_height="wrap_content"
android:layout_weight="0"
android:gravity="center_horizontal"
android:paddingEnd="2dp"
android:paddingStart="2dp"
android:text=":"
android:textSize="14sp" />
<TextView
android:id="@+id/combo_insulinstate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:paddingLeft="5dp"
android:textColor="@android:color/white"
android:textSize="14sp" />
</LinearLayout>
<View
android:layout_width="fill_parent"
android:layout_height="2dip"
android:layout_marginBottom="5dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="5dp"
android:background="@color/listdelimiter" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1.5"
android:gravity="end"
android:paddingRight="5dp"
android:text="@string/pump_lastconnection_label"
android:textSize="14sp" />
<TextView
android:layout_width="5dp"
android:layout_height="wrap_content"
android:layout_weight="0"
android:gravity="center_horizontal"
android:paddingEnd="2dp"
android:paddingStart="2dp"
android:text=":"
android:textSize="14sp" />
<TextView
android:id="@+id/combo_lastconnection"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:paddingLeft="5dp"
android:textColor="@android:color/white"
android:textSize="14sp" />
</LinearLayout>
<View
android:layout_width="fill_parent"
android:layout_height="2dip"
android:layout_marginBottom="5dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="5dp"
android:background="@color/listdelimiter" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1.5"
android:gravity="end"
android:paddingRight="5dp"
android:text="@string/pump_lastbolus_label"
android:textSize="14sp" />
<TextView
android:layout_width="5dp"
android:layout_height="wrap_content"
android:layout_weight="0"
android:gravity="center_horizontal"
android:paddingEnd="2dp"
android:paddingStart="2dp"
android:text=":"
android:textSize="14sp" />
<TextView
android:id="@+id/combo_last_bolus"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:paddingLeft="5dp"
android:textColor="@android:color/white"
android:textSize="14sp" />
</LinearLayout>
<View
android:layout_width="fill_parent"
android:layout_height="2dip"
android:layout_marginBottom="5dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="5dp"
android:background="@color/listdelimiter" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1.5"
android:gravity="end"
android:paddingRight="5dp"
android:text="@string/pump_basebasalrate_label"
android:textSize="14sp" />
<TextView
android:layout_width="5dp"
android:layout_height="wrap_content"
android:layout_weight="0"
android:gravity="center_horizontal"
android:paddingEnd="2dp"
android:paddingStart="2dp"
android:text=":"
android:textSize="14sp" />
<TextView
android:id="@+id/combo_base_basal_rate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:paddingLeft="5dp"
android:textColor="@android:color/white"
android:textSize="14sp" />
</LinearLayout>
<View
android:layout_width="fill_parent"
android:layout_height="2dip"
android:layout_marginBottom="5dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="5dp"
android:background="@color/listdelimiter" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1.5"
android:gravity="end"
android:paddingRight="5dp"
android:text="@string/pump_tempbasal_label"
android:textSize="14sp" />
<TextView
android:layout_width="5dp"
android:layout_height="wrap_content"
android:layout_weight="0"
android:gravity="center_horizontal"
android:paddingEnd="2dp"
android:paddingStart="2dp"
android:text=":"
android:textSize="14sp" />
<TextView
android:id="@+id/combo_temp_basal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:paddingLeft="5dp"
android:textColor="@android:color/white"
android:textSize="14sp" />
</LinearLayout>
<View
android:layout_width="fill_parent"
android:layout_height="2dip"
android:layout_marginBottom="5dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="5dp"
android:background="@color/listdelimiter" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1.5"
android:gravity="end"
android:paddingRight="5dp"
android:text="@string/combo_bolus_count"
android:textSize="14sp" />
<TextView
android:layout_width="5dp"
android:layout_height="wrap_content"
android:layout_weight="0"
android:gravity="center_horizontal"
android:paddingEnd="2dp"
android:paddingStart="2dp"
android:text=":"
android:textSize="14sp" />
<TextView
android:id="@+id/combo_bolus_count"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:paddingLeft="5dp"
android:textColor="@android:color/white"
android:textSize="14sp" />
</LinearLayout>
<View
android:layout_width="fill_parent"
android:layout_height="2dip"
android:layout_marginBottom="5dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="5dp"
android:background="@color/listdelimiter" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1.5"
android:gravity="end"
android:paddingRight="5dp"
android:text="@string/combo_tbr_count"
android:textSize="14sp" />
<TextView
android:layout_width="5dp"
android:layout_height="wrap_content"
android:layout_weight="0"
android:gravity="center_horizontal"
android:paddingEnd="2dp"
android:paddingStart="2dp"
android:text=":"
android:textSize="14sp" />
<TextView
android:id="@+id/combo_tbr_count"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:paddingLeft="5dp"
android:textColor="@android:color/white"
android:textSize="14sp" />
</LinearLayout>
<View
android:layout_width="fill_parent"
android:layout_height="2dip"
android:layout_marginBottom="5dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="5dp"
android:background="@color/listdelimiter" />
</LinearLayout>
</ScrollView>
<LinearLayout
android:id="@+id/combo_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:paddingRight="4dp"
android:orientation="vertical">
<LinearLayout
android:id="@+id/combo_buttons_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<info.nightscout.utils.SingleClickButton
android:id="@+id/combo_refresh_button"
style="@style/ButtonSmallFontStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0.5"
android:layout_marginRight="-4dp"
android:drawableTop="@drawable/icon_actions_refill"
android:paddingLeft="0dp"
android:paddingRight="0dp"
android:text="@string/combo_refresh" />
<info.nightscout.utils.SingleClickButton
android:id="@+id/combo_alerts_button"
style="@style/ButtonSmallFontStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0.5"
android:layout_marginRight="-4dp"
android:drawableTop="@drawable/icon_cp_announcement"
android:paddingLeft="0dp"
android:paddingRight="0dp"
android:visibility="gone"
android:text="@string/combo_pump_alerts" />
<info.nightscout.utils.SingleClickButton
android:id="@+id/combo_tdds_button"
style="@style/ButtonSmallFontStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0.5"
android:layout_marginRight="-4dp"
android:drawableTop="@drawable/icon_danarstats"
android:paddingLeft="0dp"
android:paddingRight="0dp"
android:visibility="gone"
android:text="@string/combo_tdds" />
<info.nightscout.utils.SingleClickButton
android:id="@+id/combo_full_history_button"
style="@style/ButtonSmallFontStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0.5"
android:layout_marginRight="-4dp"
android:drawableTop="@drawable/icon_danarhistory"
android:paddingLeft="0dp"
android:paddingRight="0dp"
android:visibility="gone"
android:text="@string/combo_history" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
</FrameLayout>

View file

@ -12,7 +12,6 @@
<include
layout="@layout/profileviewer_fragment"
layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>

Some files were not shown because too many files have changed in this diff Show more