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 82e3026de27326a6e6bf9a294eb1ee731697992c..75680ee06afa525abb098fc0e48cad44f539fd0d 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/MainActivity.java +++ b/app/src/main/java/info/nightscout/android/medtronic/MainActivity.java @@ -70,7 +70,6 @@ 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.MedtronicCnlAlarmReceiver; import info.nightscout.android.medtronic.service.MedtronicCnlIntentService; import info.nightscout.android.model.medtronicNg.PumpInfo; import info.nightscout.android.model.medtronicNg.PumpStatusEvent; @@ -93,7 +92,6 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc private int chartZoom = 3; private boolean hasZoomedChart = false; - private NumberFormat sgvFormatter; private boolean mEnableCgmService = true; private SharedPreferences prefs = null; @@ -104,7 +102,6 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc private Runnable mUiRefreshRunnable = new RefreshDisplayRunnable(); private Realm mRealm; private StatusMessageReceiver statusMessageReceiver = new StatusMessageReceiver(); - private MedtronicCnlAlarmReceiver medtronicCnlAlarmReceiver = new MedtronicCnlAlarmReceiver(); /** * calculate the next poll timestamp based on last svg event @@ -167,15 +164,6 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc configurationStore.setMmolxl(prefs.getBoolean("mmolxl", false)); configurationStore.setMmolxlDecimals(prefs.getBoolean("mmolDecimals", false)); - if (configurationStore.isMmolxl()) { - if (configurationStore.isMmolxlDecimals()) - sgvFormatter = new DecimalFormat("0.00"); - else - sgvFormatter = new DecimalFormat("0.0"); - } else { - sgvFormatter = new DecimalFormat("0"); - } - // Disable battery optimization to avoid missing values on 6.0+ // taken from https://github.com/NightscoutFoundation/xDrip/blob/master/app/src/main/java/com/eveningoutpost/dexdrip/Home.java#L277L298 @@ -559,14 +547,6 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc } else if (key.equals("mmolxl") || key.equals("mmolDecimals")) { configurationStore.setMmolxl(sharedPreferences.getBoolean("mmolxl", false)); configurationStore.setMmolxlDecimals(sharedPreferences.getBoolean("mmolDecimals", false)); - if (configurationStore.isMmolxl()) { - if (configurationStore.isMmolxlDecimals()) - sgvFormatter = new DecimalFormat("0.00"); - else - sgvFormatter = new DecimalFormat("0.0"); - } else { - sgvFormatter = new DecimalFormat("0"); - } refreshDisplay(); } else if (key.equals("pollInterval")) { configurationStore.setPollInterval(Long.parseLong(sharedPreferences.getString("pollInterval", @@ -944,6 +924,9 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc private class UsbReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { + // TODO move this somewhere else ... wherever it belongs + // realm might be closed ... sometimes occurs when USB is disconnected and replugged ... + if (mRealm.isClosed()) mRealm = Realm.getDefaultInstance(); String action = intent.getAction(); if (MedtronicCnlIntentService.Constants.ACTION_USB_PERMISSION.equals(action)) { boolean permissionGranted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false); 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 358cc203123e64d456f05c97cd116eb33e3af9a2..b58d702ce647bf2c1014af714954c15107661788 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 @@ -16,11 +16,10 @@ import info.nightscout.android.utils.ConfigurationStore; */ public class MedtronicCnlAlarmManager { private static final String TAG = MedtronicCnlAlarmManager.class.getSimpleName(); - private static final int ALARM_ID = 102; // Alarm id + private static final int ALARM_ID = 102; private static PendingIntent pendingIntent = null; private static AlarmManager alarmManager = null; - private static long nextAlarm = Long.MAX_VALUE; public static void setContext(Context context) { cancelAlarm(); @@ -30,11 +29,6 @@ public class MedtronicCnlAlarmManager { pendingIntent = PendingIntent.getBroadcast(context, ALARM_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT); } - // Setting the alarm in 15 seconds from now - public static void setAlarm() { - setAlarm(System.currentTimeMillis()); - } - /** * set the alarm in the future * @@ -44,7 +38,7 @@ public class MedtronicCnlAlarmManager { setAlarm(System.currentTimeMillis() + inFuture); } - // Setting the alarm to call onRecieve + // Setting the alarm to call onReceive public static void setAlarm(long millis) { if (alarmManager == null || pendingIntent == null) return; @@ -56,21 +50,14 @@ public class MedtronicCnlAlarmManager { if (millis < now) millis = now; - // only accept alarm nearer than the last one - //if (nextAlarm < millis && nextAlarm > now) { - // return; - //} - cancelAlarm(); - nextAlarm = millis; - Log.d(TAG, "Alarm set to fire at " + new Date(millis)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 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. - // Alarms are not exact. Fixed in 5.0.2 oder CM12 + // Alarms are not exact. Fixed in 5.0.2 and CM12 alarmManager.setExact(AlarmManager.RTC_WAKEUP, millis, pendingIntent); } else { alarmManager.set(AlarmManager.RTC_WAKEUP, millis, pendingIntent); 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 9b46b76a7c1261f278ce403d497c7495e08c24da..327895b86d96e6582f240f497c7c2c5765bb6532 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 @@ -57,6 +57,8 @@ public class MedtronicCnlIntentService extends IntentService { private UsbManager mUsbManager; private DataStore dataStore = DataStore.getInstance(); private ConfigurationStore configurationStore = ConfigurationStore.getInstance(); + private DateFormat dateFormatter = new SimpleDateFormat("HH:mm:ss", Locale.US); + public MedtronicCnlIntentService() { super(MedtronicCnlIntentService.class.getName()); @@ -104,159 +106,136 @@ public class MedtronicCnlIntentService extends IntentService { 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(); + final long timeLastGoodSGV = dataStore.getLastPumpStatus().getSgvDate().getTime(); + + 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; + } - long timePollStarted = System.currentTimeMillis(), - timePollExpected = timePollStarted, - timeLastGoodSGV = dataStore.getLastPumpStatus().getEventDate().getTime(); - - short pumpBatteryLevel = dataStore.getLastPumpStatus().getBatteryPercentage(); - - 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)); - } - - // 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"); - MedtronicCnlAlarmManager.setAlarm(timePollExpected); - MedtronicCnlAlarmReceiver.completeWakefulIntent(intent); - return; - } - - long pollInterval = configurationStore.getPollInterval(); - if ((pumpBatteryLevel > 0) && (pumpBatteryLevel <= 25)) { - pollInterval = configurationStore.getLowBatteryPollInterval(); - } - - if (!hasUsbHostFeature()) { - sendStatus("It appears that this device doesn't support USB OTG."); - Log.e(TAG, "Device does not support USB OTG"); - MedtronicCnlAlarmReceiver.completeWakefulIntent(intent); - // TODO - throw, don't return - return; - } - - UsbDevice cnlStick = UsbHidDriver.getUsbDevice(mUsbManager, USB_VID, USB_PID); - if (cnlStick == null) { - sendStatus("USB connection error. Is the Contour Next Link plugged in?"); - Log.w(TAG, "USB connection error. Is the CNL plugged in?"); - - // TODO - set status if offline or Nightscout not reachable - uploadToNightscout(); - MedtronicCnlAlarmReceiver.completeWakefulIntent(intent); - // TODO - throw, don't return - return; - } + // 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"); + MedtronicCnlAlarmManager.setAlarm(timePollExpected); + return; + } - if (!mUsbManager.hasPermission(UsbHidDriver.getUsbDevice(mUsbManager, USB_VID, USB_PID))) { - sendMessage(Constants.ACTION_NO_USB_PERMISSION); - MedtronicCnlAlarmReceiver.completeWakefulIntent(intent); - // TODO - throw, don't return - return; - } - mHidDevice = UsbHidDriver.acquire(mUsbManager, cnlStick); + final short pumpBatteryLevel = dataStore.getLastPumpStatus().getBatteryPercentage(); + long pollInterval = configurationStore.getPollInterval(); + if ((pumpBatteryLevel > 0) && (pumpBatteryLevel <= 25)) { + pollInterval = configurationStore.getLowBatteryPollInterval(); + } - try { - mHidDevice.open(); - } catch (Exception e) { - Log.e(TAG, "Unable to open serial device", e); - MedtronicCnlAlarmReceiver.completeWakefulIntent(intent); // TODO - throw, don't return - return; - } + if (!openUsbDevice()) + return; - DateFormat df = new SimpleDateFormat("HH:mm:ss", Locale.US); + MedtronicCnlReader cnlReader = new MedtronicCnlReader(mHidDevice); - MedtronicCnlReader cnlReader = new MedtronicCnlReader(mHidDevice); + Realm realm = Realm.getDefaultInstance(); + realm.beginTransaction(); - Realm realm = Realm.getDefaultInstance(); - realm.beginTransaction(); + try { + sendStatus("Connecting to Contour Next Link"); + Log.d(TAG, "Connecting to Contour Next Link"); + cnlReader.requestDeviceInfo(); + + // Is the device already configured? + ContourNextLinkInfo info = realm + .where(ContourNextLinkInfo.class) + .equalTo("serialNumber", cnlReader.getStickSerial()) + .findFirst(); - try { - sendStatus("Connecting to Contour Next Link"); - Log.d(TAG, "Connecting to Contour Next Link"); - cnlReader.requestDeviceInfo(); - - // Is the device already configured? - ContourNextLinkInfo info = realm - .where(ContourNextLinkInfo.class) - .equalTo("serialNumber", cnlReader.getStickSerial()) - .findFirst(); - - if (info == null) { - info = realm.createObject(ContourNextLinkInfo.class, cnlReader.getStickSerial()); - } + if (info == null) { + info = realm.createObject(ContourNextLinkInfo.class, cnlReader.getStickSerial()); + } - cnlReader.getPumpSession().setStickSerial(info.getSerialNumber()); + cnlReader.getPumpSession().setStickSerial(info.getSerialNumber()); - cnlReader.enterControlMode(); + cnlReader.enterControlMode(); - try { - cnlReader.enterPassthroughMode(); - cnlReader.openConnection(); + try { + cnlReader.enterPassthroughMode(); + cnlReader.openConnection(); - cnlReader.requestReadInfo(); + cnlReader.requestReadInfo(); - String key = info.getKey(); + String key = info.getKey(); - if (key == null) { - cnlReader.requestLinkKey(); + if (key == null) { + cnlReader.requestLinkKey(); - info.setKey(MessageUtils.byteArrayToHexString(cnlReader.getPumpSession().getKey())); - key = info.getKey(); - } + info.setKey(MessageUtils.byteArrayToHexString(cnlReader.getPumpSession().getKey())); + key = info.getKey(); + } - cnlReader.getPumpSession().setKey(MessageUtils.hexStringToByteArray(key)); + cnlReader.getPumpSession().setKey(MessageUtils.hexStringToByteArray(key)); - long pumpMAC = cnlReader.getPumpSession().getPumpMAC(); - Log.i(TAG, "PumpInfo MAC: " + (pumpMAC & 0xffffff)); - PumpInfo activePump = realm - .where(PumpInfo.class) - .equalTo("pumpMac", pumpMAC) - .findFirst(); + long pumpMAC = cnlReader.getPumpSession().getPumpMAC(); + Log.i(TAG, "PumpInfo MAC: " + (pumpMAC & 0xffffff)); + PumpInfo activePump = realm + .where(PumpInfo.class) + .equalTo("pumpMac", pumpMAC) + .findFirst(); - if (activePump == null) { - activePump = realm.createObject(PumpInfo.class, pumpMAC); - } + if (activePump == null) { + activePump = realm.createObject(PumpInfo.class, pumpMAC); + } - activePump.updateLastQueryTS(); + activePump.updateLastQueryTS(); - byte radioChannel = cnlReader.negotiateChannel(activePump.getLastRadioChannel()); - if (radioChannel == 0) { - sendStatus("Could not communicate with the pump. Is it nearby?"); - Log.i(TAG, "Could not communicate with the pump. Is it nearby?"); - pollInterval = configurationStore.getPollInterval() / (configurationStore.isReducePollOnPumpAway() ? 2L : 1L); // reduce polling interval to half until pump is available - } else { - dataStore.setActivePumpMac(pumpMAC); + byte radioChannel = cnlReader.negotiateChannel(activePump.getLastRadioChannel()); + if (radioChannel == 0) { + sendStatus("Could not communicate with the pump. Is it nearby?"); + Log.i(TAG, "Could not communicate with the pump. Is it nearby?"); + pollInterval = configurationStore.getPollInterval() / (configurationStore.isReducePollOnPumpAway() ? 2L : 1L); // reduce polling interval to half until pump is available + } else { + dataStore.setActivePumpMac(pumpMAC); - activePump.setLastRadioChannel(radioChannel); - sendStatus(String.format(Locale.getDefault(), "Connected on channel %d RSSI: %d%%", (int) radioChannel, cnlReader.getPumpSession().getRadioRSSIpercentage())); - Log.d(TAG, String.format("Connected to Contour Next Link on channel %d.", (int) radioChannel)); + activePump.setLastRadioChannel(radioChannel); + sendStatus(String.format(Locale.getDefault(), "Connected on channel %d RSSI: %d%%", (int) radioChannel, cnlReader.getPumpSession().getRadioRSSIpercentage())); + Log.d(TAG, String.format("Connected to Contour Next Link on channel %d.", (int) radioChannel)); - // read pump status - PumpStatusEvent pumpRecord = realm.createObject(PumpStatusEvent.class); + // read pump status + PumpStatusEvent pumpRecord = realm.createObject(PumpStatusEvent.class); - String deviceName = String.format("medtronic-600://%s", cnlReader.getStickSerial()); - activePump.setDeviceName(deviceName); + String deviceName = String.format("medtronic-600://%s", cnlReader.getStickSerial()); + activePump.setDeviceName(deviceName); - // TODO - this should not be necessary. We should reverse lookup the device name from PumpInfo - pumpRecord.setDeviceName(deviceName); + // TODO - this should not be necessary. We should reverse lookup the device name from PumpInfo + pumpRecord.setDeviceName(deviceName); - long pumpTime = cnlReader.getPumpTime().getTime(); - long pumpOffset = pumpTime - System.currentTimeMillis(); - Log.d(TAG, "Time offset between pump and device: " + pumpOffset + " millis."); + long pumpTime = cnlReader.getPumpTime().getTime(); + long pumpOffset = pumpTime - System.currentTimeMillis(); + Log.d(TAG, "Time offset between pump and device: " + pumpOffset + " millis."); - // TODO - send ACTION to MainActivity to show offset between pump and uploader. - pumpRecord.setPumpTimeOffset(pumpOffset); - pumpRecord.setPumpDate(new Date(pumpTime - pumpOffset)); - cnlReader.updatePumpStatus(pumpRecord); + // 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: " + df.format(pumpRecord.getEventDate().getTime()) + " Pump: " + offsetSign + (pumpOffset / 1000L) + "sec"); //note: event time is currently stored with offset + 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 (pumpRecord != null && @@ -267,15 +246,12 @@ public class MedtronicCnlIntentService extends IntentService { sendStatus("Pump sent old SGV event, re-polling..."); } - //MainActivity.timeLastGoodSGV = pumpRecord.getEventDate().getTime(); // track last good sgv event time - //MainActivity.pumpBattery = pumpRecord.getBatteryPercentage(); // track pump battery - timeLastGoodSGV = pumpRecord.getEventDate().getTime(); dataStore.clearUnavailableSGVCount(); // reset unavailable sgv count // Check that the record doesn't already exist before committing RealmResults<PumpStatusEvent> checkExistingRecords = activePump.getPumpHistory() .where() - .equalTo("eventDate", pumpRecord.getEventDate()) // >>>>>>> check as event date may not = exact pump event date due to it being stored with offset added this could lead to dup events due to slight variability in time offset + .equalTo("sgvDate", pumpRecord.getSgvDate()) .equalTo("sgv", pumpRecord.getSgv()) .findAll(); @@ -290,83 +266,120 @@ public class MedtronicCnlIntentService extends IntentService { dataStore.incUnavailableSGVCount(); // poll clash detection } - realm.commitTransaction(); - // Tell the Main Activity we have new data - sendMessage(Constants.ACTION_UPDATE_PUMP); - } + realm.commitTransaction(); + // Tell the Main Activity we have new data + sendMessage(Constants.ACTION_UPDATE_PUMP); + } - } catch (UnexpectedMessageException e) { - Log.e(TAG, "Unexpected Message", e); - sendStatus("Communication Error: " + e.getMessage()); - pollInterval = configurationStore.getPollInterval() / (configurationStore.isReducePollOnPumpAway() ? 2L : 1L); + } catch (UnexpectedMessageException e) { + Log.e(TAG, "Unexpected Message", e); + sendStatus("Communication Error: " + e.getMessage()); + pollInterval = configurationStore.getPollInterval() / (configurationStore.isReducePollOnPumpAway() ? 2L : 1L); + } catch (TimeoutException e) { + Log.e(TAG, "Timeout communicating with the Contour Next Link.", e); + sendStatus("Timeout communicating with the Contour Next Link."); + pollInterval = configurationStore.getPollInterval() / (configurationStore.isReducePollOnPumpAway() ? 2L : 1L); + } catch (NoSuchAlgorithmException e) { + Log.e(TAG, "Could not determine CNL HMAC", e); + sendStatus("Error connecting to Contour Next Link: Hashing error."); + } finally { + try { + cnlReader.closeConnection(); + cnlReader.endPassthroughMode(); + cnlReader.endControlMode(); + } catch (NoSuchAlgorithmException e) { + } + + } + } catch (IOException e) { + Log.e(TAG, "Error connecting to Contour Next Link.", e); + sendStatus("Error connecting to Contour Next Link."); + } catch (ChecksumException e) { + Log.e(TAG, "Checksum error getting message from the Contour Next Link.", e); + sendStatus("Checksum error getting message from the Contour Next Link."); + } catch (EncryptionException e) { + Log.e(TAG, "Error decrypting messages from Contour Next Link.", e); + sendStatus("Error decrypting messages from Contour Next Link."); } catch (TimeoutException e) { Log.e(TAG, "Timeout communicating with the Contour Next Link.", e); sendStatus("Timeout communicating with the Contour Next Link."); - pollInterval = configurationStore.getPollInterval() / (configurationStore.isReducePollOnPumpAway() ? 2L : 1L); - } catch (NoSuchAlgorithmException e) { - Log.e(TAG, "Could not determine CNL HMAC", e); - sendStatus("Error connecting to Contour Next Link: Hashing error."); + } catch (UnexpectedMessageException e) { + Log.e(TAG, "Could not close connection.", e); + sendStatus("Could not close connection: " + e.getMessage()); } finally { - try { - cnlReader.closeConnection(); - cnlReader.endPassthroughMode(); - cnlReader.endControlMode(); - } catch (NoSuchAlgorithmException e) { + 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(); } + uploadPollResults(); + scheduleNextPoll(timePollStarted, timeLastGoodSGV, pollInterval); } - } catch (IOException e) { - Log.e(TAG, "Error connecting to Contour Next Link.", e); - sendStatus("Error connecting to Contour Next Link."); - } catch (ChecksumException e) { - Log.e(TAG, "Checksum error getting message from the Contour Next Link.", e); - sendStatus("Checksum error getting message from the Contour Next Link."); - } catch (EncryptionException e) { - Log.e(TAG, "Error decrypting messages from Contour Next Link.", e); - sendStatus("Error decrypting messages from Contour Next Link."); - } catch (TimeoutException e) { - Log.e(TAG, "Timeout communicating with the Contour Next Link.", e); - sendStatus("Timeout communicating with the Contour Next Link."); - } catch (UnexpectedMessageException e) { - Log.e(TAG, "Could not close connection.", e); - sendStatus("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(); - } - // TODO - set status if offline or Nightscout not reachable - sendToXDrip(); - uploadToNightscout(); - - // smart polling and pump-sensor poll clash detection - 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)); - } - long nextActualPollTime = lastActualPollTime + POLL_PERIOD_MS; - long nextRequestedPollTime = lastActualPollTime + pollInterval; - if ((nextRequestedPollTime - System.currentTimeMillis()) < 10000L) { - 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("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) - } + MedtronicCnlAlarmReceiver.completeWakefulIntent(intent); + } + } + + private void scheduleNextPoll(long timePollStarted, long timeLastGoodSGV, long pollInterval) { + // smart polling and pump-sensor poll clash detection + 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)); + } + long nextActualPollTime = lastActualPollTime + POLL_PERIOD_MS; + long nextRequestedPollTime = lastActualPollTime + pollInterval; + if ((nextRequestedPollTime - System.currentTimeMillis()) < 10000L) { + 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("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) } - MedtronicCnlAlarmManager.setAlarm(nextRequestedPollTime); - sendStatus("Next poll due at: " + df.format(nextRequestedPollTime)); + } + MedtronicCnlAlarmManager.setAlarm(nextRequestedPollTime); + sendStatus("Next poll due at: " + dateFormatter.format(nextRequestedPollTime)); + } - MedtronicCnlAlarmReceiver.completeWakefulIntent(intent); + /** + * @return if device acquisition was successful + */ + private boolean openUsbDevice() { + if (!hasUsbHostFeature()) { + sendStatus("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?"); + Log.w(TAG, "USB connection error. Is the CNL plugged in?"); + return false; + } + + if (!mUsbManager.hasPermission(UsbHidDriver.getUsbDevice(mUsbManager, USB_VID, USB_PID))) { + sendMessage(Constants.ACTION_NO_USB_PERMISSION); + return false; + } + mHidDevice = UsbHidDriver.acquire(mUsbManager, cnlStick); + + try { + mHidDevice.open(); + } catch (Exception e) { + sendStatus("Unable to open USB device"); + Log.e(TAG, "Unable to open serial device", e); + return false; + } + + return true; } // reliable wake alarm manager wake up for all android versions @@ -380,6 +393,11 @@ public class MedtronicCnlIntentService extends IntentService { alarm.set(AlarmManager.RTC_WAKEUP, wakeTime, pendingIntent); } + private void uploadPollResults() { + sendToXDrip(); + uploadToNightscout(); + } + private void sendToXDrip() { final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); if (prefs.getBoolean(getString(R.string.preference_enable_xdrip_plus), false)) { @@ -392,6 +410,7 @@ public class MedtronicCnlIntentService extends IntentService { } private void uploadToNightscout() { + // TODO - set status if offline or Nightscout not reachable Intent receiverIntent = new Intent(this, NightscoutUploadReceiver.class); final long timestamp = System.currentTimeMillis() + 1000L; final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, (int) timestamp, receiverIntent, PendingIntent.FLAG_ONE_SHOT); 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 ce79ab7900ef741ecb3c8f35550eebd019f34d69..1f40ab015abd146de2c596efb414ad798df58594 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 @@ -71,6 +71,8 @@ public class EntriesSerializer implements JsonSerializer<PumpStatusEvent> { } } + // TODO currentnly unused, see info.nightscout.android.xdrip_plus.XDripPlusUploadIntentService.addSgvEntry() + // TODO also, proper method name @Override public JsonElement serialize(PumpStatusEvent src, Type typeOfSrc, JsonSerializationContext context) { final JsonObject jsonObject = new JsonObject(); @@ -78,8 +80,8 @@ public class EntriesSerializer implements JsonSerializer<PumpStatusEvent> { jsonObject.addProperty("direction", getDirectionString(src.getCgmTrend())); jsonObject.addProperty("device", src.getDeviceName()); jsonObject.addProperty("type", "sgv"); - jsonObject.addProperty("date", src.getEventDate().getTime()); - jsonObject.addProperty("dateString", String.valueOf(src.getEventDate())); + jsonObject.addProperty("date", src.getSgvDate().getTime()); + jsonObject.addProperty("dateString", String.valueOf(src.getSgvDate())); 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 fbd2a02011f40323c64f1b6cf977c59869555a3b..39fe120d971acda594d91203e040fd2125c2a3de 100644 --- a/app/src/main/java/info/nightscout/android/utils/DataStore.java +++ b/app/src/main/java/info/nightscout/android/utils/DataStore.java @@ -26,6 +26,7 @@ public class DataStore { // set some initial dummy values PumpStatusEvent dummyStatus = new PumpStatusEvent(); + dummyStatus.setSgvDate(new Date()); // bypass setter to avoid dealing with a real Realm object instance.lastPumpStatus = dummyStatus; 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 20a1183a83ecbae9432f0c048d5088a29f9ba8d5..e06a88b8cf047dde4d5a812b58198e69bd508573 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 @@ -145,8 +145,8 @@ public class XDripPlusUploadIntentService extends IntentService { json.put("direction", EntriesSerializer.getDirectionString(pumpRecord.getCgmTrend())); json.put("device", pumpRecord.getDeviceName()); json.put("type", "sgv"); - json.put("date", pumpRecord.getEventDate().getTime()); - json.put("dateString", pumpRecord.getEventDate()); + json.put("date", pumpRecord.getSgvDate().getTime()); + json.put("dateString", pumpRecord.getSgvDate()); entriesArray.put(json); }