Merge pull request #632 from jamorham/dev

Add Insight Pump Plugin
This commit is contained in:
Milos Kozak 2018-02-02 09:21:57 +01:00 committed by GitHub
commit 411add616b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 2531 additions and 0 deletions

View file

@ -200,6 +200,8 @@ dependencies {
compile "com.joanzapata.iconify:android-iconify-fontawesome:2.1.1"
compile "com.google.android.gms:play-services-wearable:7.5.0"
compile(name: "android-edittext-validator-v1.3.4-mod", ext: "aar")
compile(name: "sightparser-release", ext: "aar")
compile("com.google.android:flexbox:0.3.0") {
exclude group: "com.android.support"
}
@ -223,6 +225,7 @@ dependencies {
testCompile "org.powermock:powermock-module-junit4-rule:${powermockVersion}"
testCompile "org.powermock:powermock-module-junit4:${powermockVersion}"
testCompile "joda-time:joda-time:2.9.4.2"
testCompile "com.google.truth:truth:0.39"
androidTestCompile "org.mockito:mockito-core:2.7.22"
androidTestCompile "com.google.dexmaker:dexmaker:${dexmakerVersion}"

Binary file not shown.

View file

@ -54,6 +54,7 @@ import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPlugin;
import info.nightscout.androidaps.plugins.PumpDanaRKorean.DanaRKoreanPlugin;
import info.nightscout.androidaps.plugins.PumpDanaRS.DanaRSPlugin;
import info.nightscout.androidaps.plugins.PumpDanaRv2.DanaRv2Plugin;
import info.nightscout.androidaps.plugins.PumpInsight.InsightPumpPlugin;
import info.nightscout.androidaps.plugins.PumpMDI.MDIPlugin;
import info.nightscout.androidaps.plugins.PumpVirtual.VirtualPumpPlugin;
import info.nightscout.androidaps.plugins.SensitivityAAPS.SensitivityAAPSPlugin;
@ -129,6 +130,7 @@ public class MainApp extends Application {
if (Config.DANAR) pluginsList.add(DanaRv2Plugin.getPlugin());
if (Config.DANAR) pluginsList.add(DanaRSPlugin.getPlugin());
pluginsList.add(CareportalPlugin.getPlugin());
if (Config.DANAR) pluginsList.add(InsightPumpPlugin.getPlugin());
if (Config.MDI) pluginsList.add(MDIPlugin.getPlugin());
if (Config.VIRTUALPUMP) pluginsList.add(VirtualPumpPlugin.getPlugin());
if (Config.APS) pluginsList.add(LoopPlugin.getPlugin());

View file

@ -0,0 +1,20 @@
package info.nightscout.androidaps.plugins.PumpInsight;
/**
* Created by jamorham on 25/01/2018.
*
* Async command status
*
*/
enum Cstatus {
UNKNOWN,
PENDING,
SUCCESS,
FAILURE,
TIMEOUT;
boolean success() {
return this == SUCCESS;
}
}

View file

@ -0,0 +1,106 @@
package info.nightscout.androidaps.plugins.PumpInsight;
import android.os.PowerManager;
import com.squareup.otto.Subscribe;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.plugins.PumpInsight.events.EventInsightPumpCallback;
import static info.nightscout.androidaps.plugins.PumpInsight.utils.Helpers.getWakeLock;
import static info.nightscout.androidaps.plugins.PumpInsight.utils.Helpers.msSince;
import static info.nightscout.androidaps.plugins.PumpInsight.utils.Helpers.releaseWakeLock;
import static info.nightscout.androidaps.plugins.PumpInsight.utils.Helpers.tsl;
/**
* Created by jamorham on 25/01/2018.
*
* Asynchronous adapter
*
*/
public class InsightPumpAsyncAdapter {
private final ConcurrentHashMap<UUID, EventInsightPumpCallback> commandResults = new ConcurrentHashMap<>();
InsightPumpAsyncAdapter() {
MainApp.bus().register(this);
}
// just log during debugging
private static void log(String msg) {
android.util.Log.e("INSIGHTPUMPASYNC", msg);
}
@Subscribe
public void onStatusEvent(final EventInsightPumpCallback ev) {
log("Received callback event: " + ev.toString());
commandResults.put(ev.request_uuid, ev);
}
// poll command result
private Cstatus checkCommandResult(UUID uuid) {
if (uuid == null) return Cstatus.FAILURE;
if (commandResults.containsKey(uuid)) {
if (commandResults.get(uuid).success) {
return Cstatus.SUCCESS;
} else {
return Cstatus.FAILURE;
}
} else {
return Cstatus.PENDING;
}
}
// blocking call to wait for result callback
Cstatus busyWaitForCommandResult(final UUID uuid, long wait_time) {
final PowerManager.WakeLock wl = getWakeLock("insight-wait-cmd", 60000);
try {
log("busy wait for command " + uuid);
if (uuid == null) return Cstatus.FAILURE;
final long start_time = tsl();
Cstatus status = checkCommandResult(uuid);
while ((status == Cstatus.PENDING) && msSince(start_time) < wait_time) {
//log("command result waiting");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
log("Got interrupted exception! " + e);
}
status = checkCommandResult(uuid);
}
if (status == Cstatus.PENDING) {
return Cstatus.TIMEOUT;
} else {
return status;
}
} finally {
releaseWakeLock(wl);
}
}
// commend field preparation for results
String getCommandComment(final UUID uuid) {
if (commandResults.containsKey(uuid)) {
if (commandResults.get(uuid).success) {
return "OK";
} else {
return commandResults.get(uuid).message;
}
} else {
return "Unknown reference";
}
}
int getResponseID(UUID uuid) {
if (checkCommandResult(uuid) == Cstatus.SUCCESS) {
return commandResults.get(uuid).response_id;
} else {
return -2; // invalid
}
}
}

View file

@ -0,0 +1,113 @@
package info.nightscout.androidaps.plugins.PumpInsight;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import com.crashlytics.android.Crashlytics;
import com.squareup.otto.Subscribe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.plugins.Common.SubscriberFragment;
import info.nightscout.androidaps.plugins.PumpInsight.events.EventInsightPumpUpdateGui;
import info.nightscout.androidaps.plugins.PumpInsight.utils.StatusItem;
import info.nightscout.androidaps.plugins.PumpInsight.utils.ui.StatusItemViewAdapter;
public class InsightPumpFragment extends SubscriberFragment {
private static final Logger log = LoggerFactory.getLogger(InsightPumpFragment.class);
private static final Handler sLoopHandler = new Handler();
private static volatile boolean refresh = false;
private static volatile boolean pending = false;
StatusItemViewAdapter viewAdapter;
LinearLayout holder;
private final Runnable sRefreshLoop = new Runnable() {
@Override
public void run() {
pending = false;
updateGUI();
if (refresh) {
scheduleRefresh();
}
}
};
private synchronized void scheduleRefresh() {
if (!pending) {
pending = true;
sLoopHandler.postDelayed(sRefreshLoop, 30 * 1000L);
}
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
try {
final View view = inflater.inflate(R.layout.insightpump_fragment, container, false);
holder = (LinearLayout) view.findViewById(R.id.insightholder);
viewAdapter = new StatusItemViewAdapter(getActivity(), holder);
return view;
} catch (Exception e) {
Crashlytics.logException(e);
}
return null;
}
@Override
public void setUserVisibleHint(boolean visible) {
super.setUserVisibleHint(visible);
if (visible) {
refresh = true;
pending = false;
updateGUI();
scheduleRefresh();
} else {
refresh = false;
//sLoopHandler.removeCallbacksAndMessages(null);
}
}
@Subscribe
public void onStatusEvent(final EventInsightPumpUpdateGui ev) {
updateGUI();
}
@Override
protected void updateGUI() {
final Activity activity = getActivity();
if (activity != null && holder != null)
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
final InsightPumpPlugin insightPumpPlugin = InsightPumpPlugin.getPlugin();
final List<StatusItem> l = insightPumpPlugin.getStatusItems(refresh);
holder.removeAllViews();
for (StatusItem row : l) {
viewAdapter.inflateStatus(row);
}
}
});
}
}

View file

@ -0,0 +1,940 @@
package info.nightscout.androidaps.plugins.PumpInsight;
import android.os.Handler;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import info.nightscout.androidaps.BuildConfig;
import info.nightscout.androidaps.Config;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.DetailedBolusInfo;
import info.nightscout.androidaps.data.Profile;
import info.nightscout.androidaps.data.PumpEnactResult;
import info.nightscout.androidaps.db.ExtendedBolus;
import info.nightscout.androidaps.db.Source;
import info.nightscout.androidaps.db.TemporaryBasal;
import info.nightscout.androidaps.interfaces.PluginBase;
import info.nightscout.androidaps.interfaces.PumpDescription;
import info.nightscout.androidaps.interfaces.PumpInterface;
import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification;
import info.nightscout.androidaps.plugins.Overview.events.EventOverviewBolusProgress;
import info.nightscout.androidaps.plugins.Overview.notifications.Notification;
import info.nightscout.androidaps.plugins.PumpInsight.connector.AbsoluteTBRTaskRunner;
import info.nightscout.androidaps.plugins.PumpInsight.connector.CancelBolusTaskRunner;
import info.nightscout.androidaps.plugins.PumpInsight.connector.Connector;
import info.nightscout.androidaps.plugins.PumpInsight.events.EventInsightPumpCallback;
import info.nightscout.androidaps.plugins.PumpInsight.events.EventInsightPumpUpdateGui;
import info.nightscout.androidaps.plugins.PumpInsight.history.HistoryReceiver;
import info.nightscout.androidaps.plugins.PumpInsight.history.LiveHistory;
import info.nightscout.androidaps.plugins.PumpInsight.utils.Helpers;
import info.nightscout.androidaps.plugins.PumpInsight.utils.StatusItem;
import info.nightscout.utils.DateUtil;
import info.nightscout.utils.NSUpload;
import info.nightscout.utils.SP;
import sugar.free.sightparser.applayer.descriptors.ActiveBolus;
import sugar.free.sightparser.applayer.descriptors.ActiveBolusType;
import sugar.free.sightparser.applayer.descriptors.PumpStatus;
import sugar.free.sightparser.applayer.messages.AppLayerMessage;
import sugar.free.sightparser.applayer.messages.remote_control.BolusMessage;
import sugar.free.sightparser.applayer.messages.remote_control.CancelTBRMessage;
import sugar.free.sightparser.applayer.messages.remote_control.ExtendedBolusMessage;
import sugar.free.sightparser.applayer.messages.remote_control.StandardBolusMessage;
import sugar.free.sightparser.handling.SingleMessageTaskRunner;
import sugar.free.sightparser.handling.TaskRunner;
import sugar.free.sightparser.handling.taskrunners.SetTBRTaskRunner;
import sugar.free.sightparser.handling.taskrunners.StatusTaskRunner;
import static info.nightscout.androidaps.plugins.PumpInsight.history.PumpIdCache.getRecordUniqueID;
import static info.nightscout.androidaps.plugins.PumpInsight.utils.Helpers.roundDouble;
/**
* Created by jamorham on 23/01/2018.
*
* Connects to SightRemote app service using SightParser library
*
* SightRemote and SightParser created by Tebbe Ubben
*
* Original proof of concept SightProxy by jamorham
*
*/
public class InsightPumpPlugin implements PluginBase, PumpInterface {
private static final long BUSY_WAIT_TIME = 20000;
static Integer batteryPercent = 0;
static Integer reservoirInUnits = 0;
static boolean initialized = false;
private static volatile boolean update_pending = false;
private static Logger log = LoggerFactory.getLogger(InsightPumpPlugin.class);
private static volatile InsightPumpPlugin plugin;
private final Handler handler = new Handler();
private final InsightPumpAsyncAdapter async = new InsightPumpAsyncAdapter();
private StatusTaskRunner.StatusResult statusResult;
private long statusResultTime = -1;
private Date lastDataTime = new Date(0);
private TaskRunner taskRunner;
private boolean fragmentEnabled = true;
private boolean fragmentVisible = true;
private boolean fauxTBRcancel = true;
private PumpDescription pumpDescription = new PumpDescription();
private double basalRate = 0;
private Connector connector;
private final TaskRunner.ResultCallback statusResultHandler = new TaskRunner.ResultCallback() {
@Override
public void onError(Exception e) {
log("Got error taskrunner: " + e);
android.util.Log.e("INSIGHTPUMP", "taskrunner stacktrace: ", e);
if (e instanceof sugar.free.sightparser.error.DisconnectedError) {
if (Helpers.ratelimit("insight-reconnect", 2)) {
Connector.connectToPump();
updateGui();
}
}
}
@Override
public synchronized void onResult(Object result) {
log("GOT STATUS RESULT!!!");
statusResult = (StatusTaskRunner.StatusResult) result;
statusResultTime = Helpers.tsl();
processStatusResult();
updateGui();
connector.requestHistoryReSync();
connector.requestHistorySync();
}
};
private InsightPumpPlugin() {
log("InsightPumpPlugin");
pumpDescription.isBolusCapable = true;
pumpDescription.bolusStep = 0.05d; // specification says 0.05U up to 2U then 0.1U @ 2-5U 0.2U @ 10-20U 0.5U 10-20U (are these just UI restrictions?)
pumpDescription.isExtendedBolusCapable = true;
pumpDescription.extendedBolusStep = 0.05d; // specification probably same as above
pumpDescription.extendedBolusDurationStep = 15; // 15 minutes up to 24 hours
pumpDescription.extendedBolusMaxDuration = 24 * 60;
pumpDescription.isTempBasalCapable = true;
//pumpDescription.tempBasalStyle = PumpDescription.PERCENT | PumpDescription.ABSOLUTE;
pumpDescription.tempBasalStyle = PumpDescription.PERCENT;
pumpDescription.maxTempPercent = 250; // 0-250%
pumpDescription.tempPercentStep = 10;
pumpDescription.tempDurationStep = 15; // 15 minutes up to 24 hours
pumpDescription.tempMaxDuration = 24 * 60;
pumpDescription.isSetBasalProfileCapable = false; // leave this for now
pumpDescription.basalStep = 0.01d;
pumpDescription.basalMinimumRate = 0.02d;
pumpDescription.isRefillingCapable = true;
//pumpDescription.storesCarbInfo = false; // uncomment when PumpDescription updated to include this
this.connector = Connector.get();
this.connector.init();
log("back from init");
}
public static InsightPumpPlugin getPlugin() {
if (plugin == null) {
createInstance();
}
return plugin;
}
private static synchronized void createInstance() {
if (plugin == null) {
log("creating instance");
plugin = new InsightPumpPlugin();
}
}
// just log during debugging
private static void log(String msg) {
android.util.Log.e("INSIGHTPUMP", msg);
}
private static void updateGui() {
update_pending = false;
MainApp.bus().post(new EventInsightPumpUpdateGui());
}
private static void pushCallbackEvent(EventInsightPumpCallback e) {
MainApp.bus().post(e);
}
@Override
public String getFragmentClass() {
return InsightPumpFragment.class.getName();
}
@Override
public String getName() {
return MainApp.instance().getString(R.string.insightpump);
}
@Override
public String getNameShort() {
String name = MainApp.instance().getString(R.string.insightpump_shortname);
if (!name.trim().isEmpty()) {
//only if translation exists
return name;
}
// use long name as fallback
return getName();
}
@Override
public boolean isEnabled(int type) {
return type == PUMP && fragmentEnabled;
}
@Override
public boolean isVisibleInTabs(int type) {
return type == PUMP && fragmentVisible;
}
@Override
public boolean canBeHidden(int type) {
return true;
}
@Override
public boolean hasFragment() {
return true;
}
@Override
public boolean showInList(int type) {
return true;
}
@Override
public void setFragmentEnabled(int type, boolean fragmentEnabled) {
if (type == PUMP) this.fragmentEnabled = fragmentEnabled;
}
@Override
public void setFragmentVisible(int type, boolean fragmentVisible) {
if (type == PUMP) this.fragmentVisible = fragmentVisible;
}
@Override
public int getPreferencesId() {
return R.xml.pref_insightpump;
}
@Override
public int getType() {
return PluginBase.PUMP;
}
@Override
public boolean isFakingTempsByExtendedBoluses() {
return false;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public boolean isSuspended() {
return !isPumpRunning();
}
@Override
public boolean isBusy() {
return false;
}
@Override
public boolean isConnected() {
return Connector.get().isPumpConnected();
}
@Override
public boolean isConnecting() {
return Connector.get().isPumpConnecting();
}
@Override
public void connect(String reason) {
log("InsightPumpPlugin::connect()");
try {
if (!connector.isPumpConnected()) {
if (Helpers.ratelimit("insight-connect-timer", 40)) {
log("Actually requesting a connect");
connector.getServiceConnector().connect();
}
} else {
log("Already connected");
}
} catch (NullPointerException e) {
log("Could not sconnect - null pointer: " + e);
}
// TODO review
if (!Config.NSCLIENT && !Config.G5UPLOADER)
NSUpload.uploadDeviceStatus();
lastDataTime = new Date();
}
@Override
public void disconnect(String reason) {
log("InsightPumpPlugin::disconnect()");
try {
if (!SP.getBoolean("insight_always_connected", false)) {
log("Requesting disconnect");
connector.getServiceConnector().disconnect();
} else {
log("Not disconnecting due to preference");
}
} catch (NullPointerException e) {
log("Could not disconnect - null pointer: " + e);
}
}
@Override
public void stopConnecting() {
log("InsightPumpPlugin::stopConnecting()");
try {
if (isConnecting()) {
if (!SP.getBoolean("insight_always_connected", false)) {
log("Requesting disconnect");
connector.getServiceConnector().disconnect();
} else {
log("Not disconnecting due to preference");
}
} else {
log("Not currently trying to connect so not stopping connection");
}
} catch (NullPointerException e) {
log("Could not stop connecting - null pointer: " + e);
}
}
@Override
public void getPumpStatus() {
log("getPumpStatus");
lastDataTime = new Date();
if (Connector.get().isPumpConnected()) {
log("is connected.. requesting status");
handler.postDelayed(new Runnable() {
@Override
public void run() {
taskRunner = new StatusTaskRunner(connector.getServiceConnector());
taskRunner.fetch(statusResultHandler);
}
}
, 1000);
} else {
log("not connected.. not requesting status");
}
}
// TODO implement
@Override
public PumpEnactResult setNewBasalProfile(Profile profile) {
lastDataTime = new Date();
// Do nothing here. we are using MainApp.getConfigBuilder().getActiveProfile().getProfile();
PumpEnactResult result = new PumpEnactResult();
result.enacted = false;
result.success = false;
Notification notification = new Notification(Notification.PROFILE_SET_OK, MainApp.sResources.getString(R.string.profile_set_ok), Notification.INFO, 60);
MainApp.bus().post(new EventNewNotification(notification));
return result;
}
@Override
public boolean isThisProfileSet(Profile profile) {
return true;
}
@Override
public Date lastDataTime() {
return lastDataTime;
}
@Override
public double getBaseBasalRate() {
return basalRate;
}
public String getBaseBasalRateString() {
final DecimalFormat df = new DecimalFormat("#.##");
return df.format(basalRate);
}
@Override
public PumpEnactResult deliverTreatment(DetailedBolusInfo detailedBolusInfo) {
final PumpEnactResult result = new PumpEnactResult();
result.bolusDelivered = detailedBolusInfo.insulin;
result.carbsDelivered = detailedBolusInfo.carbs;
result.enacted = result.bolusDelivered > 0 || result.carbsDelivered > 0;
result.comment = MainApp.instance().getString(R.string.virtualpump_resultok);
result.percent = 100;
// is there an insulin component to the treatment?
if (detailedBolusInfo.insulin > 0) {
final UUID cmd = deliverBolus((float) detailedBolusInfo.insulin); // actually request delivery
if (cmd == null) {
return pumpEnactFailure();
}
final Cstatus cs = async.busyWaitForCommandResult(cmd, BUSY_WAIT_TIME);
result.success = cs.success();
if (cs.success()) {
detailedBolusInfo.pumpId = getRecordUniqueID(async.getResponseID(cmd));
}
} else {
result.success = true; // always true with carb only treatments
}
if (result.success) {
log("Success!");
final EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.getInstance();
bolusingEvent.status = String.format(MainApp.sResources.getString(R.string.bolusdelivered), detailedBolusInfo.insulin);
bolusingEvent.percent = 100;
MainApp.bus().post(bolusingEvent);
MainApp.getConfigBuilder().addToHistoryTreatment(detailedBolusInfo);
} else {
log.debug("Failure to deliver treatment");
}
if (Config.logPumpComm)
log.debug("Delivering treatment insulin: " + detailedBolusInfo.insulin + "U carbs: " + detailedBolusInfo.carbs + "g " + result);
updateGui();
connector.tryToGetPumpStatusAgain();
lastDataTime = new Date();
connector.requestHistorySync(30000);
return result;
}
@Override
public void stopBolusDelivering() {
final UUID cmd = aSyncTaskRunner(new CancelBolusTaskRunner(connector.getServiceConnector(), ActiveBolusType.STANDARD), "Cancel standard bolus");
if (cmd == null) {
return;
}
final Cstatus cs = async.busyWaitForCommandResult(cmd, BUSY_WAIT_TIME);
log("Got command status: " + cs);
}
// Temporary Basals
@Override
public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, boolean enforceNew) {
absoluteRate = Helpers.roundDouble(absoluteRate, 3);
log("Set TBR absolute: " + absoluteRate);
final AbsoluteTBRTaskRunner task = new AbsoluteTBRTaskRunner(connector.getServiceConnector(), absoluteRate, durationInMinutes);
final UUID cmd = aSyncTaskRunner(task, "Set TBR abs: " + absoluteRate + " " + durationInMinutes + "m");
if (cmd == null) {
return pumpEnactFailure();
}
Cstatus cs = async.busyWaitForCommandResult(cmd, BUSY_WAIT_TIME);
log("Got command status: " + cs);
PumpEnactResult pumpEnactResult = new PumpEnactResult().enacted(true).isPercent(false).duration(durationInMinutes);
pumpEnactResult.absolute = absoluteRate; // TODO get converted value?
pumpEnactResult.success = cs.success();
pumpEnactResult.isTempCancel = false; // do we test this here?
pumpEnactResult.comment = async.getCommandComment(cmd);
if (pumpEnactResult.success) {
// create log entry
final TemporaryBasal tempBasal = new TemporaryBasal();
tempBasal.date = System.currentTimeMillis();
tempBasal.isAbsolute = true;
tempBasal.absoluteRate = task.getCalculatedAbsolute(); // is this the correct figure to use?
tempBasal.durationInMinutes = durationInMinutes;
tempBasal.source = Source.USER;
MainApp.getConfigBuilder().addToHistoryTempBasal(tempBasal);
}
if (Config.logPumpComm)
log.debug("Setting temp basal absolute: " + pumpEnactResult.success);
lastDataTime = new Date();
updateGui();
connector.requestHistorySync(5000);
connector.tryToGetPumpStatusAgain();
return pumpEnactResult;
}
@Override
public PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMinutes, boolean enforceNew) {
log("Set TBR %");
final UUID cmd = aSyncTaskRunner(new SetTBRTaskRunner(connector.getServiceConnector(), percent, durationInMinutes), "Set TBR " + percent + "%" + " " + durationInMinutes + "m");
if (cmd == null) {
return pumpEnactFailure();
}
Cstatus cs = async.busyWaitForCommandResult(cmd, BUSY_WAIT_TIME);
log("Got command status: " + cs);
PumpEnactResult pumpEnactResult = new PumpEnactResult().enacted(true).isPercent(true).duration(durationInMinutes);
pumpEnactResult.percent = percent;
pumpEnactResult.success = cs.success();
pumpEnactResult.isTempCancel = percent == 100; // 100% temp basal is a cancellation
pumpEnactResult.comment = async.getCommandComment(cmd);
if (pumpEnactResult.success) {
// create log entry
final TemporaryBasal tempBasal = new TemporaryBasal();
tempBasal.date = System.currentTimeMillis();
tempBasal.isAbsolute = false;
tempBasal.percentRate = percent;
tempBasal.durationInMinutes = durationInMinutes;
tempBasal.source = Source.USER; // TODO check this is correct
MainApp.getConfigBuilder().addToHistoryTempBasal(tempBasal);
}
updateGui();
if (Config.logPumpComm)
log.debug("Set temp basal " + percent + "% for " + durationInMinutes + "m");
connector.requestHistorySync(5000);
connector.tryToGetPumpStatusAgain();
return pumpEnactResult;
}
@Override
public PumpEnactResult cancelTempBasal(boolean enforceNew) {
log("Cancel TBR");
fauxTBRcancel = !SP.getBoolean("insight_real_tbr_cancel", false);
final UUID cmd;
if (fauxTBRcancel) {
final int faux_percent = 90;
final int faux_duration = 15;
cmd = aSyncTaskRunner(new SetTBRTaskRunner(connector.getServiceConnector(), faux_percent, 15), "Faux Cancel TBR - setting " + faux_percent + "%" + " " + faux_duration + "m");
} else {
cmd = aSyncSingleCommand(new CancelTBRMessage(), "Cancel Temp Basal");
}
if (cmd == null) {
return pumpEnactFailure();
}
// TODO isn't conditional on one apparently being in progress only the history change
boolean enacted = false;
final Cstatus cs = async.busyWaitForCommandResult(cmd, BUSY_WAIT_TIME);
if (MainApp.getConfigBuilder().isTempBasalInProgress()) {
enacted = true;
TemporaryBasal tempStop = new TemporaryBasal(System.currentTimeMillis());
tempStop.source = Source.USER;
MainApp.getConfigBuilder().addToHistoryTempBasal(tempStop);
}
lastDataTime = new Date();
updateGui();
if (Config.logPumpComm)
log.debug("Canceling temp basal: "); // TODO get more info
connector.requestHistorySync(5000);
connector.tryToGetPumpStatusAgain();
return new PumpEnactResult().success(cs.success()).enacted(true).isTempCancel(true);
}
// Extended Boluses
@Override
public PumpEnactResult setExtendedBolus(Double insulin, Integer durationInMinutes) {
log("Set Extended bolus " + insulin + " " + durationInMinutes);
ExtendedBolusMessage extendedBolusMessage = new ExtendedBolusMessage();
extendedBolusMessage.setAmount((float) ((double) insulin));
extendedBolusMessage.setDuration((short) ((int) durationInMinutes));
final UUID cmd = aSyncSingleCommand(extendedBolusMessage, "Extended bolus U" + insulin + " mins:" + durationInMinutes);
if (cmd == null) {
return pumpEnactFailure();
}
final Cstatus cs = async.busyWaitForCommandResult(cmd, BUSY_WAIT_TIME);
log("Got command status: " + cs);
PumpEnactResult pumpEnactResult = new PumpEnactResult().enacted(true).bolusDelivered(insulin).duration(durationInMinutes);
pumpEnactResult.success = cs.success();
pumpEnactResult.comment = async.getCommandComment(cmd);
if (pumpEnactResult.success) {
// create log entry
final ExtendedBolus extendedBolus = new ExtendedBolus();
extendedBolus.date = System.currentTimeMillis();
extendedBolus.insulin = insulin;
extendedBolus.durationInMinutes = durationInMinutes;
extendedBolus.source = Source.USER;
extendedBolus.pumpId = getRecordUniqueID(async.getResponseID(cmd));
MainApp.getConfigBuilder().addToHistoryExtendedBolus(extendedBolus);
}
if (Config.logPumpComm)
log.debug("Setting extended bolus: " + insulin + " mins:" + durationInMinutes + " " + pumpEnactResult.comment);
updateGui();
connector.requestHistorySync(30000);
connector.tryToGetPumpStatusAgain();
return pumpEnactResult;
}
@Override
public PumpEnactResult cancelExtendedBolus() {
log("Cancel Extended bolus");
// TODO note always sends cancel to pump but only changes history if present
final UUID cmd = aSyncTaskRunner(new CancelBolusTaskRunner(connector.getServiceConnector(), ActiveBolusType.EXTENDED), "Cancel extended bolus");
if (cmd == null) {
return pumpEnactFailure();
}
final Cstatus cs = async.busyWaitForCommandResult(cmd, BUSY_WAIT_TIME);
if (MainApp.getConfigBuilder().isInHistoryExtendedBoluslInProgress()) {
ExtendedBolus exStop = new ExtendedBolus(System.currentTimeMillis());
exStop.source = Source.USER;
MainApp.getConfigBuilder().addToHistoryExtendedBolus(exStop);
}
if (Config.logPumpComm)
log.debug("Cancel extended bolus:");
updateGui();
connector.requestHistorySync(5000);
connector.tryToGetPumpStatusAgain();
return new PumpEnactResult().success(cs.success()).enacted(true);
}
private synchronized UUID deliverBolus(float bolusValue) {
log("DeliverBolus: " + bolusValue);
if (bolusValue == 0) return null;
if (bolusValue < 0) return null;
// TODO check limits here or they already occur via a previous constraint interface?
final StandardBolusMessage message = new StandardBolusMessage();
message.setAmount(bolusValue);
return aSyncSingleCommand(message, "Deliver Bolus " + bolusValue);
}
@Override
public JSONObject getJSONStatus() {
if (Helpers.msSince(connector.getLastContactTime()) > (60 * 60 * 1000)) {
log("getJSONStatus not returning as data likely stale");
return null;
}
final JSONObject pump = new JSONObject();
final JSONObject battery = new JSONObject();
final JSONObject status = new JSONObject();
final JSONObject extended = new JSONObject();
try {
battery.put("percent", batteryPercent);
status.put("status", isSuspended() ? "suspended" : "normal");
status.put("timestamp", DateUtil.toISOString(connector.getLastContactTime()));
extended.put("Version", BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION);
try {
extended.put("ActiveProfile", MainApp.getConfigBuilder().getProfileName());
} catch (Exception e) {
}
TemporaryBasal tb = MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis());
if (tb != null) {
extended.put("TempBasalAbsoluteRate", tb.tempBasalConvertedToAbsolute(System.currentTimeMillis()));
extended.put("TempBasalStart", DateUtil.dateAndTimeString(tb.date));
extended.put("TempBasalRemaining", tb.getPlannedRemainingMinutes());
}
ExtendedBolus eb = MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis());
if (eb != null) {
extended.put("ExtendedBolusAbsoluteRate", eb.absoluteRate());
extended.put("ExtendedBolusStart", DateUtil.dateAndTimeString(eb.date));
extended.put("ExtendedBolusRemaining", eb.getPlannedRemainingMinutes());
}
status.put("timestamp", DateUtil.toISOString(new Date()));
pump.put("battery", battery);
pump.put("status", status);
pump.put("extended", extended);
pump.put("reservoir", reservoirInUnits);
pump.put("clock", DateUtil.toISOString(new Date()));
} catch (JSONException e) {
log.error("Unhandled exception", e);
}
return pump;
}
@Override
public String deviceID() {
return "InsightPump";
}
@Override
public PumpDescription getPumpDescription() {
return pumpDescription;
}
@Override
public String shortStatus(boolean veryShort) {
String msg = gs(R.string.insightpump_shortname) + " Batt: " + batteryPercent + " Reserv: " + reservoirInUnits + " Basal: " + basalRate;
if (LiveHistory.getStatus().length() > 0) {
msg += LiveHistory.getStatus();
}
return msg;
}
private void processStatusResult() {
if (statusResult != null) {
batteryPercent = statusResult.getBatteryAmountMessage().getBatteryAmount();
reservoirInUnits = (int) statusResult.getCartridgeAmountMessage().getCartridgeAmount();
basalRate = roundDouble(statusResult.getCurrentBasalMessage().getCurrentBasalAmount(), 2);
initialized = true; // basic communication test
}
}
private String gs(int id) {
return MainApp.instance().getString(id);
}
private boolean isPumpRunning() {
if (statusResult == null) return true; // assume running if we have no information
return statusResult.getPumpStatusMessage().getPumpStatus() == PumpStatus.STARTED;
}
List<StatusItem> getStatusItems(boolean refresh) {
final List<StatusItem> l = new ArrayList<>();
// Todo last contact time
l.add(new StatusItem(gs(R.string.status_no_colon), connector.getLastStatusMessage()));
l.add(new StatusItem(gs(R.string.changed), connector.getNiceLastStatusTime()));
boolean pumpRunning;
// also check time since received
if (statusResult != null) {
pumpRunning = isPumpRunning();
if (pumpRunning) {
l.add(new StatusItem(gs(R.string.pump_basebasalrate_label), getBaseBasalRateString() + "U"));
} else {
l.add(new StatusItem(gs(R.string.combo_warning), gs(R.string.pump_stopped_uppercase), StatusItem.Highlight.CRITICAL));
}
}
final long offset_ms = Helpers.msSince(statusResultTime);
final long offset_minutes = offset_ms / 60000;
if (statusResult != null) {
l.add(new StatusItem(gs(R.string.status_updated), Helpers.niceTimeScalar(Helpers.msSince(statusResultTime)) + " " + gs(R.string.ago)));
l.add(new StatusItem(gs(R.string.pump_battery_label), batteryPercent + "%", batteryPercent < 100 ?
(batteryPercent < 90 ?
(batteryPercent < 70 ?
(StatusItem.Highlight.BAD) : StatusItem.Highlight.NOTICE) : StatusItem.Highlight.NORMAL) : StatusItem.Highlight.GOOD));
l.add(new StatusItem(gs(R.string.pump_reservoir_label), reservoirInUnits + "U"));
if (statusResult.getCurrentTBRMessage().getPercentage() != 100) {
l.add(new StatusItem(gs(R.string.insight_active_tbr), statusResult.getCurrentTBRMessage().getPercentage() + "% " + gs(R.string.with) + " "
+ Helpers.qs(statusResult.getCurrentTBRMessage().getLeftoverTime() - offset_minutes, 0)
+ " " + gs(R.string.insight_min_left), StatusItem.Highlight.NOTICE));
}
}
if (MainApp.getConfigBuilder().isTempBasalInProgress()) {
try {
l.add(new StatusItem(gs(R.string.pump_tempbasal_label), MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis()).toStringFull()));
} catch (NullPointerException e) {
//
}
}
if (statusResult != null) {
statusActiveBolus(statusResult.getActiveBolusesMessage().getBolus1(), offset_minutes, l);
statusActiveBolus(statusResult.getActiveBolusesMessage().getBolus2(), offset_minutes, l);
statusActiveBolus(statusResult.getActiveBolusesMessage().getBolus3(), offset_minutes, l);
}
if (MainApp.getConfigBuilder().isInHistoryExtendedBoluslInProgress()) {
try {
l.add(new StatusItem(gs(R.string.virtualpump_extendedbolus_label), MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()).toString()));
} catch (NullPointerException e) {
//
}
}
l.add(new StatusItem(gs(R.string.log_book), HistoryReceiver.getStatusString()));
if (LiveHistory.getStatus().length() > 0) {
l.add(new StatusItem(gs(R.string.insight_last_completed_action), LiveHistory.getStatus()));
}
if (Helpers.ratelimit("insight-status-ui-refresh", 10)) {
connector.tryToGetPumpStatusAgain();
}
connector.requestHistorySync();
if (refresh) scheduleGUIUpdate();
return l;
}
private synchronized void scheduleGUIUpdate() {
if (!update_pending && connector.uiFresh()) {
update_pending = true;
Helpers.runOnUiThreadDelayed(new Runnable() {
@Override
public void run() {
updateGui();
}
}, 500);
}
}
private void statusActiveBolus(ActiveBolus activeBolus, long offset_mins, List<StatusItem> l) {
if (activeBolus == null) return;
switch (activeBolus.getBolusType()) {
case STANDARD:
l.add(new StatusItem(activeBolus.getBolusType() + " " + gs(R.string.bolus), activeBolus.getInitialAmount() + "U", StatusItem.Highlight.NOTICE));
break;
case EXTENDED:
l.add(new StatusItem(activeBolus.getBolusType() + " " + gs(R.string.bolus), activeBolus.getInitialAmount() + "U " + gs(R.string.insight_total_with) + " "
+ activeBolus.getLeftoverAmount() + "U " + gs(R.string.insight_remaining_over) + " " + (activeBolus.getDuration() - offset_mins) + " " + gs(R.string.insight_min), StatusItem.Highlight.NOTICE));
break;
case MULTIWAVE:
l.add(new StatusItem(activeBolus.getBolusType() + " " + gs(R.string.bolus), activeBolus.getInitialAmount() + "U " + gs(R.string.insight_upfront_with) + " "
+ activeBolus.getLeftoverAmount() + "U " + gs(R.string.insight_remaining_over) + " " + (activeBolus.getDuration() - offset_mins) + " " + gs(R.string.insight_min), StatusItem.Highlight.NOTICE));
break;
default:
log("ERROR: unknown bolus type! " + activeBolus.getBolusType());
}
}
// Utility
private synchronized UUID aSyncSingleCommand(final AppLayerMessage msg, final String name) {
// if (!isConnected()) return false;
//if (isBusy()) return false;
log("asyncSinglecommand called: " + name);
final EventInsightPumpCallback event = new EventInsightPumpCallback();
new Thread() {
@Override
public void run() {
log("asyncSingleCommand thread");
final SingleMessageTaskRunner singleMessageTaskRunner = new SingleMessageTaskRunner(connector.getServiceConnector(), msg);
try {
singleMessageTaskRunner.fetch(new TaskRunner.ResultCallback() {
@Override
public void onResult(Object o) {
log(name + " success");
if (o instanceof BolusMessage) {
event.response_id = ((BolusMessage)o).getBolusId();
}
event.success = true;
pushCallbackEvent(event);
}
@Override
public void onError(Exception e) {
log(name + " error");
event.message = e.getMessage();
pushCallbackEvent(event);
}
});
} catch (Exception e) {
log("EXCEPTION" + e.toString());
}
}
}.start();
return event.request_uuid;
}
private synchronized UUID aSyncTaskRunner(final TaskRunner task, final String name) {
// if (!isConnected()) return false;
//if (isBusy()) return false;
log("asyncTaskRunner called: " + name);
final EventInsightPumpCallback event = new EventInsightPumpCallback();
new Thread() {
@Override
public void run() {
log("asyncTaskRunner thread");
try {
task.fetch(new TaskRunner.ResultCallback() {
@Override
public void onResult(Object o) {
log(name + " success");
event.success = true;
pushCallbackEvent(event);
}
@Override
public void onError(Exception e) {
log(name + " error");
event.message = e.getMessage();
pushCallbackEvent(event);
}
});
} catch (Exception e) {
log("EXCEPTION" + e.toString());
}
}
}.start();
return event.request_uuid;
}
private PumpEnactResult pumpEnactFailure() {
return new PumpEnactResult().success(false).enacted(false);
}
}

View file

@ -0,0 +1,60 @@
package info.nightscout.androidaps.plugins.PumpInsight.connector;
import info.nightscout.androidaps.plugins.PumpInsight.utils.Helpers;
import sugar.free.sightparser.applayer.messages.AppLayerMessage;
import sugar.free.sightparser.applayer.messages.remote_control.ChangeTBRMessage;
import sugar.free.sightparser.applayer.messages.remote_control.SetTBRMessage;
import sugar.free.sightparser.applayer.messages.status.CurrentBasalMessage;
import sugar.free.sightparser.applayer.messages.status.CurrentTBRMessage;
import sugar.free.sightparser.handling.SightServiceConnector;
import sugar.free.sightparser.handling.TaskRunner;
// by Tebbe Ubben
public class AbsoluteTBRTaskRunner extends TaskRunner {
private double absolute;
private int amount;
private int duration;
private int calculated_percentage;
private double calculated_absolute;
public AbsoluteTBRTaskRunner(SightServiceConnector serviceConnector, double absolute, int duration) {
super(serviceConnector);
if (absolute < 0) absolute = 0;
this.absolute = absolute;
this.duration = duration;
}
public int getCalculatedPercentage() {
return calculated_percentage;
}
public double getCalculatedAbsolute() {
return calculated_absolute;
}
@Override
protected AppLayerMessage run(AppLayerMessage message) throws Exception {
if (message == null) return new CurrentBasalMessage();
else if (message instanceof CurrentBasalMessage) {
float currentBasal = ((CurrentBasalMessage) message).getCurrentBasalAmount();
amount = (int) (100d / currentBasal * absolute);
amount = ((int) amount / 10) * 10;
if (amount > 250) amount = 250;
calculated_percentage = amount;
calculated_absolute = Helpers.roundDouble(calculated_percentage * (double) currentBasal / 100d, 3);
Connector.log("Asked: " + absolute + " current: " + currentBasal + " calculated as: " + amount + "%" + " = " + calculated_absolute);
return new CurrentTBRMessage();
} else if (message instanceof CurrentTBRMessage) {
SetTBRMessage setTBRMessage;
if (((CurrentTBRMessage) message).getPercentage() == 100)
setTBRMessage = new SetTBRMessage();
else setTBRMessage = new ChangeTBRMessage();
setTBRMessage.setAmount((short) amount);
setTBRMessage.setDuration((short) duration);
return setTBRMessage;
} else if (message instanceof SetTBRMessage) finish(amount);
return null;
}
}

View file

@ -0,0 +1,38 @@
package info.nightscout.androidaps.plugins.PumpInsight.connector;
import sugar.free.sightparser.applayer.messages.AppLayerMessage;
import sugar.free.sightparser.applayer.descriptors.ActiveBolusType;
import sugar.free.sightparser.applayer.messages.remote_control.CancelBolusMessage;
import sugar.free.sightparser.applayer.messages.status.ActiveBolusesMessage;
import sugar.free.sightparser.handling.SightServiceConnector;
import sugar.free.sightparser.handling.TaskRunner;
// by Tebbe Ubben
public class CancelBolusTaskRunner extends TaskRunner {
private ActiveBolusType bolusType;
public CancelBolusTaskRunner(SightServiceConnector serviceConnector, ActiveBolusType bolusType) {
super(serviceConnector);
this.bolusType = bolusType;
}
@Override
protected AppLayerMessage run(AppLayerMessage message) throws Exception {
if (message == null) return new ActiveBolusesMessage();
else if (message instanceof ActiveBolusesMessage) {
ActiveBolusesMessage bolusesMessage = (ActiveBolusesMessage) message;
CancelBolusMessage cancelBolusMessage = new CancelBolusMessage();
if (bolusesMessage.getBolus1().getBolusType() == bolusType)
cancelBolusMessage.setBolusId(bolusesMessage.getBolus1().getBolusID());
else if (bolusesMessage.getBolus2().getBolusType() == bolusType)
cancelBolusMessage.setBolusId(bolusesMessage.getBolus2().getBolusID());
else if (bolusesMessage.getBolus3().getBolusType() == bolusType)
cancelBolusMessage.setBolusId(bolusesMessage.getBolus3().getBolusID());
else finish(null);
return cancelBolusMessage;
} else if (message instanceof CancelBolusMessage) finish(null);
return null;
}
}

View file

@ -0,0 +1,357 @@
package info.nightscout.androidaps.plugins.PumpInsight.connector;
import android.content.Intent;
import android.os.PowerManager;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.plugins.PumpInsight.events.EventInsightPumpUpdateGui;
import info.nightscout.androidaps.plugins.PumpInsight.history.HistoryReceiver;
import info.nightscout.androidaps.plugins.PumpInsight.history.LiveHistory;
import info.nightscout.androidaps.plugins.PumpInsight.utils.Helpers;
import sugar.free.sightparser.handling.ServiceConnectionCallback;
import sugar.free.sightparser.handling.SightServiceConnector;
import sugar.free.sightparser.handling.StatusCallback;
import sugar.free.sightparser.pipeline.Status;
import static sugar.free.sightparser.handling.HistoryBroadcast.ACTION_START_RESYNC;
import static sugar.free.sightparser.handling.HistoryBroadcast.ACTION_START_SYNC;
import static sugar.free.sightparser.handling.SightService.COMPATIBILITY_VERSION;
/**
* Created by jamorham on 23/01/2018.
*
* Connects to SightRemote app service using SightParser library
*
* SightRemote and SightParser created by Tebbe Ubben
*
* Original proof of concept SightProxy by jamorham
*
*/
public class Connector {
private static final String TAG = "InsightConnector";
private static final String COMPANION_APP_PACKAGE = "sugar.free.sightremote";
private final static long FRESH_MS = 70000;
private static volatile Connector instance;
private static volatile HistoryReceiver historyReceiver;
private volatile SightServiceConnector serviceConnector;
private volatile Status lastStatus = null;
private String compatabilityMessage = null;
private volatile long lastStatusTime = -1;
private volatile long lastContactTime = -1;
private boolean companionAppInstalled = false;
private int serviceReconnects = 0;
private StatusCallback statusCallback = new StatusCallback() {
@Override
public synchronized void onStatusChange(Status status) {
log("Status change: " + status);
lastStatus = status;
lastStatusTime = Helpers.tsl();
if (status == Status.CONNECTED) {
lastContactTime = lastStatusTime;
}
MainApp.bus().post(new EventInsightPumpUpdateGui());
}
};
private ServiceConnectionCallback connectionCallback = new ServiceConnectionCallback() {
@Override
public synchronized void onServiceConnected() {
log("On service connected");
try {
final String remoteVersion = serviceConnector.getRemoteVersion();
if (remoteVersion.equals(COMPATIBILITY_VERSION)) {
serviceConnector.connect();
} else {
log("PROTOCOL VERSION MISMATCH! local: " + COMPATIBILITY_VERSION + " remote: " + remoteVersion);
statusCallback.onStatusChange(Status.INCOMPATIBLE);
compatabilityMessage = gs(R.string.insight_incompatible_compantion_app_we_need_version) + " " + getLocalVersion();
serviceConnector.disconnectFromService();
}
} catch (NullPointerException e) {
log("ERROR: null pointer when trying to connect to pump");
}
statusCallback.onStatusChange(safeGetStatus());
}
@Override
public synchronized void onServiceDisconnected() {
log("Disconnected from service");
if (Helpers.ratelimit("insight-automatic-reconnect", 30)) {
log("Scheduling automatic service reconnection");
Helpers.runOnUiThreadDelayed(new Runnable() {
@Override
public void run() {
init();
}
}, 20000);
}
}
};
private Connector() {
initializeHistoryReceiver();
}
public static Connector get() {
if (instance == null) {
init_instance();
}
return instance;
}
private synchronized static void init_instance() {
if (instance == null) {
instance = new Connector();
}
}
private static boolean isCompanionAppInstalled() {
return Helpers.checkPackageExists(MainApp.instance(), TAG, COMPANION_APP_PACKAGE);
}
public static void connectToPump() {
log("Attempting to connect to pump");
get().getServiceConnector().connect();
}
static void log(String msg) {
android.util.Log.e("INSIGHTPUMP", msg);
}
static String getLocalVersion() {
return COMPATIBILITY_VERSION;
}
private static String statusToString(Status status) {
switch (status) {
case EXCHANGING_KEYS:
return gs(R.string.connecting).toUpperCase();
case WAITING_FOR_CODE_CONFIRMATION:
return gs(R.string.insight_waiting_for_code).toUpperCase();
case CODE_REJECTED:
return gs(R.string.insight_code_rejected).toUpperCase();
case APP_BINDING:
return gs(R.string.insight_app_binding).toUpperCase();
case CONNECTING:
return gs(R.string.connecting).toUpperCase();
case CONNECTED:
return gs(R.string.connected).toUpperCase();
case DISCONNECTED:
return gs(R.string.disconnected).toUpperCase();
case NOT_AUTHORIZED:
return gs(R.string.insight_not_authorized).toUpperCase();
case INCOMPATIBLE:
return gs(R.string.insight_incompatible).toUpperCase();
default:
return status.toString();
}
}
private static String gs(int id) {
return MainApp.instance().getString(id);
}
@SuppressWarnings("AccessStaticViaInstance")
private synchronized void initializeHistoryReceiver() {
if (historyReceiver == null) {
historyReceiver = new HistoryReceiver();
}
historyReceiver.registerHistoryReceiver();
}
public synchronized void init() {
log("Connector::init()");
if (serviceConnector == null) {
companionAppInstalled = isCompanionAppInstalled();
if (companionAppInstalled) {
serviceConnector = new SightServiceConnector(MainApp.instance());
serviceConnector.removeStatusCallback(statusCallback);
serviceConnector.addStatusCallback(statusCallback);
serviceConnector.setConnectionCallback(connectionCallback);
serviceConnector.connectToService();
log("Trying to connect");
} else {
log("Not trying init due to missing companion app");
}
} else {
if (!serviceConnector.isConnectedToService()) {
if (serviceReconnects > 0) {
serviceConnector = null;
init();
} else {
log("Trying to reconnect to service (" + serviceReconnects + ")");
serviceConnector.connectToService();
serviceReconnects++;
}
} else {
serviceReconnects = 0; // everything ok
}
}
}
public SightServiceConnector getServiceConnector() {
init();
return serviceConnector;
}
public String getCurrent() {
init();
return safeGetStatus().toString();
}
public Status safeGetStatus() {
try {
if (isConnected()) return serviceConnector.getStatus();
return Status.DISCONNECTED;
} catch (IllegalArgumentException e) {
return Status.INCOMPATIBLE;
}
}
public Status getLastStatus() {
return lastStatus;
}
public boolean isConnected() {
return serviceConnector != null && serviceConnector.isConnectedToService();
}
public boolean isPumpConnected() {
return isConnected() && getLastStatus() == Status.CONNECTED;
}
public boolean isPumpConnecting() {
return isConnected() && getLastStatus() == Status.CONNECTING;
}
public long getLastContactTime() {
return lastContactTime;
}
public String getLastStatusMessage() {
if (!companionAppInstalled) {
return gs(R.string.insight_companion_app_not_installed);
}
if (!isConnected()) {
log("Not connected to companion");
if (Helpers.ratelimit("insight-app-not-connected", 5)) {
init();
}
if ((lastStatus == null) || (lastStatus != Status.INCOMPATIBLE)) {
if (compatabilityMessage != null) {
// if disconnected but previous state was incompatible
return compatabilityMessage;
} else {
return gs(R.string.insight_not_connected_to_companion_app);
}
}
}
if (lastStatus == null) {
return gs(R.string.insight_unknown);
}
switch (lastStatus) {
case CONNECTED:
if (Helpers.msSince(lastStatusTime) > (60 * 10 * 1000)) {
tryToGetPumpStatusAgain();
}
break;
case INCOMPATIBLE:
return statusToString(lastStatus) + " " + gs(R.string.insight_needs) + " " + getLocalVersion();
}
return statusToString(lastStatus);
}
public String getNiceLastStatusTime() {
if (lastStatusTime < 1) {
return gs(R.string.insight_startup_uppercase);
} else {
return Helpers.niceTimeScalar(Helpers.msSince(lastStatusTime)) + " " + gs(R.string.ago);
}
}
public boolean uiFresh() {
// todo check other changes
if (Helpers.msSince(lastStatusTime) < FRESH_MS) {
return true;
}
if (Helpers.msSince(LiveHistory.getStatusTime()) < FRESH_MS) {
return true;
}
return false;
}
@SuppressWarnings("AccessStaticViaInstance")
public void tryToGetPumpStatusAgain() {
if (Helpers.ratelimit("insight-retry-status-request", 5)) {
try {
MainApp.getConfigBuilder().getCommandQueue().readStatus("Insight. Status missing", null);
} catch (NullPointerException e) {
//
}
}
}
public void requestHistorySync() {
requestHistorySync(0);
}
public void requestHistoryReSync() {
requestHistoryReSync(0);
}
public void requestHistorySync(long delay) {
if (Helpers.ratelimit("insight-history-sync-request", 10)) {
final Intent intent = new Intent(ACTION_START_SYNC);
sendBroadcastToCompanion(intent, delay);
}
}
public void requestHistoryReSync(long delay) {
if (Helpers.ratelimit("insight-history-resync-request", 300)) {
final Intent intent = new Intent(ACTION_START_RESYNC);
sendBroadcastToCompanion(intent, delay);
}
}
private void sendBroadcastToCompanion(final Intent intent, final long delay) {
new Thread(new Runnable() {
@Override
public void run() {
final PowerManager.WakeLock wl = Helpers.getWakeLock("insight-companion-delay", 60000);
intent.setPackage(COMPANION_APP_PACKAGE);
intent.setFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
try {
if (delay > 0) {
Thread.sleep(delay);
}
} catch (InterruptedException e) {
//
} finally {
Helpers.releaseWakeLock(wl);
}
MainApp.instance().sendBroadcast(intent);
}
}).start();
}
public boolean lastStatusRecent() {
return true; // TODO evaluate whether current
}
}

View file

@ -0,0 +1,26 @@
package info.nightscout.androidaps.plugins.PumpInsight.events;
import java.util.UUID;
import info.nightscout.androidaps.events.Event;
/**
* Created by jamorham on 23/01/2018.
*/
public class EventInsightPumpCallback extends Event {
public UUID request_uuid;
public boolean success = false;
public String message = null;
public int response_id = -1;
public EventInsightPumpCallback() {
request_uuid = UUID.randomUUID();
}
@Override
public String toString() {
return "Event: " + request_uuid + " success: " + success + " msg: " + message;
}
}

View file

@ -0,0 +1,9 @@
package info.nightscout.androidaps.plugins.PumpInsight.events;
import info.nightscout.androidaps.events.EventUpdateGui;
/**
* Created by jamorham on 23/01/2018.
*/
public class EventInsightPumpUpdateGui extends EventUpdateGui {
}

View file

@ -0,0 +1,104 @@
package info.nightscout.androidaps.plugins.PumpInsight.history;
import android.content.Intent;
import java.util.Date;
import info.nightscout.utils.SP;
import sugar.free.sightparser.handling.HistoryBroadcast;
import static info.nightscout.androidaps.plugins.PumpInsight.history.PumpIdCache.updatePumpSerialNumber;
/**
* Created by jamorham on 27/01/2018.
*
* Parse inbound logbook intents
*
*/
class HistoryIntentAdapter {
private HistoryLogAdapter logAdapter = new HistoryLogAdapter();
private static Date getDateExtra(Intent intent, String name) {
return (Date) intent.getSerializableExtra(name);
}
private static void log(String msg) {
android.util.Log.e("HistoryIntentAdapter", msg);
}
static long getRecordUniqueID(long pump_serial_number, long pump_record_id) {
updatePumpSerialNumber(pump_serial_number);
return (pump_serial_number * 10000000) + pump_record_id;
}
void processTBRIntent(Intent intent) {
final int pump_tbr_duration = intent.getIntExtra(HistoryBroadcast.EXTRA_DURATION, -1);
final int pump_tbr_percent = intent.getIntExtra(HistoryBroadcast.EXTRA_TBR_AMOUNT, -1);
final int pump_record_id = intent.getIntExtra(HistoryBroadcast.EXTRA_EVENT_NUMBER, -1);
final long pump_serial_number = Long.parseLong(intent.getStringExtra(HistoryBroadcast.EXTRA_PUMP_SERIAL_NUMBER));
final Date event_time = getDateExtra(intent, HistoryBroadcast.EXTRA_EVENT_TIME);
final Date start_time = getDateExtra(intent, HistoryBroadcast.EXTRA_START_TIME);
if ((pump_tbr_duration == -1) || (pump_tbr_percent == -1) || (pump_record_id == -1)) {
log("Invalid TBR record!!!");
return;
}
final long record_unique_id = getRecordUniqueID(pump_serial_number, pump_record_id);
// other sanity checks
log("Creating TBR record: " + pump_tbr_percent + "% " + pump_tbr_duration + "m" + " id:" + record_unique_id);
logAdapter.createTBRrecord(start_time, pump_tbr_percent, pump_tbr_duration, record_unique_id);
}
void processDeliveredBolusIntent(Intent intent) {
final String bolus_type = intent.getStringExtra(HistoryBroadcast.EXTRA_BOLUS_TYPE);
final int bolus_id = intent.getIntExtra(HistoryBroadcast.EXTRA_BOLUS_ID,-1);
final int pump_record_id = intent.getIntExtra(HistoryBroadcast.EXTRA_EVENT_NUMBER, -1);
final long pump_serial_number = Long.parseLong(intent.getStringExtra(HistoryBroadcast.EXTRA_PUMP_SERIAL_NUMBER));
final Date event_time = getDateExtra(intent, HistoryBroadcast.EXTRA_EVENT_TIME);
final Date start_time = getDateExtra(intent, HistoryBroadcast.EXTRA_START_TIME);
final float immediate_amount = intent.getFloatExtra(HistoryBroadcast.EXTRA_IMMEDIATE_AMOUNT, -1);
final float extended_insulin = intent.getFloatExtra(HistoryBroadcast.EXTRA_EXTENDED_AMOUNT, -1);
final int extended_minutes = intent.getIntExtra(HistoryBroadcast.EXTRA_DURATION, -1);
final long record_unique_id = getRecordUniqueID(pump_serial_number, bolus_id > -1 ? bolus_id : pump_record_id);
switch (bolus_type) {
case "STANDARD":
if (immediate_amount == -1) {
log("ERROR Standard bolus fails sanity check");
return;
}
LiveHistory.setStatus(bolus_type + " BOLUS\n" + immediate_amount + "U ", event_time.getTime());
logAdapter.createStandardBolusRecord(start_time, immediate_amount, record_unique_id);
break;
case "EXTENDED":
if ((extended_insulin == -1) || (extended_minutes == -1)) {
log("ERROR: Extended bolus fails sanity check");
return;
}
LiveHistory.setStatus(bolus_type + " BOLUS\n" + extended_insulin + "U over " + extended_minutes + " min, ", event_time.getTime());
logAdapter.createExtendedBolusRecord(start_time, extended_insulin, extended_minutes, record_unique_id);
break;
case "MULTIWAVE":
if ((immediate_amount == -1) || (extended_insulin == -1) || (extended_minutes == -1)) {
log("ERROR: Multiwave bolus fails sanity check");
return;
}
LiveHistory.setStatus(bolus_type + " BOLUS\n" + immediate_amount + "U + " + extended_insulin + "U over " + extended_minutes + " min, ", event_time.getTime());
logAdapter.createStandardBolusRecord(start_time, immediate_amount, pump_serial_number + pump_record_id);
logAdapter.createExtendedBolusRecord(start_time, extended_insulin, extended_minutes, record_unique_id);
break;
default:
log("ERROR, UNKNWON BOLUS TYPE: " + bolus_type);
}
}
}

View file

@ -0,0 +1,57 @@
package info.nightscout.androidaps.plugins.PumpInsight.history;
import java.util.Date;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.data.DetailedBolusInfo;
import info.nightscout.androidaps.db.ExtendedBolus;
import info.nightscout.androidaps.db.Source;
import info.nightscout.androidaps.db.TemporaryBasal;
/**
* Created by jamorham on 27/01/2018.
*
* Write to the History Log
*
*/
class HistoryLogAdapter {
void createTBRrecord(Date eventDate, int percent, int duration, long record_id) {
final TemporaryBasal temporaryBasal = new TemporaryBasal();
temporaryBasal.date = eventDate.getTime();
temporaryBasal.source = Source.PUMP;
temporaryBasal.pumpId = record_id;
temporaryBasal.percentRate = percent;
temporaryBasal.durationInMinutes = duration;
MainApp.getConfigBuilder().addToHistoryTempBasal(temporaryBasal);
}
void createExtendedBolusRecord(Date eventDate, double insulin, int durationInMinutes, long record_id) {
// TODO trap items below minimum period
final ExtendedBolus extendedBolus = new ExtendedBolus();
extendedBolus.date = eventDate.getTime();
extendedBolus.insulin = insulin;
extendedBolus.durationInMinutes = durationInMinutes;
extendedBolus.source = Source.PUMP;
extendedBolus.pumpId = record_id;
MainApp.getConfigBuilder().addToHistoryExtendedBolus(extendedBolus);
}
void createStandardBolusRecord(Date eventDate, double insulin, long record_id) {
//DetailedBolusInfo detailedBolusInfo = DetailedBolusInfoStorage.findDetailedBolusInfo(eventDate.getTime());
final DetailedBolusInfo detailedBolusInfo = new DetailedBolusInfo();
detailedBolusInfo.date = eventDate.getTime();
detailedBolusInfo.source = Source.PUMP;
detailedBolusInfo.pumpId = record_id;
detailedBolusInfo.insulin = insulin;
MainApp.getConfigBuilder().addToHistoryTreatment(detailedBolusInfo);
}
}

View file

@ -0,0 +1,121 @@
package info.nightscout.androidaps.plugins.PumpInsight.history;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import static info.nightscout.androidaps.plugins.PumpInsight.history.HistoryReceiver.Status.BUSY;
import static info.nightscout.androidaps.plugins.PumpInsight.history.HistoryReceiver.Status.SYNCED;
import static info.nightscout.androidaps.plugins.PumpInsight.history.HistoryReceiver.Status.SYNCING;
import static sugar.free.sightparser.handling.HistoryBroadcast.ACTION_BOLUS_DELIVERED;
import static sugar.free.sightparser.handling.HistoryBroadcast.ACTION_BOLUS_PROGRAMMED;
import static sugar.free.sightparser.handling.HistoryBroadcast.ACTION_END_OF_TBR;
import static sugar.free.sightparser.handling.HistoryBroadcast.ACTION_PUMP_STATUS_CHANGED;
import static sugar.free.sightparser.handling.HistoryBroadcast.ACTION_STILL_SYNCING;
import static sugar.free.sightparser.handling.HistoryBroadcast.ACTION_SYNC_FINISHED;
import static sugar.free.sightparser.handling.HistoryBroadcast.ACTION_SYNC_STARTED;
/**
* Created by jamorham on 27/01/2018.
*/
public class HistoryReceiver {
private static BroadcastReceiver historyReceiver;
private volatile static Status status = Status.IDLE;
private volatile HistoryIntentAdapter intentAdapter;
public HistoryReceiver() {
initializeHistoryReceiver();
}
public static synchronized void registerHistoryReceiver() {
try {
MainApp.instance().unregisterReceiver(historyReceiver);
} catch (Exception e) {
//
}
final IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_PUMP_STATUS_CHANGED);
filter.addAction(ACTION_BOLUS_PROGRAMMED);
filter.addAction(ACTION_BOLUS_DELIVERED);
filter.addAction(ACTION_END_OF_TBR);
filter.addAction(ACTION_SYNC_STARTED);
filter.addAction(ACTION_STILL_SYNCING);
filter.addAction(ACTION_SYNC_FINISHED);
MainApp.instance().registerReceiver(historyReceiver, filter);
}
// History
private static void log(String msg) {
android.util.Log.e("INSIGHTPUMPHR", msg);
}
public static String getStatusString() {
return status.toString();
}
private synchronized void initializeHistoryReceiver() {
historyReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, final Intent intent) {
final String action = intent.getAction();
if (action == null) return;
if (intentAdapter == null) {
synchronized (this) {
if (intentAdapter == null) {
intentAdapter = new HistoryIntentAdapter();
}
}
}
switch (action) {
case ACTION_SYNC_STARTED:
status = SYNCING;
break;
case ACTION_STILL_SYNCING:
status = BUSY;
break;
case ACTION_SYNC_FINISHED:
status = SYNCED;
break;
case ACTION_BOLUS_DELIVERED:
intentAdapter.processDeliveredBolusIntent(intent);
break;
case ACTION_END_OF_TBR:
intentAdapter.processTBRIntent(intent);
break;
}
}
};
}
enum Status {
IDLE(R.string.insight_history_idle),
SYNCING(R.string.insight_history_syncing),
BUSY(R.string.insight_history_busy),
SYNCED(R.string.insight_history_synced);
private final int string_id;
Status(int string_id) {
this.string_id = string_id;
}
@Override
public String toString() {
return MainApp.instance().getString(string_id);
}
}
}

View file

@ -0,0 +1,37 @@
package info.nightscout.androidaps.plugins.PumpInsight.history;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.plugins.PumpInsight.utils.Helpers;
/**
* Created by jamorham on 27/01/2018.
*
* In memory status storage class
*/
public class LiveHistory {
private static String status = "";
private static long status_time = -1;
public static String getStatus() {
if (status.equals("")) return status;
return status + " " + Helpers.niceTimeScalar(Helpers.msSince(status_time)) + " " + gs(R.string.ago);
}
public static long getStatusTime() {
return status_time;
}
static void setStatus(String mystatus, long eventtime) {
if (eventtime > status_time) {
status_time = eventtime;
status = mystatus;
}
}
private static String gs(int id) {
return MainApp.instance().getString(id);
}
}

View file

@ -0,0 +1,33 @@
package info.nightscout.androidaps.plugins.PumpInsight.history;
import info.nightscout.utils.SP;
/**
* Created by jamorham on 01/02/2018.
*/
public class PumpIdCache {
private static final String INSIGHT_PUMP_ID_PREF = "insight-pump-id";
private static long cachedPumpSerialNumber = -1;
private static void log(String msg) {
android.util.Log.e("PumpIdCache", msg);
}
static void updatePumpSerialNumber(long pump_serial_number) {
if (pump_serial_number != cachedPumpSerialNumber) {
cachedPumpSerialNumber = pump_serial_number;
log("Updating pump serial number: " + pump_serial_number);
SP.putLong(INSIGHT_PUMP_ID_PREF, cachedPumpSerialNumber);
}
}
public static long getRecordUniqueID(long record_id) {
if (cachedPumpSerialNumber == -1) {
cachedPumpSerialNumber = SP.getLong(INSIGHT_PUMP_ID_PREF, 0L);
}
return HistoryIntentAdapter.getRecordUniqueID(cachedPumpSerialNumber, record_id);
}
}

View file

@ -0,0 +1,172 @@
package info.nightscout.androidaps.plugins.PumpInsight.utils;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.PowerManager;
import android.util.Log;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.HashMap;
import java.util.Map;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
/**
* Created by jamorham on 24/01/2018.
*
* Useful utility methods from xDrip+
*
*/
public class Helpers {
private static final String TAG = "InsightHelpers";
private static final Map<String, Long> rateLimits = new HashMap<>();
// singletons to avoid repeated allocation
private static DecimalFormatSymbols dfs;
private static DecimalFormat df;
// return true if below rate limit
public static synchronized boolean ratelimit(String name, int seconds) {
// check if over limit
if ((rateLimits.containsKey(name)) && (tsl() - rateLimits.get(name) < (seconds * 1000))) {
Log.d(TAG, name + " rate limited: " + seconds + " seconds");
return false;
}
// not over limit
rateLimits.put(name, tsl());
return true;
}
public static long tsl() {
return System.currentTimeMillis();
}
public static long msSince(long when) {
return (tsl() - when);
}
public static long msTill(long when) {
return (when - tsl());
}
public static boolean checkPackageExists(Context context, String TAG, String packageName) {
try {
final PackageManager pm = context.getPackageManager();
final PackageInfo pi = pm.getPackageInfo(packageName, 0);
return pi.packageName.equals(packageName);
} catch (PackageManager.NameNotFoundException e) {
return false;
} catch (Exception e) {
Log.wtf(TAG, "Exception trying to determine packages! " + e);
return false;
}
}
public static boolean runOnUiThreadDelayed(Runnable theRunnable, long delay) {
return new Handler(MainApp.instance().getMainLooper()).postDelayed(theRunnable, delay);
}
public static PowerManager.WakeLock getWakeLock(final String name, int millis) {
final PowerManager pm = (PowerManager) MainApp.instance().getSystemService(Context.POWER_SERVICE);
if (pm == null) return null;
final PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name);
wl.acquire(millis);
return wl;
}
public static void releaseWakeLock(PowerManager.WakeLock wl) {
if (wl == null) return;
if (wl.isHeld()) wl.release();
}
public static String niceTimeSince(long t) {
return niceTimeScalar(msSince(t));
}
public static String niceTimeTill(long t) {
return niceTimeScalar(-msSince(t));
}
public static String niceTimeScalar(long t) {
String unit = gs(R.string.second);
t = t / 1000;
if (t > 59) {
unit = gs(R.string.minute);
t = t / 60;
if (t > 59) {
unit = gs(R.string.hour);
t = t / 60;
if (t > 24) {
unit = gs(R.string.day);
t = t / 24;
if (t > 28) {
unit = gs(R.string.week);
t = t / 7;
}
}
}
}
if (t != 1) unit = unit + gs(R.string.time_plural);
return qs((double) t, 0) + " " + unit;
}
private static String gs(int id) {
return MainApp.instance().getString(id);
}
public static String qs(double x, int digits) {
if (digits == -1) {
digits = 0;
if (((int) x != x)) {
digits++;
if ((((int) x * 10) / 10 != x)) {
digits++;
if ((((int) x * 100) / 100 != x)) digits++;
}
}
}
if (dfs == null) {
final DecimalFormatSymbols local_dfs = new DecimalFormatSymbols();
local_dfs.setDecimalSeparator('.');
dfs = local_dfs; // avoid race condition
}
final DecimalFormat this_df;
// use singleton if on ui thread otherwise allocate new as DecimalFormat is not thread safe
if (Thread.currentThread().getId() == 1) {
if (df == null) {
final DecimalFormat local_df = new DecimalFormat("#", dfs);
local_df.setMinimumIntegerDigits(1);
df = local_df; // avoid race condition
}
this_df = df;
} else {
this_df = new DecimalFormat("#", dfs);
}
this_df.setMaximumFractionDigits(digits);
return this_df.format(x);
}
public static String niceTimeScalarRedux(long t) {
return niceTimeScalar(t).replaceFirst("^1 ", "");
}
public static double roundDouble(double value, int places) {
if (places < 0) throw new IllegalArgumentException("Invalid decimal places");
BigDecimal bd = new BigDecimal(value);
bd = bd.setScale(places, RoundingMode.HALF_UP);
return bd.doubleValue();
}
}

View file

@ -0,0 +1,64 @@
package info.nightscout.androidaps.plugins.PumpInsight.utils;
/**
* Created by jamorham on 26/01/2018.
*
* For representing row status items
*/
public class StatusItem {
public enum Highlight {
NORMAL,
GOOD,
BAD,
NOTICE,
CRITICAL
}
public String name;
public String value;
public Highlight highlight;
public String button_name;
public Runnable runnable;
public StatusItem(String name, String value) {
this(name, value, Highlight.NORMAL);
}
public StatusItem() {
this("line-break", "", Highlight.NORMAL);
}
public StatusItem(String name, Highlight highlight) {
this("heading-break", name, highlight);
}
public StatusItem(String name, Runnable runnable) {
this("button-break", "", Highlight.NORMAL, name, runnable);
}
public StatusItem(String name, String value, Highlight highlight) {
this(name, value, highlight, null, null);
}
public StatusItem(String name, String value, Highlight highlight, String button_name, Runnable runnable) {
this.name = name;
this.value = value;
this.highlight = highlight;
this.button_name = button_name;
this.runnable = runnable;
}
public StatusItem(String name, Integer value) {
this(name, value, Highlight.NORMAL);
}
public StatusItem(String name, Integer value, Highlight highlight) {
this.name = name;
this.value = Integer.toString(value);
this.highlight = highlight;
}
}

View file

@ -0,0 +1,85 @@
package info.nightscout.androidaps.plugins.PumpInsight.utils.ui;
import android.app.Activity;
import android.graphics.Color;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.plugins.PumpInsight.utils.StatusItem;
/**
* Created by jamorham on 26/01/2018.
*
* Convert StatusItem to View
*/
public class StatusItemViewAdapter {
private final Activity activity;
private final ViewGroup holder;
public StatusItemViewAdapter(Activity activity, ViewGroup holder) {
this.activity = activity;
this.holder = holder;
}
public View inflateStatus(StatusItem statusItem) {
if (activity == null) return null;
final View child = activity.getLayoutInflater().inflate(R.layout.insightpump_statuselements, null);
final TextView name = (TextView) child.findViewById(R.id.insightstatuslabel);
final TextView value = (TextView)child.findViewById(R.id.insightstatusvalue);
final TextView spacer = (TextView)child.findViewById(R.id.insightstatusspacer);
final LinearLayout layout = (LinearLayout)child.findViewById(R.id.insightstatuslayout);
if (statusItem.name.equals("line-break")) {
spacer.setVisibility(View.GONE);
name.setVisibility(View.GONE);
value.setVisibility(View.GONE);
layout.setPadding(10, 10, 10, 10);
} else if (statusItem.name.equals("heading-break")) {
value.setVisibility(View.GONE);
spacer.setVisibility(View.GONE);
name.setText(statusItem.value);
name.setGravity(Gravity.CENTER_HORIZONTAL);
name.setTextColor(Color.parseColor("#fff9c4"));
} else {
name.setText(statusItem.name);
value.setText(statusItem.value);
}
final int this_color = getHighlightColor(statusItem);
name.setBackgroundColor(this_color);
value.setBackgroundColor(this_color);
spacer.setBackgroundColor(this_color);
if (this_color != Color.TRANSPARENT) {
name.setTextColor(Color.WHITE);
spacer.setTextColor(Color.WHITE);
}
if (holder != null) {
holder.addView(child);
}
return child;
}
private static int getHighlightColor(StatusItem row) {
switch (row.highlight) {
case BAD:
return Color.parseColor("#480000");
case NOTICE:
return Color.parseColor("#403000");
case GOOD:
return Color.parseColor("#003000");
case CRITICAL:
return Color.parseColor("#770000");
default:
return Color.TRANSPARENT;
}
}
}

View file

@ -0,0 +1,37 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="info.nightscout.androidaps.plugins.PumpInsight.InsightPumpFragment">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/insighttopholder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="10dp"
android:layout_marginBottom="15dp"
android:text="@string/insightpump"
android:textAppearance="?android:attr/textAppearanceLarge" />
<LinearLayout
android:id="@+id/insightholder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>
</LinearLayout>
</ScrollView>
</FrameLayout>

View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/insightstatusmasterlayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<LinearLayout
android:id="@+id/insightstatuslayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingBottom="5dp">
<TextView
android:id="@+id/insightstatuslabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="end"
android:paddingRight="5dp"
android:text="Label"
android:textSize="14sp" />
<TextView
android:id="@+id/insightstatusspacer"
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:textColor="@android:color/white"
android:textSize="14sp" />
<TextView
android:id="@+id/insightstatusvalue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:paddingLeft="5dp"
android:text="Value"
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="0dp"
android:background="@color/listdelimiter" />
</LinearLayout>

View file

@ -861,5 +861,45 @@
<string name="combo_error_bolus_recovery_progress">Recovering from connection loss</string>
<string name="combo_reservoir_level_insufficient_for_bolus">Not enough insulin for bolus left in reservoir</string>
<string name="extendedbolusdeliveryerror">Extended bolus delivery error</string>
<string name="insightpump_shortname">Insight</string>
<string name="insightpump">Insight Pump</string>
<string name="status_no_colon">Status</string>
<string name="changed">Changed</string>
<string name="pump_stopped_uppercase">PUMP STOPPED</string>
<string name="status_updated">Status Updated</string>
<string name="ago">ago</string>
<string name="with">with</string>
<string name="insight_active_tbr">Active TBR</string>
<string name="insight_min_left">min left</string>
<string name="log_book">Log book</string>
<string name="insight_last_completed_action">Last Completed Action</string>
<string name="insight_min">min</string>
<string name="insight_remaining_over">remaining over</string>
<string name="insight_total_with">total with</string>
<string name="insight_upfront_with">upfront with</string>
<string name="insight_stay_always_connected">Stay always connected</string>
<string name="insight_use_real_tbr_cancels">Use Real TBR cancels</string>
<string name="insight_actually_cancel_tbr_summary">Actually cancel a TBR (creates pump alarm) instead of setting 90% or 110% TBR for 15 minutes</string>
<string name="insight_history_idle">IDLE</string>
<string name="insight_history_syncing">SYNCING</string>
<string name="insight_history_busy">BUSY</string>
<string name="insight_history_synced">SYNCED</string>
<string name="insight_startup_uppercase">STARTUP</string>
<string name="insight_needs">needs</string>
<string name="insight_not_connected_to_companion_app">Not connected to companion app!</string>
<string name="insight_companion_app_not_installed">Companion app does not appear to be installed!</string>
<string name="insight_incompatible_compantion_app_we_need_version">Incompatible companion app, we need version</string>
<string name="insight_unknown">Unknown</string>
<string name="insight_waiting_for_code">Waiting for code confirmation</string>
<string name="insight_code_rejected">Code rejected</string>
<string name="insight_app_binding">App binding</string>
<string name="insight_not_authorized">Not authorized</string>
<string name="insight_incompatible">Incompatible</string>
<string name="second">second</string>
<string name="minute">minute</string>
<string name="hour">hour</string>
<string name="day">day</string>
<string name="week">week</string>
<string name="time_plural">s</string>
</resources>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:key="insightpump"
android:title="@string/insightpump">
</PreferenceCategory>
<SwitchPreference
android:defaultValue="false"
android:key="insight_always_connected"
android:title="@string/insight_stay_always_connected" />
<SwitchPreference
android:defaultValue="false"
android:key="insight_real_tbr_cancel"
android:title="@string/insight_use_real_tbr_cancels"
android:summary="@string/insight_actually_cancel_tbr_summary"/>
</PreferenceScreen >

View file

@ -0,0 +1,31 @@
package info.nightscout.androidaps.plugins.PumpInsight;
import com.google.common.truth.Truth;
import org.junit.Test;
import static info.nightscout.androidaps.plugins.PumpInsight.utils.Helpers.roundDouble;
/**
* Created by jamorham on 26.01.2018.
*/
public class HelpersTest {
@Test
public void checkRounding() throws Exception {
// TODO more test cases including known precision breakdowns
Truth.assertThat(roundDouble(Double.parseDouble("0.999999"),0))
.isEqualTo(1d);
Truth.assertThat(roundDouble(Double.parseDouble("0.123456"),0))
.isEqualTo(0d);
}
}