Merge pull request #775 from jotomo/dev-merge

Merge remaining development combonents
This commit is contained in:
Milos Kozak 2018-03-18 20:45:00 +01:00 committed by GitHub
commit 3f04d10f79
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
90 changed files with 6076 additions and 83 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -54,6 +54,7 @@ import info.nightscout.androidaps.plugins.Persistentnotification.PersistentNotif
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;
@ -78,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;
@ -123,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();
@ -141,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 && engineeringMode) 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());
@ -202,12 +211,6 @@ public class MainApp extends Application {
}
}).start();
String extFilesDir = this.getLogDirectory();
File engineeringModeSemaphore = new File(extFilesDir,"engineering_mode");
engineeringMode = engineeringModeSemaphore.exists() && engineeringModeSemaphore.isFile();
devBranch = BuildConfig.VERSION.contains("dev");
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));

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1241,9 +1241,7 @@ public class OverviewFragment extends Fragment implements View.OnClickListener,
flag &= ~Paint.STRIKE_THRU_TEXT_FLAG;
bgView.setPaintFlags(flag);
Long agoMsec = System.currentTimeMillis() - lastBG.date;
int agoMin = (int) (agoMsec / 60d / 1000d);
timeAgoView.setText(String.format(MainApp.sResources.getString(R.string.minago), agoMin));
timeAgoView.setText(DateUtil.minAgo(lastBG.date));
// iob
MainApp.getConfigBuilder().updateTotalIOBTreatments();

View file

@ -50,13 +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 NOT_ENG_MODE_OR_RELEASE = 32;
public static final int PROFILE_SWITCH_MISSING = 32;
public static final int NOT_ENG_MODE_OR_RELEASE = 33;
public int id;
public Date date;
@ -201,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;

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,6 +2,7 @@ package info.nightscout.androidaps.queue;
import android.content.Context;
import android.content.Intent;
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.text.Html;
import android.text.Spanned;
@ -81,7 +82,7 @@ public class CommandQueue {
return new PumpEnactResult().success(false).enacted(false).comment(MainApp.sResources.getString(R.string.executingrightnow));
}
public boolean isRunning(Command.CommandType type) {
private boolean isRunning(Command.CommandType type) {
if (performing != null && performing.commandType == type)
return true;
return false;
@ -293,6 +294,21 @@ 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));
@ -305,8 +321,8 @@ public class CommandQueue {
Profile.BasalValue[] basalValues = profile.getBasalValues();
PumpInterface pump = ConfigBuilderPlugin.getActivePump();
for (int index = 0; index < basalValues.length; index++) {
if (basalValues[index].value < pump.getPumpDescription().basalMinimumRate) {
for (Profile.BasalValue basalValue : basalValues) {
if (basalValue.value < pump.getPumpDescription().basalMinimumRate) {
Notification notification = new Notification(Notification.BASAL_VALUE_BELOW_MINIMUM, MainApp.sResources.getString(R.string.basalvaluebelowminimum), Notification.URGENT);
MainApp.bus().post(new EventNewNotification(notification));
if (callback != null)

View file

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

View file

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

View file

@ -15,6 +15,8 @@ import info.nightscout.androidaps.interfaces.PumpInterface;
import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin;
import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification;
import info.nightscout.androidaps.plugins.Overview.notifications.Notification;
import info.nightscout.androidaps.receivers.KeepAliveReceiver;
import info.nightscout.utils.NSUpload;
/**
* Created by adrian on 17/12/17.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -11,6 +11,8 @@
android:id="@+id/overview_bolusprogress_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:layout_gravity="center_horizontal" />
<TextView

View file

@ -11,6 +11,8 @@
android:id="@+id/overview_error_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:layout_gravity="center_horizontal" />
<Button

View file

@ -223,7 +223,6 @@
<string name="danar_iob_label">IOB на помпата</string>
<string name="danar_dailyunits">Инсулин за деня</string>
<string name="pump_lastbolus_label">Последен болус:</string>
<string name="hoursago">ч по-рано</string>
<string name="danar_invalidinput">Грешни входящи данни</string>
<string name="danar_valuenotsetproperly">Неправилна стойност</string>
<string name="reloadprofile">Презареди профил</string>

View file

@ -731,7 +731,7 @@
<string name="combo_pump_activity_label">Aktivita</string>
<string name="combo_no_pump_connection" formatted="false">Žádné spojení %d min</string>
<string name="combo_tbr_remaining" formatted="false">%d%% (%d min zbývá)</string>
<string name="combo_pump_action_initializing">Inicializace</string>
<string name="combo_pump_state_initializing">Inicializace</string>
<string name="combo_pump_state_disconnected">Odpojeno</string>
<string name="combo_pump_state_suspended_due_to_error">Vypnuto díky chybě</string>
<string name="combo_pump_state_suspended_by_user">Vypnuto uživatelem</string>

View file

@ -221,7 +221,6 @@
<string name="end_user_license_agreement_i_understand">Ich verstehe und stimme zu</string>
<string name="end_user_license_agreement_text">DAS PROGRAMM DARF NICHT FÜR MEDIZINISCHE ENTSCHEIDUNGEN BENUTZT WERDEN. ES GIBT IN DIESEM PROJEKT KEINE GEWÄHRLEISTUNG ODER GARANTIERTE UNTERSTÜTZUNG IN IRGENDEINER ART. WENN DU DICH ENTSCHEIDEST ES ZU NUTZEN, HÄNGT DIE QUALITÄT UND LEISTUNGSFÄHIGKEIT DIESES PROJEKTES VON DIR SELBST AB. ES WIRD \"WIE BESEHEN\" ZUR VERFÜGUNG GESTELLT. SOLLTE SICH DAS PROGRAMM ALS FEHLERHAFT ERWEISEN, ÜBERNEHMEN SIE DIE KOSTEN ALLER NOTWENDIGEN KRANKHEITSKOSTEN, SERVICELEISTUNGEN, REPARATUREN ODER KORREKTUREN.</string>
<string name="failedupdatebasalprofile">Fehler beim Aktualisieren der Basalrate</string>
<string name="hoursago">"h her "</string>
<string name="smscommunicator">SMS-Kommunikator</string>
<string name="smscommunicator_allowednumbers">Erlaubte Telefonnummern</string>
<string name="waitingforpumpresult">Auf Pumpenergebnis warten</string>
@ -714,7 +713,7 @@
<string name="combo_pump_action_bolusing">Bolus (%.1f IE) wird abgegeben</string>
<string name="alert_dialog_storage_permission_text">Bitte starte dein Telefon neu oder starte AndroidAPS in den System-Einstellungen neu. Andernfalls wird AndroidAPS nicht protokolliert (wichtig zum Nachverfolgen und Verifizieren, dass der Algorithmus korrekt funktioniert)</string>
<string name="pump_tempbasal_label">TBR</string>
<string name="bolus_frequency_exceeded">Ein gleich großer Bolus wurde in der letzten Minute angefordert. Dies ist nicht zulässig, um ungewollte Doppelboli zu verhindern und vor eventuellen Bugs zu schützen.</string>
<string name="bolus_frequency_exceeded">Ein gleich großer Bolus wurde in den letzten zwei Minuten angefordert. Dies ist nicht zulässig, um ungewollte Doppelboli zu verhindern und vor eventuellen Bugs zu schützen.</string>
<string name="combo_activity_reading_pump_history">Historie wird gelesen</string>
<string name="combo_activity_setting_basal_profile">Basalratenprofil wird aktualisiert</string>
<string name="combo_error_bolus_recovery_progress">Verbindung wird wieder hergestellt</string>
@ -723,7 +722,7 @@
<string name="combo_error_partial_bolus_delivered">Wegen eines Fehlers wurden nur %.2f IE von den angeforderten %.2f IE abgegeben. Bitte prüfe den abgegebenen Bolus auf der Pumpe.</string>
<string name="combo_history">Historie</string>
<string name="combo_pump_action_refreshing">Status wird aktualisiert</string>
<string name="combo_pump_action_initializing">Die Pumpe wird initialisiert</string>
<string name="combo_pump_state_initializing">Die Pumpe wird initialisiert</string>
<string name="combo_pump_connected_now">Jetzt</string>
<string name="combo_pump_never_connected">Nie</string>
<string name="combo_pump_tbr_cancelled_warrning">Der Alarm \"TBR ABBRUCH\" wurde bestätigt</string>
@ -741,7 +740,7 @@
<string name="combo_is_in_error_state">Die Pumpe zeigt einen Fehler an E%d: %s</string>
<string name="combo_force_disabled_notification">Unsichere Verwendung: In der Pumpe ist nicht das erste Basalratenprofil gewählt. Der Loop wird deaktiviert bis dies korrigiert ist.</string>
<string name="combo_low_suspend_forced_notification">Unsichere Verwendung: Ein erweiterter oder Multiwave-Bolus ist aktiv. Der Loop wird für die nächsten 6 Stunden kein zusätzliches Insulin abgeben.</string>
<string name="combo_no_alert_data_note">Um die Fehlerhistorie der Pumpe zu lesen, drücke lange auf ALARME.\nWARNUNG: Es gibt einen bekannten Fehler in der Pumpe, der dazu führt, dass die Pumpe nach dieser Aktion erst wieder Verbindungen annimmt, wenn auf der Pumpe selbst eine Taste gedrückt wird. Aus diesem Grund sollte diese Aktion nicht durchgeführt werden.</string>
<string name="combo_no_alert_data_note">Um die Fehlerhistorie der Pumpe zu lesen, drücke lange auf ALARME.</string>
<string name="combo_notification_check_time_date">Bitte aktualisiere die Uhrzeit der Pumpe</string>
<string name="combo_no_tdd_data_note">Um die TDD-Statistik der Pumpe zu lesen, drücken Sie den TDDS Knopf lange.\nWARNUNG: Es gibt einen bekannten Fehler in der Pumpe der dazu führt, dass die Pumpe nach dieser Aktion erst wieder Verbindungen annimmt, wenn auf der Pumpe selbst ein Konpf gedrückt wird. Aus diesem Grund sollte diese Aktion nicht durchgeführt werden.</string>
<string name="combo_read_full_history_warning">Dies wird den gesamten Speicher und den Status der Pumpe auslesen sowie alle Einträge in „Meine Daten“ und die Basalrate. Boli und TBR werden unter Behandlungen gespeichert, sofern sie nicht bereits vorhanden sind. Dies kann zu doppelten Einträgen führen, wenn die Uhrzeit der Pumpe abweicht. Das Auslesen des Speichers ist normaler Weise für das Loopen unnötig und nur für besondere Umstände vorgesehen. Wenn Du es dennoch tun willst, drücke noch einmal länger den Button. ACHTUNG: Dies kann einen Fehler auslösen, der dazu führt, dass die Pumpe keine Verbindungsversuche mehr akzeptiert. Erst die Betätigung einer Taste an der Pumpe beendet diesen Zustand. Nach Möglichkeit sollte daher das Auslesen vermieden werden.</string>
@ -765,4 +764,25 @@
<string name="openapsama_bolussnooze_dia_divisor_summary">Standarwert: 2\nBolus snooze (\"Bolus-Schlummer\") bremst den Loop nach einem Mahleiten-Bolus, damit dieser nicht mit niedrigen TBR reagiert, wenn Du gerade gegessen hast. Beispiel: Der Standardwert 2 bewirkt, dass bei einem 3 Stunden DIA der Bolus snooze während 1.5 Stunden nach dem Bolus linear ausläuft (3 h Dia / 2 = 1.5 h Bolus snooze).</string>
<string name="openapsama_min_5m_carbimpact_summary">Standardwert: 3.0\nDies ist eine Einstellung für die Standard-Kohlenhydrat-Absorptionswirkung pro 5 Minuten. Der Standardwert ist 3mg/dl/5min. Dies wirkt sich darauf aus, wie schnell der COB-Wert fällt und wieviel KH-Absorption bei der Berechnung des vorhergesagten BZ angenommen wird, wenn der BZ stärker als erwartet fällt oder nicht so stark wie erwartet steigt.</string>
<string name="openapsama_link_to_preferncejson_doc_txt">Achtung! Normalerweise musst Du diese Werte nicht ändern. Bitte KLICKE HIER und LESE den Text. Verändere Werte erst, wenn Du den Inhalt des Textes verstanden hast.</string>
<string name="combo_actvity_reading_basal_profile">Basalratenprofil wird gelesen</string>
<string name="pump_basebasalrate">%.2f IE/h</string>
<string name="combo_read_full_history_info">Drücke den Button lange, um die gesamte Historie und das Basal-Profil der Pumpe auszulesen. Dies ist eigentlich unnötig, weil die Historie regelmäßig gelesen wird. Hilfreich kann dies jedoch sein, wenn Datum und Zeit grundlegend verändert wurden oder die Pumpe ausgetauscht wurde.</string>
<string name="combo_error_no_connection_no_bolus_delivered">Keine Verbindung zur Pumpe: Es wurde kein Bolus abgegeben.</string>
<string name="extendedbolusdeliveryerror">Fehler bei der Abgabe eines verlängerten Bolus</string>
<string name="combo_bolus_rejected_due_to_pump_history_change">Nach der Berechnung des Bolus hat sich die Pumpenhistorie geändert. Daher wurde kein Bolus abgegeben. Bitte prüfe, ob überhaupt noch ein Bolus benötigt wird. Wenn die gleiche Bolusmenge erforderlich ist, warte zwei Minuten ab, denn es werden aus Sicherheitsgründen keine gleich großen Boli abgegeben, wenn sie innerhalb von zwei Minuten angefordert wurden (unabhängig davon, ob sie verabreicht wurden oder nicht).</string>
<string name="combo_error_updating_treatment_record">Der Bolus wurde erfolgreich abgegeben, aber nicht als Behandlungseintrag gespeichert. Dies kann passieren, wenn zwei kleine, gleich große Boli innerhalb von zwei Minuten verabreicht werden. Bitte überprüfe die Pumpenhistorie und Behandlungseinträge. Verwende das Careportal, um fehlende Einträge hinzuzufügen. Stelle sicher, dass keine Einträge für genau dieselbe Minute und dieselbe Menge hinzugefügt werden.</string>
<string name="combo_activity_checking_pump_state">Status wird aktualisiert</string>
<string name="combo_warning_pump_basal_rate_changed">Die Basalrate in der Pumpe hat sich geändert und wird aktualisiert</string>
<string name="combo_error_failure_reading_changed_basal_rate">Das Einlesen der geänderten Basalrate in der Pumpe schlug fehl.</string>
<string name="combo_activity_checking_for_history_changes">Änderungen der Historie werden gesucht</string>
<string name="combo_error_multiple_boluses_with_identical_timestamp">Der Import mehrerer Boli der gleichen Menge, abgegeben in der gleichen Minute, ist gescheitert: Nur ein Datensatz konnte den Behandlungen hinzugefügt werden. Bitte überprüfe die Pumpe und verwende das Careportal, um fehlende Einträge hinzuzufügen. Stelle sicher, dass keine Einträge für genau dieselbe Minute mit derselben Menge hinzugefügt werden.</string>
<string name="mute">Alarm stoppen</string>
<string name="bolusstopped">Bolus gestoppt</string>
<string name="bolusstopping">Bolus wird gestoppt</string>
<string name="basalprofilenotaligned">Basalraten beginnen nicht zur vollen Stunde: %s</string>
<string name="zerovalueinprofile">Ungültiges Profil: %s</string>
<string name="hoursago">vor %.1f h</string>
<string name="combo_high_temp_rejected_due_to_pump_history_changes">Es wurde keine hohe TBR gesetzt, da nach der Berechnung Boluseinträge in der Pumpenhistorik gefunden wurden.</string>
<string name="combo_check_date">Der letzte Bolus liegt mehr als 24 Stunden zurück oder liegt in der Zukunft. Prüfe bitte das Datum auf der Pumpe.</string>
<string name="combo_suspious_bolus_time">Zeit/Datum des abgegebenen Boluses auf der Pumpe erscheint falsch, IOB ist wahrscheinlich nicht korrekt. Bitte prüfe Zeit/Datum der Pumpe.</string>
</resources>

View file

@ -219,7 +219,6 @@
<string name="danar_iob_label">IOB αντλίας</string>
<string name="danar_dailyunits">"Μονάδες ανά ημέρα "</string>
<string name="pump_lastbolus_label">Τελευταίο Bolus:</string>
<string name="hoursago">ώρες πριν</string>
<string name="danar_invalidinput">Μη έγκυρα δεδομένα</string>
<string name="danar_valuenotsetproperly">Η τιμή δεν μπήκε σωστά</string>
<string name="reloadprofile">Ξαναφορτώστε το προφίλ</string>

View file

@ -37,7 +37,7 @@
<string name="treatments_newtreatment_insulinamount_label">Insulina [U]</string>
<string name="treatments_newtreatment_carbsamount_label">Carbohidratos [g]</string>
<string name="treatments_wizard_bg_label">Glucosa</string>
<string name="treatments_wizard_carbs_label">Carbohidratos</string>
<string name="treatments_wizard_carbs_label">Hidratos Carbono</string>
<string name="treatments_wizard_correction_label">Corrección</string>
<string name="treatments_wizard_unit_label">U</string>
<string name="treatments_wizard_bolusiob_label">Bolo IOB</string>
@ -51,7 +51,7 @@
<string name="pump_reservoir_label">Depósito:</string>
<string name="virtualpump_resultok">OK</string>
<string name="virtualpump_sqlerror">Error de SQL</string>
<string name="openapsma_lastrun_label">Última acción</string>
<string name="openapsma_lastrun_label">Última ejecución</string>
<string name="openapsma_inputparameters_label">Parámetros de entrada</string>
<string name="openapsma_glucosestatus_label">Estado de glucosa</string>
<string name="openapsma_currenttemp_label">Basal temporal actual</string>
@ -59,7 +59,7 @@
<string name="openapsma_profile_label">Perfil</string>
<string name="openapsma_mealdata_label">Datos de comidas</string>
<string name="result">Resultado</string>
<string name="openapsma_noglucosedata">No hay datos disponibles de glucosa</string>
<string name="openapsma_noglucosedata">No hay disponibles datos de glucosa</string>
<string name="openapsma_noprofile">Sin perfil disponible</string>
<string name="openapsma_nopump">No se dispone de bomba</string>
<string name="nochangerequested">Ninguna acción requerida</string>
@ -81,7 +81,7 @@
<string name="careportal">Careportal</string>
<string name="configbuilder_pump">Bomba</string>
<string name="configbuilder_treatments">Tratamientos</string>
<string name="configbuilder_tempbasals">Basales temporales</string>
<string name="configbuilder_tempbasals">Temp Basales</string>
<string name="configbuilder_profile">Perfil</string>
<string name="configbuilder_aps">APS</string>
<string name="configbuilder_general">General</string>
@ -120,11 +120,11 @@
<string name="closedloop">Lazo cerrado</string>
<string name="openloop">Lazo abierto</string>
<string name="openloop_newsuggestion">Nueva propuesta disponible</string>
<string name="unsupportedclientver">Versión de NSClient no apoyada</string>
<string name="unsupportedclientver">Versión de NSClient no soportada</string>
<string name="nsclientnotinstalled">NSClient no instalado. Registro perdido!</string>
<string name="objectives_bgavailableinns">BG disponible en NS</string>
<string name="objectives_pumpstatusavailableinns">Estado de la bomba disponible en NS</string>
<string name="objectives_manualenacts">Acceptado manualmente</string>
<string name="objectives_manualenacts">Aceptados</string>
<string name="loopdisabled">LAZO DESACTIVADO POR RESTRICCIONES</string>
<string name="cs_lang">Czech</string>
<string name="en_lang">English</string>
@ -136,7 +136,7 @@
<string name="careportal_note">NOTA</string>
<string name="careportal_question">Pregunta</string>
<string name="careportal_exercise">Ejercicio</string>
<string name="careportal_pumpsitechange">Cambio Lugar Cánula</string>
<string name="careportal_pumpsitechange">Cambio Lugar Bomba</string>
<string name="careportal_cgmsensorinsert">Insertar sensor</string>
<string name="careportal_cgmsensorstart">Iniciar sensor</string>
<string name="careportal_insulincartridgechange">Cambio Cartucho insulina</string>
@ -147,13 +147,13 @@
<string name="careportal_combobolus">Combo bolo</string>
<string name="careportal_tempbasalstart">Basal Temporal Inicio</string>
<string name="careportal_tempbasalend">Basal Temporal Fin</string>
<string name="careportal_carbscorrection">Corrección Carbohidratos</string>
<string name="careportal_carbscorrection">Hidratos Carbono Corrección</string>
<string name="careportal_openapsoffline">OpenAPS Offline</string>
<string name="careportal_newnstreatment_eventtype">Tipo de evento</string>
<string name="careportal_newnstreatment_other">Otro</string>
<string name="careportal_newnstreatment_meter">Medidor</string>
<string name="careportal_newnstreatment_sensor">Sensor</string>
<string name="careportal_newnstreatment_carbs_label">Carbohidratos</string>
<string name="careportal_newnstreatment_carbs_label">Hidratos Carbono</string>
<string name="careportal_newnstreatment_insulin_label">Insulina</string>
<string name="careportal_newnstreatment_carbtime_label">Tiempo absorción</string>
<string name="careportal_newnstreatment_split_label">Dividir</string>
@ -196,7 +196,7 @@
<string name="end_user_license_agreement">Acuerdo de licencia de usuario final</string>
<string name="end_user_license_agreement_text">No deben utilizarse para tomar decisiones médicas. NO HAY GARANTÍA PARA EL PROGRAMA, la extensión permitida por la legislación aplicable. Excepto cuando se indique de otra forma por escrito, los tenedores del copyright y / u otras partes proporcionan el programa \"tal cual\" sin garantía de ningún tipo, ya sea expresa o implícita, incluyendo, pero no limitado a, las garantías implícitas de COMERCIALIZACIÓN E IDONEIDAD PARA UN FIN DETERMINADO . TODO EL RIESGO EN CUANTO A LA CALIDAD Y RENDIMIENTO DEL PROGRAMA ES CON USTED. SI EL PROGRAMA TIENE UN ERROR, asume el coste de cualquier servicio, reparación o corrección.</string>
<string name="end_user_license_agreement_i_understand">Entiendo y acepto</string>
<string name="save">Guardar</string>
<string name="save">Salvar</string>
<string name="nobtadapter">No se encuentra adaptador Bluetooth</string>
<string name="devicenotfound">El dispositivo seleccionado no se encuentra</string>
<string name="connectionerror">Error de conexión de la bomba</string>
@ -204,8 +204,7 @@
<string name="danar_iob_label">Bomba IOB</string>
<string name="danar_dailyunits">Unidades diarias</string>
<string name="pump_lastbolus_label">Último bolo:</string>
<string name="hoursago">h antes</string>
<string name="danar_invalidinput">Datos inválidos</string>
<string name="danar_invalidinput">Datos invalidos</string>
<string name="danar_valuenotsetproperly">Valor no establecido correctamente</string>
<string name="reloadprofile">Recargar Perfil</string>
<string name="danar_viewprofile">Ver perfil</string>
@ -219,7 +218,7 @@
<string name="waitingforpumpresult">Esperando resultado</string>
<string name="smscommunicator_allowednumbers">Números de teléfono permitidos</string>
<string name="smscommunicator_allowednumbers_summary">XXXXXXXXXX +; + YYYYYYYYYY</string>
<string formatted="false" name="smscommunicator_bolusreplywithcode">Para entregar bolo% .2fU responder con código% s</string>
<string formatted="false" name="smscommunicator_bolusreplywithcode">Para entregar bolo %.2fU responder con código% s</string>
<string name="smscommunicator_bolusfailed">Bolo falló</string>
<string formatted="false" name="bolusdelivered">Bolo %.2fU entregado con éxito</string>
<string formatted="false" name="bolusdelivering">Entregando %.2fU</string>
@ -284,21 +283,21 @@
<string name="ko_lang">Korean</string>
<string name="actions">Acciones</string>
<string name="correctionbous">Corr</string>
<string name="disabledloop">Lazo Inactivo</string>
<string name="disabledloop">Loop Inactivo</string>
<string name="mealbolus">Bolo Comida</string>
<string name="valueoutofrange" formatted="false">Valor %s fuera de limites</string>
<string name="overview_editquickwizard_buttontext">Botón Texto:</string>
<string name="overview_editquickwizard_carbs">Carbohidratos:</string>
<string name="overview_editquickwizard_carbs">Carbs:</string>
<string name="overview_editquickwizard_valid">Validar:</string>
<string name="overview_editquickwizardlistactivity_add">Añadir</string>
<string name="overview_quickwizard_item_edit_button">Editar</string>
<string name="overview_quickwizard_item_remove_button">Eliminar</string>
<string name="quickwizard">Asistente</string>
<string name="quickwizardsettings">Ajustes asistente</string>
<string name="smscommunicator_loophasbeendisabled">Lazo se ha desactivado</string>
<string name="smscommunicator_loophasbeenenabled">Lazo se ha activado</string>
<string name="smscommunicator_loopisdisabled">Lazo inactivo</string>
<string name="smscommunicator_loopisenabled">Lazo activo</string>
<string name="smscommunicator_loophasbeendisabled">Loop se ha desactivado</string>
<string name="smscommunicator_loophasbeenenabled">Loop se ha activado</string>
<string name="smscommunicator_loopisdisabled">Loop inactivo</string>
<string name="smscommunicator_loopisenabled">loop activo</string>
<string name="smscommunicator_tempbasalcanceled">Basal temporal cancelada</string>
<string name="smscommunicator_tempbasalcancelfailed">Fallo cancelación basal temporal</string>
<string name="smscommunicator_tempbasalfailed">Fallo inicio basal temporal</string>
@ -345,7 +344,7 @@
<string name="danar_disableeasymode">Inhabilitar EasyUI modo en bomba</string>
<string name="danar_enableextendedbolus">Habilitar bolos extendidos en bomba</string>
<string name="danar_switchtouhmode">Cambio de modo de U/d a U/h en bomba</string>
<string name="eatingsoon">Comer pronto</string>
<string name="eatingsoon">Comida temprano</string>
<string name="high_mark">Marca ALTA</string>
<string name="initializing">Iniciando . . .</string>
<string name="localprofile">Perfil Local</string>

View file

@ -177,7 +177,6 @@
<string name="gettingpumpstatus">Stato Micro</string>
<string name="glucose">Glucosio</string>
<string name="glucosetype_sensor">Sensore</string>
<string name="hoursago">h fa</string>
<string name="import_from">Importa impstazioni da</string>
<string name="initializing">Inizzializzazione</string>
<string name="insulin_shortname">INS</string>

View file

@ -224,7 +224,6 @@
<string name="danar_iob_label">펌프 IOB</string>
<string name="danar_dailyunits">일 인슐린 총량</string>
<string name="pump_lastbolus_label">최근 식사주입:</string>
<string name="hoursago">시간 전</string>
<string name="danar_invalidinput">사용할수 없는 입력 데이터</string>
<string name="danar_valuenotsetproperly">값이 제대로 설정되지 않았습니다</string>
<string name="reloadprofile">Reload profile</string>

View file

@ -412,7 +412,6 @@
<string name="overview_bolusprogress_stoppressed">STOP INGEDRUKT</string>
<string name="overview_calibration">Kalibratie</string>
<string name="danar_stats_olddata_Message">Oude gegevens druk \"VERNIEUW\" a.u.b.</string>
<string name="hoursago">u geleden</string>
<string name="minago">%d min geleden</string>
<string name="reason">Berekening</string>
<string name="rate">Dosis</string>
@ -727,7 +726,8 @@
<string name="combo_programming_bolus">"Bolus in pomp programmeren "</string>
<string name="combo_refresh">Vernieuw</string>
<string name="combo_pump_state_label">Status</string>
<string name="combo_no_pump_connection">Geen verbinding</string><string name="combo_tbr_remaining">%d%% (%d min resterend)</string>
<string name="combo_no_pump_connection">Geen verbinding gedurende %d minuten</string>
<string name="combo_tbr_remaining">%d%% (%d min resterend)</string>
<string name="combo_pump_action_initializing">Initialiseren</string>
<string name="combo_pump_state_disconnected">Verbinding verbroken</string>
<string name="combo_error_bolus_recovery_progress">Herstel van verbroken verbindng</string>
@ -750,14 +750,14 @@
<string name="combo_pump_state_suspended_by_user">Gestopt door de gebruiker</string>
<string name="combo_pump_state_running">Actief</string>
<string name="combo_pump_cartridge_low_warrning">Insuline ampul is bijna leeg</string>
<string name="combo_is_in_error_state">Pomp is in storing, controleer op de pomp</string>
<string name="combo_is_in_error_state">Pomp is in storing, controleer op de pomp: E%d %s</string>
<string name="combo_pump_action_bolusing">Bolus (%.1f E)</string>
<string name="combo_tdd_minimum">Minimum: %3.1f E</string>
<string name="combo_tdd_average">Gemiddelde: %3.1f E</string>
<string name="combo_activity_setting_basal_profile">Instellen van basaal profiel</string>
<string name="combo_activity_reading_pump_history">Lezen van pomp historiek</string>
<string name="combo_read_full_history_confirmation">Ben je zeker dat je alle data van de pomp wil ophalen en de consequenties hiervan wil dragen?</string>
<string name="combo_no_alert_data_note">Om de pomp fouthistoriek op te halen, druk lang op de Storingen knop OPGELET: dit kan een bug veroorzaken waardoor de pomp alle verbindingen verbreekt en het vereist is op een knop op de pomp te duwen, dit wordt daarom afgeraden.</string>
<string name="combo_no_alert_data_note">Om de pomp fouthistoriek op te halen, druk lang op de Storingen knop.</string>
<string name="combo_error_partial_bolus_delivered">Maar %.2f E van de gevraagde %.2f E zijn toegediend door een storing. Gelieve op de pomp te controleren en het gepaste gevolg uit te voeren.</string>
<string name="combo_no_tdd_data_note">"Om de TTD van de pomp op te halen, lang duwen op de TDDS knop OPGELET: dit kan een bug veroorzaken waardoor de pomp alle verbindingen verbreekt en het vereist is op een knop op de pomp te duwen, dit wordt daarom afgeraden."</string>
<string name="combo_error_bolus_verification_failed">Toedienen en controleren van de bolus in de pomp historiek is mislukt, controleer de pomp en creëer een manuele bolus in het Careportal tabblad</string>
@ -770,4 +770,29 @@
<string name="combo_low_suspend_forced_notification">Opgelet: verlengde en multi wafe bolussen zijn actief. Loop is naar onderdruk lage waardes enkel overgeschakeld gedurende 6 uur. Alleen gewone bolussen worden onderdsteund in loop modus.</string>
<string name="combo_reservoir_level_insufficient_for_bolus">Niet genoeg insuline aanwezig in reservoir voor de bolus</string>
<string name="careportal_combobolus">Combinatie-Bolus</string>
<string name="pump_basebasalrate">%.2f E/u</string>
<string name="hoursago">%.1fu geleden</string>
<string name="bolusstopped">Bolus gestopt</string>
<string name="bolusstopping">Stoppen van bolus</string>
<string name="combo_actvity_reading_basal_profile">Basaal profiel wordt gelezen</string>
<string name="basalprofilenotaligned">Basale patroon niet geschikt op complete uren: %s</string>
<string name="zerovalueinprofile">Ongeldig profiel: %s</string>
<string name="combo_read_full_history_info">Lang duwen op deze knop zal de volledige historiek en basaal profiel uit de pomp ophalen. Dit is normaal gezien niet nodig, daar de pomp historiek permanent wordt gelezen, maar kan nuttig zijn wanneer de pomp dat en tijd grote afwijkingen hadden of de pomp vervangen is.</string>
<string name="combo_error_no_connection_no_bolus_delivered">Er kon geen verbinding met de pomp gemaakt worden. De Bolus is niet toegediend.</string>
<string name="extendedbolusdeliveryerror">Vertraagde bolus toedien storing</string>
<string name="combo_bolus_rejected_due_to_pump_history_change">De pomp historiek is gewijzigd nadat de bolus berend was. De bolus is NIET toegediend. Programmeer een nieuwe bolus indien nodig. Als dezelfde bolus hoeveelheid moet worden toegediend, gelieve 2 minuten te wachten. Gelijke bolussen worden geweigerd om veiligheidsredenen (toegediend of niet).</string>
<string name="combo_error_updating_treatment_record">Bolus succesvol toegediend, maar toevoegend van de behandeling is gefaald. Dit kan voorvallen wanneer twee kleine bolussen van dezelfde grote gekozen waren gedurende de laatste 2 minuten. Controleer aub de pomphistoriek en de behandelingen, voeg de ontbrekende toe via het careportal. Let op dat je geen 2 dezelfde hoeveelheden hebt op hetzelfde ogenblik.</string>
<string name="combo_high_temp_rejected_due_to_pump_history_changes">Tijdelijk basaal geweigerd doordat de berekeningen geen rekening hielden met de recente wijzigingen in de pomp historiek</string>
<string name="combo_activity_checking_pump_state">Vernieuwen van pomp status</string>
<string name="combo_warning_pump_basal_rate_changed">Het basaal patroon is op de pomp gewijzigd en zal binnenkort geupdate worden.</string>
<string name="combo_error_failure_reading_changed_basal_rate">Basaal patroon op de pomp is gewijzigd, maar konnen niet worden uitgelezen</string>
<string name="combo_activity_checking_for_history_changes">Controle van historiek op wijzigingen</string>
<string name="combo_error_multiple_boluses_with_identical_timestamp">"Verschillend bolussen met dezelfde hoeveelheid op hetzelfde tijdstip zijn geïmporteerd. Er is er maar 1 toegevoegd aan de behandelingen lijst. Controleer op de pomp en voeg eventueel toe via het Careportal menu. Er mogen geen 2 bolussen op hetzelfde tijdstip aanwezig zijn! "</string>
<string name="mute">Geluid dempen</string>
<string name="combo_check_date">De laatste bolus is ouder dan 24 uren of bevind zich in de toekomst. Controleer de datum en tijd in de pomp aub.</string>
<string name="ns_create_announcements_from_errors_title">Creëer een melding bij storingen</string>
<string name="ns_create_announcements_from_errors_summary">Creëer een Nightscout melding voor storingen en lokale waarschuwingen (ook zichtbaar inder het Careportal en behandelingen)</string>
<string name="combo_suspious_bolus_time">Datum/tijd van de geleverde bolus op de pomp is niet correct, IOB is waarschijnlijk foutief. Controleer aub de datum/tijd op de pomp.</string>
<string name="profileswitch_ismissing">Profiel wissel ontbreekt. Doe aub een profiel wissel of duw op Activeer Profiel in het Lokale profiel.</string>
</resources>

View file

@ -220,7 +220,6 @@
<string name="glucosetype_finger">палец</string>
<string name="glucosetype_sensor">сенсор</string>
<string name="high_mark">ВЕРХНЯЯ отметка</string>
<string name="hoursago">час. назад</string>
<string name="import_from">импортировать настройки из</string>
<string name="initializing">инициализация...</string>
<string name="insulin_shortname">ИНС</string>

View file

@ -202,9 +202,8 @@
<string name="glucosetype_sensor">Sensor</string>
<string name="high_mark">HÖG markering</string>
<string name="hours">timmar</string>
<string name="hoursago">h sedan</string>
<string name="import_from">Importera inställningar från</string>
<string name="initializing">Startar...</string>
<string name="initializing">Startar</string>
<string name="invalidprofile">Ogiltig profil !!!</string>
<string name="iob">IOB</string>
<string name="it_lang">Italienska</string>

View file

@ -238,7 +238,7 @@
<string name="danar_iob_label">Pump IOB</string>
<string name="danar_dailyunits">Daily units</string>
<string name="pump_lastbolus_label">Last bolus</string>
<string name="hoursago">h ago</string>
<string name="hoursago">%.1fh ago</string>
<string name="danar_invalidinput">Invalid input data</string>
<string name="danar_valuenotsetproperly">Value not set properly</string>
<string name="reloadprofile">Reload profile</string>
@ -299,6 +299,8 @@
<string name="pumpbusy">Pump is busy</string>
<string name="overview_bolusprogress_delivered">Delivered</string>
<string name="overview_bolusprogress_stoped">Stopped</string>
<string name="bolusstopped">Bolus stopped</string>
<string name="bolusstopping">Stopping bolus</string>
<string name="occlusion">Occlusion</string>
<string name="overview_bolusprogress_stop">Stop</string>
<string name="overview_bolusprogress_stoppressed">STOP PRESSED</string>
@ -824,8 +826,8 @@
<string name="combo_pump_activity_label">Activity</string>
<string name="combo_no_pump_connection">No connection for %d min</string>
<string name="combo_tbr_remaining">%d%% (%d min remaining)</string>
<string name="combo_last_bolus" translatable="false">%.1f U (%s, %s)</string>
<string name="combo_pump_action_initializing">Initializing</string>
<string name="combo_last_bolus" translatable="false">%.1f %s (%s)</string>
<string name="combo_pump_state_initializing">Initializing</string>
<string name="combo_pump_state_disconnected">Disconnected</string>
<string name="combo_pump_state_suspended_due_to_error">Suspended due to error</string>
<string name="combo_pump_state_suspended_by_user">Suspended by user</string>
@ -838,7 +840,7 @@
<string name="combo_pump_unsupported_operation">Requested operation not supported by pump</string>
<string name="combo_low_suspend_forced_notification">Unsafe usage: extended or multiwave boluses are active. Loop mode has been set to low-suspend only 6 hours. Only normal boluses are supported in loop mode</string>
<string name="combo_force_disabled_notification">Unsafe usage: the pump uses a different basal rate profile than the first. The loop has been disabled. Select the first profile on the pump and refresh.</string>
<string name="bolus_frequency_exceeded">A bolus with the same amount was requested within the last minute. To prevent accidental double boluses and to guard against bugs this is disallowed.</string>
<string name="bolus_frequency_exceeded">A bolus with the same amount was requested within the last two minutes. To prevent accidental double boluses and to guard against bugs this is disallowed.</string>
<string name="combo_pump_connected_now">Now</string>
<string name="combo_activity_reading_pump_history">Reading pump history</string>
<string name="danar_history">pump history</string>
@ -847,8 +849,8 @@
<string name="combo_pump_cartridge_low_warrning">Pump cartridge level is low</string>
<string name="combo_pump_battery_low_warrning">Pump battery is low</string>
<string name="combo_is_in_error_state">The pump is showing the error E%d: %s</string>
<string name="combo_no_alert_data_note">To read the pump\'s error history, long press the ALERTS button\n\nWARNING: this can trigger a bug which causes the pump to reject all connection attempts and requires pressing a button on the pump to recover and should therefore be avoided.</string>
<string name="combo_no_tdd_data_note">To read the pump\'s TDD history, long press the TDDS button\n\nWARNING: this can trigger a bug which causes the pump to reject all connection attempts and requires pressing a button on the pump to recover and should therefore be avoided.</string>
<string name="combo_no_alert_data_note">To read the pump\'s error history, long press this button</string>
<string name="combo_no_tdd_data_note">To read the pump\'s TDD history, long press this button</string>
<string name="combo_tdd_minimum">Minimum: %3.1f U</string>
<string name="combo_tdd_average">Average: %3.1f U</string>
<string name="combo_tdd_maximum">Maximum: %3.1f U</string>
@ -858,9 +860,11 @@
<string name="combo_notification_check_time_date">Pump clock update needed</string>
<string name="combo_history">History</string>
<string name="combo_warning">Warning</string>
<string name="combo_read_full_history_info">Long press this button to force a full read of history and basal profile from the pump. This is generally not needed, since the pump\'s history is read continuously, but can be useful if the pump\'s date and time changed significantly or the pump was replaced.</string>
<string name="combo_read_full_history_warning">This will read the full history and state of the pump. Everything in My Data and the basal rate. Boluses and TBRs will be added to Treatments if they don\'t already exist. This can cause entries to be duplicated because the pump\'s time is imprecise. Using this when normally looping with the pump is highly discouraged and reserved for special circumstances. If you still want to do this, long press this button again. WARNING: this can trigger a bug which causes the pump to reject all connection attempts and requires pressing a button on the pump to recover and should therefore be avoided.</string>
<string name="combo_read_full_history_confirmation">Are you really sure you want to read all pump data and take the consequences of this action?</string>
<string name="combo_pump_tbr_cancelled_warrning">TBR CANCELLED warning was confirmed</string>
<string name="combo_error_no_connection_no_bolus_delivered">The pump could not be reached. No bolus was given</string>
<string name="combo_error_no_bolus_delivered">Bolus delivery failed. It appears no bolus was delivered. To be sure, please check the pump to avoid a double bolus and then bolus again. To guard against bugs, boluses are not automatically retried.</string>
<string name="combo_error_partial_bolus_delivered">Only %.2f U of the requested bolus of %.2f U was delivered due to an error. Please check the pump to verify this and take appropriate actions.</string>
<string name="combo_error_bolus_verification_failed">Delivering the bolus and verifying the pump\'s history failed, please check the pump and manually create a bolus record using the Careportal tab if a bolus was delivered.</string>
@ -980,9 +984,25 @@
<string name="overview_show_cob">Carbs On Board</string>
<string name="overview_show_iob">Insulin On Board</string>
<string name="overview_show_basals">Basals</string>
<string name="closed_loop_disabled_on_dev_branch">Running dev version. Closed loop is disabled.</string>
<string name="key_fromNSAreCommingFakedExtendedBoluses" translatable="false">fromNSAreCommingFakedExtendedBoluses</string>
<string name="closed_loop_disabled_on_dev_branch">Running dev version. Closed loop is disabled</string>
<string name="engineering_mode_enabled">Engineering mode enabled</string>
<string name="not_eng_mode_or_release">Engineering mode not enabled and not on release branch</string>
<string name="pump_basebasalrate">%.2f U/h</string>
<string name="combo_actvity_reading_basal_profile">Reading basal profile</string>
<string name="combo_bolus_rejected_due_to_pump_history_change">The pump history has changed after the bolus calculation was performed. The bolus was not delivered. Please recalculate if a bolus is still needed. If the same bolus amount is required, please wait two minutes since boluses with the same amount are blocked when requested with less than two minutes between them for safety (regardless of whether they were administered or not).</string>
<string name="combo_error_updating_treatment_record">Bolus successfully delivered, but adding the treatment entry failed. This can happen if two small boluses of the same size are administered within the last two minutes. Please check the pump history and treatment entries and use the Careportal to add missing entries. Make sure not to add any entries for the exact same minute and same amount.</string>
<string name="combo_high_temp_rejected_due_to_pump_history_changes">Rejecting high temp since calculation didn\'t consider recently changed pump history</string>
<string name="combo_activity_checking_pump_state">Refreshing pump state</string>
<string name="combo_warning_pump_basal_rate_changed">The basal rate on the pump has changed and will be updated soon</string>
<string name="combo_error_failure_reading_changed_basal_rate">Basal rate changed on pump, but reading it failed</string>
<string name="combo_activity_checking_for_history_changes">Checking for history changes</string>
<string name="combo_error_multiple_boluses_with_identical_timestamp">Multiple boluses with the same amount within the same minute were just imported. Only one record could be added to treatments. Please check the pump and manually add a bolus record using the Careportal tab. Make sure to create a bolus with a time no other bolus uses.</string>
<string name="about_link_urls">\n\nhttp://www.androidaps.org\nhttp://www.androidaps.de (de)\n\nfacebook:\nhttp://facebook.androidaps.org\nhttp://facebook.androidaps.de (de)</string>
<string name="combo_check_date">The last bolus is older than 24 hours or is in the future. Please check the date on the pump is set correctly.</string>
<string name="combo_suspious_bolus_time">Time/date of the delivered bolus on pump seems wrong, IOB is likely incorrect. Please check pump time/date.</string>
<string name="profileswitch_ismissing">ProfileSwitch missing. Please do a profile switch or press \"Activate Profile\" in the LocalProfile.</string>
<string name="combo_bolus_count">Bolus count</string>
<string name="combo_tbr_count">TBR count</string>
</resources>

View file

@ -0,0 +1,63 @@
package info.nightscout.androidaps.plugins.PumpCombo;
import android.content.Context;
import com.squareup.otto.Bus;
import com.squareup.otto.ThreadEnforcer;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin;
import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.Bolus;
import info.nightscout.utils.ToastUtils;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@RunWith(PowerMockRunner.class)
@PrepareForTest({MainApp.class, ConfigBuilderPlugin.class, ConfigBuilderPlugin.class, ToastUtils.class, Context.class})
public class ComboPluginTest {
@Before
public void prepareMocks() throws Exception {
ConfigBuilderPlugin configBuilderPlugin = mock(ConfigBuilderPlugin.class);
PowerMockito.mockStatic(ConfigBuilderPlugin.class);
PowerMockito.mockStatic(MainApp.class);
MainApp mainApp = mock(MainApp.class);
when(MainApp.getConfigBuilder()).thenReturn(configBuilderPlugin);
when(MainApp.instance()).thenReturn(mainApp);
Bus bus = new Bus(ThreadEnforcer.ANY);
when(MainApp.bus()).thenReturn(bus);
when(MainApp.gs(0)).thenReturn("");
}
@Test
public void calculateFakePumpTimestamp() throws Exception {
ComboPlugin plugin = ComboPlugin.getPlugin();
long now = System.currentTimeMillis();
long pumpTimestamp = now - now % 1000;
// same timestamp, different bolus leads to different fake timestamp
Assert.assertNotEquals(
plugin.calculateFakeBolusDate(new Bolus(pumpTimestamp, 0.1, true)),
plugin.calculateFakeBolusDate(new Bolus(pumpTimestamp, 0.3, true))
);
// different timestamp, same bolus leads to different fake timestamp
Assert.assertNotEquals(
plugin.calculateFakeBolusDate(new Bolus(pumpTimestamp, 0.3, true)),
plugin.calculateFakeBolusDate(new Bolus(pumpTimestamp + 60 * 1000, 0.3, true))
);
// generated timestamp has second-precision
Bolus bolus = new Bolus(pumpTimestamp, 0.2, true);
long calculatedTimestamp = plugin.calculateFakeBolusDate(bolus);
assertEquals(calculatedTimestamp, calculatedTimestamp - calculatedTimestamp % 1000);
}
}

View file

@ -80,17 +80,18 @@ public class CommandQueueTest extends CommandQueue {
extendedBolus(1, 30, null);
Assert.assertEquals(2, size());
// add setProfile
// add setProfile (command is not queued before unless a ProfileSwitch exists)
// TODO test with profile switch set
setProfile(new Profile(new JSONObject(profileJson), Constants.MGDL), null);
Assert.assertEquals(3, size());
Assert.assertEquals(2, size());
// add loadHistory
loadHistory((byte) 0, null);
Assert.assertEquals(4, size());
Assert.assertEquals(3, size());
// add loadEvents
loadEvents(null);
Assert.assertEquals(5, size());
Assert.assertEquals(4, size());
clear();
tempBasalAbsolute(0, 30, true, null);

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

View file

@ -47,6 +47,10 @@ android {
publishNonDefault true
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
allprojects {