diff --git a/app/build.gradle b/app/build.gradle
index 13710f8..a8b8c6d 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -51,8 +51,15 @@ def getBugfenderApiKey() {
String bugfenderApiKey = System.getenv("BUGFENDER_API_KEY")
if(bugfenderApiKey == null) {
- logger.warn("Bugfender API key not set")
- bugfenderApiKey = ""
+ File file = new File("app/bugfender.properties")
+ if (file.exists()) {
+ Properties properties = new Properties()
+ properties.load(new FileInputStream(file.getAbsolutePath().toString()))
+ bugfenderApiKey = properties.getProperty("apiKey", "")
+ } else {
+ logger.warn("Bugfender API key not set")
+ bugfenderApiKey = ""
+ }
}
return "\"" + bugfenderApiKey + "\""
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 3e5115f..a4320cc 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -80,6 +80,10 @@
android:name=".medtronic.service.MedtronicCnlIntentService"
android:icon="@drawable/ic_launcher" />
+
+
diff --git a/app/src/main/java/info/nightscout/android/LockScreenNotification.java b/app/src/main/java/info/nightscout/android/LockScreenNotification.java
new file mode 100644
index 0000000..8430b8f
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/LockScreenNotification.java
@@ -0,0 +1,241 @@
+package info.nightscout.android;
+
+import android.app.AlarmManager;
+import android.app.IntentService;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.support.v4.app.TaskStackBuilder;
+import android.support.v7.app.NotificationCompat;
+import android.util.Log;
+import android.widget.RemoteViews;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
+
+import info.nightscout.android.medtronic.MainActivity;
+import info.nightscout.android.model.medtronicNg.PumpStatusEvent;
+import info.nightscout.android.utils.ConfigurationStore;
+import info.nightscout.android.utils.DataStore;
+import info.nightscout.api.DeviceEndpoints;
+import io.realm.Realm;
+import io.realm.RealmResults;
+import io.realm.Sort;
+
+
+// Updates most important values to the lock screen through a constant notification
+// updates once per minute if screen is on or always if there's new info from pump
+public class LockScreenNotification extends IntentService {
+
+ private DataStore dataStore = DataStore.getInstance();
+ private ConfigurationStore configurationStore = ConfigurationStore.getInstance();
+
+ //helper for testing layouts during dev
+ private boolean fakeValues = false;
+
+ public LockScreenNotification() {
+ super("Notifications");
+ Log.i("Notifications", "Running Notifications Intent Service");
+ }
+
+ //Ref: https://thinkandroid.wordpress.com/2010/01/24/handling-screen-off-and-screen-on-intents/
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ // REGISTER RECEIVER THAT HANDLES SCREEN ON AND SCREEN OFF LOGIC
+ IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ BroadcastReceiver mReceiver = new ScreenReceiver();
+ registerReceiver(mReceiver, filter);
+ }
+
+ //https://thinkandroid.wordpress.com/2010/01/24/handling-screen-off-and-screen-on-intents/
+ @Override
+ public void onStart(Intent intent, int startId) {
+ if (intent.hasExtra("screenOn")) {
+ dataStore.setScreenOn(intent.getBooleanExtra("screenOn", false));
+ }
+ Log.d("Notifications", "ScreenOn=" + dataStore.isScreenOn());
+ onHandleIntent(intent);
+ }
+
+
+ public void updateNotification() {
+ final int NOTIFICATION_ID = 1;
+ NotificationManager mNotificationManager =
+ (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+
+
+
+ int svg = 0;
+ float iob = 0;
+ long timeLastGoodSGV = 0;
+ PumpStatusEvent.CGM_TREND trend = PumpStatusEvent.CGM_TREND.NONE;
+
+ if (fakeValues) {
+ svg = (int) (Math.random() * 20 * 18);
+ iob = (float) Math.floor((float) Math.random() * 40) / 10f;
+ timeLastGoodSGV = System.currentTimeMillis() - ((int) (Math.random() * 20 * 1000 * 60));
+ }else {
+ Realm mRealm = Realm.getDefaultInstance();
+ // most recent sgv status
+ RealmResults sgv_results =
+ mRealm.where(PumpStatusEvent.class)
+ .equalTo("validSGV", true)
+ .findAllSorted("cgmDate", Sort.ASCENDING);
+ if (sgv_results.size() > 0) {
+ PumpStatusEvent lastEvent = sgv_results.last();
+ svg = lastEvent.getSgv();
+ iob = lastEvent.getActiveInsulin();
+ timeLastGoodSGV = lastEvent.getCgmDate().getTime();
+ trend = lastEvent.getCgmTrend();
+ }
+ }
+
+
+ long age = System.currentTimeMillis() - timeLastGoodSGV;
+ boolean valid = timeLastGoodSGV > 0 && (age < TimeUnit.MINUTES.toMillis(15));
+
+
+ RemoteViews contentView = new RemoteViews(getPackageName(), R.layout.custom_notification);
+ final int COLOR_WARN = getResources().getColor(R.color.md_deep_orange_800);
+ final int COLOR_INVALID = getResources().getColor(R.color.md_grey_500);
+ final int COLOR_OK = getResources().getColor(R.color.md_green_800);
+
+
+ //default values
+ String svgStr = " -- ";
+ String iobStr = " -- ";
+ String timeStr = ">15";
+ String trendStr = " ";
+
+ if (!valid) {
+ //update colors
+ contentView.setTextColor(R.id.blood, COLOR_INVALID);
+ contentView.setTextColor(R.id.bloodtrend, COLOR_INVALID);
+ contentView.setTextColor(R.id.time, COLOR_WARN);
+ contentView.setTextColor(R.id.iob, COLOR_INVALID);
+ } else {
+
+ //update values
+ svgStr = StringUtils.leftPad(MainActivity.strFormatSGV(svg), 5);
+ trendStr = renderTrendSymbol(trend);
+ iobStr = StringUtils.leftPad(String.format(Locale.getDefault(), "%.2f", iob) + "U", 5);
+ timeStr = StringUtils.leftPad("" + Math.round(TimeUnit.MILLISECONDS.toMinutes(age)), 2);
+
+ //update colors
+ contentView.setTextColor(R.id.time, COLOR_OK);
+ contentView.setTextColor(R.id.iob, COLOR_OK);
+ contentView.setTextColor(R.id.blood, COLOR_OK);
+ contentView.setTextColor(R.id.bloodtrend, COLOR_OK);
+
+ if (svg > 216 || svg < 76) {
+ //high sugar (>12mmolx)
+ //low sugar (<4.2mmolx)
+ contentView.setTextColor(R.id.blood, COLOR_WARN);
+ contentView.setTextColor(R.id.bloodtrend, COLOR_WARN);
+ }
+ }
+
+ //set values on screen
+ contentView.setTextViewText(R.id.blood, svgStr);
+ contentView.setTextViewText(R.id.bloodtrend, trendStr);
+ contentView.setTextViewText(R.id.time, timeStr);
+ contentView.setTextViewText(R.id.iob, iobStr);
+
+ if (configurationStore.isMmolxl()) {
+ contentView.setTextViewText(R.id.bloodunit, getString(R.string.text_unit_mmolxl));
+ } else {
+ contentView.setTextViewText(R.id.bloodunit, getString(R.string.text_unit_mgxdl));
+ }
+
+
+ NotificationCompat.Builder mBuilder =
+ (NotificationCompat.Builder) new NotificationCompat.Builder(this)
+ .setPriority(NotificationCompat.PRIORITY_MAX)
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+ .setSmallIcon(R.drawable.ic_launcher) // FIXME - this icon doesn't follow the standards (ie, it has black in it)
+ .setContent(contentView)
+ //custom big opens only on arrow(?)
+ //.setCustomBigContentView(contentView)
+ //.setContentTitle(title)
+ //.setContentText(message)
+ //.setColor(getResources().getColor(R.color.md_deep_orange_A100))
+ .setCategory(NotificationCompat.CATEGORY_STATUS);
+ // Creates an explicit intent for an Activity in your app
+ Intent resultIntent = new Intent(this, MainActivity.class);
+
+ TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
+ // Adds the back stack for the Intent (but not the Intent itself)
+ stackBuilder.addParentStack(MainActivity.class);
+ // Adds the Intent that starts the Activity to the top of the stack
+ stackBuilder.addNextIntent(resultIntent);
+ PendingIntent resultPendingIntent =
+ stackBuilder.getPendingIntent(
+ 0,
+ PendingIntent.FLAG_UPDATE_CURRENT
+ );
+ mBuilder.setContentIntent(resultPendingIntent);
+ mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
+ }
+
+
+
+ private static String renderTrendSymbol(PumpStatusEvent.CGM_TREND trend) {
+ // TODO - symbols used for trend arrow may vary per device, find a more robust solution
+ switch (trend) {
+ case DOUBLE_UP:
+ return "\u21c8";
+ case SINGLE_UP:
+ return "\u2191";
+ case FOURTY_FIVE_UP:
+ return "\u2197";
+ case FLAT:
+ return "\u2192";
+ case FOURTY_FIVE_DOWN:
+ return "\u2198";
+ case SINGLE_DOWN:
+ return "\u2193";
+ case DOUBLE_DOWN:
+ return "\u21ca";
+ default:
+ return "\u2014";
+ }
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ //PowerManager pm = (PowerManager) getApplicationContext().getSystemService(Context.POWER_SERVICE);
+ //PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "NotificationsIntent");
+ //wl.acquire();
+
+ updateNotification();
+
+
+ int delayMs = 60 * 1000;
+ if (dataStore.isScreenOn()) {
+ Log.d("Notifications", "Updated notification, next update in " + delayMs);
+ scheduleNextUpdate(this, delayMs);
+ } else {
+ Log.d("Notifications", "Updated notification, screen is off, no updates scheduled");
+ }
+ //wl.release();
+ }
+
+ public static void scheduleNextUpdate(Context c, int delayMs) {
+ Intent intent = new Intent(c, LockScreenNotification.class);
+ PendingIntent pendingIntent =
+ PendingIntent.getService(c, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ long currentTimeMillis = System.currentTimeMillis();
+ long nextUpdateTimeMillis = currentTimeMillis + delayMs;
+
+ AlarmManager alarmManager = (AlarmManager) c.getSystemService(Context.ALARM_SERVICE);
+ alarmManager.set(AlarmManager.RTC, nextUpdateTimeMillis, pendingIntent);
+ }
+}
diff --git a/app/src/main/java/info/nightscout/android/ScreenReceiver.java b/app/src/main/java/info/nightscout/android/ScreenReceiver.java
new file mode 100644
index 0000000..e141d48
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/ScreenReceiver.java
@@ -0,0 +1,24 @@
+package info.nightscout.android;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+//Ref: https://thinkandroid.wordpress.com/2010/01/24/handling-screen-off-and-screen-on-intents/
+public class ScreenReceiver extends BroadcastReceiver {
+
+ private boolean screenOn;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
+ screenOn = false;
+ } else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
+ screenOn = true;
+ }
+ Intent i = new Intent(context, LockScreenNotification.class);
+ i.putExtra("screenOn", screenOn);
+ context.startService(i);
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/android/UploaderApplication.java b/app/src/main/java/info/nightscout/android/UploaderApplication.java
index 1bfc87f..28e99ae 100644
--- a/app/src/main/java/info/nightscout/android/UploaderApplication.java
+++ b/app/src/main/java/info/nightscout/android/UploaderApplication.java
@@ -8,9 +8,15 @@
import com.crashlytics.android.Crashlytics;
import com.crashlytics.android.answers.Answers;
+import info.nightscout.android.model.medtronicNg.BasalRate;
+import info.nightscout.android.model.medtronicNg.BasalSchedule;
+import info.nightscout.android.model.medtronicNg.ContourNextLinkInfo;
+import info.nightscout.android.model.medtronicNg.PumpInfo;
+import info.nightscout.android.model.medtronicNg.PumpStatusEvent;
import io.fabric.sdk.android.Fabric;
import io.realm.Realm;
import io.realm.RealmConfiguration;
+import io.realm.annotations.RealmModule;
import uk.co.chrisjenx.calligraphy.CalligraphyConfig;
/**
@@ -43,9 +49,15 @@ public void onCreate() {
Realm.init(this);
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder()
+ .modules(new MainModule())
.deleteRealmIfMigrationNeeded()
.build();
Realm.setDefaultConfiguration(realmConfiguration);
}
+
+ @RealmModule(classes = {BasalRate.class, BasalSchedule.class, ContourNextLinkInfo.class, PumpInfo.class, PumpStatusEvent.class})
+ public class MainModule {
+ }
+
}
diff --git a/app/src/main/java/info/nightscout/android/medtronic/MainActivity.java b/app/src/main/java/info/nightscout/android/medtronic/MainActivity.java
index f0b9dd1..07e7934 100644
--- a/app/src/main/java/info/nightscout/android/medtronic/MainActivity.java
+++ b/app/src/main/java/info/nightscout/android/medtronic/MainActivity.java
@@ -64,24 +64,24 @@
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
-import java.util.Queue;
-import java.util.concurrent.ArrayBlockingQueue;
+import info.nightscout.android.LockScreenNotification;
import info.nightscout.android.R;
import info.nightscout.android.USB.UsbHidDriver;
import info.nightscout.android.eula.Eula;
import info.nightscout.android.eula.Eula.OnEulaAgreedTo;
import info.nightscout.android.medtronic.service.MedtronicCnlAlarmManager;
import info.nightscout.android.medtronic.service.MedtronicCnlIntentService;
-import info.nightscout.android.model.medtronicNg.PumpInfo;
import info.nightscout.android.model.medtronicNg.PumpStatusEvent;
import info.nightscout.android.settings.SettingsActivity;
import info.nightscout.android.utils.ConfigurationStore;
import info.nightscout.android.utils.DataStore;
+import info.nightscout.android.utils.StatusStore;
import io.realm.Realm;
-import io.realm.RealmChangeListener;
+import io.realm.RealmConfiguration;
import io.realm.RealmResults;
import io.realm.Sort;
+import io.realm.annotations.RealmModule;
import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper;
public class MainActivity extends AppCompatActivity implements OnSharedPreferenceChangeListener, OnEulaAgreedTo {
@@ -97,14 +97,20 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
private boolean mEnableCgmService = true;
private SharedPreferences prefs = null;
- private PumpInfo mActivePump;
private TextView mTextViewLog; // This will eventually move to a status page.
+ private TextView mTextViewLogButtonTop;
+ private TextView mTextViewLogButtonTopRecent;
+ private TextView mTextViewLogButtonBottom;
+ private TextView mTextViewLogButtonBottomRecent;
+
private ScrollView mScrollView;
private GraphView mChart;
private Handler mUiRefreshHandler = new Handler();
private Runnable mUiRefreshRunnable = new RefreshDisplayRunnable();
private Realm mRealm;
+ private Realm storeRealm;
private StatusMessageReceiver statusMessageReceiver = new StatusMessageReceiver();
+ private UpdatePumpReceiver updatePumpReceiver = new UpdatePumpReceiver();
private UsbReceiver usbReceiver = new UsbReceiver();
private BatteryReceiver batteryReceiver = new BatteryReceiver();
@@ -117,35 +123,8 @@ protected void sendStatus(String message) {
LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent);
}
- /**
- * calculate the next poll timestamp based on last svg event
- *
- * @param pumpStatusData
- * @return timestamp
- */
- public static long getNextPoll(PumpStatusEvent pumpStatusData) {
- long nextPoll = pumpStatusData.getSgvDate().getTime(),
- now = System.currentTimeMillis(),
- pollInterval = ConfigurationStore.getInstance().getPollInterval();
-
- // align to next poll slot
- if (nextPoll + 2 * 60 * 60 * 1000 < now) { // last event more than 2h old -> could be a calibration
- nextPoll = System.currentTimeMillis() + 1000;
- } else {
- // align to poll interval
- nextPoll += (((now - nextPoll) / pollInterval)) * pollInterval
- + MedtronicCnlIntentService.POLL_GRACE_PERIOD_MS;
- if (pumpStatusData.getBatteryPercentage() > 25) {
- // poll every 5 min
- nextPoll += pollInterval;
- } else {
- // if pump battery seems to be empty reduce polling to save battery (every 15 min)
- //TODO add message & document it
- nextPoll += ConfigurationStore.getInstance().getLowBatteryPollInterval();
- }
- }
-
- return nextPoll;
+ @RealmModule(classes = {StatusStore.class})
+ private class StoreModule {
}
@Override
@@ -155,10 +134,12 @@ public void onCreate(Bundle savedInstanceState) {
mRealm = Realm.getDefaultInstance();
- RealmResults data = mRealm.where(PumpStatusEvent.class)
- .findAllSorted("eventDate", Sort.DESCENDING);
- if (data.size() > 0)
- dataStore.setLastPumpStatus(data.first());
+ RealmConfiguration realmConfiguration = new RealmConfiguration.Builder()
+ .name("storerealm.realm")
+ .modules(new StoreModule())
+ .deleteRealmIfMigrationNeeded()
+ .build();
+ storeRealm = Realm.getInstance(realmConfiguration);
setContentView(R.layout.activity_main);
@@ -204,7 +185,7 @@ public void onCreate(Bundle savedInstanceState) {
statusMessageReceiver,
new IntentFilter(MedtronicCnlIntentService.Constants.ACTION_STATUS_MESSAGE));
LocalBroadcastManager.getInstance(this).registerReceiver(
- new UpdatePumpReceiver(),
+ updatePumpReceiver,
new IntentFilter(MedtronicCnlIntentService.Constants.ACTION_UPDATE_PUMP));
mEnableCgmService = Eula.show(this, prefs);
@@ -228,7 +209,6 @@ public void onCreate(Bundle savedInstanceState) {
new IntentFilter(MedtronicCnlIntentService.Constants.ACTION_USB_REGISTER));
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
-
if (toolbar != null) {
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
@@ -315,6 +295,39 @@ public boolean onItemClick(View view, int position, IDrawerItem drawerItem) {
mTextViewLog = (TextView) findViewById(R.id.textview_log);
mScrollView = (ScrollView) findViewById(R.id.scrollView);
mScrollView.setSmoothScrollingEnabled(true);
+ mTextViewLogButtonTop = (TextView) findViewById(R.id.button_log_top);
+ mTextViewLogButtonTop.setVisibility(View.GONE);
+ mTextViewLogButtonTop.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ statusMessageReceiver.changeStatusViewOlder();
+ }
+ });
+ mTextViewLogButtonTopRecent = (TextView) findViewById(R.id.button_log_top_recent);
+ mTextViewLogButtonTopRecent.setVisibility(View.GONE);
+ mTextViewLogButtonTopRecent.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ statusMessageReceiver.changeStatusViewRecent();
+ }
+ });
+
+ mTextViewLogButtonBottom = (TextView) findViewById(R.id.button_log_bottom);
+ mTextViewLogButtonBottom.setVisibility(View.GONE);
+ mTextViewLogButtonBottom.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ statusMessageReceiver.changeStatusViewNewer();
+ }
+ });
+ mTextViewLogButtonBottomRecent = (TextView) findViewById(R.id.button_log_bottom_recent);
+ mTextViewLogButtonBottomRecent.setVisibility(View.GONE);
+ mTextViewLogButtonBottomRecent.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ statusMessageReceiver.changeStatusViewRecent();
+ }
+ });
mChart = (GraphView) findViewById(R.id.chart);
@@ -386,6 +399,7 @@ protected void onPostCreate(Bundle savedInstanceState) {
startDisplayRefreshLoop();
statusStartup();
startCgmService();
+ startService(new Intent(this, LockScreenNotification.class));
}
@Override
@@ -468,11 +482,12 @@ private void statusStartup() {
private void refreshDisplay() {
cancelDisplayRefreshLoop();
- mUiRefreshHandler.post(mUiRefreshRunnable);;
+ mUiRefreshHandler.post(mUiRefreshRunnable);
}
private void refreshDisplay(int delay) {
cancelDisplayRefreshLoop();
mUiRefreshHandler.postDelayed(mUiRefreshRunnable, delay);
+ LockScreenNotification.scheduleNextUpdate(this, 50);
}
private void startDisplayRefreshLoop() {
@@ -488,20 +503,28 @@ private void startCgmService() {
}
private void startCgmServiceDelayed(long delay) {
+ long now = System.currentTimeMillis();
+ long start = now + 1000;
+
if (!mRealm.isClosed()) {
+
RealmResults results = mRealm.where(PumpStatusEvent.class)
- .findAllSorted("eventDate", Sort.DESCENDING);
+ .greaterThan("eventDate", new Date(System.currentTimeMillis() - (24 * 60 * 1000)))
+ .equalTo("validCGM", true)
+ .findAllSorted("cgmDate", Sort.DESCENDING);
+
if (results.size() > 0) {
- long nextPoll = getNextPoll(results.first());
- long pollInterval = results.first().getBatteryPercentage() > 25 ? ConfigurationStore.getInstance().getPollInterval() : ConfigurationStore.getInstance().getLowBatteryPollInterval();
- if ((nextPoll - MedtronicCnlIntentService.POLL_GRACE_PERIOD_MS - results.first().getSgvDate().getTime()) <= pollInterval) {
- startCgmService(nextPoll + delay);
- sendStatus("Next poll due at: " + dateFormatter.format(nextPoll + delay));
- return;
- }
+ long timeLastCGM = results.first().getCgmDate().getTime();
+ if (now - timeLastCGM < MedtronicCnlIntentService.POLL_GRACE_PERIOD_MS + MedtronicCnlIntentService.POLL_PERIOD_MS)
+ start = timeLastCGM + MedtronicCnlIntentService.POLL_GRACE_PERIOD_MS + MedtronicCnlIntentService.POLL_PERIOD_MS;
}
}
- startCgmService(System.currentTimeMillis() + (delay == 0 ? 1000 : delay));
+
+ if (start - now < delay) start = now + delay;
+ startCgmService(start);
+
+ if (start - now > 10 * 1000)
+ sendStatus("Next poll due at: " + dateFormatter.format(start));
}
private void startCgmService(long initialPoll) {
@@ -559,24 +582,30 @@ protected void onResume() {
Log.i(TAG, "onResume called");
super.onResume();
// Focus status log to most recent on returning to app
- mScrollView.post(new Runnable() {
- public void run() {
- mScrollView.fullScroll(View.FOCUS_DOWN);
- }
- });
+ statusMessageReceiver.changeStatusViewRecent();
}
@Override
protected void onDestroy() {
Log.i(TAG, "onDestroy called");
+ statusMessageReceiver.addMessage(MedtronicCnlIntentService.ICON_INFO + "Shutting down uploader.");
+ statusMessageReceiver.addMessage("-----------------------------------------------------");
+
super.onDestroy();
+ cancelDisplayRefreshLoop();
+
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(statusMessageReceiver);
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(updatePumpReceiver);
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(usbReceiver);
unregisterReceiver(usbReceiver);
unregisterReceiver(batteryReceiver);
PreferenceManager.getDefaultSharedPreferences(getBaseContext()).unregisterOnSharedPreferenceChangeListener(this);
- cancelDisplayRefreshLoop();
+ if (!storeRealm.isClosed()) {
+ storeRealm.close();
+ }
if (!mRealm.isClosed()) {
mRealm.close();
}
@@ -635,77 +664,6 @@ public void openUsbRegistration() {
startActivity(manageCNLIntent);
}
- private PumpInfo getActivePump() {
- long activePumpMac = dataStore.getActivePumpMac();
- if (activePumpMac != 0L && (mActivePump == null || !mActivePump.isValid() || mActivePump.getPumpMac() != activePumpMac)) {
- if (mActivePump != null) {
- // remove listener on old pump
- mRealm.executeTransaction(new Realm.Transaction() {
- @Override
- public void execute(Realm sRealm) {
- mActivePump.removeAllChangeListeners();
- }
- });
- mActivePump = null;
- }
-
- PumpInfo pump = mRealm
- .where(PumpInfo.class)
- .equalTo("pumpMac", activePumpMac)
- .findFirst();
-
- if (pump != null && pump.isValid()) {
-
- // first change listener start can miss fresh data and not update until next poll, force a refresh now
- RemoveOutdatedRecords();
- refreshDisplay(1000);
-
- mActivePump = pump;
- mActivePump.addChangeListener(new RealmChangeListener() {
- long lastQueryTS = 0;
-
- @Override
- public void onChange(PumpInfo pump) {
- // prevent double updating after deleting old events below
- if (pump.getLastQueryTS() == lastQueryTS || !pump.isValid()) {
- return;
- }
-
- lastQueryTS = pump.getLastQueryTS();
-
- RemoveOutdatedRecords();
- refreshDisplay(1000);
-
- // TODO - handle isOffline in NightscoutUploadIntentService?
- }
- });
- }
- }
-
- return mActivePump;
- }
-
- private void RemoveOutdatedRecords() {
- // Delete invalid or old records from Realm
- // TODO - show an error message if the valid records haven't been uploaded
- final RealmResults results =
- mRealm.where(PumpStatusEvent.class)
- .equalTo("sgv", 0)
- .or()
- .lessThan("sgvDate", new Date(System.currentTimeMillis() - (24 * 60 * 60 * 1000)))
- .findAll();
- if (results.size() > 0) {
- mRealm.executeTransaction(new Realm.Transaction() {
- @Override
- public void execute(Realm realm) {
- // Delete all matches
- Log.d(TAG, "Deleting " + results.size() + " records from realm");
- results.deleteAllFromRealm();
- }
- });
- }
- }
-
public static String strFormatSGV(double sgvValue) {
ConfigurationStore configurationStore = ConfigurationStore.getInstance();
@@ -723,87 +681,166 @@ public static String strFormatSGV(double sgvValue) {
}
}
- public static String renderTrendSymbol(PumpStatusEvent.CGM_TREND trend) {
- // TODO - symbols used for trend arrow may vary per device, find a more robust solution
- switch (trend) {
- case DOUBLE_UP:
- return "\u21c8";
- case SINGLE_UP:
- return "\u2191";
- case FOURTY_FIVE_UP:
- return "\u2197";
- case FLAT:
- return "\u2192";
- case FOURTY_FIVE_DOWN:
- return "\u2198";
- case SINGLE_DOWN:
- return "\u2193";
- case DOUBLE_DOWN:
- return "\u21ca";
- default:
- return "\u2014";
- }
- }
-
private class StatusMessageReceiver extends BroadcastReceiver {
- private class StatusMessage {
- private long timestamp;
- private String message;
+ private static final int PAGE_SIZE = 300;
+ private static final int FIRSTPAGE_SIZE = 100;
+ private static final int STALE_MS = 72 * 60 * 60 * 1000;
+ private int viewPosition = 0;
+ private int viewPositionSecondPage = 0;
- StatusMessage(String message) {
- this(System.currentTimeMillis(), message);
- }
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String message = intent.getStringExtra(MedtronicCnlIntentService.Constants.EXTENDED_DATA);
+ Log.i(TAG, "Message Receiver: " + message);
+ addMessage(message);
+ }
- StatusMessage(long timestamp, String message) {
- this.timestamp = timestamp;
- this.message = message;
+ private void addMessage(final String message) {
+ if (storeRealm.isClosed()) return;
+
+ storeRealm.executeTransaction(
+ new Realm.Transaction() {
+ @Override
+ public void execute(Realm realm) {
+ realm.createObject(StatusStore.class).StatusMessage(message);
+ }
+ });
+ if (viewPositionSecondPage < FIRSTPAGE_SIZE) viewPositionSecondPage++; // older session log begins on next page
+ if (viewPosition > 0) viewPosition++; // move the view pointer when not on first page to keep aligned
+ showLog();
+
+ if (viewPosition == 0) {
+ // auto scroll status log
+ if ((mScrollView.getChildAt(0).getBottom() < mScrollView.getHeight()) || ((mScrollView.getChildAt(0).getBottom() - mScrollView.getScrollY() - mScrollView.getHeight()) < (mScrollView.getHeight() / 3))) {
+ mScrollView.post(new Runnable() {
+ public void run() {
+ mScrollView.fullScroll(View.FOCUS_DOWN);
+ }
+ });
+ }
}
- public String toString() {
- return DateFormat.getTimeInstance(DateFormat.MEDIUM).format(timestamp) + ": " + message;
+ // remove stale items but not while viewing older paged entries
+ if (viewPosition < viewPositionSecondPage) {
+ final RealmResults results = storeRealm.where(StatusStore.class)
+ .lessThan("timestamp", System.currentTimeMillis() - STALE_MS)
+ .findAll();
+ if (results.size() > 0) {
+ storeRealm.executeTransaction(
+ new Realm.Transaction() {
+ @Override
+ public void execute(Realm realm) {
+ results.deleteAllFromRealm();
+ }
+ });
+ }
}
}
- private final Queue messages = new ArrayBlockingQueue<>(400);
-
- @Override
- public void onReceive(Context context, Intent intent) {
- String message = intent.getStringExtra(MedtronicCnlIntentService.Constants.EXTENDED_DATA);
- Log.i(TAG, "Message Receiver: " + message);
+ private void showLog() {
+ RealmResults results = storeRealm.where(StatusStore.class)
+ .findAllSorted("timestamp", Sort.DESCENDING);
- synchronized (messages) {
- while (messages.size() > 398) {
- messages.poll();
- }
- messages.add(new StatusMessage(message));
- }
+ int remain = results.size() - viewPosition;
+ int segment = remain;
+ if (viewPosition == 0 && viewPositionSecondPage < PAGE_SIZE) segment = viewPositionSecondPage;
+ if (segment > PAGE_SIZE) segment = PAGE_SIZE;
StringBuilder sb = new StringBuilder();
- for (StatusMessage msg : messages) {
- if (sb.length() > 0)
- sb.append("\n");
- sb.append(msg);
+ if (segment > 0) {
+ for (int index = viewPosition; index < viewPosition + segment; index++)
+ sb.insert(0, results.get(index) + (sb.length() > 0 ? "\n" : ""));
}
-
mTextViewLog.setText(sb.toString(), BufferType.EDITABLE);
- // auto scroll status log
- if ((mScrollView.getChildAt(0).getBottom() < mScrollView.getHeight()) || ((mScrollView.getChildAt(0).getBottom() - mScrollView.getScrollY() - mScrollView.getHeight()) < (mScrollView.getHeight() / 3))) {
- mScrollView.post(new Runnable() {
- public void run() {
- mScrollView.fullScroll(View.FOCUS_DOWN);
- }
- });
+ if (viewPosition > 0) {
+ mTextViewLogButtonBottom.setVisibility(View.VISIBLE);
+ mTextViewLogButtonBottomRecent.setVisibility(View.VISIBLE);
+ } else {
+ mTextViewLogButtonBottom.setVisibility(View.GONE);
+ mTextViewLogButtonBottomRecent.setVisibility(View.GONE);
+ }
+ if (remain > segment) {
+ mTextViewLogButtonTop.setVisibility(View.VISIBLE);
+ } else {
+ mTextViewLogButtonTop.setVisibility(View.GONE);
+ }
+ if (viewPosition > 0 || mScrollView.getChildAt(0).getBottom() > mScrollView.getHeight() + 100) {
+ mTextViewLogButtonTopRecent.setVisibility(View.VISIBLE);
+ } else {
+ mTextViewLogButtonTopRecent.setVisibility(View.GONE);
}
}
- public void clearMessages() {
- synchronized (messages) {
- messages.clear();
+ private void clearMessages() {
+ final RealmResults results = storeRealm.where(StatusStore.class)
+ .findAll();
+ if (results.size() > 0) {
+ storeRealm.executeTransaction(
+ new Realm.Transaction() {
+ @Override
+ public void execute(Realm realm) {
+ results.deleteAllFromRealm();
+ }
+ });
}
-
mTextViewLog.setText("", BufferType.EDITABLE);
+ mTextViewLogButtonTop.setVisibility(View.GONE);
+ mTextViewLogButtonTopRecent.setVisibility(View.GONE);
+ mTextViewLogButtonBottom.setVisibility(View.GONE);
+ mTextViewLogButtonBottomRecent.setVisibility(View.GONE);
+ viewPosition = 0;
+ viewPositionSecondPage = 0;
+ }
+
+ private void changeStatusViewOlder() {
+ if (viewPosition == 0 && viewPositionSecondPage < PAGE_SIZE) {
+ viewPosition = viewPositionSecondPage;
+ } else {
+ viewPosition += PAGE_SIZE;
+ }
+ showLog();
+ mScrollView.post(new Runnable() {
+ public void run() {
+ mScrollView.fullScroll(View.FOCUS_DOWN);
+ if (viewPosition > 0 || mScrollView.getChildAt(0).getBottom() > mScrollView.getHeight() + 100) {
+ mTextViewLogButtonTopRecent.setVisibility(View.VISIBLE);
+ } else {
+ mTextViewLogButtonTopRecent.setVisibility(View.GONE);
+ }
+ }
+ });
}
+ private void changeStatusViewNewer() {
+ viewPosition -= PAGE_SIZE;
+ if (viewPosition < 0) viewPosition = 0;
+ showLog();
+ mScrollView.post(new Runnable() {
+ public void run() {
+ mScrollView.fullScroll(View.FOCUS_UP);
+ if (viewPosition > 0 || mScrollView.getChildAt(0).getBottom() > mScrollView.getHeight() + 100) {
+ mTextViewLogButtonTopRecent.setVisibility(View.VISIBLE);
+ } else {
+ mTextViewLogButtonTopRecent.setVisibility(View.GONE);
+ }
+ }
+ });
+ }
+ private void changeStatusViewRecent() {
+ viewPosition = 0;
+ showLog();
+ mScrollView.post(new Runnable() {
+ public void run() {
+ mScrollView.fullScroll(View.FOCUS_DOWN);
+ if (viewPosition > 0 || mScrollView.getChildAt(0).getBottom() > mScrollView.getHeight() + 100) {
+ mTextViewLogButtonTopRecent.setVisibility(View.VISIBLE);
+ } else {
+ mTextViewLogButtonTopRecent.setVisibility(View.GONE);
+ }
+ }
+ });
+ }
+
}
private class RefreshDisplayRunnable implements Runnable {
@@ -811,52 +848,96 @@ private class RefreshDisplayRunnable implements Runnable {
public void run() {
long nextRun = 60000L;
- TextView textViewBg = (TextView) findViewById(R.id.textview_bg);
- TextView textViewBgTime = (TextView) findViewById(R.id.textview_bg_time);
- TextView textViewUnits = (TextView) findViewById(R.id.textview_units);
- if (configurationStore.isMmolxl()) {
- textViewUnits.setText(R.string.text_unit_mmolxl);
- } else {
- textViewUnits.setText(R.string.text_unit_mgxdl);
- }
- TextView textViewTrend = (TextView) findViewById(R.id.textview_trend);
- TextView textViewIOB = (TextView) findViewById(R.id.textview_iob);
-
- // Get the most recently written CGM record for the active pump.
- PumpStatusEvent pumpStatusData = null;
-
- if (dataStore.getLastPumpStatus().getEventDate().getTime() > 0) {
- pumpStatusData = dataStore.getLastPumpStatus();
- }
-
- updateChart(mRealm.where(PumpStatusEvent.class)
- .notEqualTo("sgv", 0)
- .greaterThan("sgvDate", new Date(System.currentTimeMillis() - 1000 * 60 * 60 * 24))
- .findAllSorted("sgvDate", Sort.ASCENDING));
+ if (!mRealm.isClosed()) {
- if (pumpStatusData != null) {
- String sgvString;
- if (pumpStatusData.isCgmActive()) {
- sgvString = MainActivity.strFormatSGV(pumpStatusData.getSgv());
+ TextView textViewBg = (TextView) findViewById(R.id.textview_bg);
+ TextView textViewBgTime = (TextView) findViewById(R.id.textview_bg_time);
+ TextView textViewUnits = (TextView) findViewById(R.id.textview_units);
+ if (configurationStore.isMmolxl()) {
+ textViewUnits.setText(R.string.text_unit_mmolxl);
+ } else {
+ textViewUnits.setText(R.string.text_unit_mgxdl);
+ }
+ TextView textViewTrend = (TextView) findViewById(R.id.textview_trend);
+ TextView textViewIOB = (TextView) findViewById(R.id.textview_iob);
+
+ String timeString = "never";
+ String sgvString = "\u2014"; // —
+ String trendString = "{ion_ios_minus_empty}";
+ int trendRotation = 0;
+ float iob = 0;
+ int battery = 0;
+
+ // most recent sgv status
+ RealmResults sgv_results =
+ mRealm.where(PumpStatusEvent.class)
+ .equalTo("validSGV", true)
+ .findAllSorted("cgmDate", Sort.ASCENDING);
+
+ if (sgv_results.size() > 0) {
+ long sgvtime = sgv_results.last().getCgmDate().getTime();
+ nextRun = 60000L - (System.currentTimeMillis() - sgvtime) % 60000L;
+ timeString = (DateUtils.getRelativeTimeSpanString(sgvtime)).toString();
+ sgvString = MainActivity.strFormatSGV(sgv_results.last().getSgv());
if (configurationStore.isMmolxl()) {
Log.d(TAG, sgvString + " mmol/L");
} else {
Log.d(TAG, sgvString + " mg/dL");
}
- } else {
- sgvString = "\u2014"; // —
+
+ switch (sgv_results.last().getCgmTrend()) {
+ case DOUBLE_UP:
+ trendString = "{ion_ios_arrow_thin_up}{ion_ios_arrow_thin_up}";
+ break;
+ case SINGLE_UP:
+ trendString = "{ion_ios_arrow_thin_up}";
+ break;
+ case FOURTY_FIVE_UP:
+ trendRotation = -45;
+ trendString = "{ion_ios_arrow_thin_right}";
+ break;
+ case FLAT:
+ trendString = "{ion_ios_arrow_thin_right}";
+ break;
+ case FOURTY_FIVE_DOWN:
+ trendRotation = 45;
+ trendString = "{ion_ios_arrow_thin_right}";
+ break;
+ case SINGLE_DOWN:
+ trendString = "{ion_ios_arrow_thin_down}";
+ break;
+ case DOUBLE_DOWN:
+ trendString = "{ion_ios_arrow_thin_down}{ion_ios_arrow_thin_down}";
+ break;
+ default:
+ trendString = "{ion_ios_minus_empty}";
+ break;
+ }
+
+ updateChart(sgv_results.where()
+ .greaterThan("cgmDate", new Date(sgvtime - 1000 * 60 * 60 * 24))
+ .findAllSorted("cgmDate", Sort.ASCENDING));
}
- nextRun = 60000L - (System.currentTimeMillis() - pumpStatusData.getSgvDate().getTime()) % 60000L;
- textViewBg.setText(sgvString);
- textViewBgTime.setText(DateUtils.getRelativeTimeSpanString(pumpStatusData.getSgvDate().getTime()));
+ // most recent pump status
+ RealmResults pump_results =
+ mRealm.where(PumpStatusEvent.class)
+ .findAllSorted("eventDate", Sort.ASCENDING);
- textViewTrend.setText(MainActivity.renderTrendSymbol(pumpStatusData.getCgmTrend()));
- textViewIOB.setText(String.format(Locale.getDefault(), "%.2f", pumpStatusData.getActiveInsulin()));
+ if (pump_results.size() > 0) {
+ iob = pump_results.last().getActiveInsulin();
+ battery = pump_results.last().getBatteryPercentage();
+ }
+
+ textViewBg.setText(sgvString);
+ textViewBgTime.setText(timeString);
+ textViewIOB.setText(String.format(Locale.getDefault(), "%.2f", iob));
+ textViewTrend.setText(trendString);
+ textViewTrend.setRotation(trendRotation);
ActionMenuItemView batIcon = ((ActionMenuItemView) findViewById(R.id.status_battery));
if (batIcon != null) {
- switch (pumpStatusData.getBatteryPercentage()) {
+ switch (battery) {
case 0:
batIcon.setTitle("0%");
batIcon.setIcon(getResources().getDrawable(R.drawable.battery_0));
@@ -882,7 +963,6 @@ public void run() {
batIcon.setIcon(getResources().getDrawable(R.drawable.battery_unknown));
}
}
-
}
// Run myself again in 60 (or less) seconds;
@@ -915,7 +995,7 @@ private void updateChart(RealmResults results) {
int pos = 0;
for (PumpStatusEvent pumpStatus : results) {
// turn your data into Entry objects
- entries[pos++] = new DataPoint(pumpStatus.getSgvDate(), (double) pumpStatus.getSgv());
+ entries[pos++] = new DataPoint(pumpStatus.getCgmDate(), (double) pumpStatus.getSgv());
}
if (mChart.getSeries().size() == 0) {
@@ -927,12 +1007,9 @@ private void updateChart(RealmResults results) {
// }
// entries = Arrays.copyOfRange(entries, 0, j);
- PointsGraphSeries sgvSerie = new PointsGraphSeries(entries);
-// sgvSerie.setSize(3.6f);
-// sgvSerie.setColor(Color.LTGRAY);
+ PointsGraphSeries sgvSeries = new PointsGraphSeries(entries);
-
- sgvSerie.setOnDataPointTapListener(new OnDataPointTapListener() {
+ sgvSeries.setOnDataPointTapListener(new OnDataPointTapListener() {
DateFormat mFormat = DateFormat.getTimeInstance(DateFormat.MEDIUM);
@Override
@@ -945,7 +1022,7 @@ public void onTap(Series series, DataPointInterface dataPoint) {
}
});
- sgvSerie.setCustomShape(new PointsGraphSeries.CustomShape() {
+ sgvSeries.setCustomShape(new PointsGraphSeries.CustomShape() {
@Override
public void draw(Canvas canvas, Paint paint, float x, float y, DataPointInterface dataPoint) {
double sgv = dataPoint.getY();
@@ -967,7 +1044,7 @@ else if (sgv <= 260)
});
mChart.getViewport().setYAxisBoundsManual(false);
- mChart.addSeries(sgvSerie);
+ mChart.addSeries(sgvSeries);
} else {
if (entries.length > 0) {
((PointsGraphSeries) mChart.getSeries().get(0)).resetData(entries);
@@ -1002,16 +1079,9 @@ private static float dipToPixels(Context context, float dipValue) {
* has to be done in MainActivity thread
*/
private class UpdatePumpReceiver extends BroadcastReceiver {
-
@Override
public void onReceive(Context context, Intent intent) {
- // If the MainActivity has already been destroyed (meaning the Realm instance has been closed)
- // then don't worry about processing this broadcast
- if (mRealm.isClosed()) {
- return;
- }
- //init local pump listener
- getActivePump();
+ refreshDisplay(500);
}
}
@@ -1026,6 +1096,7 @@ public void onReceive(Context context, Intent intent) {
boolean permissionGranted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);
if (permissionGranted) {
Log.d(TAG, "Got permission to access USB");
+ sendStatus(MedtronicCnlIntentService.ICON_INFO + "Got permission to access USB.");
startCgmService();
} else {
Log.d(TAG, "Still no permission for USB. Waiting...");
@@ -1043,6 +1114,7 @@ public void onReceive(Context context, Intent intent) {
startCgmServiceDelayed(MedtronicCnlIntentService.USB_WARMUP_TIME_MS);
} else {
Log.d(TAG, "No permission for USB. Waiting.");
+ sendStatus(MedtronicCnlIntentService.ICON_INFO + "Waiting for USB permission.");
waitForUsbPermission();
}
} else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
@@ -1053,6 +1125,7 @@ public void onReceive(Context context, Intent intent) {
}
} else if (MedtronicCnlIntentService.Constants.ACTION_NO_USB_PERMISSION.equals(action)) {
Log.d(TAG, "No permission to read the USB device.");
+ sendStatus(MedtronicCnlIntentService.ICON_INFO + "Requesting USB permission.");
requestUsbPermission();
} else if (MedtronicCnlIntentService.Constants.ACTION_USB_REGISTER.equals(action)) {
openUsbRegistration();
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/PumpStatusResponseMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/PumpStatusResponseMessage.java
index 5778359..8325d44 100644
--- a/app/src/main/java/info/nightscout/android/medtronic/message/PumpStatusResponseMessage.java
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/PumpStatusResponseMessage.java
@@ -13,7 +13,6 @@
import info.nightscout.android.medtronic.exception.EncryptionException;
import info.nightscout.android.medtronic.exception.UnexpectedMessageException;
import info.nightscout.android.model.medtronicNg.PumpStatusEvent;
-import info.nightscout.android.utils.DataStore;
import info.nightscout.android.utils.HexDump;
/**
@@ -23,15 +22,24 @@ public class PumpStatusResponseMessage extends MedtronicSendMessageResponseMessa
private static final String TAG = PumpStatusResponseMessage.class.getSimpleName();
// Data from the Medtronic Pump Status message
+
+ private byte pumpStatus;
+ private byte cgmStatus;
private boolean suspended;
- private boolean bolusing;
+ private boolean bolusingNormal;
+ private boolean bolusingSquare;
+ private boolean bolusingDual;
private boolean deliveringInsulin;
private boolean tempBasalActive;
private boolean cgmActive;
+ private boolean cgmCalibrating;
+ private boolean cgmCalibrationComplete;
+ private boolean cgmException;
+ private boolean cgmWarmUp;
private byte activeBasalPattern;
private float basalRate;
private float tempBasalRate;
- private byte tempBasalPercentage;
+ private short tempBasalPercentage;
private short tempBasalMinutesRemaining;
private float basalUnitsDeliveredToday;
private short batteryPercentage;
@@ -39,12 +47,24 @@ public class PumpStatusResponseMessage extends MedtronicSendMessageResponseMessa
private short minutesOfInsulinRemaining; // 25h == "more than 1 day"
private float activeInsulin;
private int sgv;
- private Date sgvDate;
+ private Date cgmDate;
+ private byte cgmExceptionType;
private boolean lowSuspendActive;
private PumpStatusEvent.CGM_TREND cgmTrend;
-
private boolean recentBolusWizard; // Whether a bolus wizard has been run recently
- private int bolusWizardBGL; // in mg/dL. 0 means no recent bolus wizard reading.
+ private int recentBGL; // in mg/dL. 0 means no recent finger bg reading.
+ private short alert;
+ private Date alertDate;
+ private float bolusingDelivered;
+ private short bolusingMinutesRemaining;
+ private short bolusingReference;
+ private float lastBolusAmount;
+ private Date lastBolusDate;
+ private short lastBolusReference;
+ private byte transmitterBattery;
+ private byte transmitterControl;
+ private short calibrationDueMinutes;
+ private float sensorRateOfChange;
protected PumpStatusResponseMessage(MedtronicCnlSession pumpSession, byte[] payload) throws EncryptionException, ChecksumException, UnexpectedMessageException {
super(pumpSession, payload);
@@ -65,77 +85,119 @@ protected PumpStatusResponseMessage(MedtronicCnlSession pumpSession, byte[] payl
String outputString = HexDump.dumpHexString(statusBuffer.array());
Log.d(TAG, "PAYLOAD: " + outputString);
}
+
// Status Flags
- suspended = (statusBuffer.get(0x03) & 0x01) != 0x00;
- bolusing = (statusBuffer.get(0x03) & 0x02) != 0x00;
- deliveringInsulin = (statusBuffer.get(0x03) & 0x10) != 0x00;
- tempBasalActive = (statusBuffer.get(0x03) & 0x20) != 0x00;
- cgmActive = (statusBuffer.get(0x03) & 0x40) != 0x00;
+ pumpStatus = statusBuffer.get(0x03);
+ cgmStatus = statusBuffer.get(0x41);
+
+ suspended = (pumpStatus & 0x01) != 0x00;
+ bolusingNormal = (pumpStatus & 0x02) != 0x00;
+ bolusingSquare = (pumpStatus & 0x04) != 0x00;
+ bolusingDual = (pumpStatus & 0x08) != 0x00;
+ deliveringInsulin = (pumpStatus & 0x10) != 0x00;
+ tempBasalActive = (pumpStatus & 0x20) != 0x00;
+ cgmActive = (pumpStatus & 0x40) != 0x00;
+ cgmCalibrating = (cgmStatus & 0x01) != 0x00;
+ cgmCalibrationComplete = (cgmStatus & 0x02) != 0x00;
+ cgmException = (cgmStatus & 0x04) != 0x00;
// Active basal pattern
- activeBasalPattern = statusBuffer.get(0x1a);
+ activeBasalPattern = statusBuffer.get(0x1A);
// Normal basal rate
- long rawNormalBasal = statusBuffer.getInt(0x1b);
+ long rawNormalBasal = statusBuffer.getInt(0x1B);
basalRate = new BigDecimal(rawNormalBasal / 10000f).setScale(3, BigDecimal.ROUND_HALF_UP).floatValue();
// Temp basal rate
- long rawTempBasal = statusBuffer.getShort(0x21) & 0x0000ffff;
+ long rawTempBasal = statusBuffer.getInt(0x1F) & 0x0000000000FFFFFFL;
tempBasalRate = new BigDecimal(rawTempBasal / 10000f).setScale(3, BigDecimal.ROUND_HALF_UP).floatValue();
// Temp basal percentage
- tempBasalPercentage = statusBuffer.get(0x23);
+ tempBasalPercentage = (short) (statusBuffer.get(0x23) & 0x00FF);
// Temp basal minutes remaining
- tempBasalMinutesRemaining = (short) (statusBuffer.getShort(0x24) & 0x0000ffff);
+ tempBasalMinutesRemaining = (short) (statusBuffer.getShort(0x24) & 0x00FF);
// Units of insulin delivered as basal today
- // TODO - is this basal? Do we have a total Units delivered elsewhere?
- basalUnitsDeliveredToday = statusBuffer.getInt(0x26);
+ long rawBasalUnitsDeliveredToday = statusBuffer.getInt(0x26) & 0x0000000000FFFFFFL;
+ basalUnitsDeliveredToday = new BigDecimal(rawBasalUnitsDeliveredToday / 10000f).setScale(3, BigDecimal.ROUND_HALF_UP).floatValue();
// Pump battery percentage
- batteryPercentage = statusBuffer.get(0x2a);
+ batteryPercentage = statusBuffer.get(0x2A);
// Reservoir amount
- long rawReservoirAmount = statusBuffer.getInt(0x2b);
+ long rawReservoirAmount = statusBuffer.getInt(0x2B) & 0x0000000000FFFFFFL;
reservoirAmount = new BigDecimal(rawReservoirAmount / 10000f).setScale(3, BigDecimal.ROUND_HALF_UP).floatValue();
// Amount of insulin left in pump (in minutes)
- byte insulinHours = statusBuffer.get(0x2f);
+ byte insulinHours = statusBuffer.get(0x2F);
byte insulinMinutes = statusBuffer.get(0x30);
minutesOfInsulinRemaining = (short) ((insulinHours * 60) + insulinMinutes);
// Active insulin
- long rawActiveInsulin = statusBuffer.getInt(0x31);
+ long rawActiveInsulin = statusBuffer.getInt(0x31) & 0x0000000000FFFFFFL;
activeInsulin = new BigDecimal(rawActiveInsulin / 10000f).setScale(3, BigDecimal.ROUND_HALF_UP).floatValue();
// CGM SGV
- sgv = (statusBuffer.getShort(0x35) & 0x0000ffff); // In mg/DL. 0 means no CGM reading
- long rtc;
- long offset;
- if ((sgv & 0x200) == 0x200) {
- // Sensor error. Let's reset. FIXME - solve this more elegantly later
- sgv = 0;
- rtc = 0;
- offset = 0;
+ sgv = (statusBuffer.getShort(0x35) & 0x0000FFFF); // In mg/DL. 0x0000 = no CGM reading, 0x03NN = sensor exception
+ cgmDate = MessageUtils.decodeDateTime((long) statusBuffer.getInt(0x37) & 0x00000000FFFFFFFFL, (long) statusBuffer.getInt(0x3B));
+ Log.d(TAG, "original cgm/sgv date: " + cgmDate);
+
+ if (sgv >= 0x0300) {
+ cgmExceptionType = (byte) (sgv & 0x00FF);
cgmTrend = PumpStatusEvent.CGM_TREND.NOT_SET;
+ if (cgmExceptionType == 0x01) cgmWarmUp = true;
+ sgv = 0;
} else {
- rtc = statusBuffer.getInt(0x37) & 0x00000000ffffffffL;
- offset = statusBuffer.getInt(0x3b);
- cgmTrend = PumpStatusEvent.CGM_TREND.fromMessageByte(statusBuffer.get(0x40));
+ cgmExceptionType = 0;
+ cgmTrend = PumpStatusEvent.CGM_TREND.fromMessageByte((byte) (statusBuffer.get(0x40) & 0xF0)); // masked as low nibble can contain value when transmitter battery low
+ cgmWarmUp = false;
}
- // SGV Date
- sgvDate = MessageUtils.decodeDateTime(rtc, offset);
- Log.d(TAG, "original sgv date: " + sgvDate);
-
// Predictive low suspend
// TODO - there is more status info in this byte other than just a boolean yes/no
- lowSuspendActive = statusBuffer.get(0x3f) != 0;
+ // noted: 0x01=high 0x04=before high 0x08=before low 0x0A=low 0x80=suspend 0x92=suspend low
+ lowSuspendActive = statusBuffer.get(0x3F) != 0;
- // Recent Bolus Wizard BGL
+ // Recent Bolus Wizard
recentBolusWizard = statusBuffer.get(0x48) != 0;
- bolusWizardBGL = statusBuffer.getShort(0x49) & 0x0000ffff; // In mg/DL
+
+ // Recent BGL
+ recentBGL = statusBuffer.getShort(0x49) & 0x0000FFFF; // In mg/DL
+
+ // Active alert
+ alert = statusBuffer.getShort(0x4B);
+ alertDate = MessageUtils.decodeDateTime((long) statusBuffer.getInt(0x4D) & 0x00000000FFFFFFFFL, (long) statusBuffer.getInt(0x51));
+
+ // Now bolusing
+ long rawBolusingDelivered = statusBuffer.getInt(0x04) & 0x0000000000FFFFFFL;
+ bolusingDelivered = new BigDecimal(rawBolusingDelivered / 10000f).setScale(3, BigDecimal.ROUND_HALF_UP).floatValue();
+ bolusingMinutesRemaining = statusBuffer.getShort(0x0C);
+ bolusingReference = statusBuffer.getShort(0x0E);
+
+ // Last bolus
+ long rawLastBolusAmount = statusBuffer.getInt(0x10) & 0x0000000000FFFFFFL;
+ lastBolusAmount = new BigDecimal(rawLastBolusAmount / 10000f).setScale(3, BigDecimal.ROUND_HALF_UP).floatValue();
+ lastBolusDate = MessageUtils.decodeDateTime((long) statusBuffer.getInt(0x14) & 0x00000000FFFFFFFFL, 0);
+ lastBolusReference = statusBuffer.getShort(0x18);
+
+ // Calibration
+ calibrationDueMinutes = statusBuffer.getShort(0x43);
+
+ // Transmitter
+ transmitterControl = statusBuffer.get(0x43);
+ transmitterBattery = statusBuffer.get(0x45);
+ // Normalise transmitter battery to percentage shown on pump sensor status screen
+ if (transmitterBattery == 0x3F) transmitterBattery = 100;
+ else if (transmitterBattery == 0x2B) transmitterBattery = 73;
+ else if (transmitterBattery == 0x27) transmitterBattery = 47;
+ else if (transmitterBattery == 0x23) transmitterBattery = 20;
+ else if (transmitterBattery == 0x10) transmitterBattery = 0;
+ else transmitterBattery = 0;
+
+ // Sensor
+ long rawSensorRateOfChange = statusBuffer.getShort(0x46);
+ sensorRateOfChange = new BigDecimal(rawSensorRateOfChange / 100f).setScale(3, BigDecimal.ROUND_HALF_UP).floatValue();
}
/**
@@ -144,13 +206,24 @@ protected PumpStatusResponseMessage(MedtronicCnlSession pumpSession, byte[] payl
* @param pumpRecord
*/
public void updatePumpRecord(PumpStatusEvent pumpRecord) {
+
// Status Flags
+ pumpRecord.setPumpStatus(pumpStatus);
+ pumpRecord.setCgmStatus(cgmStatus);
+
pumpRecord.setSuspended(suspended);
- pumpRecord.setBolusing(bolusing);
+ pumpRecord.setBolusingNormal(bolusingNormal);
+ pumpRecord.setBolusingSquare(bolusingSquare);
+ pumpRecord.setBolusingDual(bolusingDual);
pumpRecord.setDeliveringInsulin(deliveringInsulin);
pumpRecord.setTempBasalActive(tempBasalActive);
pumpRecord.setCgmActive(cgmActive);
+ pumpRecord.setCgmCalibrating(cgmCalibrating);
+ pumpRecord.setCgmCalibrationComplete(cgmCalibrationComplete);
+ pumpRecord.setCgmException(cgmException);
+ pumpRecord.setCgmWarmUp(cgmWarmUp);
+
// Active basal pattern
pumpRecord.setActiveBasalPattern(activeBasalPattern);
@@ -183,26 +256,42 @@ public void updatePumpRecord(PumpStatusEvent pumpRecord) {
// CGM SGV data
pumpRecord.setSgv(sgv);
- pumpRecord.setSgvDate(new Date(sgvDate.getTime() - pumpRecord.getPumpTimeOffset()));
+ pumpRecord.setCgmPumpDate(cgmDate);
+ pumpRecord.setCgmDate(new Date(cgmDate.getTime() - pumpRecord.getPumpTimeOffset()));
pumpRecord.setCgmTrend(cgmTrend);
+ pumpRecord.setCgmExceptionType(cgmExceptionType);
// Predictive low suspend
// TODO - there is more status info in this byte other than just a boolean yes/no
pumpRecord.setLowSuspendActive(lowSuspendActive);
- // Recent Bolus Wizard BGL
- if (bolusWizardBGL > 0
- && (DataStore.getInstance().getLastPumpStatus().getSgvDate().getTime() - System.currentTimeMillis() > 15 * 60 * 1000
- || (DataStore.getInstance().getLastBolusWizardBGL() != bolusWizardBGL
- && DataStore.getInstance().getLastPumpStatus().getBolusWizardBGL() != bolusWizardBGL)
- )
- ) {
- pumpRecord.setRecentBolusWizard(true);
- pumpRecord.setBolusWizardBGL(bolusWizardBGL); // In mg/DL
- } else {
- pumpRecord.setRecentBolusWizard(false);
- pumpRecord.setBolusWizardBGL(0); // In mg/DL
- }
- DataStore.getInstance().setLastBolusWizardBGL(bolusWizardBGL);
+ // Recent BGL
+ pumpRecord.setRecentBGL(recentBGL); // In mg/DL
+
+ // Active alert
+ pumpRecord.setAlert(alert);
+ pumpRecord.setAlertPumpDate(alertDate);
+ pumpRecord.setAlertDate(new Date(alertDate.getTime() - pumpRecord.getPumpTimeOffset()));
+
+ // Now bolusing
+ pumpRecord.setBolusingDelivered(bolusingDelivered);
+ pumpRecord.setBolusingMinutesRemaining(bolusingMinutesRemaining);
+ pumpRecord.setBolusingReference(bolusingReference);
+
+ // Last bolus
+ pumpRecord.setLastBolusAmount(lastBolusAmount);
+ pumpRecord.setLastBolusPumpDate(lastBolusDate);
+ pumpRecord.setLastBolusDate(new Date(lastBolusDate.getTime() - pumpRecord.getPumpTimeOffset()));
+ pumpRecord.setLastBolusReference(lastBolusReference);
+
+ // Calibration
+ pumpRecord.setCalibrationDueMinutes(calibrationDueMinutes);
+
+ // Transmitter
+ pumpRecord.setTransmitterBattery(transmitterBattery);
+ pumpRecord.setTransmitterControl(transmitterControl);
+
+ // Sensor
+ pumpRecord.setSensorRateOfChange(sensorRateOfChange);
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/android/medtronic/service/MedtronicCnlIntentService.java b/app/src/main/java/info/nightscout/android/medtronic/service/MedtronicCnlIntentService.java
index bf3c333..d1a80ed 100644
--- a/app/src/main/java/info/nightscout/android/medtronic/service/MedtronicCnlIntentService.java
+++ b/app/src/main/java/info/nightscout/android/medtronic/service/MedtronicCnlIntentService.java
@@ -40,33 +40,49 @@
import info.nightscout.android.xdrip_plus.XDripPlusUploadReceiver;
import io.realm.Realm;
import io.realm.RealmResults;
+import io.realm.Sort;
public class MedtronicCnlIntentService extends IntentService {
public final static int USB_VID = 0x1a79;
public final static int USB_PID = 0x6210;
public final static long USB_WARMUP_TIME_MS = 5000L;
+
public final static long POLL_PERIOD_MS = 300000L;
public final static long LOW_BATTERY_POLL_PERIOD_MS = 900000L;
// Number of additional seconds to wait after the next expected CGM poll, so that we don't interfere with CGM radio comms.
public final static long POLL_GRACE_PERIOD_MS = 30000L;
+ // Number of seconds before the next expected CGM poll that we will allow uploader comms to start
public final static long POLL_PRE_GRACE_PERIOD_MS = 45000L;
-
- public static final String ICON_WARN = "{ion-alert-circled} ";
- public static final String ICON_BGL = "{ion-waterdrop} ";
- public static final String ICON_USB = "{ion-usb} ";
- public static final String ICON_INFO = "{ion-information_circled} ";
- public static final String ICON_HELP = "{ion-ios-lightbulb} ";
- public static final String ICON_SETTING = "{ion-android-settings} ";
- public static final String ICON_HEART = "{ion-heart} ";
- public static final String ICON_STAR = "{ion-ios-star} ";
+ // cgm n/a events to trigger anti clash poll timing
+ public final static int POLL_ANTI_CLASH = 3;
+
+ // TODO - use a message type and insert icon as part of ui status message handling
+ public static final String ICON_WARN = "{ion_alert_circled} ";
+ public static final String ICON_BGL = "{ion_waterdrop} ";
+ public static final String ICON_USB = "{ion_usb} ";
+ public static final String ICON_INFO = "{ion_information_circled} ";
+ public static final String ICON_HELP = "{ion_ios_lightbulb} ";
+ public static final String ICON_SETTING = "{ion_android_settings} ";
+ public static final String ICON_HEART = "{ion_heart} ";
+ public static final String ICON_LOW = "{ion_battery_low} ";
+ public static final String ICON_FULL = "{ion_battery_full} ";
+ public static final String ICON_CGM = "{ion_ios_pulse_strong} ";
+ public static final String ICON_SUSPEND = "{ion_pause} ";
+ public static final String ICON_RESUME = "{ion_play} ";
+ public static final String ICON_BOLUS = "{ion_skip_forward} ";
+ public static final String ICON_BASAL = "{ion_skip_forward} ";
+ public static final String ICON_CHANGE = "{ion_android_hand} ";
+ public static final String ICON_BELL = "{ion_android_notifications} ";
+ public static final String ICON_NOTE = "{ion_clipboard} ";
// show warning message after repeated errors
private final static int ERROR_COMMS_AT = 4;
- private final static int ERROR_CONNECT_AT = 4;
- private final static int ERROR_SIGNAL_AT = 4;
- private final static float ERROR_UNAVAILABLE_AT = 12; // warning at
- private final static float ERROR_UNAVAILABLE_RATE = 12 / 3; // expected per hour / acceptable unavailable per hour
- private final static float ERROR_UNAVAILABLE_DECAY = -1; // decay rate for each good sgv received
+ private final static int ERROR_CONNECT_AT = 6;
+ private final static int ERROR_SIGNAL_AT = 6;
+ private final static int ERROR_PUMPLOSTSENSOR_AT = 6;
+ private final static int ERROR_PUMPBATTERY_AT = 3;
+ private final static int ERROR_PUMPCLOCK_AT = 8;
+ private final static int ERROR_PUMPCLOCK_MS = 10 * 60 * 1000;
private static final String TAG = MedtronicCnlIntentService.class.getSimpleName();
@@ -77,7 +93,9 @@ public class MedtronicCnlIntentService extends IntentService {
private DataStore dataStore = DataStore.getInstance();
private ConfigurationStore configurationStore = ConfigurationStore.getInstance();
private DateFormat dateFormatter = new SimpleDateFormat("HH:mm:ss", Locale.US);
-
+ private DateFormat dateFormatterNote = new SimpleDateFormat("E HH:mm", Locale.US);
+ private Realm realm;
+ private long pumpOffset;
public MedtronicCnlIntentService() {
super(MedtronicCnlIntentService.class.getName());
@@ -143,57 +161,23 @@ public void onDestroy() {
protected void onHandleIntent(Intent intent) {
Log.d(TAG, "onHandleIntent called");
try {
- // TODO use of ConfigurationStore is confusinng if pollInterval uses the CS, which
- // uses the POLL_PERIOD_MS, while the latter constant is also used directly.
-
- // Note that the variable pollInterval refers to the poll we'd like to make to the pump,
- // based on settings and battery level, while POLL_PERIOD_MS is used to calculate
- // when the pump is going to poll data from the transmitter again.
- // Thus POLL_PERIOD_MS is important to calculate times we'd be clashing with transmitter
- // to pump transmissions, which are then checked against the time the uploader would
- // like to poll, which is calculated using the pollInterval variable.
- // TODO find better variable names to make this distinction clearer and/or if possible
- // do more method extraction refactorings to make this method easier to grasp
-
final long timePollStarted = System.currentTimeMillis();
+ long pollInterval = configurationStore.getPollInterval();
+ realm = Realm.getDefaultInstance();
- long timeLastGoodSGV = dataStore.getLastPumpStatus().getSgvDate().getTime();
- if (dataStore.getLastPumpStatus().getSgv() == 0
- || timePollStarted - timeLastGoodSGV > 24 * 60 * 60 * 1000) {
- timeLastGoodSGV = 0;
- }
-
- final long timePollExpected;
- if (timeLastGoodSGV != 0) {
- timePollExpected = timeLastGoodSGV + POLL_PERIOD_MS + POLL_GRACE_PERIOD_MS + (POLL_PERIOD_MS * ((timePollStarted - 1000L - (timeLastGoodSGV + POLL_GRACE_PERIOD_MS)) / POLL_PERIOD_MS));
- } else {
- timePollExpected = timePollStarted;
- }
-
- // avoid polling when too close to sensor-pump comms
- if (((timePollExpected - timePollStarted) > 5000L) && ((timePollExpected - timePollStarted) < (POLL_PRE_GRACE_PERIOD_MS + POLL_GRACE_PERIOD_MS))) {
- sendStatus("Please wait: Pump is expecting sensor communication. Poll due in " + ((timePollExpected - timePollStarted) / 1000L) + " seconds");
- MedtronicCnlAlarmManager.setAlarm(timePollExpected);
+ long due = checkPollTime();
+ if (due > 0) {
+ sendStatus("Please wait: Pump is expecting sensor communication. Poll due in " + ((due - System.currentTimeMillis()) / 1000L) + " seconds");
+ MedtronicCnlAlarmManager.setAlarm(due);
return;
}
- final short pumpBatteryLevel = dataStore.getLastPumpStatus().getBatteryPercentage();
- long pollInterval = configurationStore.getPollInterval();
- if ((pumpBatteryLevel > 0) && (pumpBatteryLevel <= 25)) {
- pollInterval = configurationStore.getLowBatteryPollInterval();
- sendStatus(ICON_WARN + "Warning: pump battery low");
- if (pollInterval != configurationStore.getPollInterval()) {
- sendStatus(ICON_SETTING + "Low battery poll interval: " + (pollInterval / 60000) +" minutes");
- }
- }
-
// TODO - throw, don't return
if (!openUsbDevice())
return;
MedtronicCnlReader cnlReader = new MedtronicCnlReader(mHidDevice);
- Realm realm = Realm.getDefaultInstance();
realm.beginTransaction();
try {
@@ -223,7 +207,7 @@ protected void onHandleIntent(Intent intent) {
// always get LinkKey on startup to handle re-paired CNL-PUMP key changes
String key = null;
- if (dataStore.getCommsSuccessCount() > 0) {
+ if (dataStore.getCommsSuccess() > 0) {
key = info.getKey();
}
@@ -253,22 +237,21 @@ protected void onHandleIntent(Intent intent) {
if (radioChannel == 0) {
sendStatus(ICON_WARN + "Could not communicate with the pump. Is it nearby?");
Log.i(TAG, "Could not communicate with the pump. Is it nearby?");
- dataStore.incCommsConnectThreshold();
- pollInterval = configurationStore.getPollInterval() / (configurationStore.isReducePollOnPumpAway() ? 2L : 1L); // reduce polling interval to half until pump is available
+ dataStore.incCommsConnectError();
+ pollInterval = POLL_PERIOD_MS / (configurationStore.isReducePollOnPumpAway() ? 2L : 1L); // reduce polling interval to half until pump is available
} else if (cnlReader.getPumpSession().getRadioRSSIpercentage() < 5) {
sendStatus(String.format(Locale.getDefault(), "Connected on channel %d RSSI: %d%%", (int) radioChannel, cnlReader.getPumpSession().getRadioRSSIpercentage()));
sendStatus(ICON_WARN + "Warning: pump signal too weak. Is it nearby?");
Log.i(TAG, "Warning: pump signal too weak. Is it nearby?");
- dataStore.incCommsConnectThreshold();
- dataStore.incCommsSignalThreshold();
- pollInterval = configurationStore.getPollInterval() / (configurationStore.isReducePollOnPumpAway() ? 2L : 1L); // reduce polling interval to half until pump is available
+ dataStore.incCommsConnectError();
+ dataStore.incCommsSignalError();
+ pollInterval = POLL_PERIOD_MS / (configurationStore.isReducePollOnPumpAway() ? 2L : 1L); // reduce polling interval to half until pump is available
} else {
- dataStore.decCommsConnectThreshold();
- if (cnlReader.getPumpSession().getRadioRSSIpercentage() < 20) {
- if (dataStore.getCommsSignalThreshold() < ERROR_SIGNAL_AT) dataStore.incCommsSignalThreshold();
- } else {
- dataStore.decCommsSignalThreshold();
- }
+ dataStore.decCommsConnectError();
+ if (cnlReader.getPumpSession().getRadioRSSIpercentage() < 20)
+ dataStore.incCommsSignalError();
+ else
+ dataStore.decCommsSignalError();
dataStore.setActivePumpMac(pumpMAC);
@@ -285,79 +268,85 @@ protected void onHandleIntent(Intent intent) {
// TODO - this should not be necessary. We should reverse lookup the device name from PumpInfo
pumpRecord.setDeviceName(deviceName);
+ if (radioChannel == 26) cnlReader.beginEHSMSession(); // gentle persuasion to leave channel 26 (weakest for CNL and causes Pebble to lose BT often) by using EHSM to influence pump channel change
+
long pumpTime = cnlReader.getPumpTime().getTime();
- long pumpOffset = pumpTime - System.currentTimeMillis();
+ pumpOffset = pumpTime - System.currentTimeMillis();
Log.d(TAG, "Time offset between pump and device: " + pumpOffset + " millis.");
- if (Math.abs(pumpOffset) > 10 * 60 * 1000) {
- sendStatus(ICON_WARN + "Warning: Time difference between Pump and Uploader excessive."
- + " Pump is over " + (Math.abs(pumpOffset) / 60000L) + " minutes " + (pumpOffset > 0 ? "ahead" : "behind") + " of time used by uploader.");
- sendStatus(ICON_HELP + "The uploader phone/device should have the current time provided by network. Pump clock drifts forward and needs to be set to correct time occasionally.");
- }
-
- // TODO - send ACTION to MainActivity to show offset between pump and uploader.
pumpRecord.setPumpTimeOffset(pumpOffset);
- pumpRecord.setPumpDate(new Date(pumpTime - pumpOffset));
+ pumpRecord.setPumpDate(new Date(pumpTime));
+ pumpRecord.setEventDate(new Date(System.currentTimeMillis()));
cnlReader.updatePumpStatus(pumpRecord);
- if (pumpRecord.getSgv() != 0) {
- sendStatus("SGV: " + MainActivity.strFormatSGV(pumpRecord.getSgv())
- + " At: " + dateFormatter.format(pumpRecord.getSgvDate().getTime())
- + " Pump: " + (pumpOffset > 0 ? "+" : "") + (pumpOffset / 1000L) + "sec");
- // Check if pump sent old event when new expected
- if (dataStore.getLastPumpStatus() != null &&
- dataStore.getLastPumpStatus().getSgvDate() != null &&
- pumpRecord.getSgvDate().getTime() - dataStore.getLastPumpStatus().getSgvDate().getTime() < 5000L &&
- timePollExpected - timePollStarted < 5000L) {
- sendStatus(ICON_WARN + "Pump sent old SGV event");
- if (dataStore.getCommsUnavailableThreshold() < ERROR_UNAVAILABLE_AT) dataStore.addCommsUnavailableThreshold(ERROR_UNAVAILABLE_RATE / (configurationStore.isReducePollOnPumpAway() ? 2L : 1L));
- // pump may have missed sensor transmission or be delayed in posting to status message
- // in most cases the next scheduled poll will have latest sgv, occasionally it is available this period after a delay
- // if user selects double poll option we try again this period or wait until next
- pollInterval = POLL_PERIOD_MS / (configurationStore.isReducePollOnPumpAway() ? 2L : 1L);
- } else {
- dataStore.addCommsUnavailableThreshold(ERROR_UNAVAILABLE_DECAY);
- }
+ if (radioChannel == 26) cnlReader.endEHSMSession();
+
+ validatePumpRecord(pumpRecord, activePump);
+ activePump.getPumpHistory().add(pumpRecord);
+ realm.commitTransaction();
+
+ dataStore.incCommsSuccess();
+ dataStore.clearCommsError();
+
+ if (pumpRecord.getBatteryPercentage() <= 25) {
+ dataStore.incPumpBatteryError();
+ pollInterval = configurationStore.getLowBatteryPollInterval();
+ } else {
+ dataStore.clearPumpBatteryError();
+ }
- dataStore.clearUnavailableSGVCount(); // reset unavailable sgv count
-
- // Check that the record doesn't already exist before committing
- RealmResults checkExistingRecords = activePump.getPumpHistory()
- .where()
- .equalTo("sgvDate", pumpRecord.getSgvDate())
- .equalTo("sgv", pumpRecord.getSgv())
- .findAll();
-
- // There should be the 1 record we've already added in this transaction.
- if (checkExistingRecords.size() == 0) {
- timeLastGoodSGV = pumpRecord.getSgvDate().getTime();
- activePump.getPumpHistory().add(pumpRecord);
- dataStore.setLastPumpStatus(pumpRecord);
- if (pumpRecord.getBolusWizardBGL() != 0) {
- sendStatus(ICON_BGL +"Recent finger BG: " + MainActivity.strFormatSGV(pumpRecord.getBolusWizardBGL()));
+ if (pumpRecord.isCgmActive()) {
+ dataStore.clearPumpCgmNA(); // poll clash detection
+ dataStore.clearPumpLostSensorError();
+
+ if (pumpRecord.isCgmWarmUp())
+ sendStatus(ICON_CGM + "sensor is in warm-up phase");
+ else if (pumpRecord.getCalibrationDueMinutes() == 0)
+ sendStatus(ICON_CGM + "sensor calibration is due now!");
+ else if (pumpRecord.getSgv() == 0 && pumpRecord.isCgmCalibrating())
+ sendStatus(ICON_CGM + "sensor is calibrating");
+ else if (pumpRecord.getSgv() == 0)
+ sendStatus(ICON_CGM + "sensor error (pump graph gap)");
+ else {
+ dataStore.incCommsSgvSuccess();
+ // TODO - don't convert SGV here, convert dynamically in ui status message handler
+ sendStatus("SGV: " + MainActivity.strFormatSGV(pumpRecord.getSgv())
+ + " At: " + dateFormatter.format(pumpRecord.getCgmDate().getTime())
+ + " Pump: " + (pumpOffset > 0 ? "+" : "") + (pumpOffset / 1000L) + "sec");
+ if (pumpRecord.isCgmCalibrating())
+ sendStatus(ICON_CGM + "sensor is calibrating");
+ if (pumpRecord.isOldSgvWhenNewExpected()) {
+ sendStatus(ICON_CGM + "old SGV event received");
+ // pump may have missed sensor transmission or be delayed in posting to status message
+ // in most cases the next scheduled poll will have latest sgv, occasionally it is available this period after a delay
+ // if user selects double poll option we try again this period or wait until next
+ pollInterval = configurationStore.isReducePollOnPumpAway() ? 60000 : POLL_PERIOD_MS;
}
}
} else {
- sendStatus(ICON_WARN + "SGV: unavailable from pump");
- dataStore.incUnavailableSGVCount(); // poll clash detection
- if (dataStore.getCommsUnavailableThreshold() < ERROR_UNAVAILABLE_AT) dataStore.addCommsUnavailableThreshold(ERROR_UNAVAILABLE_RATE);
+ dataStore.incPumpCgmNA(); // poll clash detection
+ if (dataStore.getCommsSgvSuccess() > 0) {
+ dataStore.incPumpLostSensorError(); // only count errors if cgm is being used
+ sendStatus(ICON_CGM + "cgm n/a (pump lost sensor)");
+ } else {
+ sendStatus(ICON_CGM + "cgm n/a");
+ }
}
- realm.commitTransaction();
+ sendStatusNotifications(pumpRecord);
+
// Tell the Main Activity we have new data
sendMessage(Constants.ACTION_UPDATE_PUMP);
- dataStore.incCommsSuccessCount();
- dataStore.clearCommsErrorCount();
}
} catch (UnexpectedMessageException e) {
- dataStore.incCommsErrorCount();
+ dataStore.incCommsError();
pollInterval = 60000L; // retry once during this poll period, this allows for transient radio noise
Log.e(TAG, "Unexpected Message", e);
sendStatus(ICON_WARN + "Communication Error: " + e.getMessage());
} catch (TimeoutException e) {
- dataStore.incCommsErrorCount();
+ dataStore.incCommsError();
pollInterval = 90000L; // retry once during this poll period, this allows for transient radio noise
Log.e(TAG, "Timeout communicating with the Contour Next Link.", e);
sendStatus(ICON_WARN + "Timeout communicating with the Contour Next Link / Pump.");
@@ -369,102 +358,496 @@ protected void onHandleIntent(Intent intent) {
cnlReader.closeConnection();
cnlReader.endPassthroughMode();
cnlReader.endControlMode();
- } catch (NoSuchAlgorithmException e) {
- }
-
+ } catch (NoSuchAlgorithmException e) {}
}
} catch (IOException e) {
- dataStore.incCommsErrorCount();
+ dataStore.incCommsError();
Log.e(TAG, "Error connecting to Contour Next Link.", e);
sendStatus(ICON_WARN + "Error connecting to Contour Next Link.");
} catch (ChecksumException e) {
- dataStore.incCommsErrorCount();
+ dataStore.incCommsError();
Log.e(TAG, "Checksum error getting message from the Contour Next Link.", e);
sendStatus(ICON_WARN + "Checksum error getting message from the Contour Next Link.");
} catch (EncryptionException e) {
- dataStore.incCommsErrorCount();
+ dataStore.incCommsError();
Log.e(TAG, "Error decrypting messages from Contour Next Link.", e);
sendStatus(ICON_WARN + "Error decrypting messages from Contour Next Link.");
} catch (TimeoutException e) {
- dataStore.incCommsErrorCount();
+ dataStore.incCommsError();
Log.e(TAG, "Timeout communicating with the Contour Next Link.", e);
sendStatus(ICON_WARN + "Timeout communicating with the Contour Next Link.");
} catch (UnexpectedMessageException e) {
- dataStore.incCommsErrorCount();
+ dataStore.incCommsError();
Log.e(TAG, "Could not close connection.", e);
sendStatus(ICON_WARN + "Could not close connection: " + e.getMessage());
} finally {
- if (!realm.isClosed()) {
- if (realm.isInTransaction()) {
- // If we didn't commit the transaction, we've run into an error. Let's roll it back
- realm.cancelTransaction();
- }
- realm.close();
+ if (realm.isInTransaction()) {
+ // If we didn't commit the transaction, we've run into an error. Let's roll it back
+ realm.cancelTransaction();
}
+ long nextpoll = requestPollTime(timePollStarted, pollInterval);
+ MedtronicCnlAlarmManager.setAlarm(nextpoll);
+ sendStatus("Next poll due at: " + dateFormatter.format(nextpoll));
+
+ RemoveOutdatedRecords();
uploadPollResults();
- scheduleNextPoll(timePollStarted, timeLastGoodSGV, pollInterval);
+ sendStatusWarnings();
+ }
+
+ } finally {
+ if (!realm.isClosed()) realm.close();
+ MedtronicCnlAlarmReceiver.completeWakefulIntent(intent);
+ }
+ }
+
+ private void sendStatusNotifications(PumpStatusEvent pumpRecord) {
+ if (pumpRecord.isValidBGL())
+ // TODO - don't convert BGL here, convert dynamically in ui status message handler
+ sendStatus(ICON_BGL + "Recent finger BG: " + MainActivity.strFormatSGV(pumpRecord.getRecentBGL()));
+
+ if (pumpRecord.isValidBolus()) {
+ if (pumpRecord.isValidBolusSquare())
+ sendStatus(ICON_BOLUS + "Square bolus delivered: " + pumpRecord.getLastBolusAmount() + "u Started: " + dateFormatter.format(pumpRecord.getLastBolusDate()) + " Duration: " + pumpRecord.getLastBolusDuration() + " minutes");
+ else if (pumpRecord.isValidBolusDual())
+ sendStatus(ICON_BOLUS + "Bolus (dual normal part): " + pumpRecord.getLastBolusAmount() + "u At: " + dateFormatter.format(pumpRecord.getLastBolusDate()));
+ else
+ sendStatus(ICON_BOLUS + "Bolus: " + pumpRecord.getLastBolusAmount() + "u At: " + dateFormatter.format(pumpRecord.getLastBolusDate()));
+ }
+
+ if (pumpRecord.isValidTEMPBASAL()) {
+ if (pumpRecord.getTempBasalMinutesRemaining() > 0 && pumpRecord.getTempBasalPercentage() > 0)
+ sendStatus(ICON_BASAL + "Temp basal: " + pumpRecord.getTempBasalPercentage() + "% Remaining: " + pumpRecord.getTempBasalMinutesRemaining() + " minutes");
+ else if (pumpRecord.getTempBasalMinutesRemaining() > 0)
+ sendStatus(ICON_BASAL + "Temp basal: " + pumpRecord.getTempBasalRate() + "u Remaining: " + pumpRecord.getTempBasalMinutesRemaining() + " minutes");
+ else
+ sendStatus(ICON_BASAL + "Temp basal: stopped before expected duration");
+ }
+
+ if (pumpRecord.isValidSUSPEND())
+ sendStatus(ICON_SUSPEND + "Pump suspended insulin delivery approx: " + dateFormatterNote.format(pumpRecord.getSuspendAfterDate()) + " - " + dateFormatterNote.format(pumpRecord.getSuspendBeforeDate()));
+ if (pumpRecord.isValidSUSPENDOFF())
+ sendStatus(ICON_RESUME + "Pump resumed insulin delivery approx: " + dateFormatterNote.format(pumpRecord.getSuspendAfterDate()) + " - " + dateFormatterNote.format(pumpRecord.getSuspendBeforeDate()));
+
+ if (pumpRecord.isValidSAGE())
+ sendStatus(ICON_CHANGE + "Sensor changed approx: " + dateFormatterNote.format(pumpRecord.getSageAfterDate()) + " - " + dateFormatterNote.format(pumpRecord.getSageBeforeDate()));
+ if (pumpRecord.isValidCAGE())
+ sendStatus(ICON_CHANGE + "Reservoir changed approx: " + dateFormatterNote.format(pumpRecord.getCageAfterDate()) + " - " + dateFormatterNote.format(pumpRecord.getCageBeforeDate()));
+ if (pumpRecord.isValidBATTERY())
+ sendStatus(ICON_CHANGE + "Pump battery changed approx: " + dateFormatterNote.format(pumpRecord.getBatteryAfterDate()) + " - " + dateFormatterNote.format(pumpRecord.getBatteryBeforeDate()));
+
+ if (pumpRecord.isValidALERT())
+ sendStatus(ICON_BELL + "Active alert on pump At: " + dateFormatter.format(pumpRecord.getAlertDate()));
+ }
+
+ private void sendStatusWarnings() {
+
+ if (dataStore.getPumpBatteryError() >= ERROR_PUMPBATTERY_AT) {
+ dataStore.clearPumpBatteryError();
+ sendStatus(ICON_WARN + "Warning: pump battery low");
+ if (configurationStore.getLowBatteryPollInterval() != configurationStore.getPollInterval())
+ sendStatus(ICON_SETTING + "Low battery poll interval: " + (configurationStore.getLowBatteryPollInterval() / 60000) + " minutes");
+ }
+
+ if (Math.abs(pumpOffset) > ERROR_PUMPCLOCK_MS)
+ dataStore.incPumpClockError();
+ if (dataStore.getPumpClockError() >= ERROR_PUMPCLOCK_AT) {
+ dataStore.clearPumpClockError();
+ sendStatus(ICON_WARN + "Warning: Time difference between Pump and Uploader excessive."
+ + " Pump is over " + (Math.abs(pumpOffset) / 60000L) + " minutes " + (pumpOffset > 0 ? "ahead" : "behind") + " of time used by uploader.");
+ sendStatus(ICON_HELP + "The uploader phone/device should have the current time provided by network. Pump clock drifts forward and needs to be set to correct time occasionally.");
+ }
+
+ if (dataStore.getCommsError() >= ERROR_COMMS_AT) {
+ sendStatus(ICON_WARN + "Warning: multiple comms/timeout errors detected.");
+ sendStatus(ICON_HELP + "Try: disconnecting and reconnecting the Contour Next Link to phone / restarting phone / check pairing of CNL with Pump.");
+ }
+
+ if (dataStore.getPumpLostSensorError() >= ERROR_PUMPLOSTSENSOR_AT) {
+ dataStore.clearPumpLostSensorError();
+ sendStatus(ICON_WARN + "Warning: SGV is unavailable from pump often. The pump is missing transmissions from the sensor.");
+ sendStatus(ICON_HELP + "Keep pump on same side of body as sensor. Avoid using body sensor locations that can block radio signal.");
+ }
+
+ if (dataStore.getCommsConnectError() >= ERROR_CONNECT_AT * (configurationStore.isReducePollOnPumpAway() ? 2 : 1)) {
+ dataStore.clearCommsConnectError();
+ sendStatus(ICON_WARN + "Warning: connecting to pump is failing often.");
+ sendStatus(ICON_HELP + "Keep pump nearby to uploader phone/device. The body can block radio signals between pump and uploader.");
+ }
+
+ if (dataStore.getCommsSignalError() >= ERROR_SIGNAL_AT) {
+ dataStore.clearCommsSignalError();
+ sendStatus(ICON_WARN + "Warning: RSSI radio signal from pump is generally weak and may increase errors.");
+ sendStatus(ICON_HELP + "Keep pump nearby to uploader phone/device. The body can block radio signals between pump and uploader.");
+ }
+ }
+
+ private void RemoveOutdatedRecords() {
+ final RealmResults results =
+ realm.where(PumpStatusEvent.class)
+ .lessThan("eventDate", new Date(System.currentTimeMillis() - (48 * 60 * 60 * 1000)))
+ .findAll();
+
+ if (results.size() > 0) {
+ realm.executeTransaction(new Realm.Transaction() {
+ @Override
+ public void execute(Realm realm) {
+ // Delete all matches
+ Log.d(TAG, "Deleting " + results.size() + " records from realm");
+ results.deleteAllFromRealm();
+ }
+ });
+ }
+ }
+
+ // TODO - check on optimal use of Realm search+results as we make heavy use for validation
- // TODO - Refactor warning system
- if (dataStore.getCommsErrorCount() >= ERROR_COMMS_AT) {
- sendStatus(ICON_WARN + "Warning: multiple comms/timeout errors detected.");
- sendStatus(ICON_HELP + "Try: disconnecting and reconnecting the Contour Next Link to phone / restarting phone / check pairing of CNL with Pump.");
+ private void validatePumpRecord(PumpStatusEvent pumpRecord, PumpInfo activePump) {
+
+ int index;
+ Date uploaderStartDate = dataStore.getUploaderStartDate();
+
+ // TODO - pump validation is unused but will allow for future record manipulation when adding data from pump history message (gap fill)
+
+ // validate that this contains a new PUMP record
+ pumpRecord.setValidPUMP(true);
+
+ // TODO - cgm validation - handle sensor exceptions
+
+ // validate that this contains a new CGM record
+ if (pumpRecord.isCgmActive()) {
+ pumpRecord.setValidCGM(true);
+ }
+
+ // validate that this contains a new SGV record
+ if (pumpRecord.isCgmActive()) {
+ if (pumpRecord.getPumpDate().getTime() - pumpRecord.getCgmPumpDate().getTime() > POLL_PERIOD_MS + (POLL_GRACE_PERIOD_MS / 2))
+ pumpRecord.setOldSgvWhenNewExpected(true);
+ else if (!pumpRecord.isCgmWarmUp() && pumpRecord.getSgv() > 0) {
+ RealmResults sgv_results = activePump.getPumpHistory()
+ .where()
+ .equalTo("cgmPumpDate", pumpRecord.getCgmPumpDate())
+ .equalTo("validSGV", true)
+ .findAll();
+ if (sgv_results.size() == 0)
+ pumpRecord.setValidSGV(true);
+ }
+ }
+
+ // validate that this contains a new BGL record
+ if (pumpRecord.getRecentBGL() != 0) {
+ RealmResults bgl_results = activePump.getPumpHistory()
+ .where()
+ .greaterThan("eventDate", new Date(System.currentTimeMillis() - (20 * 60 * 1000)))
+ .equalTo("recentBGL", pumpRecord.getRecentBGL())
+ .findAll();
+ if (bgl_results.size() == 0) {
+ pumpRecord.setValidBGL(true);
+ }
+ }
+
+ // TODO - square/dual stopped and new bolus started between poll snapshots, check realm history to handle this?
+
+ // validate that this contains a new BOLUS record
+ RealmResults lastbolus_results = activePump.getPumpHistory()
+ .where()
+ .equalTo("lastBolusPumpDate", pumpRecord.getLastBolusPumpDate())
+ .equalTo("lastBolusReference", pumpRecord.getLastBolusReference())
+ .equalTo("validBolus", true)
+ .findAll();
+
+ if (lastbolus_results.size() == 0) {
+
+ RealmResults bolusing_results = activePump.getPumpHistory()
+ .where()
+ .equalTo("bolusingReference", pumpRecord.getLastBolusReference())
+ .greaterThan("bolusingMinutesRemaining", 10) // if a manual normal bolus referred to here while square is being delivered it will show the remaining time for all bolusing
+ .findAllSorted("eventDate", Sort.ASCENDING);
+
+ if (bolusing_results.size() > 0) {
+ long start = pumpRecord.getLastBolusPumpDate().getTime();
+ long start_bolusing = bolusing_results.first().getPumpDate().getTime();
+
+ // if pump battery is changed during square/dual bolus period the last bolus time will be set to this time (pump asks user to resume/cancel bolus)
+ // use bolusing start time when this is detected
+ if (start - start_bolusing > 10 * 60)
+ start = start_bolusing;
+
+ long end = pumpRecord.getPumpDate().getTime();
+ long duration = start_bolusing - start + (bolusing_results.first().getBolusingMinutesRemaining() * 60000);
+ if (start + duration > end) // was square bolus stopped before expected duration?
+ duration = end - start;
+
+ // check that this was a square bolus and not a normal bolus
+ if (duration > 10) {
+ pumpRecord.setValidBolus(true);
+ pumpRecord.setValidBolusSquare(true);
+ pumpRecord.setLastBolusDate(new Date (start));
+ pumpRecord.setLastBolusDuration((short) (duration / 60000));
}
- if (dataStore.getCommsUnavailableThreshold() >= ERROR_UNAVAILABLE_AT) {
- dataStore.clearCommsUnavailableThreshold();
- sendStatus(ICON_WARN + "Warning: SGV unavailable from pump is happening often. The pump is missing transmissions from the sensor / in warm-up phase / environment radio noise.");
- sendStatus(ICON_HELP + "Keep pump on same side of body as sensor. Avoid using body sensor locations that can block radio signal. Sensor may be old / faulty and need changing (check pump graph for gaps).");
+ }
+
+ else if (pumpRecord.getLastBolusDate().getTime() >= uploaderStartDate.getTime()) {
+ pumpRecord.setValidBolus(true);
+ if (pumpRecord.getBolusingReference() == pumpRecord.getLastBolusReference()
+ && pumpRecord.getBolusingMinutesRemaining() > 10) {
+ pumpRecord.setValidBolusDual(true);
+ }
+ }
+ }
+
+ // validate that this contains a new TEMP BASAL record
+ // temp basal: rate / percentage can be set on pump for max duration of 24 hours / 1440 minutes
+ RealmResults tempbasal_results = activePump.getPumpHistory()
+ .where()
+ .greaterThan("eventDate", new Date(System.currentTimeMillis() - (24 * 60 * 60 * 1000)))
+ .findAllSorted("eventDate", Sort.DESCENDING);
+ if (pumpRecord.getTempBasalMinutesRemaining() > 0) {
+ index = 0;
+ if (tempbasal_results.size() > 1) {
+ short minutes = pumpRecord.getTempBasalMinutesRemaining();
+ for (index = 0; index < tempbasal_results.size(); index++) {
+ if (tempbasal_results.get(index).getTempBasalMinutesRemaining() < minutes ||
+ tempbasal_results.get(index).getTempBasalPercentage() != pumpRecord.getTempBasalPercentage() ||
+ tempbasal_results.get(index).getTempBasalRate() != pumpRecord.getTempBasalRate() ||
+ tempbasal_results.get(index).isValidTEMPBASAL())
+ break;
+ minutes = tempbasal_results.get(index).getTempBasalMinutesRemaining();
}
- if (dataStore.getCommsConnectThreshold() >= ERROR_CONNECT_AT * (configurationStore.isReducePollOnPumpAway() ? 2 : 1)) {
- dataStore.clearCommsConnectThreshold();
- sendStatus(ICON_WARN + "Warning: connecting to pump is failing often.");
- sendStatus(ICON_HELP + "Keep pump nearby to uploader phone/device. The body can block radio signals between pump and uploader.");
+ }
+ if (tempbasal_results.size() > 0)
+ if (!tempbasal_results.get(index).isValidTEMPBASAL() ||
+ tempbasal_results.get(index).getTempBasalPercentage() != pumpRecord.getTempBasalPercentage() ||
+ tempbasal_results.get(index).getTempBasalRate() != pumpRecord.getTempBasalRate()) {
+ pumpRecord.setValidTEMPBASAL(true);
+ pumpRecord.setTempBasalAfterDate(tempbasal_results.get(index).getEventDate());
+ pumpRecord.setTempBasalBeforeDate(pumpRecord.getEventDate());
}
- if (dataStore.getCommsSignalThreshold() >= ERROR_SIGNAL_AT) {
- dataStore.clearCommsSignalThreshold();
- sendStatus(ICON_WARN + "Warning: RSSI radio signal from pump is generally weak and may increase errors.");
- sendStatus(ICON_HELP + "Keep pump nearby to uploader phone/device. The body can block radio signals between pump and uploader.");
+ } else {
+ // check if stopped before expected duration
+ if (tempbasal_results.size() > 0)
+ if (pumpRecord.getPumpDate().getTime() - tempbasal_results.first().getPumpDate().getTime() - (tempbasal_results.first().getTempBasalMinutesRemaining() * 60 * 1000) < -60 * 1000) {
+ pumpRecord.setValidTEMPBASAL(true);
+ pumpRecord.setTempBasalAfterDate(tempbasal_results.first().getEventDate());
+ pumpRecord.setTempBasalBeforeDate(pumpRecord.getEventDate());
}
+ }
+ // validate that this contains a new SUSPEND record
+ RealmResults suspend_results = activePump.getPumpHistory()
+ .where()
+ .greaterThan("eventDate", new Date(System.currentTimeMillis() - (2 * 60 * 60 * 1000)))
+ .equalTo("validSUSPEND", true)
+ .or()
+ .equalTo("validSUSPENDOFF", true)
+ .findAllSorted("eventDate", Sort.DESCENDING);
+
+ if (suspend_results.size() > 0) {
+ // new valid suspend - set temp basal for 0u 60m in NS
+ if (pumpRecord.isSuspended() && suspend_results.first().isValidSUSPENDOFF()) {
+ pumpRecord.setValidSUSPEND(true);
}
- } finally {
- MedtronicCnlAlarmReceiver.completeWakefulIntent(intent);
+ // continuation valid suspend every 30m - set temp basal for 0u 60m in NS
+ else if (pumpRecord.isSuspended() && suspend_results.first().isValidSUSPEND() &&
+ pumpRecord.getEventDate().getTime() - suspend_results.first().getEventDate().getTime() >= 30 * 60 * 1000) {
+ pumpRecord.setValidSUSPEND(true);
+ }
+ // valid suspendoff - set temp stopped in NS
+ else if (!pumpRecord.isSuspended() && suspend_results.first().isValidSUSPEND() &&
+ pumpRecord.getEventDate().getTime() - suspend_results.first().getEventDate().getTime() <= 60 * 60 * 1000) {
+ pumpRecord.setValidSUSPENDOFF(true);
+ RealmResults suspendended_results = activePump.getPumpHistory()
+ .where()
+ .greaterThan("eventDate", new Date(System.currentTimeMillis() - (2 * 60 * 60 * 1000)))
+ .findAllSorted("eventDate", Sort.DESCENDING);
+ pumpRecord.setSuspendAfterDate(suspendended_results.first().getEventDate());
+ pumpRecord.setSuspendBeforeDate(pumpRecord.getEventDate());
+ }
+ }
+ else if (pumpRecord.isSuspended()) {
+ pumpRecord.setValidSUSPEND(true);
}
+
+ // absolute suspend start time approx to after-before range
+ if (pumpRecord.isValidSUSPEND()) {
+ RealmResults suspendstarted_results = activePump.getPumpHistory()
+ .where()
+ .greaterThan("eventDate", new Date(System.currentTimeMillis() - (12 * 60 * 60 * 1000)))
+ .findAllSorted("eventDate", Sort.ASCENDING);
+ index = suspendstarted_results.size();
+ if (index > 0) {
+ while (index > 0) {
+ index--;
+ if (!suspendstarted_results.get(index).isSuspended())
+ break;
+ }
+ pumpRecord.setSuspendAfterDate(suspendstarted_results.get(index).getEventDate());
+ }
+ else {
+ pumpRecord.setSuspendAfterDate(pumpRecord.getEventDate());
+ }
+ if (++index < suspendstarted_results.size())
+ pumpRecord.setSuspendBeforeDate(suspendstarted_results.get(index).getEventDate());
+ else
+ pumpRecord.setSuspendBeforeDate(pumpRecord.getEventDate());
+ }
+
+ // validate that this contains a new SAGE record
+ if (pumpRecord.isCgmWarmUp()) {
+ RealmResults sage_results = activePump.getPumpHistory()
+ .where()
+ .greaterThan("eventDate", new Date(System.currentTimeMillis() - (130 * 60 * 1000)))
+ .equalTo("validSAGE", true)
+ .findAll();
+ if (sage_results.size() == 0) {
+ pumpRecord.setValidSAGE(true);
+ RealmResults sagedate_results = activePump.getPumpHistory()
+ .where()
+ .greaterThan("eventDate", new Date(System.currentTimeMillis() - (6 * 60 * 60 * 1000)))
+ .findAllSorted("eventDate", Sort.DESCENDING);
+ pumpRecord.setSageAfterDate(sagedate_results.first().getEventDate());
+ pumpRecord.setSageBeforeDate(pumpRecord.getEventDate());
+ }
+ } else if (pumpRecord.isCgmActive() && pumpRecord.getTransmitterBattery() == 100) {
+ // note: transmitter battery can fluctuate when on the edge of a state change
+ RealmResults sagebattery_results = activePump.getPumpHistory()
+ .where()
+ .greaterThan("eventDate", new Date(System.currentTimeMillis() - (6 * 60 * 60 * 1000)))
+ .equalTo("cgmActive", true)
+ .lessThan("transmitterBattery", 50)
+ .findAllSorted("eventDate", Sort.DESCENDING);
+ if (sagebattery_results.size() > 0) {
+ RealmResults sage_valid_results = activePump.getPumpHistory()
+ .where()
+ .greaterThanOrEqualTo("eventDate", sagebattery_results.first().getEventDate())
+ .equalTo("validSAGE", true)
+ .findAll();
+ if (sage_valid_results.size() == 0) {
+ pumpRecord.setValidSAGE(true);
+ pumpRecord.setSageAfterDate(sagebattery_results.first().getEventDate());
+ pumpRecord.setSageBeforeDate(pumpRecord.getEventDate());
+ }
+ }
+ }
+
+ // validate that this contains a new CAGE record
+ RealmResults cage_results = activePump.getPumpHistory()
+ .where()
+ .greaterThan("eventDate", new Date(System.currentTimeMillis() - (6 * 60 * 60 * 1000)))
+ .lessThan("reservoirAmount", pumpRecord.getReservoirAmount())
+ .findAllSorted("eventDate", Sort.DESCENDING);
+ if (cage_results.size() > 0) {
+ RealmResults cage_valid_results = activePump.getPumpHistory()
+ .where()
+ .greaterThanOrEqualTo("eventDate", cage_results.first().getEventDate())
+ .equalTo("validCAGE", true)
+ .findAll();
+ if (cage_valid_results.size() == 0) {
+ pumpRecord.setValidCAGE(true);
+ pumpRecord.setCageAfterDate(cage_results.first().getEventDate());
+ pumpRecord.setCageBeforeDate(pumpRecord.getEventDate());
+ }
+ }
+
+ // validate that this contains a new BATTERY record
+ RealmResults battery_results = activePump.getPumpHistory()
+ .where()
+ .greaterThan("eventDate", new Date(System.currentTimeMillis() - (6 * 60 * 60 * 1000)))
+ .lessThan("batteryPercentage", pumpRecord.getBatteryPercentage())
+ .findAllSorted("eventDate", Sort.DESCENDING);
+ if (battery_results.size() > 0) {
+ RealmResults battery_valid_results = activePump.getPumpHistory()
+ .where()
+ .greaterThanOrEqualTo("eventDate", battery_results.first().getEventDate())
+ .equalTo("validBATTERY", true)
+ .findAll();
+ if (battery_valid_results.size() == 0) {
+ pumpRecord.setValidBATTERY(true);
+ pumpRecord.setBatteryAfterDate(battery_results.first().getEventDate());
+ pumpRecord.setBatteryBeforeDate(pumpRecord.getEventDate());
+ }
+ }
+
+ // validate that this contains a new ALERT record
+ if (pumpRecord.getAlert() > 0) {
+ RealmResults alert_results = activePump.getPumpHistory()
+ .where()
+ .greaterThan("eventDate", new Date(System.currentTimeMillis() - (6 * 60 * 60 * 1000)))
+ .findAllSorted("eventDate", Sort.DESCENDING);
+ if (alert_results.size() > 0) {
+ if (alert_results.first().getAlert() != pumpRecord.getAlert())
+ pumpRecord.setValidALERT(true);
+ }
+ }
+
}
- // TODO - Refactor polling system and make super clear how polling is calculated and why certain precautions are needed
- private void scheduleNextPoll(long timePollStarted, long timeLastGoodSGV, long pollInterval) {
- // smart polling and pump-sensor poll clash detection
- long now = System.currentTimeMillis();
- long lastActualPollTime = timePollStarted;
- if (timeLastGoodSGV > 0) {
- lastActualPollTime = timeLastGoodSGV + POLL_GRACE_PERIOD_MS + (POLL_PERIOD_MS * ((now - timeLastGoodSGV + POLL_GRACE_PERIOD_MS) / POLL_PERIOD_MS));
+ // pollInterval: default = POLL_PERIOD_MS (time to pump cgm reading)
+ //
+ // Can be requested at a shorter or longer interval, used to request a retry before next expected cgm data or to extend poll times due to low pump battery.
+ // Requests the next poll based on the actual time last cgm data was available on the pump and adding the interval
+ // if this time is already stale then the next actual cgm time will be used
+ // Any poll time request that falls within the pre-grace/grace period will be pushed to the next safe time slot
+
+ private long requestPollTime(long lastPoll, long pollInterval) {
+
+ int pumpCgmNA = dataStore.getPumpCgmNA();
+
+ RealmResults cgmresults = realm.where(PumpStatusEvent.class)
+ .greaterThan("eventDate", new Date(System.currentTimeMillis() - (24 * 60 * 1000)))
+ .equalTo("validCGM", true)
+ .findAllSorted("cgmDate", Sort.DESCENDING);
+ long timeLastCGM = 0;
+ if (cgmresults.size() > 0) {
+ timeLastCGM = cgmresults.first().getCgmDate().getTime();
}
+
+ long now = System.currentTimeMillis();
+ long lastActualPollTime = lastPoll;
+ if (timeLastCGM > 0)
+ lastActualPollTime = timeLastCGM + POLL_GRACE_PERIOD_MS + (POLL_PERIOD_MS * ((now - timeLastCGM + POLL_GRACE_PERIOD_MS) / POLL_PERIOD_MS));
long nextActualPollTime = lastActualPollTime + POLL_PERIOD_MS;
long nextRequestedPollTime = lastActualPollTime + pollInterval;
- // check if request is really needed
- if (nextRequestedPollTime - now < 10000L) {
+
+ // check if requested poll is stale
+ if (nextRequestedPollTime - now < 10 * 1000)
nextRequestedPollTime = nextActualPollTime;
- }
- // extended unavailable SGV may be due to clash with the current polling time
- // while we wait for a good SGV event, polling is auto adjusted by offsetting the next poll based on miss count
- if (dataStore.getUnavailableSGVCount() > 0) {
- if (timeLastGoodSGV == 0) {
- nextRequestedPollTime += POLL_PERIOD_MS / 5L; // if there is a uploader/sensor poll clash on startup then this will push the next attempt out by 60 seconds
- } else if (dataStore.getUnavailableSGVCount() > 2) {
- sendStatus(ICON_WARN + "Warning: No SGV available from pump for " + dataStore.getUnavailableSGVCount() + " attempts");
- long offsetPollTime = ((long) ((dataStore.getUnavailableSGVCount() - 2) % 5)) * (POLL_PERIOD_MS / 10L); // adjust poll time in 1/10 steps to avoid potential poll clash (max adjustment at 5/10)
- sendStatus("Adjusting poll: " + dateFormatter.format(nextRequestedPollTime) + " +" + (offsetPollTime / 1000) + "sec");
- nextRequestedPollTime += offsetPollTime;
- }
- }
+
+ // extended unavailable cgm may be due to clash with the current polling time
+ // while we wait for a cgm event, polling is auto adjusted by offsetting the next poll based on miss count
+
+ if (timeLastCGM == 0)
+ nextRequestedPollTime += 15 * 1000; // push poll time forward to avoid potential clash when no previous poll time available to sync with
+ else if (pumpCgmNA >= POLL_ANTI_CLASH)
+ nextRequestedPollTime += (((pumpCgmNA - POLL_ANTI_CLASH) % 3) + 1) * 30 * 1000; // adjust poll time in 30 second steps to avoid potential poll clash (adjustment: poll+30s / poll+60s / poll+90s)
+
// check if requested poll time is too close to next actual poll time
if (nextRequestedPollTime > nextActualPollTime - POLL_GRACE_PERIOD_MS - POLL_PRE_GRACE_PERIOD_MS
&& nextRequestedPollTime < nextActualPollTime) {
nextRequestedPollTime = nextActualPollTime;
}
- MedtronicCnlAlarmManager.setAlarm(nextRequestedPollTime);
- sendStatus("Next poll due at: " + dateFormatter.format(nextRequestedPollTime));
+
+ return nextRequestedPollTime;
+ }
+
+ private long checkPollTime() {
+ long due = 0;
+
+ RealmResults cgmresults = realm.where(PumpStatusEvent.class)
+ .greaterThan("eventDate", new Date(System.currentTimeMillis() - (24 * 60 * 1000)))
+ .equalTo("validCGM", true)
+ .findAllSorted("cgmDate", Sort.DESCENDING);
+
+ if (cgmresults.size() > 0) {
+ long now = System.currentTimeMillis();
+ long timeLastCGM = cgmresults.first().getCgmDate().getTime();
+ long timePollExpected = timeLastCGM + POLL_PERIOD_MS + POLL_GRACE_PERIOD_MS + (POLL_PERIOD_MS * ((now - 1000L - (timeLastCGM + POLL_GRACE_PERIOD_MS)) / POLL_PERIOD_MS));
+ // avoid polling when too close to sensor-pump comms
+ if (((timePollExpected - now) > 5000L) && ((timePollExpected - now) < (POLL_PRE_GRACE_PERIOD_MS + POLL_GRACE_PERIOD_MS)))
+ due = timePollExpected;
+ }
+
+ return due;
}
/**
@@ -549,4 +932,4 @@ public final class Constants {
public static final String EXTENDED_DATA = "info.nightscout.android.medtronic.service.DATA";
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/android/model/medtronicNg/PumpStatusEvent.java b/app/src/main/java/info/nightscout/android/model/medtronicNg/PumpStatusEvent.java
index 75847c8..f0d9469 100644
--- a/app/src/main/java/info/nightscout/android/model/medtronicNg/PumpStatusEvent.java
+++ b/app/src/main/java/info/nightscout/android/model/medtronicNg/PumpStatusEvent.java
@@ -3,7 +3,6 @@
import java.util.Date;
import io.realm.RealmObject;
-import io.realm.annotations.Ignore;
import io.realm.annotations.Index;
/**
@@ -11,20 +10,31 @@
*/
public class PumpStatusEvent extends RealmObject {
@Index
- private Date eventDate; // The actual time of the event (assume the capture device eventDate/time is accurate)
- private Date pumpDate; // The eventDate/time on the pump at the time of the event
+ private Date eventDate; // The actual time of the this event (uploader device time)
+ private Date pumpDate; // The time on the pump at the time of the this event
+ private long pumpTimeOffset; // millis the pump is ahead
+
private String deviceName;
// Data from the Medtronic Pump Status message
+
+ private byte pumpStatus;
+ private byte cgmStatus;
private boolean suspended;
- private boolean bolusing;
+ private boolean bolusingNormal;
+ private boolean bolusingSquare;
+ private boolean bolusingDual;
private boolean deliveringInsulin;
private boolean tempBasalActive;
private boolean cgmActive;
+ private boolean cgmCalibrating;
+ private boolean cgmCalibrationComplete;
+ private boolean cgmException;
+ private boolean cgmWarmUp;
private byte activeBasalPattern;
private float basalRate;
private float tempBasalRate;
- private byte tempBasalPercentage;
+ private short tempBasalPercentage;
private short tempBasalMinutesRemaining;
private float basalUnitsDeliveredToday;
private short batteryPercentage;
@@ -32,15 +42,55 @@ public class PumpStatusEvent extends RealmObject {
private short minutesOfInsulinRemaining; // 25h == "more than 1 day"
private float activeInsulin;
private int sgv;
- private Date sgvDate;
+ private Date cgmDate;
+ private Date cgmPumpDate;
+ private byte cgmExceptionType;
private boolean lowSuspendActive;
private String cgmTrend;
-
private boolean recentBolusWizard; // Whether a bolus wizard has been run recently
- private int bolusWizardBGL; // in mg/dL. 0 means no recent bolus wizard reading.
-
- @Ignore
- private long pumpTimeOffset; // millis the pump is ahead
+ private int recentBGL; // in mg/dL. 0 means no recent finger bg reading.
+ private short alert;
+ private Date alertDate;
+ private Date alertPumpDate;
+ private float bolusingDelivered;
+ private short bolusingMinutesRemaining;
+ private short bolusingReference;
+ private float lastBolusAmount;
+ private Date lastBolusDate;
+ private Date lastBolusPumpDate;
+ private short lastBolusDuration;
+ private short lastBolusReference;
+ private byte transmitterBattery;
+ private byte transmitterControl;
+ private short calibrationDueMinutes;
+ private float sensorRateOfChange;
+
+ private boolean oldSgvWhenNewExpected = false;
+
+ private boolean validPUMP = false;
+ private boolean validCGM = false;
+ private boolean validSGV = false;
+ private boolean validBGL = false;
+ private boolean validBolus = false;
+ private boolean validBolusDual = false;
+ private boolean validBolusSquare = false;
+ private boolean validALERT = false;
+ private boolean validSUSPEND = false;
+ private boolean validSUSPENDOFF = false;
+ private Date suspendAfterDate;
+ private Date suspendBeforeDate;
+ private boolean validTEMPBASAL = false;
+ private Date tempBasalAfterDate;
+ private Date tempBasalBeforeDate;
+ private boolean validCAGE = false;
+ private Date cageAfterDate;
+ private Date cageBeforeDate;
+ private boolean validSAGE = false;
+ private Date sageAfterDate;
+ private Date sageBeforeDate;
+ private boolean validBATTERY = false;
+ private Date batteryAfterDate;
+ private Date batteryBeforeDate;
@Index
private boolean uploaded = false;
@@ -50,12 +100,14 @@ public PumpStatusEvent() {
this.eventDate = new Date();
}
+ public void setEventDate(Date eventDate) {
+ this.eventDate = eventDate;
+ }
+
public Date getEventDate() {
return eventDate;
}
- // No EventDate setter. The eventDate is set at the time that the PumpStatusEvent is created.
-
public Date getPumpDate() {
return pumpDate;
}
@@ -131,12 +183,12 @@ public boolean hasRecentBolusWizard() {
return recentBolusWizard;
}
- public int getBolusWizardBGL() {
- return bolusWizardBGL;
+ public int getRecentBGL() {
+ return recentBGL;
}
- public void setBolusWizardBGL(int bolusWizardBGL) {
- this.bolusWizardBGL = bolusWizardBGL;
+ public void setRecentBGL(int recentBGL) {
+ this.recentBGL = recentBGL;
}
public boolean isUploaded() {
@@ -147,6 +199,207 @@ public void setUploaded(boolean uploaded) {
this.uploaded = uploaded;
}
+ public boolean isOldSgvWhenNewExpected() {
+ return oldSgvWhenNewExpected;
+ }
+
+ public void setOldSgvWhenNewExpected(boolean oldSgvWhenNewExpected) {
+ this.oldSgvWhenNewExpected = oldSgvWhenNewExpected;
+ }
+
+ public boolean isValidPUMP() {
+ return validPUMP;
+ }
+
+ public void setValidPUMP(boolean validPUMP) {
+ this.validPUMP = validPUMP;
+ }
+
+ public boolean isValidCGM() {
+ return validCGM;
+ }
+
+ public void setValidCGM(boolean validCGM) {
+ this.validCGM = validCGM;
+ }
+
+ public boolean isValidSGV() {
+ return validSGV;
+ }
+
+ public void setValidSGV(boolean validSGV) {
+ this.validSGV = validSGV;
+ }
+
+ public boolean isValidBGL() {
+ return validBGL;
+ }
+
+ public void setValidBGL(boolean validBGL) {
+ this.validBGL = validBGL;
+ }
+
+ public boolean isValidBolus() {
+ return validBolus;
+ }
+
+ public void setValidBolus(boolean validBolus) {
+ this.validBolus = validBolus;
+ }
+
+ public boolean isValidBolusDual() {
+ return validBolusDual;
+ }
+
+ public void setValidBolusDual(boolean validBolusDual) {
+ this.validBolusDual = validBolusDual;
+ }
+
+ public boolean isValidBolusSquare() {
+ return validBolusSquare;
+ }
+
+ public void setValidBolusSquare(boolean validBolusSquare) {
+ this.validBolusSquare = validBolusSquare;
+ }
+
+ public boolean isValidALERT() {
+ return validALERT;
+ }
+
+ public void setValidALERT(boolean validALERT) {
+ this.validALERT = validALERT;
+ }
+
+ public boolean isValidSUSPEND() {
+ return validSUSPEND;
+ }
+
+ public void setValidSUSPEND(boolean validSUSPEND) {
+ this.validSUSPEND = validSUSPEND;
+ }
+
+ public boolean isValidSUSPENDOFF() {
+ return validSUSPENDOFF;
+ }
+
+ public void setValidSUSPENDOFF(boolean validSUSPENDOFF) {
+ this.validSUSPENDOFF = validSUSPENDOFF;
+ }
+
+ public Date getSuspendAfterDate() {
+ return suspendAfterDate;
+ }
+
+ public void setSuspendAfterDate(Date suspendAfterDate) {
+ this.suspendAfterDate = suspendAfterDate;
+ }
+
+ public Date getSuspendBeforeDate() {
+ return suspendBeforeDate;
+ }
+
+ public void setSuspendBeforeDate(Date suspendBeforeDate) {
+ this.suspendBeforeDate = suspendBeforeDate;
+ }
+
+
+ public boolean isValidTEMPBASAL() {
+ return validTEMPBASAL;
+ }
+
+ public void setValidTEMPBASAL(boolean validTEMPBASAL) {
+ this.validTEMPBASAL = validTEMPBASAL;
+ }
+
+ public Date getTempBasalAfterDate() {
+ return tempBasalAfterDate;
+ }
+
+ public void setTempBasalAfterDate(Date tempBasalAfterDate) {
+ this.tempBasalAfterDate = tempBasalAfterDate;
+ }
+
+ public Date getTempBasalBeforeDate() {
+ return tempBasalBeforeDate;
+ }
+
+ public void setTempBasalBeforeDate(Date tempBasalBeforeDate) {
+ this.tempBasalBeforeDate = tempBasalBeforeDate;
+ }
+
+ public boolean isValidCAGE() {
+ return validCAGE;
+ }
+
+ public void setValidCAGE(boolean validCAGE) {
+ this.validCAGE = validCAGE;
+ }
+
+ public Date getCageAfterDate() {
+ return cageAfterDate;
+ }
+
+ public void setCageAfterDate(Date cageAfterDate) {
+ this.cageAfterDate = cageAfterDate;
+ }
+
+ public Date getCageBeforeDate() {
+ return cageBeforeDate;
+ }
+
+ public void setCageBeforeDate(Date cageBeforeDate) {
+ this.cageBeforeDate = cageBeforeDate;
+ }
+
+ public boolean isValidSAGE() {
+ return validSAGE;
+ }
+
+ public void setValidSAGE(boolean validSAGE) {
+ this.validSAGE = validSAGE;
+ }
+
+ public Date getSageAfterDate() {
+ return sageAfterDate;
+ }
+
+ public void setSageAfterDate(Date sageAfterDate) {
+ this.sageAfterDate = sageAfterDate;
+ }
+
+ public Date getSageBeforeDate() {
+ return sageBeforeDate;
+ }
+
+ public void setSageBeforeDate(Date sageBeforeDate) {
+ this.sageBeforeDate = sageBeforeDate;
+ }
+
+ public boolean isValidBATTERY() {
+ return validBATTERY;
+ }
+
+ public void setValidBATTERY(boolean validBATTERY) {
+ this.validBATTERY = validBATTERY;
+ }
+
+ public Date getBatteryAfterDate() {
+ return batteryAfterDate;
+ }
+
+ public void setBatteryAfterDate(Date batteryAfterDate) {
+ this.batteryAfterDate = batteryAfterDate;
+ }
+
+ public Date getBatteryBeforeDate() {
+ return batteryBeforeDate;
+ }
+
+ public void setBatteryBeforeDate(Date batteryBeforeDate) {
+ this.batteryBeforeDate = batteryBeforeDate;
+ }
+
public boolean isSuspended() {
return suspended;
}
@@ -155,12 +408,28 @@ public void setSuspended(boolean suspended) {
this.suspended = suspended;
}
- public boolean isBolusing() {
- return bolusing;
+ public boolean isBolusingNormal() {
+ return bolusingNormal;
+ }
+
+ public void setBolusingNormal(boolean bolusingNormal) {
+ this.bolusingNormal = bolusingNormal;
+ }
+
+ public boolean isBolusingSquare() {
+ return bolusingSquare;
+ }
+
+ public void setBolusingSquare(boolean bolusingSquare) {
+ this.bolusingSquare = bolusingSquare;
}
- public void setBolusing(boolean bolusing) {
- this.bolusing = bolusing;
+ public boolean isBolusingDual() {
+ return bolusingDual;
+ }
+
+ public void setBolusingDual(boolean bolusingDual) {
+ this.bolusingDual = bolusingDual;
}
public boolean isDeliveringInsulin() {
@@ -211,11 +480,11 @@ public void setTempBasalRate(float tempBasalRate) {
this.tempBasalRate = tempBasalRate;
}
- public byte getTempBasalPercentage() {
+ public short getTempBasalPercentage() {
return tempBasalPercentage;
}
- public void setTempBasalPercentage(byte tempBasalPercentage) {
+ public void setTempBasalPercentage(short tempBasalPercentage) {
this.tempBasalPercentage = tempBasalPercentage;
}
@@ -243,28 +512,28 @@ public void setMinutesOfInsulinRemaining(short minutesOfInsulinRemaining) {
this.minutesOfInsulinRemaining = minutesOfInsulinRemaining;
}
- public Date getSgvDate() {
- return sgvDate;
+ public Date getCgmDate() {
+ return cgmDate;
}
- public void setSgvDate(Date sgvDate) {
- this.sgvDate = sgvDate;
+ public void setCgmDate(Date cgmDate) {
+ this.cgmDate = cgmDate;
}
- public boolean isLowSuspendActive() {
- return lowSuspendActive;
+ public Date getCgmPumpDate() {
+ return cgmPumpDate;
}
- public void setLowSuspendActive(boolean lowSuspendActive) {
- this.lowSuspendActive = lowSuspendActive;
+ public void setCgmPumpDate(Date cgmPumpDate) {
+ this.cgmPumpDate = cgmPumpDate;
}
- public boolean isRecentBolusWizard() {
- return recentBolusWizard;
+ public boolean isLowSuspendActive() {
+ return lowSuspendActive;
}
- public void setRecentBolusWizard(boolean recentBolusWizard) {
- this.recentBolusWizard = recentBolusWizard;
+ public void setLowSuspendActive(boolean lowSuspendActive) {
+ this.lowSuspendActive = lowSuspendActive;
}
public long getPumpTimeOffset() {
@@ -275,17 +544,202 @@ public void setPumpTimeOffset(long pumpTimeOffset) {
this.pumpTimeOffset = pumpTimeOffset;
}
+ public int getPumpStatus() {
+ return pumpStatus;
+ }
+
+ public void setPumpStatus(byte pumpStatus) {
+ this.pumpStatus = pumpStatus;
+ }
+
+ public int getCgmStatus() {
+ return cgmStatus;
+ }
+
+ public void setCgmStatus(byte cgmStatus) {
+ this.cgmStatus = cgmStatus;
+ }
+
+ public boolean isCgmCalibrating() {
+ return cgmCalibrating;
+ }
+
+ public void setCgmCalibrating(boolean cgmCalibrating) {
+ this.cgmCalibrating = cgmCalibrating;
+ }
+
+ public boolean isCgmCalibrationComplete() {
+ return cgmCalibrationComplete;
+ }
+
+ public void setCgmCalibrationComplete(boolean cgmCalibrationComplete) {
+ this.cgmCalibrationComplete = cgmCalibrationComplete;
+ }
+
+ public boolean isCgmException() {
+ return cgmException;
+ }
+
+ public void setCgmException(boolean cgmException) {
+ this.cgmException = cgmException;
+ }
+
+ public boolean isCgmWarmUp() {
+ return cgmWarmUp;
+ }
+
+ public void setCgmWarmUp(boolean cgmWarmUp) {
+ this.cgmWarmUp = cgmWarmUp;
+ }
+
+ public byte getCgmExceptionType() {
+ return cgmExceptionType;
+ }
+
+ public void setCgmExceptionType(byte cgmExceptionType) {
+ this.cgmExceptionType = cgmExceptionType;
+ }
+
+ public short getAlert() {
+ return alert;
+ }
+
+ public void setAlert(short alert) {
+ this.alert = alert;
+ }
+
+ public Date getAlertDate() {
+ return alertDate;
+ }
+
+ public void setAlertDate(Date alertDate) {
+ this.alertDate = alertDate;
+ }
+
+ public Date getAlertPumpDate() {
+ return alertPumpDate;
+ }
+
+ public void setAlertPumpDate(Date alertPumpDate) {
+ this.alertPumpDate = alertPumpDate;
+ }
+
+ public float getBolusingDelivered() {
+ return bolusingDelivered;
+ }
+
+ public void setBolusingDelivered(float bolusingDelivered) {
+ this.bolusingDelivered = bolusingDelivered;
+ }
+
+ public short getBolusingMinutesRemaining() {
+ return bolusingMinutesRemaining;
+ }
+
+ public void setBolusingMinutesRemaining(short bolusingMinutesRemaining) {
+ this.bolusingMinutesRemaining = bolusingMinutesRemaining;
+ }
+
+ public short getBolusingReference() {
+ return bolusingReference;
+ }
+
+ public void setBolusingReference(short bolusingReference) {
+ this.bolusingReference = bolusingReference;
+ }
+
+ public float getLastBolusAmount() {
+ return lastBolusAmount;
+ }
+
+ public void setLastBolusAmount(float lastBolusAmount) {
+ this.lastBolusAmount = lastBolusAmount;
+ }
+
+ public Date getLastBolusDate() {
+ return lastBolusDate;
+ }
+
+ public void setLastBolusDate(Date lastBolusDate) {
+ this.lastBolusDate = lastBolusDate;
+ }
+
+ public Date getLastBolusPumpDate() {
+ return lastBolusPumpDate;
+ }
+
+ public void setLastBolusPumpDate(Date lastBolusPumpDate) {
+ this.lastBolusPumpDate = lastBolusPumpDate;
+ }
+
+ public short getLastBolusReference() {
+ return lastBolusReference;
+ }
+
+ public void setLastBolusReference(short lastBolusReference) {
+ this.lastBolusReference = lastBolusReference;
+ }
+
+ public short getLastBolusDuration() {
+ return lastBolusDuration;
+ }
+
+ public void setLastBolusDuration(short lastBolusDuration) {
+ this.lastBolusDuration = lastBolusDuration;
+ }
+
+ public byte getTransmitterBattery() {
+ return transmitterBattery;
+ }
+
+ public void setTransmitterBattery(byte transmitterBattery) {
+ this.transmitterBattery = transmitterBattery;
+ }
+
+ public byte getTransmitterControl() {
+ return transmitterControl;
+ }
+
+ public void setTransmitterControl(byte transmitterControl) {
+ this.transmitterControl = transmitterControl;
+ }
+
+ public short getCalibrationDueMinutes() {
+ return calibrationDueMinutes;
+ }
+
+ public void setCalibrationDueMinutes(short calibrationDueMinutes) {
+ this.calibrationDueMinutes = calibrationDueMinutes;
+ }
+
+ public float getSensorRateOfChange() {
+ return sensorRateOfChange;
+ }
+
+ public void setSensorRateOfChange(float sensorRateOfChange) {
+ this.sensorRateOfChange = sensorRateOfChange;
+ }
+
@Override
public String toString() {
return "PumpStatusEvent{" +
"eventDate=" + eventDate +
", pumpDate=" + pumpDate +
+ ", pumpTimeOffset=" + pumpTimeOffset +
", deviceName='" + deviceName + '\'' +
+ ", pumpStatus=" + pumpStatus +
+ ", cgmStatus=" + cgmStatus +
", suspended=" + suspended +
- ", bolusing=" + bolusing +
+ ", bolusingNormal=" + bolusingNormal +
+ ", bolusingSquare=" + bolusingSquare +
+ ", bolusingDual=" + bolusingDual +
", deliveringInsulin=" + deliveringInsulin +
", tempBasalActive=" + tempBasalActive +
", cgmActive=" + cgmActive +
+ ", cgmCalibrating=" + cgmCalibrating +
+ ", cgmCalibrationComplete=" + cgmCalibrationComplete +
+ ", cgmException=" + cgmException +
+ ", cgmWarmUp=" + cgmWarmUp +
", activeBasalPattern=" + activeBasalPattern +
", basalRate=" + basalRate +
", tempBasalRate=" + tempBasalRate +
@@ -297,12 +751,53 @@ public String toString() {
", minutesOfInsulinRemaining=" + minutesOfInsulinRemaining +
", activeInsulin=" + activeInsulin +
", sgv=" + sgv +
- ", sgvDate=" + sgvDate +
+ ", cgmDate=" + cgmDate +
+ ", cgmPumpDate=" + cgmPumpDate +
+ ", cgmExceptionType=" + cgmExceptionType +
", lowSuspendActive=" + lowSuspendActive +
", cgmTrend='" + cgmTrend + '\'' +
", recentBolusWizard=" + recentBolusWizard +
- ", bolusWizardBGL=" + bolusWizardBGL +
- ", pumpTimeOffset=" + pumpTimeOffset +
+ ", recentBGL=" + recentBGL +
+ ", alert=" + alert +
+ ", alertDate=" + alertDate +
+ ", alertPumpDate=" + alertPumpDate +
+ ", bolusingDelivered=" + bolusingDelivered +
+ ", bolusingMinutesRemaining=" + bolusingMinutesRemaining +
+ ", bolusingReference=" + bolusingReference +
+ ", lastBolusAmount=" + lastBolusAmount +
+ ", lastBolusDate=" + lastBolusDate +
+ ", lastBolusPumpDate=" + lastBolusPumpDate +
+ ", lastBolusDuration=" + lastBolusDuration +
+ ", lastBolusReference=" + lastBolusReference +
+ ", transmitterBattery=" + transmitterBattery +
+ ", transmitterControl=" + transmitterControl +
+ ", calibrationDueMinutes=" + calibrationDueMinutes +
+ ", sensorRateOfChange=" + sensorRateOfChange +
+ ", oldSgvWhenNewExpected=" + oldSgvWhenNewExpected +
+ ", validPUMP=" + validPUMP +
+ ", validCGM=" + validCGM +
+ ", validSGV=" + validSGV +
+ ", validBGL=" + validBGL +
+ ", validBolus=" + validBolus +
+ ", validBolusDual=" + validBolusDual +
+ ", validBolusSquare=" + validBolusSquare +
+ ", validALERT=" + validALERT +
+ ", validSUSPEND=" + validSUSPEND +
+ ", validSUSPENDOFF=" + validSUSPENDOFF +
+ ", suspendAfterDate=" + suspendAfterDate +
+ ", suspendBeforeDate=" + suspendBeforeDate +
+ ", validTEMPBASAL=" + validTEMPBASAL +
+ ", tempBasalAfterDate=" + tempBasalAfterDate +
+ ", tempBasalBeforeDate=" + tempBasalBeforeDate +
+ ", validCAGE=" + validCAGE +
+ ", cageAfterDate=" + cageAfterDate +
+ ", cageBeforeDate=" + cageBeforeDate +
+ ", validSAGE=" + validSAGE +
+ ", sageAfterDate=" + sageAfterDate +
+ ", sageBeforeDate=" + sageBeforeDate +
+ ", validBATTERY=" + validBATTERY +
+ ", batteryAfterDate=" + batteryAfterDate +
+ ", batteryBeforeDate=" + batteryBeforeDate +
", uploaded=" + uploaded +
'}';
}
diff --git a/app/src/main/java/info/nightscout/android/upload/nightscout/NightScoutUpload.java b/app/src/main/java/info/nightscout/android/upload/nightscout/NightScoutUpload.java
index 819f82e..06f7f79 100644
--- a/app/src/main/java/info/nightscout/android/upload/nightscout/NightScoutUpload.java
+++ b/app/src/main/java/info/nightscout/android/upload/nightscout/NightScoutUpload.java
@@ -1,7 +1,5 @@
package info.nightscout.android.upload.nightscout;
-import android.util.Log;
-
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.security.MessageDigest;
@@ -16,11 +14,22 @@
import android.support.annotation.NonNull;
+import info.nightscout.android.utils.ConfigurationStore;
import info.nightscout.api.UploadApi;
-import info.nightscout.api.GlucoseEndpoints;
-import info.nightscout.api.BolusEndpoints.BolusEntry;
-import info.nightscout.api.GlucoseEndpoints.GlucoseEntry;
-import info.nightscout.api.BolusEndpoints;
+import info.nightscout.api.SgvEndpoints;
+import info.nightscout.api.SgvEndpoints.SgvEntry;
+import info.nightscout.api.MbgEndpoints;
+import info.nightscout.api.MbgEndpoints.MbgEntry;
+import info.nightscout.api.TreatmentEndpoints;
+import info.nightscout.api.TreatmentEndpoints.TreatmentEntry;
+import info.nightscout.api.TempBasalRateEndpoints.TempBasalRateEntry;
+import info.nightscout.api.TempBasalRateEndpoints;
+import info.nightscout.api.TempBasalPercentEndpoints;
+import info.nightscout.api.TempBasalPercentEndpoints.TempBasalPercentEntry;
+import info.nightscout.api.TempBasalCancelEndpoints;
+import info.nightscout.api.TempBasalCancelEndpoints.TempBasalCancelEntry;
+import info.nightscout.api.NoteEndpoints;
+import info.nightscout.api.NoteEndpoints.NoteEntry;
import info.nightscout.api.DeviceEndpoints;
import info.nightscout.api.DeviceEndpoints.Iob;
import info.nightscout.api.DeviceEndpoints.Battery;
@@ -30,39 +39,43 @@
import okhttp3.ResponseBody;
import retrofit2.Response;
+import static info.nightscout.android.medtronic.MainActivity.MMOLXLFACTOR;
+
class NightScoutUpload {
private static final String TAG = NightscoutUploadIntentService.class.getSimpleName();
private static final SimpleDateFormat ISO8601_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault());
+ private static final SimpleDateFormat NOTE_DATE_FORMAT = new SimpleDateFormat("E h:mm a", Locale.getDefault());
NightScoutUpload() {
}
- Boolean doRESTUpload(String url,
+ boolean doRESTUpload(String url,
String secret,
+ boolean treatments,
int uploaderBatteryLevel,
- List records) {
- Boolean success = false;
- try {
- success = isUploaded(records, url, secret, uploaderBatteryLevel);
- } catch (Exception e) {
- Log.e(TAG, "Unable to do REST API Upload to: " + url, e);
- }
- return success;
+ List records) throws Exception {
+ return isUploaded(records, url, secret, treatments, uploaderBatteryLevel);
}
-
private boolean isUploaded(List records,
String baseURL,
String secret,
+ boolean treatments,
int uploaderBatteryLevel) throws Exception {
UploadApi uploadApi = new UploadApi(baseURL, formToken(secret));
- boolean eventsUploaded = uploadEvents(uploadApi.getGlucoseEndpoints(),
- uploadApi.getBolusEndpoints(),
- records);
+ boolean eventsUploaded = uploadEvents(
+ uploadApi.getSgvEndpoints(),
+ uploadApi.getMbgEndpoints(),
+ uploadApi.getTreatmentEndpoints(),
+ uploadApi.getTempBasalRateEndpoints(),
+ uploadApi.getTempBasalPercentEndpoints(),
+ uploadApi.getTempBasalCancelEndpoints(),
+ uploadApi.getNoteEndpoints(),
+ records, treatments);
boolean deviceStatusUploaded = uploadDeviceStatus(uploadApi.getDeviceEndpoints(),
uploaderBatteryLevel, records);
@@ -70,48 +83,200 @@ private boolean isUploaded(List records,
return eventsUploaded && deviceStatusUploaded;
}
- private boolean uploadEvents(GlucoseEndpoints glucoseEndpoints,
- BolusEndpoints bolusEndpoints,
- List records) throws Exception {
-
-
- List glucoseEntries = new ArrayList<>();
- List bolusEntries = new ArrayList<>();
+ private boolean uploadEvents(SgvEndpoints sgvEndpoints,
+ MbgEndpoints mbgEndpoints,
+ TreatmentEndpoints treatmentEndpoints,
+ TempBasalRateEndpoints tempBasalRateEndpoints,
+ TempBasalPercentEndpoints tempBasalPercentEndpoints,
+ TempBasalCancelEndpoints tempBasalCancelEndpoints,
+ NoteEndpoints noteEndpoints,
+ List records,
+ boolean treatments) throws Exception {
+
+
+ List sgvEntries = new ArrayList<>();
+ List mbgEntries = new ArrayList<>();
+ List treatmentEntries = new ArrayList<>();
+ List tempBasalRateEntries = new ArrayList<>();
+ List tempBasalPercentEntries = new ArrayList<>();
+ List tempBasalCancelEntries = new ArrayList<>();
+ List noteEntries = new ArrayList<>();
for (PumpStatusEvent record : records) {
- GlucoseEntry glucoseEntry = new GlucoseEntry();
-
- glucoseEntry.setType("sgv");
- glucoseEntry.setDirection(EntriesSerializer.getDirectionStringStatus(record.getCgmTrend()));
- glucoseEntry.setDevice(record.getDeviceName());
- glucoseEntry.setSgv(record.getSgv());
- glucoseEntry.setDate(record.getSgvDate().getTime());
- glucoseEntry.setDateString(record.getSgvDate().toString());
-
- glucoseEntries.add(glucoseEntry);
+ if (record.isValidSGV()) {
+ SgvEntry sgvEntry = new SgvEntry();
+ sgvEntry.setType("sgv");
+ sgvEntry.setDirection(EntriesSerializer.getDirectionStringStatus(record.getCgmTrend()));
+ sgvEntry.setDevice(record.getDeviceName());
+ sgvEntry.setSgv(record.getSgv());
+ sgvEntry.setDate(record.getCgmDate().getTime());
+ sgvEntry.setDateString(record.getCgmDate().toString());
+ sgvEntries.add(sgvEntry);
+ }
- if (record.getBolusWizardBGL() != 0) {
- BolusEntry bolusEntry = new BolusEntry();
+ if (record.isValidBGL()) {
+
+ MbgEntry mbgEntry = new MbgEntry();
+ mbgEntry.setType("mbg");
+ mbgEntry.setDate(record.getEventDate().getTime());
+ mbgEntry.setDateString(record.getEventDate().toString());
+ mbgEntry.setDevice(record.getDeviceName());
+ mbgEntry.setMbg(record.getRecentBGL());
+ mbgEntries.add(mbgEntry);
+
+ // cgm offline or not in use (needed for NS to show bgl when no sgv data)
+ if (!record.isCgmActive() || record.isCgmWarmUp()) {
+ ConfigurationStore configurationStore = ConfigurationStore.getInstance();
+ BigDecimal bgl;
+ String units;
+ if (configurationStore.isMmolxl()) {
+ bgl = new BigDecimal(record.getRecentBGL() / MMOLXLFACTOR).setScale(1, BigDecimal.ROUND_HALF_UP);
+ units = "mmol";
+ } else {
+ bgl = new BigDecimal(record.getRecentBGL()).setScale(0);
+ units = "mg/dl";
+ }
+
+ TreatmentEntry treatmentEntry = new TreatmentEntry();
+ treatmentEntry.setCreatedAt(ISO8601_DATE_FORMAT.format(record.getEventDate()));
+ treatmentEntry.setEventType("BG Check");
+ treatmentEntry.setGlucoseType("Finger");
+ treatmentEntry.setGlucose(bgl);
+ treatmentEntry.setUnits(units);
+ treatmentEntries.add(treatmentEntry);
+ }
+ }
- bolusEntry.setType("mbg");
- bolusEntry.setDate(record.getEventDate().getTime());
- bolusEntry.setDateString(record.getEventDate().toString());
- bolusEntry.setDevice(record.getDeviceName());
- bolusEntry.setMbg(record.getBolusWizardBGL());
+ if (treatments) {
+
+ if (record.isValidBolus()) {
+
+ if (record.isValidBolusDual()) {
+ TreatmentEntry treatmentEntry = new TreatmentEntry();
+ treatmentEntry.setCreatedAt(ISO8601_DATE_FORMAT.format(record.getLastBolusDate()));
+ treatmentEntry.setEventType("Bolus");
+ treatmentEntry.setInsulin(record.getLastBolusAmount());
+ treatmentEntry.setNotes("Dual bolus normal part delivered: " + record.getLastBolusAmount() + "u");
+ treatmentEntries.add(treatmentEntry);
+
+ } else if (record.isValidBolusSquare()) {
+ TreatmentEntry treatmentEntry = new TreatmentEntry();
+ treatmentEntry.setCreatedAt(ISO8601_DATE_FORMAT.format(record.getLastBolusDate()));
+ treatmentEntry.setEventType("Combo Bolus");
+ treatmentEntry.setDuration(record.getLastBolusDuration());
+ treatmentEntry.setSplitNow("0");
+ treatmentEntry.setSplitExt("100");
+ treatmentEntry.setRelative(2);
+ treatmentEntry.setEnteredinsulin(String.valueOf(record.getLastBolusAmount()));
+ treatmentEntries.add(treatmentEntry);
+
+ noteEntries.add(new NoteEntry(
+ "Announcement",
+ ISO8601_DATE_FORMAT.format(record.getLastBolusDate().getTime() + (record.getLastBolusDuration() * 60 * 1000)),
+ "Square bolus delivered: " + record.getLastBolusAmount() + "u Duration: " + record.getLastBolusDuration() + " minutes"
+ ));
+
+ } else {
+ TreatmentEntry treatmentEntry = new TreatmentEntry();
+ treatmentEntry.setCreatedAt(ISO8601_DATE_FORMAT.format(record.getLastBolusDate()));
+ treatmentEntry.setEventType("Bolus");
+ treatmentEntry.setInsulin(record.getLastBolusAmount());
+ treatmentEntries.add(treatmentEntry);
+ }
+ }
+
+ if (record.isValidTEMPBASAL()) {
+ if (record.getTempBasalMinutesRemaining() > 0 && record.getTempBasalPercentage() > 0) {
+ tempBasalPercentEntries.add(new TempBasalPercentEntry(
+ ISO8601_DATE_FORMAT.format(record.getEventDate()),
+ "Temp Basal started approx: " + NOTE_DATE_FORMAT.format(record.getTempBasalAfterDate()) + " - " + NOTE_DATE_FORMAT.format(record.getTempBasalBeforeDate()),
+ record.getTempBasalMinutesRemaining(),
+ record.getTempBasalPercentage() - 100
+ ));
+ } else if (record.getTempBasalMinutesRemaining() > 0) {
+ tempBasalRateEntries.add(new TempBasalRateEntry(
+ ISO8601_DATE_FORMAT.format(record.getEventDate()),
+ "Temp Basal started approx: " + NOTE_DATE_FORMAT.format(record.getTempBasalAfterDate()) + " - " + NOTE_DATE_FORMAT.format(record.getTempBasalBeforeDate()),
+ record.getTempBasalMinutesRemaining(),
+ record.getTempBasalRate()
+ ));
+ } else {
+ tempBasalCancelEntries.add(new TempBasalCancelEntry(
+ ISO8601_DATE_FORMAT.format(record.getEventDate()),
+ "Temp Basal stopped approx: " + NOTE_DATE_FORMAT.format(record.getTempBasalAfterDate()) + " - " + NOTE_DATE_FORMAT.format(record.getTempBasalBeforeDate())
+ ));
+ }
+ }
+
+ if (record.isValidSUSPEND()) {
+ tempBasalRateEntries.add(new TempBasalRateEntry(
+ ISO8601_DATE_FORMAT.format(record.getEventDate()),
+ "Pump suspended insulin delivery approx: " + NOTE_DATE_FORMAT.format(record.getSuspendAfterDate()) + " - " + NOTE_DATE_FORMAT.format(record.getSuspendBeforeDate()),
+ 60,
+ 0
+ ));
+ }
+ if (record.isValidSUSPENDOFF()) {
+ tempBasalCancelEntries.add(new TempBasalCancelEntry(
+ ISO8601_DATE_FORMAT.format(record.getEventDate()),
+ "Pump resumed insulin delivery approx: " + NOTE_DATE_FORMAT.format(record.getSuspendAfterDate()) + " - " + NOTE_DATE_FORMAT.format(record.getSuspendBeforeDate())
+ ));
+ }
+
+ if (record.isValidSAGE()) {
+ noteEntries.add(new NoteEntry(
+ "Sensor Start",
+ ISO8601_DATE_FORMAT.format(record.getSageAfterDate().getTime() - (record.getSageAfterDate().getTime() - record.getSageBeforeDate().getTime()) / 2),
+ "Sensor changed approx: " + NOTE_DATE_FORMAT.format(record.getSageAfterDate()) + " - " + NOTE_DATE_FORMAT.format(record.getSageBeforeDate())
+ ));
+ }
+ if (record.isValidCAGE()) {
+ noteEntries.add(new NoteEntry(
+ "Site Change",
+ ISO8601_DATE_FORMAT.format(record.getCageAfterDate().getTime() - (record.getCageAfterDate().getTime() - record.getCageBeforeDate().getTime()) / 2),
+ "Reservoir changed approx: " + NOTE_DATE_FORMAT.format(record.getCageAfterDate()) + " - " + NOTE_DATE_FORMAT.format(record.getCageBeforeDate())
+ ));
+ }
+ if (record.isValidBATTERY()) {
+ noteEntries.add(new NoteEntry(
+ "Note",
+ ISO8601_DATE_FORMAT.format(record.getBatteryAfterDate().getTime() - (record.getBatteryAfterDate().getTime() - record.getBatteryBeforeDate().getTime()) / 2),
+ "Pump battery changed approx: " + NOTE_DATE_FORMAT.format(record.getBatteryAfterDate()) + " - " + NOTE_DATE_FORMAT.format(record.getBatteryBeforeDate())
+ ));
+ }
- bolusEntries.add(bolusEntry);
}
}
boolean uploaded = true;
- if (glucoseEntries.size() > 0) {
- Response result = glucoseEndpoints.sendEntries(glucoseEntries).execute();
+ if (sgvEntries.size() > 0) {
+ Response result = sgvEndpoints.sendEntries(sgvEntries).execute();
uploaded = result.isSuccessful();
}
- if (bolusEntries.size() > 0) {
- Response result = bolusEndpoints.sendEntries(bolusEntries).execute();
+ if (mbgEntries.size() > 0) {
+ Response result = mbgEndpoints.sendEntries(mbgEntries).execute();
+ uploaded = uploaded && result.isSuccessful();
+ }
+ if (treatmentEntries.size() > 0) {
+ Response result = treatmentEndpoints.sendEntries(treatmentEntries).execute();
+ uploaded = uploaded && result.isSuccessful();
+ }
+ if (tempBasalRateEntries.size() > 0) {
+ Response result = tempBasalRateEndpoints.sendEntries(tempBasalRateEntries).execute();
+ uploaded = uploaded && result.isSuccessful();
+ }
+ if (tempBasalPercentEntries.size() > 0) {
+ Response result = tempBasalPercentEndpoints.sendEntries(tempBasalPercentEntries).execute();
+ uploaded = uploaded && result.isSuccessful();
+ }
+ if (tempBasalCancelEntries.size() > 0) {
+ Response result = tempBasalCancelEndpoints.sendEntries(tempBasalCancelEntries).execute();
+ uploaded = uploaded && result.isSuccessful();
+ }
+ if (noteEntries.size() > 0) {
+ Response result = noteEndpoints.sendEntries(noteEntries).execute();
uploaded = uploaded && result.isSuccessful();
}
return uploaded;
@@ -125,21 +290,89 @@ private boolean uploadDeviceStatus(DeviceEndpoints deviceEndpoints,
List deviceEntries = new ArrayList<>();
for (PumpStatusEvent record : records) {
- Iob iob = new Iob(record.getPumpDate(), record.getActiveInsulin());
+ Iob iob = new Iob(record.getEventDate(), record.getActiveInsulin());
Battery battery = new Battery(record.getBatteryPercentage());
PumpStatus pumpstatus;
- if (record.isBolusing()) {
- pumpstatus = new PumpStatus(true, false, "");
+ // shorten pump status when needed to accommodate mobile browsers
+ boolean shorten = false;
+ if ((record.isBolusingSquare() || record.isBolusingDual()) && record.isTempBasalActive())
+ shorten = true;
+
+ String statusPUMP = "normal";
+ if (record.isBolusingNormal()) {
+ statusPUMP = "bolusing";
} else if (record.isSuspended()) {
- pumpstatus = new PumpStatus(false, true, "");
+ statusPUMP = "suspended";
+ } else {
+ if (record.isBolusingSquare()) {
+ if (shorten)
+ statusPUMP = "S>" + record.getBolusingDelivered() + "u-" + record.getBolusingMinutesRemaining() + "m";
+ else
+ statusPUMP = "square>>" + record.getBolusingDelivered() + "u-" + (record.getBolusingMinutesRemaining() >= 60 ? record.getBolusingMinutesRemaining() / 60 + "h" : "") + record.getBolusingMinutesRemaining() % 60 + "m";
+ shorten = true;
+ } else if (record.isBolusingDual()) {
+ if (shorten)
+ statusPUMP = "D>" + record.getBolusingDelivered() + "-" + record.getBolusingMinutesRemaining() + "m";
+ else
+ statusPUMP = "dual>>" + record.getBolusingDelivered() + "u-" + (record.getBolusingMinutesRemaining() >= 60 ? record.getBolusingMinutesRemaining() / 60 + "h" : "") + record.getBolusingMinutesRemaining() % 60 + "m";
+ shorten = true;
+ }
+ if (record.getTempBasalMinutesRemaining() > 0 & record.getTempBasalPercentage() != 0) {
+ if (shorten)
+ statusPUMP = " T>" + record.getTempBasalPercentage() + "%-" + record.getTempBasalMinutesRemaining() + "m";
+ else
+ statusPUMP = "temp>>" + record.getTempBasalPercentage() + "%-" + (record.getTempBasalMinutesRemaining() >= 60 ? record.getTempBasalMinutesRemaining() / 60 + "h" : "") + record.getTempBasalMinutesRemaining() % 60 + "m";
+ shorten = true;
+ } else if (record.getTempBasalMinutesRemaining() > 0) {
+ if (shorten)
+ statusPUMP = " T>" + record.getTempBasalRate() + "-" + record.getTempBasalMinutesRemaining() + "m";
+ else
+ statusPUMP = "temp>>" + record.getTempBasalRate() + "u-" + (record.getTempBasalMinutesRemaining() >= 60 ? record.getTempBasalMinutesRemaining() / 60 + "h" : "") + record.getTempBasalMinutesRemaining() % 60 + "m";
+ shorten = true;
+ }
+ }
+
+ if (record.getAlert() > 0)
+ statusPUMP = "⚠ " + statusPUMP;
+
+ String statusCGM = "";
+ if (record.isCgmActive()) {
+ if (record.getTransmitterBattery() > 80)
+ statusCGM = shorten ? "" : ":::: ";
+ else if (record.getTransmitterBattery() > 55)
+ statusCGM = shorten ? "" : ":::. ";
+ else if (record.getTransmitterBattery() > 30)
+ statusCGM = shorten ? "" : "::.. ";
+ else if (record.getTransmitterBattery() > 10)
+ statusCGM = shorten ? "" : ":... ";
+ else
+ statusCGM = shorten ? "" : ".... ";
+ if (record.isCgmCalibrating())
+ statusCGM += shorten ? "cal" : "calibrating";
+ else if (record.isCgmCalibrationComplete())
+ statusCGM += shorten ? "cal" : "cal.complete";
+ else {
+ if (record.isCgmWarmUp())
+ statusCGM += shorten ? "WU" : "warmup ";
+ if (record.getCalibrationDueMinutes() > 0) {
+ if (shorten)
+ statusCGM += (record.getCalibrationDueMinutes() >= 120 ? record.getCalibrationDueMinutes() / 60 + "h" : record.getCalibrationDueMinutes() + "m");
+ else
+ statusCGM += (record.getCalibrationDueMinutes() >= 60 ? record.getCalibrationDueMinutes() / 60 + "h" : "") + record.getCalibrationDueMinutes() % 60 + "m";
+ }
+ else
+ statusCGM += shorten ? "cal.now" : "calibrate now!";
+ }
} else {
- pumpstatus = new PumpStatus(false, false, "normal");
+ statusCGM += shorten ? "n/a" : "cgm n/a";
}
+ pumpstatus = new PumpStatus(false, false, statusPUMP + " " + statusCGM);
+
PumpInfo pumpInfo = new PumpInfo(
- ISO8601_DATE_FORMAT.format(record.getPumpDate()),
- new BigDecimal(record.getReservoirAmount()).setScale(3, BigDecimal.ROUND_HALF_UP),
+ ISO8601_DATE_FORMAT.format(record.getEventDate()),
+ new BigDecimal(record.getReservoirAmount()).setScale(0, BigDecimal.ROUND_HALF_UP),
iob,
battery,
pumpstatus
@@ -148,7 +381,7 @@ private boolean uploadDeviceStatus(DeviceEndpoints deviceEndpoints,
DeviceStatus deviceStatus = new DeviceStatus(
uploaderBatteryLevel,
record.getDeviceName(),
- ISO8601_DATE_FORMAT.format(record.getPumpDate()),
+ ISO8601_DATE_FORMAT.format(record.getEventDate()),
pumpInfo
);
@@ -176,5 +409,4 @@ private String formToken(String secret) throws NoSuchAlgorithmException, Unsuppo
}
return sb.toString();
}
-
}
diff --git a/app/src/main/java/info/nightscout/android/upload/nightscout/NightscoutUploadIntentService.java b/app/src/main/java/info/nightscout/android/upload/nightscout/NightscoutUploadIntentService.java
index 0382189..30dcd7c 100644
--- a/app/src/main/java/info/nightscout/android/upload/nightscout/NightscoutUploadIntentService.java
+++ b/app/src/main/java/info/nightscout/android/upload/nightscout/NightscoutUploadIntentService.java
@@ -11,6 +11,7 @@
import android.util.Log;
import info.nightscout.android.R;
+import info.nightscout.android.medtronic.service.MedtronicCnlIntentService;
import info.nightscout.android.model.medtronicNg.PumpStatusEvent;
import info.nightscout.android.utils.DataStore;
import io.realm.Realm;
@@ -29,8 +30,8 @@ public NightscoutUploadIntentService() {
protected void sendStatus(String message) {
Intent localIntent =
- new Intent(Constants.ACTION_STATUS_MESSAGE)
- .putExtra(Constants.EXTENDED_DATA, message);
+ new Intent(MedtronicCnlIntentService.Constants.ACTION_STATUS_MESSAGE)
+ .putExtra(MedtronicCnlIntentService.Constants.EXTENDED_DATA, message);
LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent);
}
@@ -53,13 +54,14 @@ protected void onHandleIntent(Intent intent) {
RealmResults records = mRealm
.where(PumpStatusEvent.class)
.equalTo("uploaded", false)
- .notEqualTo("sgv", 0)
.findAll();
if (records.size() > 0) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
Boolean enableRESTUpload = prefs.getBoolean("EnableRESTUpload", false);
+ Boolean enableTreatmentsUpload = prefs.getBoolean("EnableTreatmentsUpload", false);
+
try {
if (enableRESTUpload) {
long start = System.currentTimeMillis();
@@ -67,21 +69,30 @@ protected void onHandleIntent(Intent intent) {
String urlSetting = prefs.getString(mContext.getString(R.string.preference_nightscout_url), "");
String secretSetting = prefs.getString(mContext.getString(R.string.preference_api_secret), "YOURAPISECRET");
Boolean uploadSuccess = mNightScoutUpload.doRESTUpload(urlSetting,
- secretSetting, DataStore.getInstance().getUploaderBatteryLevel(), records);
+ secretSetting, enableTreatmentsUpload, DataStore.getInstance().getUploaderBatteryLevel(), records);
if (uploadSuccess) {
mRealm.beginTransaction();
for (PumpStatusEvent updateRecord : records) {
updateRecord.setUploaded(true);
}
mRealm.commitTransaction();
+ } else {
+ sendStatus(MedtronicCnlIntentService.ICON_WARN + "Uploading to Nightscout returned unsuccessful");
}
Log.i(TAG, String.format("Finished upload of %s record using a REST API in %s ms", records.size(), System.currentTimeMillis() - start));
+ } else {
+ mRealm.beginTransaction();
+ for (PumpStatusEvent updateRecord : records) {
+ updateRecord.setUploaded(true);
+ }
+ mRealm.commitTransaction();
}
} catch (Exception e) {
+ sendStatus(MedtronicCnlIntentService.ICON_WARN + "Error uploading: " + e.getMessage());
Log.e(TAG, "ERROR uploading data!!!!!", e);
}
} else {
- Log.i(TAG, "No records has to be uploaded");
+ Log.i(TAG, "No records have to be uploaded");
}
mRealm.close();
@@ -100,5 +111,4 @@ public final class Constants {
public static final String EXTENDED_DATA = "info.nightscout.android.upload.nightscout.DATA";
}
-
}
diff --git a/app/src/main/java/info/nightscout/android/upload/nightscout/serializer/EntriesSerializer.java b/app/src/main/java/info/nightscout/android/upload/nightscout/serializer/EntriesSerializer.java
index 1f40ab0..0b44575 100644
--- a/app/src/main/java/info/nightscout/android/upload/nightscout/serializer/EntriesSerializer.java
+++ b/app/src/main/java/info/nightscout/android/upload/nightscout/serializer/EntriesSerializer.java
@@ -80,8 +80,8 @@ public JsonElement serialize(PumpStatusEvent src, Type typeOfSrc, JsonSerializat
jsonObject.addProperty("direction", getDirectionString(src.getCgmTrend()));
jsonObject.addProperty("device", src.getDeviceName());
jsonObject.addProperty("type", "sgv");
- jsonObject.addProperty("date", src.getSgvDate().getTime());
- jsonObject.addProperty("dateString", String.valueOf(src.getSgvDate()));
+ jsonObject.addProperty("date", src.getCgmDate().getTime());
+ jsonObject.addProperty("dateString", String.valueOf(src.getCgmDate()));
return jsonObject;
}
diff --git a/app/src/main/java/info/nightscout/android/utils/DataStore.java b/app/src/main/java/info/nightscout/android/utils/DataStore.java
index 86bfa94..cdc04e7 100644
--- a/app/src/main/java/info/nightscout/android/utils/DataStore.java
+++ b/app/src/main/java/info/nightscout/android/utils/DataStore.java
@@ -1,13 +1,8 @@
package info.nightscout.android.utils;
-import org.apache.commons.lang3.time.DateUtils;
-
import java.util.Date;
-import info.nightscout.android.model.medtronicNg.PumpStatusEvent;
-import io.realm.Realm;
-
/**
* Created by volker on 30.03.2017.
*/
@@ -15,16 +10,23 @@
public class DataStore {
private static DataStore instance;
- private PumpStatusEvent lastPumpStatus;
- private int uploaderBatteryLevel = 0;
- private int unavailableSGVCount = 0;
- private int lastBolusWizardBGL = 0;
+ private Date uploaderStartDate;
+
private long activePumpMac = 0;
- private int commsErrorCount = 0;
- private int commsSuccessCount = 0;
- private int commsConnectThreshold = 0;
- private int commsSignalThreshold = 0;
- private float commsUnavailableThreshold = 0;
+ private int uploaderBatteryLevel = 0;
+
+ private int pumpCgmNA = 0;
+
+ private int CommsSuccess = 0;
+ private int CommsError = 0;
+ private int CommsConnectError = 0;
+ private int CommsSignalError = 0;
+ private int CommsSgvSuccess = 0;
+ private int PumpLostSensorError = 0;
+ private int PumpClockError = 0;
+ private int PumpBatteryError = 0;
+
+ private boolean screenOn = true;
private DataStore() {}
@@ -32,27 +34,21 @@ public static DataStore getInstance() {
if (DataStore.instance == null) {
instance = new DataStore();
- // set some initial dummy values
- PumpStatusEvent dummyStatus = new PumpStatusEvent();
- dummyStatus.setSgvDate(DateUtils.addDays(new Date(), -1));
- dummyStatus.setSgv(0);
-
- // bypass setter to avoid dealing with a real Realm object
- instance.lastPumpStatus = dummyStatus;
+ instance.uploaderStartDate = new Date();
}
-
return instance;
}
- public PumpStatusEvent getLastPumpStatus() {
- return lastPumpStatus;
+ public Date getUploaderStartDate() {
+ return uploaderStartDate;
}
- public void setLastPumpStatus(PumpStatusEvent lastPumpStatus) {
- Realm realm = Realm.getDefaultInstance();
+ public long getActivePumpMac() {
+ return activePumpMac;
+ }
- this.lastPumpStatus = realm.copyFromRealm(lastPumpStatus);
- if (!realm.isClosed()) realm.close();
+ public void setActivePumpMac(long activePumpMac) {
+ this.activePumpMac = activePumpMac;
}
public int getUploaderBatteryLevel() {
@@ -63,112 +59,118 @@ public void setUploaderBatteryLevel(int uploaderBatteryLevel) {
this.uploaderBatteryLevel = uploaderBatteryLevel;
}
- public int getUnavailableSGVCount() {
- return unavailableSGVCount;
+ public int getPumpCgmNA() {
+ return pumpCgmNA;
}
- public int incUnavailableSGVCount() {
- return unavailableSGVCount++;
+ public int incPumpCgmNA() {
+ return pumpCgmNA++;
}
- public void clearUnavailableSGVCount() {
- this.unavailableSGVCount = 0;
+ public void clearPumpCgmNA() {
+ this.pumpCgmNA = 0;
}
- public void setUnavailableSGVCount(int unavailableSGVCount) {
- this.unavailableSGVCount = unavailableSGVCount;
+ public int getCommsSuccess() {
+ return CommsSuccess;
}
- public int getLastBolusWizardBGL() {
- return lastBolusWizardBGL;
- }
+ public int incCommsSuccess() { return CommsSuccess++; }
- public void setLastBolusWizardBGL(int lastBolusWizardBGL) {
- this.lastBolusWizardBGL = lastBolusWizardBGL;
+ public void clearCommsSuccess() {
+ this.CommsSuccess = 0;
}
- public long getActivePumpMac() {
- return activePumpMac;
+ public int getCommsError() {
+ return CommsError;
}
- public void setActivePumpMac(long activePumpMac) {
- this.activePumpMac = activePumpMac;
+ public int incCommsError() { return CommsError++; }
+
+ public void clearCommsError() {
+ this.CommsError = 0;
}
- public int getCommsErrorCount() {
- return commsErrorCount;
+ public int getCommsConnectError() {
+ return CommsConnectError;
}
- public int incCommsErrorCount() { return commsErrorCount++; }
+ public int incCommsConnectError() { return CommsConnectError++; }
- public int decCommsErrorThreshold() {
- if (commsErrorCount > 0) commsErrorCount--;
- return commsErrorCount;}
+ public int decCommsConnectError() {
+ if (CommsConnectError > 0) CommsConnectError--;
+ return CommsConnectError;}
- public void clearCommsErrorCount() {
- this.commsErrorCount = 0;
+ public void clearCommsConnectError() {
+ this.CommsConnectError = 0;
}
- public int getCommsSuccessCount() {
- return commsSuccessCount;
+ public int getCommsSignalError() {
+ return CommsSignalError;
}
- public int incCommsSuccessCount() { return commsSuccessCount++; }
+ public int incCommsSignalError() { return CommsSignalError++; }
- public int decCommsSuccessCount() {
- if (commsSuccessCount > 0) commsSuccessCount--;
- return commsSuccessCount;}
+ public int decCommsSignalError() {
+ if (CommsSignalError > 0) CommsSignalError--;
+ return CommsSignalError;}
- public void clearCommsSuccessCount() {
- this.commsSuccessCount = 0;
+ public void clearCommsSignalError() {
+ this.CommsSignalError = 0;
}
- public int getCommsConnectThreshold() {
- return commsConnectThreshold;
+ public int getCommsSgvSuccess() {
+ return CommsSgvSuccess;
}
- public int incCommsConnectThreshold() { return commsConnectThreshold++; }
-
- public int decCommsConnectThreshold() {
- if (commsConnectThreshold > 0) commsConnectThreshold--;
- return commsConnectThreshold;}
+ public int incCommsSgvSuccess() { return CommsSgvSuccess++; }
- public void clearCommsConnectThreshold() {
- this.commsConnectThreshold = 0;
+ public int getPumpLostSensorError() {
+ return PumpLostSensorError;
}
- public int getCommsSignalThreshold() {
- return commsSignalThreshold;
+ public int incPumpLostSensorError() { return PumpLostSensorError++; }
+
+ public void clearPumpLostSensorError() {
+ this.PumpLostSensorError = 0;
}
- public int incCommsSignalThreshold() { return commsSignalThreshold++; }
+ public int getPumpClockError() {
+ return PumpClockError;
+ }
- public int decCommsSignalThreshold() {
- if (commsSignalThreshold > 0) commsSignalThreshold--;
- return commsSignalThreshold;}
+ public int incPumpClockError() { return PumpClockError++; }
- public void clearCommsSignalThreshold() {
- this.commsSignalThreshold = 0;
+ public void clearPumpClockError() {
+ this.PumpClockError = 0;
}
- public float getCommsUnavailableThreshold() {
- return commsUnavailableThreshold;
+ public int getPumpBatteryError() {
+ return PumpBatteryError;
}
- public float addCommsUnavailableThreshold(float value) {
- commsUnavailableThreshold+= value;
- if (commsUnavailableThreshold < 0) commsUnavailableThreshold = 0;
- return commsUnavailableThreshold;}
+ public int incPumpBatteryError() { return PumpBatteryError++; }
- public void clearCommsUnavailableThreshold() {
- this.commsUnavailableThreshold = 0;
+ public void clearPumpBatteryError() {
+ this.PumpBatteryError = 0;
}
public void clearAllCommsErrors() {
- this.commsErrorCount = 0;
- this.commsSuccessCount = 0;
- this.commsConnectThreshold = 0;
- this.commsSignalThreshold = 0;
- this.commsUnavailableThreshold = 0;
+ this.CommsSuccess = 0;
+ this.CommsError = 0;
+ this.CommsConnectError = 0;
+ this.CommsSignalError = 0;
+ this.PumpLostSensorError = 0;
+ this.PumpClockError = 0;
+ this.PumpBatteryError = 0;
+ }
+
+
+ public boolean isScreenOn() {
+ return screenOn;
+ }
+
+ public void setScreenOn(boolean screenOn) {
+ this.screenOn = screenOn;
}
}
diff --git a/app/src/main/java/info/nightscout/android/utils/StatusStore.java b/app/src/main/java/info/nightscout/android/utils/StatusStore.java
new file mode 100644
index 0000000..fd0e39e
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/utils/StatusStore.java
@@ -0,0 +1,27 @@
+package info.nightscout.android.utils;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+
+import io.realm.RealmObject;
+import io.realm.annotations.Index;
+
+public class StatusStore extends RealmObject {
+ @Index
+ private long timestamp;
+ private String message;
+
+ public void StatusMessage(String message) {
+ StatusMessage(System.currentTimeMillis(), message);
+ }
+
+ public void StatusMessage(long timestamp, String message) {
+ this.timestamp = timestamp;
+ this.message = message;
+ }
+
+ public String toString() {
+ DateFormat df = new SimpleDateFormat("E HH:mm:ss");
+ return df.format(timestamp) + ": " + message;
+ }
+}
diff --git a/app/src/main/java/info/nightscout/android/xdrip_plus/XDripPlusUploadIntentService.java b/app/src/main/java/info/nightscout/android/xdrip_plus/XDripPlusUploadIntentService.java
index e06a88b..22be136 100644
--- a/app/src/main/java/info/nightscout/android/xdrip_plus/XDripPlusUploadIntentService.java
+++ b/app/src/main/java/info/nightscout/android/xdrip_plus/XDripPlusUploadIntentService.java
@@ -61,7 +61,9 @@ protected void onHandleIntent(Intent intent) {
RealmResults all_records = mRealm
.where(PumpStatusEvent.class)
- .notEqualTo("sgv", 0)
+ .equalTo("validSGV", true)
+ .or()
+ .equalTo("validBGL", true)
.findAllSorted("eventDate", Sort.DESCENDING);
// get the most recent record and send that
@@ -117,14 +119,14 @@ private void addDeviceStatus(JSONArray devicestatusArray, PumpStatusEvent record
JSONObject json = new JSONObject();
json.put("uploaderBattery", DataStore.getInstance().getUploaderBatteryLevel());
json.put("device", record.getDeviceName());
- json.put("created_at", ISO8601_DATE_FORMAT.format(record.getPumpDate()));
+ json.put("created_at", ISO8601_DATE_FORMAT.format(record.getEventDate()));
JSONObject pumpInfo = new JSONObject();
- pumpInfo.put("clock", ISO8601_DATE_FORMAT.format(record.getPumpDate()));
+ pumpInfo.put("clock", ISO8601_DATE_FORMAT.format(record.getEventDate()));
pumpInfo.put("reservoir", new BigDecimal(record.getReservoirAmount()).setScale(3, BigDecimal.ROUND_HALF_UP));
JSONObject iob = new JSONObject();
- iob.put("timestamp", record.getPumpDate());
+ iob.put("timestamp", record.getEventDate());
iob.put("bolusiob", record.getActiveInsulin());
JSONObject battery = new JSONObject();
@@ -139,25 +141,27 @@ private void addDeviceStatus(JSONArray devicestatusArray, PumpStatusEvent record
}
private void addSgvEntry(JSONArray entriesArray, PumpStatusEvent pumpRecord) throws Exception {
- JSONObject json = new JSONObject();
- // TODO replace with Retrofit/EntriesSerializer
- json.put("sgv", pumpRecord.getSgv());
- json.put("direction", EntriesSerializer.getDirectionString(pumpRecord.getCgmTrend()));
- json.put("device", pumpRecord.getDeviceName());
- json.put("type", "sgv");
- json.put("date", pumpRecord.getSgvDate().getTime());
- json.put("dateString", pumpRecord.getSgvDate());
-
- entriesArray.put(json);
+ if (pumpRecord.isValidSGV()) {
+ JSONObject json = new JSONObject();
+ // TODO replace with Retrofit/EntriesSerializer
+ json.put("sgv", pumpRecord.getSgv());
+ json.put("direction", EntriesSerializer.getDirectionString(pumpRecord.getCgmTrend()));
+ json.put("device", pumpRecord.getDeviceName());
+ json.put("type", "sgv");
+ json.put("date", pumpRecord.getCgmDate().getTime());
+ json.put("dateString", pumpRecord.getCgmDate());
+
+ entriesArray.put(json);
+ }
}
private void addMbgEntry(JSONArray entriesArray, PumpStatusEvent pumpRecord) throws Exception {
- if (pumpRecord.hasRecentBolusWizard()) {
+ if (pumpRecord.isValidBGL()) {
JSONObject json = new JSONObject();
// TODO replace with Retrofit/EntriesSerializer
json.put("type", "mbg");
- json.put("mbg", pumpRecord.getBolusWizardBGL());
+ json.put("mbg", pumpRecord.getRecentBGL());
json.put("device", pumpRecord.getDeviceName());
json.put("date", pumpRecord.getEventDate().getTime());
json.put("dateString", pumpRecord.getEventDate());
diff --git a/app/src/main/java/info/nightscout/api/BolusEndpoints.java b/app/src/main/java/info/nightscout/api/MbgEndpoints.java
similarity index 63%
rename from app/src/main/java/info/nightscout/api/BolusEndpoints.java
rename to app/src/main/java/info/nightscout/api/MbgEndpoints.java
index a1c2a56..9c44334 100644
--- a/app/src/main/java/info/nightscout/api/BolusEndpoints.java
+++ b/app/src/main/java/info/nightscout/api/MbgEndpoints.java
@@ -8,57 +8,36 @@
import retrofit2.http.Headers;
import retrofit2.http.POST;
-public interface BolusEndpoints {
+public interface MbgEndpoints {
- class BolusEntry {
+ class MbgEntry {
String type;
String dateString;
long date;
int mbg;
String device;
- public BolusEntry() { }
-
- public String getType() {
- return type;
- }
+ public MbgEntry() { }
public void setType(String type) {
this.type = type;
}
- public String getDateString() {
- return dateString;
- }
-
public void setDateString(String dateString) {
this.dateString = dateString;
}
- public long getDate() {
- return date;
- }
-
public void setDate(long date) {
this.date = date;
}
- public int getMbg() {
- return mbg;
- }
-
public void setMbg(int mbg) {
this.mbg = mbg;
}
- public String getDevice() {
- return device;
- }
-
public void setDevice(String device) {
this.device = device;
}
-
}
@Headers({
@@ -66,8 +45,7 @@ public void setDevice(String device) {
"Content-type: application/json"
})
@POST("/api/v1/entries")
- Call sendEntries(@Body List entries);
-
+ Call sendEntries(@Body List entries);
}
diff --git a/app/src/main/java/info/nightscout/api/NoteEndpoints.java b/app/src/main/java/info/nightscout/api/NoteEndpoints.java
new file mode 100644
index 0000000..cd51af7
--- /dev/null
+++ b/app/src/main/java/info/nightscout/api/NoteEndpoints.java
@@ -0,0 +1,31 @@
+package info.nightscout.api;
+
+import java.util.List;
+
+import okhttp3.ResponseBody;
+import retrofit2.Call;
+import retrofit2.http.Body;
+import retrofit2.http.Headers;
+import retrofit2.http.POST;
+
+public interface NoteEndpoints {
+
+ class NoteEntry {
+ String eventType;
+ String created_at;
+ String notes;
+
+ public NoteEntry(String eventType, String created_at, String notes) {
+ this.eventType = eventType;
+ this.created_at = created_at;
+ this.notes = notes;
+ }
+ }
+
+ @Headers({
+ "Accept: application/json",
+ "Content-type: application/json"
+ })
+ @POST("/api/v1/treatments")
+ Call sendEntries(@Body List noteEntries);
+}
diff --git a/app/src/main/java/info/nightscout/api/GlucoseEndpoints.java b/app/src/main/java/info/nightscout/api/SgvEndpoints.java
similarity index 62%
rename from app/src/main/java/info/nightscout/api/GlucoseEndpoints.java
rename to app/src/main/java/info/nightscout/api/SgvEndpoints.java
index d34c5e7..96dd7bb 100644
--- a/app/src/main/java/info/nightscout/api/GlucoseEndpoints.java
+++ b/app/src/main/java/info/nightscout/api/SgvEndpoints.java
@@ -8,10 +8,9 @@
import retrofit2.http.Headers;
import retrofit2.http.POST;
-public interface GlucoseEndpoints {
-
- class GlucoseEntry {
+public interface SgvEndpoints {
+ class SgvEntry {
String type;
String dateString;
long date;
@@ -19,55 +18,31 @@ class GlucoseEntry {
String direction;
String device;
- public String getType() {
- return type;
- }
-
public void setType(String type) {
this.type = type;
}
- public String getDateString() {
- return dateString;
- }
-
public void setDateString(String dateString) {
this.dateString = dateString;
}
- public long getDate() {
- return date;
- }
-
public void setDate(long date) {
this.date = date;
}
- public int getSgv() {
- return sgv;
- }
-
public void setSgv(int sgv) {
this.sgv = sgv;
}
- public String getDirection() {
- return direction;
- }
-
public void setDirection(String direction) {
this.direction = direction;
}
- public String getDevice() {
- return device;
- }
-
public void setDevice(String device) {
this.device = device;
}
- public GlucoseEntry() { }
+ public SgvEntry() { }
}
@Headers({
@@ -75,8 +50,7 @@ public GlucoseEntry() { }
"Content-type: application/json"
})
@POST("/api/v1/entries")
- Call sendEntries(@Body List entries);
-
+ Call sendEntries(@Body List entries);
}
diff --git a/app/src/main/java/info/nightscout/api/TempBasalCancelEndpoints.java b/app/src/main/java/info/nightscout/api/TempBasalCancelEndpoints.java
new file mode 100644
index 0000000..1d0ca98
--- /dev/null
+++ b/app/src/main/java/info/nightscout/api/TempBasalCancelEndpoints.java
@@ -0,0 +1,31 @@
+package info.nightscout.api;
+
+import java.util.List;
+
+import okhttp3.ResponseBody;
+import retrofit2.Call;
+import retrofit2.http.Body;
+import retrofit2.http.Headers;
+import retrofit2.http.POST;
+
+public interface TempBasalCancelEndpoints {
+
+ class TempBasalCancelEntry {
+ String eventType = "Temp Basal";
+ String created_at;
+ String notes;
+ float duration = 0;
+
+ public TempBasalCancelEntry(String created_at, String notes) {
+ this.created_at = created_at;
+ this.notes = notes;
+ }
+ }
+
+ @Headers({
+ "Accept: application/json",
+ "Content-type: application/json"
+ })
+ @POST("/api/v1/treatments")
+ Call sendEntries(@Body List entries);
+}
diff --git a/app/src/main/java/info/nightscout/api/TempBasalPercentEndpoints.java b/app/src/main/java/info/nightscout/api/TempBasalPercentEndpoints.java
new file mode 100644
index 0000000..2622b3c
--- /dev/null
+++ b/app/src/main/java/info/nightscout/api/TempBasalPercentEndpoints.java
@@ -0,0 +1,34 @@
+package info.nightscout.api;
+
+import java.util.List;
+
+import okhttp3.ResponseBody;
+import retrofit2.Call;
+import retrofit2.http.Body;
+import retrofit2.http.Headers;
+import retrofit2.http.POST;
+
+public interface TempBasalPercentEndpoints {
+
+ class TempBasalPercentEntry {
+ String eventType = "Temp Basal";
+ String created_at;
+ String notes;
+ float duration;
+ float percent;
+
+ public TempBasalPercentEntry(String created_at, String notes, float duration, float percent) {
+ this.created_at = created_at;
+ this.notes = notes;
+ this.duration = duration;
+ this.percent = percent;
+ }
+ }
+
+ @Headers({
+ "Accept: application/json",
+ "Content-type: application/json"
+ })
+ @POST("/api/v1/treatments")
+ Call sendEntries(@Body List entries);
+}
diff --git a/app/src/main/java/info/nightscout/api/TempBasalRateEndpoints.java b/app/src/main/java/info/nightscout/api/TempBasalRateEndpoints.java
new file mode 100644
index 0000000..27f7d4f
--- /dev/null
+++ b/app/src/main/java/info/nightscout/api/TempBasalRateEndpoints.java
@@ -0,0 +1,34 @@
+package info.nightscout.api;
+
+import java.util.List;
+
+import okhttp3.ResponseBody;
+import retrofit2.Call;
+import retrofit2.http.Body;
+import retrofit2.http.Headers;
+import retrofit2.http.POST;
+
+public interface TempBasalRateEndpoints {
+
+ class TempBasalRateEntry {
+ String eventType = "Temp Basal";
+ String created_at;
+ String notes;
+ float duration;
+ float absolute;
+
+ public TempBasalRateEntry(String created_at, String notes, float duration, float absolute) {
+ this.created_at = created_at;
+ this.notes = notes;
+ this.duration = duration;
+ this.absolute = absolute;
+ }
+ }
+
+ @Headers({
+ "Accept: application/json",
+ "Content-type: application/json"
+ })
+ @POST("/api/v1/treatments")
+ Call sendEntries(@Body List entries);
+}
diff --git a/app/src/main/java/info/nightscout/api/TreatmentEndpoints.java b/app/src/main/java/info/nightscout/api/TreatmentEndpoints.java
new file mode 100644
index 0000000..129af21
--- /dev/null
+++ b/app/src/main/java/info/nightscout/api/TreatmentEndpoints.java
@@ -0,0 +1,88 @@
+package info.nightscout.api;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import okhttp3.ResponseBody;
+import retrofit2.Call;
+import retrofit2.http.Body;
+import retrofit2.http.Headers;
+import retrofit2.http.POST;
+
+public interface TreatmentEndpoints {
+
+ class TreatmentEntry {
+ String eventType;
+ String created_at;
+ String enteredinsulin;
+ String splitNow;
+ String splitExt;
+ String units;
+ String glucoseType;
+ String notes;
+ String device;
+ float insulin;
+ float duration;
+ float relative;
+ BigDecimal glucose;
+
+ public void setEventType(String eventType) {
+ this.eventType = eventType;
+ }
+
+ public void setCreatedAt(String created_at) {
+ this.created_at = created_at;
+ }
+
+ public void setDevice(String device) {
+ this.device = device;
+ }
+
+ public void setInsulin(float insulin) {
+ this.insulin = insulin;
+ }
+
+ public void setGlucose(BigDecimal glucose) {
+ this.glucose = glucose;
+ }
+
+ public void setDuration(float duration) {
+ this.duration = duration;
+ }
+
+ public void setRelative(float relative) {
+ this.relative = relative;
+ }
+
+ public void setEnteredinsulin(String enteredinsulin) {
+ this.enteredinsulin = enteredinsulin;
+ }
+
+ public void setSplitNow(String splitNow) {
+ this.splitNow = splitNow;
+ }
+
+ public void setSplitExt(String splitExt) {
+ this.splitExt = splitExt;
+ }
+
+ public void setUnits(String units) {
+ this.units = units;
+ }
+
+ public void setGlucoseType(String glucoseType) {
+ this.glucoseType = glucoseType;
+ }
+
+ public void setNotes(String notes) {
+ this.notes = notes;
+ }
+ }
+
+ @Headers({
+ "Accept: application/json",
+ "Content-type: application/json"
+ })
+ @POST("/api/v1/treatments")
+ Call sendEntries(@Body List entries);
+}
diff --git a/app/src/main/java/info/nightscout/api/UploadApi.java b/app/src/main/java/info/nightscout/api/UploadApi.java
index a2c4a1a..70ce741 100644
--- a/app/src/main/java/info/nightscout/api/UploadApi.java
+++ b/app/src/main/java/info/nightscout/api/UploadApi.java
@@ -14,21 +14,39 @@
public class UploadApi {
private Retrofit retrofit;
- private GlucoseEndpoints glucoseEndpoints;
- private BolusEndpoints bolusEndpoints;
+ private SgvEndpoints sgvEndpoints;
+ private MbgEndpoints mbgEndpoints;
private DeviceEndpoints deviceEndpoints;
-
- public GlucoseEndpoints getGlucoseEndpoints() {
- return glucoseEndpoints;
+ private TreatmentEndpoints treatmentEndpoints;
+ private TempBasalRateEndpoints tempBasalRateEndpoints;
+ private TempBasalPercentEndpoints tempBasalPercentEndpoints;
+ private TempBasalCancelEndpoints tempBasalCancelEndpoints;
+ private NoteEndpoints noteEndpoints;
+
+ public SgvEndpoints getSgvEndpoints() {
+ return sgvEndpoints;
}
-
- public BolusEndpoints getBolusEndpoints() {
- return bolusEndpoints;
+ public MbgEndpoints getMbgEndpoints() {
+ return mbgEndpoints;
}
-
public DeviceEndpoints getDeviceEndpoints() {
return deviceEndpoints;
}
+ public TreatmentEndpoints getTreatmentEndpoints() {
+ return treatmentEndpoints;
+ }
+ public TempBasalRateEndpoints getTempBasalRateEndpoints() {
+ return tempBasalRateEndpoints;
+ }
+ public TempBasalPercentEndpoints getTempBasalPercentEndpoints() {
+ return tempBasalPercentEndpoints;
+ }
+ public TempBasalCancelEndpoints getTempBasalCancelEndpoints() {
+ return tempBasalCancelEndpoints;
+ }
+ public NoteEndpoints getNoteEndpoints() {
+ return noteEndpoints;
+ }
public UploadApi(String baseURL, String token) {
@@ -70,8 +88,13 @@ public Response intercept(Interceptor.Chain chain) throws IOException {
.addConverterFactory(GsonConverterFactory.create())
.build();
- glucoseEndpoints = retrofit.create(GlucoseEndpoints.class);
- bolusEndpoints = retrofit.create(BolusEndpoints.class);
+ sgvEndpoints = retrofit.create(SgvEndpoints.class);
+ mbgEndpoints = retrofit.create(MbgEndpoints.class);
deviceEndpoints = retrofit.create(DeviceEndpoints.class);
+ treatmentEndpoints = retrofit.create(TreatmentEndpoints.class);
+ tempBasalRateEndpoints = retrofit.create(TempBasalRateEndpoints.class);
+ tempBasalPercentEndpoints = retrofit.create(TempBasalPercentEndpoints.class);
+ tempBasalCancelEndpoints = retrofit.create(TempBasalCancelEndpoints.class);
+ noteEndpoints = retrofit.create(NoteEndpoints.class);
}
}
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 8d2fad1..6e9a1e7 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -66,7 +66,7 @@
android:gravity="bottom|center_horizontal"
android:orientation="vertical">
-
+
+
+
+
+
+
+
+ android:text=""
+ android:layout_weight="0.33" />
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/custom_notification.xml b/app/src/main/res/layout/custom_notification.xml
new file mode 100644
index 0000000..91c28c3
--- /dev/null
+++ b/app/src/main/res/layout/custom_notification.xml
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index ff89a6f..d590b78 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -19,12 +19,12 @@
android:switchTextOn="2"
android:title="Decimals"/>
+ android:entryValues="@array/poll_interval_millis"
+ android:key="pollInterval"
+ android:summary="%s"
+ android:title="@string/preferences_poll_interval" />
+