diff --git a/app/build.gradle b/app/build.gradle index d056240b5279649d8dbba489ecfc0876187aeae7..2196be3c7498a513b019b2390c94f56395dc3be8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -115,6 +115,7 @@ dependencies { compile 'com.android.support:cardview-v7:25.3.1' compile 'org.apache.commons:commons-lang3:3.4' compile 'com.mikepenz:google-material-typeface:2.2.0.1.original@aar' + compile 'com.mikepenz:ionicons-typeface:2.0.1.3@aar' compile 'uk.co.chrisjenx:calligraphy:2.2.0' compile 'com.bugfender.sdk:android:0.7.2' compile 'com.jjoe64:graphview:4.0.1' 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 373489b351c88f124b26921b5d3b02bba0f35699..f0b9dd1aa68aa5965e29420cd5c23df5870f20a9 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/MainActivity.java +++ b/app/src/main/java/info/nightscout/android/medtronic/MainActivity.java @@ -30,11 +30,14 @@ import android.support.v7.app.NotificationCompat; import android.support.v7.view.menu.ActionMenuItemView; import android.support.v7.widget.Toolbar; import android.text.format.DateUtils; +import android.util.DisplayMetrics; import android.util.Log; +import android.util.TypedValue; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; +import android.widget.ScrollView; import android.widget.TextView; import android.widget.TextView.BufferType; import android.widget.Toast; @@ -96,11 +99,23 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc private SharedPreferences prefs = null; private PumpInfo mActivePump; private TextView mTextViewLog; // This will eventually move to a status page. + private ScrollView mScrollView; private GraphView mChart; private Handler mUiRefreshHandler = new Handler(); private Runnable mUiRefreshRunnable = new RefreshDisplayRunnable(); private Realm mRealm; private StatusMessageReceiver statusMessageReceiver = new StatusMessageReceiver(); + private UsbReceiver usbReceiver = new UsbReceiver(); + private BatteryReceiver batteryReceiver = new BatteryReceiver(); + + private DateFormat dateFormatter = new SimpleDateFormat("HH:mm:ss", Locale.US); + + protected void sendStatus(String message) { + Intent localIntent = + new Intent(MedtronicCnlIntentService.Constants.ACTION_STATUS_MESSAGE) + .putExtra(MedtronicCnlIntentService.Constants.EXTENDED_DATA, message); + LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent); + } /** * calculate the next poll timestamp based on last svg event @@ -109,7 +124,7 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc * @return timestamp */ public static long getNextPoll(PumpStatusEvent pumpStatusData) { - long nextPoll = pumpStatusData.getSgvDate().getTime() + pumpStatusData.getPumpTimeOffset(), + long nextPoll = pumpStatusData.getSgvDate().getTime(), now = System.currentTimeMillis(), pollInterval = ConfigurationStore.getInstance().getPollInterval(); @@ -198,9 +213,8 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc batteryIntentFilter.addAction(Intent.ACTION_BATTERY_LOW); batteryIntentFilter.addAction(Intent.ACTION_BATTERY_CHANGED); batteryIntentFilter.addAction(Intent.ACTION_BATTERY_OKAY); - registerReceiver(new BatteryReceiver(), batteryIntentFilter); + registerReceiver(batteryReceiver, batteryIntentFilter); - UsbReceiver usbReceiver = new UsbReceiver(); IntentFilter usbIntentFilter = new IntentFilter(); usbIntentFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); usbIntentFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); @@ -285,7 +299,8 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc finish(); } else if (drawerItem.equals(itemGetNow)) { // It was triggered by user so start reading of data now and not based on last poll. - startCgmService(0); + sendStatus("Requesting poll now..."); + startCgmService(System.currentTimeMillis() + 1000); } else if (drawerItem.equals(itemClearLog)) { clearLogText(); } else if (drawerItem.equals(itemCheckForUpdate)) { @@ -298,12 +313,17 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc .build(); mTextViewLog = (TextView) findViewById(R.id.textview_log); + mScrollView = (ScrollView) findViewById(R.id.scrollView); + mScrollView.setSmoothScrollingEnabled(true); mChart = (GraphView) findViewById(R.id.chart); // disable scrolling at the moment mChart.getViewport().setScalable(false); mChart.getViewport().setScrollable(false); + mChart.getViewport().setYAxisBoundsManual(true); + mChart.getViewport().setMinY(80); + mChart.getViewport().setMaxY(120); mChart.getViewport().setXAxisBoundsManual(true); final long now = System.currentTimeMillis(), left = now - chartZoom * 60 * 60 * 1000; @@ -363,8 +383,9 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); - startCgmService(); startDisplayRefreshLoop(); + statusStartup(); + startCgmService(); } @Override @@ -419,11 +440,6 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc } } - private void refreshDisplay() { - cancelDisplayRefreshLoop(); - startDisplayRefreshLoop(); - } - private void clearLogText() { statusMessageReceiver.clearMessages(); } @@ -444,8 +460,23 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc .start(); } + private void statusStartup() { + sendStatus(MedtronicCnlIntentService.ICON_HEART + "Nightscout 600 Series Uploader"); + sendStatus(MedtronicCnlIntentService.ICON_SETTING + "Poll interval: " + (configurationStore.getPollInterval() / 60000) +" minutes"); + sendStatus(MedtronicCnlIntentService.ICON_SETTING + "Low battery poll interval: " + (configurationStore.getLowBatteryPollInterval() / 60000) +" minutes"); + } + + private void refreshDisplay() { + cancelDisplayRefreshLoop(); + mUiRefreshHandler.post(mUiRefreshRunnable);; + } + private void refreshDisplay(int delay) { + cancelDisplayRefreshLoop(); + mUiRefreshHandler.postDelayed(mUiRefreshRunnable, delay); + } + private void startDisplayRefreshLoop() { - mUiRefreshHandler.post(mUiRefreshRunnable); + refreshDisplay(50); } private void cancelDisplayRefreshLoop() { @@ -461,8 +492,13 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc RealmResults<PumpStatusEvent> results = mRealm.where(PumpStatusEvent.class) .findAllSorted("eventDate", Sort.DESCENDING); if (results.size() > 0) { - startCgmService(getNextPoll(results.first()) + delay); - return; + 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; + } } } startCgmService(System.currentTimeMillis() + (delay == 0 ? 1000 : delay)); @@ -518,16 +554,32 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc notificationManager.cancel(MainActivity.USB_DISCONNECT_NOFICATION_ID); } + @Override + 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); + } + }); + } + @Override protected void onDestroy() { Log.i(TAG, "onDestroy called"); super.onDestroy(); + unregisterReceiver(usbReceiver); + unregisterReceiver(batteryReceiver); + PreferenceManager.getDefaultSharedPreferences(getBaseContext()).unregisterOnSharedPreferenceChangeListener(this); cancelDisplayRefreshLoop(); - mRealm.close(); - + if (!mRealm.isClosed()) { + mRealm.close(); + } if (!mEnableCgmService) { stopCgmService(); } @@ -588,7 +640,12 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc if (activePumpMac != 0L && (mActivePump == null || !mActivePump.isValid() || mActivePump.getPumpMac() != activePumpMac)) { if (mActivePump != null) { // remove listener on old pump - mActivePump.removeAllChangeListeners(); + mRealm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm sRealm) { + mActivePump.removeAllChangeListeners(); + } + }); mActivePump = null; } @@ -598,6 +655,11 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc .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<PumpInfo>() { long lastQueryTS = 0; @@ -611,28 +673,10 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc lastQueryTS = pump.getLastQueryTS(); - // Delete invalid or old records from Realm - // TODO - show an error message if the valid records haven't been uploaded - final RealmResults<PumpStatusEvent> results = - mRealm.where(PumpStatusEvent.class) - .equalTo("sgv", 0) - .or() - .lessThan("eventDate", 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(); - } - }); - } + RemoveOutdatedRecords(); + refreshDisplay(1000); // TODO - handle isOffline in NightscoutUploadIntentService? - refreshDisplay(); } }); } @@ -641,6 +685,26 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc 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<PumpStatusEvent> 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(); @@ -660,6 +724,7 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc } 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"; @@ -721,6 +786,15 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc } 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); + } + }); + } } public void clearMessages() { @@ -735,6 +809,8 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc private class RefreshDisplayRunnable implements Runnable { @Override 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); @@ -754,6 +830,7 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc } updateChart(mRealm.where(PumpStatusEvent.class) + .notEqualTo("sgv", 0) .greaterThan("sgvDate", new Date(System.currentTimeMillis() - 1000 * 60 * 60 * 24)) .findAllSorted("sgvDate", Sort.ASCENDING)); @@ -770,6 +847,7 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc sgvString = "\u2014"; // — } + nextRun = 60000L - (System.currentTimeMillis() - pumpStatusData.getSgvDate().getTime()) % 60000L; textViewBg.setText(sgvString); textViewBgTime.setText(DateUtils.getRelativeTimeSpanString(pumpStatusData.getSgvDate().getTime())); @@ -807,8 +885,8 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc } - // Run myself again in 60 seconds; - mUiRefreshHandler.postDelayed(this, 60000L); + // Run myself again in 60 (or less) seconds; + mUiRefreshHandler.postDelayed(this, nextRun); } private void updateChart(RealmResults<PumpStatusEvent> results) { @@ -879,7 +957,12 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc paint.setColor(Color.YELLOW); else paint.setColor(Color.RED); - canvas.drawCircle(x, y, 3.6f, paint); + float dotSize = 3.0f; + if (chartZoom == 3) dotSize = 2.0f; + else if (chartZoom == 6) dotSize = 2.0f; + else if (chartZoom == 12) dotSize = 1.65f; + else if (chartZoom == 24) dotSize = 1.25f; + canvas.drawCircle(x, y, dipToPixels(getApplicationContext(), dotSize), paint); } }); @@ -891,15 +974,30 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc } } + // TODO - chart viewport needs rework as currently using a workaround to handle updating + // set viewport to latest SGV long lastSGVTimestamp = (long) mChart.getSeries().get(0).getHighestValueX(); + + long min_x = (((lastSGVTimestamp + 150000 - (chartZoom * 60 * 60 * 1000)) / 60000) * 60000); + long max_x = lastSGVTimestamp + 90000; + if (!hasZoomedChart) { - mChart.getViewport().setMaxX(lastSGVTimestamp); - mChart.getViewport().setMinX(lastSGVTimestamp - chartZoom * 60 * 60 * 1000); + mChart.getViewport().setMinX(min_x); + mChart.getViewport().setMaxX(max_x); + } + if (entries.length > 0) { + ((PointsGraphSeries) mChart.getSeries().get(0)).resetData(entries); } + } } + private static float dipToPixels(Context context, float dipValue) { + DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dipValue, metrics); + } + /** * has to be done in MainActivity thread */ @@ -938,7 +1036,8 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc if (mEnableCgmService) { clearDisconnectionNotification(); } - + dataStore.clearAllCommsErrors(); + sendStatus(MedtronicCnlIntentService.ICON_INFO + "Contour Next Link plugged in."); if (hasUsbPermission()) { // Give the USB a little time to warm up first startCgmServiceDelayed(MedtronicCnlIntentService.USB_WARMUP_TIME_MS); @@ -950,6 +1049,7 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc Log.d(TAG, "USB unplugged"); if (mEnableCgmService) { showDisconnectionNotification("USB Error", "Contour Next Link unplugged."); + sendStatus(MedtronicCnlIntentService.ICON_WARN + "USB error. Contour Next Link unplugged."); } } else if (MedtronicCnlIntentService.Constants.ACTION_NO_USB_PERMISSION.equals(action)) { Log.d(TAG, "No permission to read the USB device."); diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicResponseMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicResponseMessage.java index 47c33dcf6a980e1a93dff4fb3ebefc40ecae3767..83efbe0ef2da5b2ae6f80fd8a21bda4e38392d9b 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicResponseMessage.java +++ b/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicResponseMessage.java @@ -40,10 +40,18 @@ public class MedtronicResponseMessage extends ContourNextLinkResponseMessage { // Replace the encrypted bytes by their decrypted equivalent (same block size) byte encryptedPayloadSize = payload[56]; + if (encryptedPayloadSize == 0) { + throw new EncryptionException( "Could not decrypt Medtronic Message (encryptedPayloadSize == 0)" ); + } + ByteBuffer encryptedPayload = ByteBuffer.allocate(encryptedPayloadSize); encryptedPayload.put(payload, 57, encryptedPayloadSize); byte[] decryptedPayload = decrypt(pumpSession.getKey(), pumpSession.getIV(), encryptedPayload.array()); + if (decryptedPayload == null) { + throw new EncryptionException( "Could not decrypt Medtronic Message (decryptedPayload == null)" ); + } + // Now that we have the decrypted payload, rewind the mPayload, and overwrite the bytes // TODO - because this messes up the existing CCITT, do we want to have a separate buffer for the decrypted payload? // Should be fine provided we check the CCITT first... diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/PumpStatusRequestMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/PumpStatusRequestMessage.java index 6fbe8768dadc0ece6003a59b3cdad7dcc88d32a6..9e98c309fdb0310012e3d640e4ce717b64b4e346 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/message/PumpStatusRequestMessage.java +++ b/app/src/main/java/info/nightscout/android/medtronic/message/PumpStatusRequestMessage.java @@ -42,6 +42,10 @@ public class PumpStatusRequestMessage extends MedtronicSendMessageRequestMessage } // Read the 0x80 byte[] payload = readMessage(mDevice); + // if pump sends an unexpected response get the next response as pump can resend or send out of sequence and this avoids comms errors + if (payload.length < 0x9C) { + payload = readMessage(mDevice); + } // clear unexpected incoming messages clearMessage(mDevice); 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 6a70ee7ed1b6d3248102202e43f49d88e26bca86..5778359c0995759e19d9856d5cc20a020a40f775 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 @@ -191,12 +191,18 @@ public class PumpStatusResponseMessage extends MedtronicSendMessageResponseMessa pumpRecord.setLowSuspendActive(lowSuspendActive); // Recent Bolus Wizard BGL - pumpRecord.setRecentBolusWizard(recentBolusWizard); - // there is a BolusWizard usage & the IOB increased - if (activeInsulin > DataStore.getInstance().getLastPumpStatus().getActiveInsulin()) { + 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); } } diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/PumpTimeRequestMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/PumpTimeRequestMessage.java index 7332a48e0c88c609cd9f2db4a7687b6f6c6195d3..56a53fba524f6c1374f40689017897b48f61192c 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/message/PumpTimeRequestMessage.java +++ b/app/src/main/java/info/nightscout/android/medtronic/message/PumpTimeRequestMessage.java @@ -37,6 +37,10 @@ public class PumpTimeRequestMessage extends MedtronicSendMessageRequestMessage<P } // Read the 0x80 byte[] payload = readMessage(mDevice); + // if pump sends an unexpected response get the next response as pump can resend or send out of sequence and this avoids comms errors + if (payload.length < 0x49) { + payload = readMessage(mDevice); + } // Pump sends additional 0x80 message when not using EHSM, lets clear this and any unexpected incoming messages clearMessage(mDevice); diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/RequestLinkKeyRequestMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/RequestLinkKeyRequestMessage.java index 1814fe87fa8df4322fdeead228477434986ca466..bf1a7ea9a35fd995d7ba49e7beb31ad4a3996100 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/message/RequestLinkKeyRequestMessage.java +++ b/app/src/main/java/info/nightscout/android/medtronic/message/RequestLinkKeyRequestMessage.java @@ -3,6 +3,7 @@ package info.nightscout.android.medtronic.message; import info.nightscout.android.medtronic.MedtronicCnlSession; import info.nightscout.android.medtronic.exception.ChecksumException; import info.nightscout.android.medtronic.exception.EncryptionException; +import info.nightscout.android.medtronic.exception.UnexpectedMessageException; /** * Created by volker on 10.12.2016. @@ -14,7 +15,7 @@ public class RequestLinkKeyRequestMessage extends ContourNextLinkBinaryRequestMe } @Override - protected RequestLinkKeyResponseMessage getResponse(byte[] payload) throws ChecksumException, EncryptionException { + protected RequestLinkKeyResponseMessage getResponse(byte[] payload) throws ChecksumException, EncryptionException, UnexpectedMessageException { return new RequestLinkKeyResponseMessage(mPumpSession, payload); } } diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/RequestLinkKeyResponseMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/RequestLinkKeyResponseMessage.java index e5bb5ce76b280c6d8dd3a8508653b289feac251f..fceea5a4a0898797e56d7d342389763d64b95fdb 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/message/RequestLinkKeyResponseMessage.java +++ b/app/src/main/java/info/nightscout/android/medtronic/message/RequestLinkKeyResponseMessage.java @@ -1,22 +1,33 @@ package info.nightscout.android.medtronic.message; +import android.util.Log; + import java.nio.ByteBuffer; import java.nio.ByteOrder; import info.nightscout.android.medtronic.MedtronicCnlSession; import info.nightscout.android.medtronic.exception.ChecksumException; import info.nightscout.android.medtronic.exception.EncryptionException; +import info.nightscout.android.medtronic.exception.UnexpectedMessageException; /** * Created by lgoedhart on 10/05/2016. */ public class RequestLinkKeyResponseMessage extends MedtronicResponseMessage { + private static final String TAG = RequestLinkKeyResponseMessage.class.getSimpleName(); private byte[] key; - protected RequestLinkKeyResponseMessage(MedtronicCnlSession pumpSession, byte[] payload) throws EncryptionException, ChecksumException { + protected RequestLinkKeyResponseMessage(MedtronicCnlSession pumpSession, byte[] payload) throws EncryptionException, ChecksumException, UnexpectedMessageException { super(pumpSession, payload); + if (this.encode().length < (0x57 - 4)) { + // Invalid message. Don't try and parse it + // TODO - deal with this more elegantly + Log.e(TAG, "Invalid message received for requestLinkKey"); + throw new UnexpectedMessageException("Invalid message received for requestLinkKey, Contour Next Link is not paired with pump."); + } + ByteBuffer infoBuffer = ByteBuffer.allocate(55); infoBuffer.order(ByteOrder.BIG_ENDIAN); infoBuffer.put(this.encode(), 0x21, 55); diff --git a/app/src/main/java/info/nightscout/android/medtronic/service/MedtronicCnlAlarmManager.java b/app/src/main/java/info/nightscout/android/medtronic/service/MedtronicCnlAlarmManager.java index b58d702ce647bf2c1014af714954c15107661788..6b92546346bbcdc3674f54aba25813ebc9153dbe 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/service/MedtronicCnlAlarmManager.java +++ b/app/src/main/java/info/nightscout/android/medtronic/service/MedtronicCnlAlarmManager.java @@ -53,7 +53,7 @@ public class MedtronicCnlAlarmManager { cancelAlarm(); Log.d(TAG, "Alarm set to fire at " + new Date(millis)); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { alarmManager.setAlarmClock(new AlarmManager.AlarmClockInfo(millis, null), pendingIntent); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // Android 5.0.0 + 5.0.1 (e.g. Galaxy S4) has a bug. @@ -66,8 +66,11 @@ public class MedtronicCnlAlarmManager { // restarting the alarm after MedtronicCnlIntentService.POLL_PERIOD_MS from now public static void restartAlarm() { - //setAlarmAfterMillis(MainActivity.pollInterval + MedtronicCnlIntentService.POLL_GRACE_PERIOD_MS); - setAlarmAfterMillis(ConfigurationStore.getInstance().getPollInterval()); // grace already accounted for when using current intent time to set default restart + // Due to potential of some versions of android to mangle alarms and clash with polling times + // the default alarm reset is set to POLL_PERIOD_MS + 60 seconds + // It's expected to trigger between polls if alarm has not been honored with a safe margin greater then + // the around 10 minutes that some OS versions force during sleep + setAlarmAfterMillis(MedtronicCnlIntentService.POLL_PERIOD_MS + 60000L); // grace already accounted for when using current intent time to set default restart } // Cancel the alarm. 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 1ef2deaa0780b4b08c679af3365a0c1009c7cf32..bf3c333c40b8b990778f18afff76b49638c68651 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 @@ -49,6 +49,25 @@ public class MedtronicCnlIntentService extends IntentService { 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; + 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} "; + + // 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 static final String TAG = MedtronicCnlIntentService.class.getSimpleName(); private UsbHidDriver mHidDevice; @@ -104,6 +123,23 @@ public class MedtronicCnlIntentService extends IntentService { } } +/* + +Notes on Errors: + +CNL-PUMP pairing and registered devices + +CNL: paired PUMP: paired UPLOADER: registered = ok +CNL: paired PUMP: paired UPLOADER: unregistered = ok +CNL: paired PUMP: unpaired UPLOADER: registered = "Could not communicate with the pump. Is it nearby?" +CNL: paired PUMP: unpaired UPLOADER: unregistered = "Could not communicate with the pump. Is it nearby?" +CNL: unpaired PUMP: paired UPLOADER: registered = "Timeout communicating with the Contour Next Link." +CNL: unpaired PUMP: paired UPLOADER: unregistered = "Invalid message received for requestLinkKey, Contour Next Link is not paired with pump." +CNL: unpaired PUMP: unpaired UPLOADER: registered = "Timeout communicating with the Contour Next Link." +CNL: unpaired PUMP: unpaired UPLOADER: unregistered = "Invalid message received for requestLinkKey, Contour Next Link is not paired with pump." + +*/ + protected void onHandleIntent(Intent intent) { Log.d(TAG, "onHandleIntent called"); try { @@ -120,7 +156,12 @@ public class MedtronicCnlIntentService extends IntentService { // do more method extraction refactorings to make this method easier to grasp final long timePollStarted = System.currentTimeMillis(); - final long timeLastGoodSGV = dataStore.getLastPumpStatus().getSgvDate().getTime(); + + 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) { @@ -130,8 +171,8 @@ public class MedtronicCnlIntentService extends IntentService { } // avoid polling when too close to sensor-pump comms - if (((timePollExpected - timePollStarted) > 5000L) && ((timePollExpected - timePollStarted) < (POLL_GRACE_PERIOD_MS + 45000L))) { - sendStatus("Please wait: Poll due in " + ((timePollExpected - timePollStarted) / 1000L) + " seconds"); + 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); return; } @@ -140,6 +181,10 @@ public class MedtronicCnlIntentService extends IntentService { 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 @@ -176,7 +221,11 @@ public class MedtronicCnlIntentService extends IntentService { cnlReader.requestReadInfo(); - String key = info.getKey(); + // always get LinkKey on startup to handle re-paired CNL-PUMP key changes + String key = null; + if (dataStore.getCommsSuccessCount() > 0) { + key = info.getKey(); + } if (key == null) { cnlReader.requestLinkKey(); @@ -202,15 +251,25 @@ public class MedtronicCnlIntentService extends IntentService { byte radioChannel = cnlReader.negotiateChannel(activePump.getLastRadioChannel()); if (radioChannel == 0) { - sendStatus("Could not communicate with the pump. Is it nearby?"); + 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 } else if (cnlReader.getPumpSession().getRadioRSSIpercentage() < 5) { sendStatus(String.format(Locale.getDefault(), "Connected on channel %d RSSI: %d%%", (int) radioChannel, cnlReader.getPumpSession().getRadioRSSIpercentage())); - sendStatus("Warning: pump signal too weak. Is it nearby?"); + 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 } else { + dataStore.decCommsConnectThreshold(); + if (cnlReader.getPumpSession().getRadioRSSIpercentage() < 20) { + if (dataStore.getCommsSignalThreshold() < ERROR_SIGNAL_AT) dataStore.incCommsSignalThreshold(); + } else { + dataStore.decCommsSignalThreshold(); + } + dataStore.setActivePumpMac(pumpMAC); activePump.setLastRadioChannel(radioChannel); @@ -230,62 +289,81 @@ public class MedtronicCnlIntentService extends IntentService { long 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)); cnlReader.updatePumpStatus(pumpRecord); - if (pumpRecord.getSgv() != 0) { - String offsetSign = ""; - if (pumpOffset > 0) { - offsetSign = "+"; - } - sendStatus("SGV: " + MainActivity.strFormatSGV(pumpRecord.getSgv()) + " At: " + dateFormatter.format(pumpRecord.getSgvDate().getTime()) + " Pump: " + offsetSign + (pumpOffset / 1000L) + "sec"); //note: event time is currently stored with offset - - // 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("Pump sent old SGV event"); + 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); + } + + dataStore.clearUnavailableSGVCount(); // reset unavailable sgv count + + // Check that the record doesn't already exist before committing + RealmResults<PumpStatusEvent> 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())); + } + } + + } 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.clearUnavailableSGVCount(); // reset unavailable sgv count - - // Check that the record doesn't already exist before committing - RealmResults<PumpStatusEvent> 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) { - activePump.getPumpHistory().add(pumpRecord); - dataStore.setLastPumpStatus(pumpRecord); - } - - } else { - sendStatus("SGV: unavailable from pump"); - dataStore.incUnavailableSGVCount(); // poll clash detection - } - realm.commitTransaction(); // Tell the Main Activity we have new data sendMessage(Constants.ACTION_UPDATE_PUMP); + dataStore.incCommsSuccessCount(); + dataStore.clearCommsErrorCount(); } } catch (UnexpectedMessageException e) { - Log.e(TAG, "Unexpected Message", e); - sendStatus("Communication Error: " + e.getMessage()); + dataStore.incCommsErrorCount(); 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(); + 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("Timeout communicating with the Contour Next Link."); - pollInterval = 60000L; // retry once during this poll period, this allows for transient radio noise + sendStatus(ICON_WARN + "Timeout communicating with the Contour Next Link / Pump."); } catch (NoSuchAlgorithmException e) { Log.e(TAG, "Could not determine CNL HMAC", e); - sendStatus("Error connecting to Contour Next Link: Hashing error."); + sendStatus(ICON_WARN + "Error connecting to Contour Next Link: Hashing error."); } finally { try { cnlReader.closeConnection(); @@ -296,20 +374,25 @@ public class MedtronicCnlIntentService extends IntentService { } } catch (IOException e) { + dataStore.incCommsErrorCount(); Log.e(TAG, "Error connecting to Contour Next Link.", e); - sendStatus("Error connecting to Contour Next Link."); + sendStatus(ICON_WARN + "Error connecting to Contour Next Link."); } catch (ChecksumException e) { + dataStore.incCommsErrorCount(); Log.e(TAG, "Checksum error getting message from the Contour Next Link.", e); - sendStatus("Checksum error getting message from the Contour Next Link."); + sendStatus(ICON_WARN + "Checksum error getting message from the Contour Next Link."); } catch (EncryptionException e) { + dataStore.incCommsErrorCount(); Log.e(TAG, "Error decrypting messages from Contour Next Link.", e); - sendStatus("Error decrypting messages from Contour Next Link."); + sendStatus(ICON_WARN + "Error decrypting messages from Contour Next Link."); } catch (TimeoutException e) { + dataStore.incCommsErrorCount(); Log.e(TAG, "Timeout communicating with the Contour Next Link.", e); - sendStatus("Timeout communicating with the Contour Next Link."); + sendStatus(ICON_WARN + "Timeout communicating with the Contour Next Link."); } catch (UnexpectedMessageException e) { + dataStore.incCommsErrorCount(); Log.e(TAG, "Could not close connection.", e); - sendStatus("Could not close connection: " + e.getMessage()); + sendStatus(ICON_WARN + "Could not close connection: " + e.getMessage()); } finally { if (!realm.isClosed()) { if (realm.isInTransaction()) { @@ -321,21 +404,46 @@ public class MedtronicCnlIntentService extends IntentService { uploadPollResults(); scheduleNextPoll(timePollStarted, timeLastGoodSGV, pollInterval); + + // 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."); + } + 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)."); + } + 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 (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."); + } + } } finally { MedtronicCnlAlarmReceiver.completeWakefulIntent(intent); } } + // 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 * ((System.currentTimeMillis() - (timeLastGoodSGV + POLL_GRACE_PERIOD_MS)) / POLL_PERIOD_MS)); + lastActualPollTime = timeLastGoodSGV + POLL_GRACE_PERIOD_MS + (POLL_PERIOD_MS * ((now - timeLastGoodSGV + POLL_GRACE_PERIOD_MS) / POLL_PERIOD_MS)); } long nextActualPollTime = lastActualPollTime + POLL_PERIOD_MS; long nextRequestedPollTime = lastActualPollTime + pollInterval; - if ((nextRequestedPollTime - System.currentTimeMillis()) < 10000L) { + // check if request is really needed + if (nextRequestedPollTime - now < 10000L) { nextRequestedPollTime = nextActualPollTime; } // extended unavailable SGV may be due to clash with the current polling time @@ -344,10 +452,17 @@ public class MedtronicCnlIntentService extends IntentService { 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("Warning: No SGV available from pump for " + dataStore.getUnavailableSGVCount() + " attempts"); - nextRequestedPollTime += ((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(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; } } + // 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)); } @@ -357,14 +472,14 @@ public class MedtronicCnlIntentService extends IntentService { */ private boolean openUsbDevice() { if (!hasUsbHostFeature()) { - sendStatus("It appears that this device doesn't support USB OTG."); + sendStatus(ICON_WARN + "It appears that this device doesn't support USB OTG."); Log.e(TAG, "Device does not support USB OTG"); return false; } UsbDevice cnlStick = UsbHidDriver.getUsbDevice(mUsbManager, USB_VID, USB_PID); if (cnlStick == null) { - sendStatus("USB connection error. Is the Contour Next Link plugged in?"); + sendStatus(ICON_WARN + "USB connection error. Is the Contour Next Link plugged in?"); Log.w(TAG, "USB connection error. Is the CNL plugged in?"); return false; } @@ -378,7 +493,7 @@ public class MedtronicCnlIntentService extends IntentService { try { mHidDevice.open(); } catch (Exception e) { - sendStatus("Unable to open USB device"); + sendStatus(ICON_WARN + "Unable to open USB device"); Log.e(TAG, "Unable to open serial device", e); return false; } 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 39fe120d971acda594d91203e040fd2125c2a3de..86bfa944f893a0fa8925c7c0da7d6c7a00a2b73c 100644 --- a/app/src/main/java/info/nightscout/android/utils/DataStore.java +++ b/app/src/main/java/info/nightscout/android/utils/DataStore.java @@ -1,6 +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; @@ -16,7 +18,13 @@ public class DataStore { private PumpStatusEvent lastPumpStatus; private int uploaderBatteryLevel = 0; private int unavailableSGVCount = 0; + private int lastBolusWizardBGL = 0; 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 DataStore() {} @@ -26,7 +34,8 @@ public class DataStore { // set some initial dummy values PumpStatusEvent dummyStatus = new PumpStatusEvent(); - dummyStatus.setSgvDate(new Date()); + dummyStatus.setSgvDate(DateUtils.addDays(new Date(), -1)); + dummyStatus.setSgv(0); // bypass setter to avoid dealing with a real Realm object instance.lastPumpStatus = dummyStatus; @@ -65,10 +74,19 @@ public class DataStore { public void clearUnavailableSGVCount() { this.unavailableSGVCount = 0; } + public void setUnavailableSGVCount(int unavailableSGVCount) { this.unavailableSGVCount = unavailableSGVCount; } + public int getLastBolusWizardBGL() { + return lastBolusWizardBGL; + } + + public void setLastBolusWizardBGL(int lastBolusWizardBGL) { + this.lastBolusWizardBGL = lastBolusWizardBGL; + } + public long getActivePumpMac() { return activePumpMac; } @@ -76,4 +94,81 @@ public class DataStore { public void setActivePumpMac(long activePumpMac) { this.activePumpMac = activePumpMac; } + + public int getCommsErrorCount() { + return commsErrorCount; + } + + public int incCommsErrorCount() { return commsErrorCount++; } + + public int decCommsErrorThreshold() { + if (commsErrorCount > 0) commsErrorCount--; + return commsErrorCount;} + + public void clearCommsErrorCount() { + this.commsErrorCount = 0; + } + + public int getCommsSuccessCount() { + return commsSuccessCount; + } + + public int incCommsSuccessCount() { return commsSuccessCount++; } + + public int decCommsSuccessCount() { + if (commsSuccessCount > 0) commsSuccessCount--; + return commsSuccessCount;} + + public void clearCommsSuccessCount() { + this.commsSuccessCount = 0; + } + + public int getCommsConnectThreshold() { + return commsConnectThreshold; + } + + public int incCommsConnectThreshold() { return commsConnectThreshold++; } + + public int decCommsConnectThreshold() { + if (commsConnectThreshold > 0) commsConnectThreshold--; + return commsConnectThreshold;} + + public void clearCommsConnectThreshold() { + this.commsConnectThreshold = 0; + } + + public int getCommsSignalThreshold() { + return commsSignalThreshold; + } + + public int incCommsSignalThreshold() { return commsSignalThreshold++; } + + public int decCommsSignalThreshold() { + if (commsSignalThreshold > 0) commsSignalThreshold--; + return commsSignalThreshold;} + + public void clearCommsSignalThreshold() { + this.commsSignalThreshold = 0; + } + + public float getCommsUnavailableThreshold() { + return commsUnavailableThreshold; + } + + public float addCommsUnavailableThreshold(float value) { + commsUnavailableThreshold+= value; + if (commsUnavailableThreshold < 0) commsUnavailableThreshold = 0; + return commsUnavailableThreshold;} + + public void clearCommsUnavailableThreshold() { + this.commsUnavailableThreshold = 0; + } + + public void clearAllCommsErrors() { + this.commsErrorCount = 0; + this.commsSuccessCount = 0; + this.commsConnectThreshold = 0; + this.commsSignalThreshold = 0; + this.commsUnavailableThreshold = 0; + } } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 9810c1ad7339738df8fb7cde34bbd38dbee7c78a..8d2fad1e9ffb117584baa409ab72db8c361d45df 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -59,8 +59,8 @@ android:textSize="70sp" /> <LinearLayout - android:layout_width="0dp" - android:layout_height="fill_parent" + android:layout_width="wrap_content" + android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_weight="1" android:gravity="bottom|center_horizontal" @@ -135,7 +135,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - <TextView + <com.mikepenz.iconics.view.IconicsTextView android:id="@+id/textview_log" android:layout_width="fill_parent" android:layout_height="wrap_content" diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index cfb3458e897140a9308cac32f15041128630caad..8e0fb08456bf9ed24e653d8372d145c94e0ec3e9 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -3,7 +3,6 @@ <string-array name="poll_interval"> <item>5 min</item> <item>10 min</item> - <item>12 min</item> <item>15 min</item> <item>20 min</item> <item>30 min</item> @@ -14,7 +13,6 @@ <string-array name="poll_interval_millis"> <item>300000</item> <item>600000</item> - <item>720000</item> <item>900000</item> <item>1200000</item> <item>1800000</item>