Merge branch 'dev' into remove-pre-bolus
This commit is contained in:
commit
da014daec8
130 changed files with 6658 additions and 494 deletions
|
@ -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}"
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package org.monkey.d.ruffy.ruffy.driver.display;
|
||||
|
||||
parcelable Menu;
|
|
@ -0,0 +1 @@
|
|||
//b916a900c0899ef58ad58c7427d1c30d3c8731f4
|
|
@ -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">
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package info.nightscout.androidaps;
|
||||
|
||||
import com.j256.ormlite.stmt.query.In;
|
||||
|
||||
/**
|
||||
* Created by mike on 07.06.2016.
|
||||
*/
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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() +
|
||||
|
|
|
@ -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() {
|
||||
return getValuesList(ic_v, null, new DecimalFormat("0.0"), " g/U");
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,19 +169,24 @@ public class NewNSTreatmentDialog extends DialogFragment implements View.OnClick
|
|||
|
||||
// profile
|
||||
profile = MainApp.getConfigBuilder().getProfile();
|
||||
profileStore = ConfigBuilderPlugin.getActiveProfileInterface().getProfile();
|
||||
ArrayList<CharSequence> profileList;
|
||||
units = profile != null ? profile.getUnits() : Constants.MGDL;
|
||||
profileList = profileStore.getProfileList();
|
||||
ArrayAdapter<CharSequence> adapter = new ArrayAdapter<CharSequence>(getContext(),
|
||||
R.layout.spinner_centered, profileList);
|
||||
profileSpinner.setAdapter(adapter);
|
||||
// set selected to actual profile
|
||||
for (int p = 0; p < profileList.size(); p++) {
|
||||
if (profileList.get(p).equals(MainApp.getConfigBuilder().getProfileName(false)))
|
||||
profileSpinner.setSelection(p);
|
||||
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<>(getContext(),
|
||||
R.layout.spinner_centered, profileList);
|
||||
profileSpinner.setAdapter(adapter);
|
||||
// set selected to actual profile
|
||||
for (int p = 0; p < profileList.size(); p++) {
|
||||
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;
|
||||
|
|
|
@ -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,33 +762,21 @@ 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) {
|
||||
return customized ? profileSwitch.getCustomizedName() : profileSwitch.profileName;
|
||||
} else {
|
||||
Profile profile = activeProfile.getProfile().getSpecificProfile(profileSwitch.profileName);
|
||||
if (profile != null)
|
||||
return profileSwitch.profileName;
|
||||
}
|
||||
ProfileSwitch profileSwitch = getProfileSwitchFromHistory(time);
|
||||
if (profileSwitch != null) {
|
||||
if (profileSwitch.profileJson != null) {
|
||||
return customized ? profileSwitch.getCustomizedName() : profileSwitch.profileName;
|
||||
} else {
|
||||
Profile profile = activeProfile.getProfile().getSpecificProfile(profileSwitch.profileName);
|
||||
if (profile != null)
|
||||
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,41 +796,16 @@ 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) {
|
||||
return profileSwitch.getProfileObject();
|
||||
} else if (activeProfile.getProfile() != null) {
|
||||
Profile profile = activeProfile.getProfile().getSpecificProfile(profileSwitch.profileName);
|
||||
if (profile != null)
|
||||
return profile;
|
||||
}
|
||||
ProfileSwitch profileSwitch = getProfileSwitchFromHistory(time);
|
||||
if (profileSwitch != null) {
|
||||
if (profileSwitch.profileJson != null) {
|
||||
return profileSwitch.getProfileObject();
|
||||
} else if (activeProfile.getProfile() != null) {
|
||||
Profile profile = activeProfile.getProfile().getSpecificProfile(profileSwitch.profileName);
|
||||
if (profile != null)
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() + ")");
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -142,14 +142,16 @@ public class WizardDialog extends DialogFragment implements OnClickListener, Com
|
|||
|
||||
@Subscribe
|
||||
public void onStatusEvent(final EventNewBG e) {
|
||||
Activity activity = getActivity();
|
||||
if (activity != null)
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
calculateInsulin();
|
||||
}
|
||||
});
|
||||
if (e.isFromActiveBgSource && e.isNew && e.isCurrent()) {
|
||||
Activity activity = getActivity();
|
||||
if (activity != null)
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
calculateInsulin();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
|
@ -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();
|
||||
|
|
|
@ -187,7 +187,8 @@ public class OverviewFragment extends Fragment implements View.OnClickListener,
|
|||
|
||||
final Object updateSync = new Object();
|
||||
|
||||
public enum CHARTTYPE {PRE,BAS, IOB, COB, DEV, SEN};
|
||||
public enum CHARTTYPE {PRE, BAS, IOB, COB, DEV, SEN};
|
||||
|
||||
private static final ScheduledExecutorService worker = Executors.newSingleThreadScheduledExecutor();
|
||||
private static ScheduledFuture<?> scheduledUpdate = null;
|
||||
|
||||
|
@ -344,7 +345,7 @@ public class OverviewFragment extends Fragment implements View.OnClickListener,
|
|||
SpannableString s;
|
||||
PopupMenu popup = new PopupMenu(v.getContext(), v);
|
||||
|
||||
if(predictionsAvailable) {
|
||||
if (predictionsAvailable) {
|
||||
item = popup.getMenu().add(Menu.NONE, CHARTTYPE.PRE.ordinal(), Menu.NONE, "Predictions");
|
||||
title = item.getTitle();
|
||||
s = new SpannableString(title);
|
||||
|
@ -464,7 +465,9 @@ 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));
|
||||
menu.add(MainApp.sResources.getString(R.string.careportal_profileswitch));
|
||||
if (MainApp.getConfigBuilder().getActiveProfileInterface().getProfile() != null) {
|
||||
menu.add(MainApp.sResources.getString(R.string.careportal_profileswitch));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
@ -203,7 +203,7 @@ public class Notification {
|
|||
//log.debug("OpenAPS Alerts enabled: "+openAPSEnabledAlerts);
|
||||
// if no thresshold from Ns get it loccally
|
||||
if(threshold == null) threshold = SP.getDouble(R.string.key_nsalarm_staledatavalue,15D);
|
||||
// No threshold of OpenAPS Alarm so using the one for BG
|
||||
// No threshold of OpenAPS Alarm so using the one for BG
|
||||
// Added OpenAPSEnabledAlerts to alarm check
|
||||
if((bgReadingAgoMin > threshold && SP.getBoolean(R.string.key_nsalarm_staledata, false))||(bgReadingAgoMin > threshold && openAPSEnabledAlerts)){
|
||||
return true;
|
||||
|
|
|
@ -255,7 +255,8 @@ public class PersistentNotificationPlugin implements PluginBase {
|
|||
|
||||
@Subscribe
|
||||
public void onStatusEvent(final EventNewBG ev) {
|
||||
updateNotification();
|
||||
if (ev.isFromActiveBgSource && ev.isNew && ev.isCurrent())
|
||||
updateNotification();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
|
|
|
@ -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("");
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package info.nightscout.androidaps.plugins.PumpCombo.events;
|
||||
|
||||
/**
|
||||
* Created by mike on 24.05.2017.
|
||||
*/
|
||||
|
||||
public class EventComboPumpUpdateGUI {
|
||||
}
|
|
@ -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" +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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{}";
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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{}";
|
||||
}
|
||||
}
|
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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{}";
|
||||
}
|
||||
}
|
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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() +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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) + ")") : "") +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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,7 +182,8 @@ public class WearPlugin implements PluginBase {
|
|||
|
||||
@Subscribe
|
||||
public void onStatusEvent(final EventNewBG ev) {
|
||||
sendDataToWatch(true, true, true);
|
||||
if (ev.isFromActiveBgSource)
|
||||
sendDataToWatch(true, true, true);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
|
|
|
@ -235,7 +235,8 @@ public class StatuslinePlugin implements PluginBase {
|
|||
|
||||
@Subscribe
|
||||
public void onStatusEvent(final EventNewBG ev) {
|
||||
sendStatus();
|
||||
if (ev.isFromActiveBgSource && ev.isNew && ev.isCurrent())
|
||||
sendStatus();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<>();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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)+".";
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
53
app/src/main/res/layout/combo_alert_history_fragment.xml
Normal file
53
app/src/main/res/layout/combo_alert_history_fragment.xml
Normal 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>
|
54
app/src/main/res/layout/combo_tdd_history_fragment.xml
Normal file
54
app/src/main/res/layout/combo_tdd_history_fragment.xml
Normal 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>
|
548
app/src/main/res/layout/combopump_fragment.xml
Normal file
548
app/src/main/res/layout/combopump_fragment.xml
Normal 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>
|
|
@ -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
Loading…
Reference in a new issue