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 9c632ee91d22a1c65128f9215a2e42ae3eb1c11c..f73e3ab923c5960483afd1e14d839d8612435ec8 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/MainActivity.java +++ b/app/src/main/java/info/nightscout/android/medtronic/MainActivity.java @@ -15,6 +15,7 @@ import android.hardware.usb.UsbManager; import android.os.BatteryManager; import android.os.Bundle; import android.os.Handler; +import android.os.SystemClock; import android.preference.PreferenceManager; import android.support.v4.app.TaskStackBuilder; import android.support.v4.content.LocalBroadcastManager; @@ -32,7 +33,8 @@ import android.view.View; import android.widget.TextView; import android.widget.TextView.BufferType; -import com.github.mikephil.charting.data.realm.implementation.RealmLineData; +import com.github.mikephil.charting.charts.LineChart; +import com.github.mikephil.charting.data.LineData; import com.github.mikephil.charting.data.realm.implementation.RealmLineDataSet; import com.github.mikephil.charting.interfaces.datasets.ILineDataSet; import com.github.mikephil.charting.utils.ColorTemplate; @@ -52,9 +54,11 @@ 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.MedtronicCnlAlarmReceiver; import info.nightscout.android.medtronic.service.MedtronicCnlIntentService; import info.nightscout.android.model.CgmStatusEvent; import info.nightscout.android.model.medtronicNg.ContourNextLinkInfo; +import info.nightscout.android.model.medtronicNg.PumpInfo; import info.nightscout.android.settings.SettingsActivity; import info.nightscout.android.upload.MedtronicNG.PumpStatusRecord; import info.nightscout.android.upload.nightscout.NightscoutUploadIntentService; @@ -63,28 +67,32 @@ import io.realm.RealmResults; import io.realm.Sort; import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper; -/* Main activity for the MainActivity program */ public class MainActivity extends AppCompatActivity implements OnSharedPreferenceChangeListener, OnEulaAgreedTo { private static final String TAG = MainActivity.class.getSimpleName(); public static int batLevel = 0; public static PumpStatusRecord pumpStatusRecord = new PumpStatusRecord(); + private static long activePumpMac; boolean mEnableCgmService = true; SharedPreferences prefs = null; + private PumpInfo mActivePump; private TextView mTextViewLog; // This will eventually move to a status page. - private Intent mCnlIntentService; + private LineChart mChart; private Intent mNightscoutUploadService; private Handler mUiRefreshHandler = new Handler(); private Runnable mUiRefreshRunnable = new RefreshDisplayRunnable(); private Realm mRealm; + public static void setActivePumpMac(long pumpMac) { + activePumpMac = pumpMac; + } + @Override public void onCreate(Bundle savedInstanceState) { Log.i(TAG, "onCreate called"); super.onCreate(savedInstanceState); mRealm = Realm.getDefaultInstance(); - mCnlIntentService = new Intent(this, MedtronicCnlIntentService.class); mNightscoutUploadService = new Intent(this, NightscoutUploadIntentService.class); setContentView(R.layout.activity_main); @@ -186,7 +194,7 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc } else if (drawerItem.equals(itemGetNow)) { startCgmService(); } else if (drawerItem.equals(itemClearLog)) { - mTextViewLog.setText("", BufferType.EDITABLE); + clearLogText(); } return false; @@ -195,6 +203,7 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc .build(); mTextViewLog = (TextView) findViewById(R.id.textview_log); + mChart = (LineChart) findViewById(R.id.chart); } @Override @@ -276,6 +285,10 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc startDisplayRefreshLoop(); } + private void clearLogText() { + mTextViewLog.setText("", BufferType.EDITABLE); + } + private void startDisplayRefreshLoop() { mUiRefreshHandler.post(mUiRefreshRunnable); } @@ -288,35 +301,26 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc startCgmService(0L); } - private void startCgmService(long runAtTime) { + private void startCgmService(long initialPoll) { Log.i(TAG, "startCgmService called"); if (!mEnableCgmService) { return; } - if (runAtTime == 0L) { - startService(mCnlIntentService); - } else { - AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - PendingIntent pending = PendingIntent.getService(this, 0, mCnlIntentService, 0); - - alarmManager.set(AlarmManager.RTC_WAKEUP, runAtTime, pending); + if (initialPoll == 0) { + initialPoll = SystemClock.currentThreadTimeMillis(); } - } - - private void startCgmServicePolling(long initialPoll) { - Log.i(TAG, "startCgmServicePolling called"); - if (!mEnableCgmService) { - return; - } + Log.d(TAG, "startCgmService set to fire at " + new Date(initialPoll)); + clearLogText(); // Cancel any existing polling. stopCgmService(); AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - PendingIntent pending = PendingIntent.getService(this, 0, mCnlIntentService, 0); + Intent receiverIntent = new Intent(this, MedtronicCnlAlarmReceiver.class); + PendingIntent pending = PendingIntent.getBroadcast(this, 0, receiverIntent, 0); alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, initialPoll, MedtronicCnlIntentService.POLL_PERIOD_MS, pending); @@ -330,7 +334,8 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc Log.i(TAG, "stopCgmService called"); AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - PendingIntent pending = PendingIntent.getService(this, 0, mCnlIntentService, 0); + Intent receiverIntent = new Intent(this, MedtronicCnlAlarmReceiver.class); + PendingIntent pending = PendingIntent.getBroadcast(this, 0, receiverIntent, 0); alarmManager.cancel(pending); } @@ -369,15 +374,16 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc @Override protected void onDestroy() { Log.i(TAG, "onDestroy called"); + super.onDestroy(); + PreferenceManager.getDefaultSharedPreferences(getBaseContext()).unregisterOnSharedPreferenceChangeListener(this); cancelDisplayRefreshLoop(); + mRealm.close(); + if (!mEnableCgmService) { stopCgmService(); } - - mRealm.close(); - super.onDestroy(); } @Override @@ -439,6 +445,23 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc } } + private PumpInfo getActivePump() { + if (activePumpMac != 0L && (mActivePump == null || !mActivePump.isValid() || mActivePump.getPumpMac() != activePumpMac)) { + mActivePump = null; + + PumpInfo pump = mRealm + .where(PumpInfo.class) + .equalTo("pumpMac", MainActivity.activePumpMac) + .findFirst(); + + if (pump != null & pump.isValid()) { + mActivePump = pump; + } + } + + return mActivePump; + } + private class StatusMessageReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { @@ -463,7 +486,6 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc } TextView textViewTrend = (TextView) findViewById(R.id.textview_trend); TextView textViewIOB = (TextView) findViewById(R.id.textview_iob); - //LineChart chart = (LineChart) findViewById(R.id.chart); // Get the most recently written CGM record. RealmResults<CgmStatusEvent> results = @@ -505,17 +527,28 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc textViewTrend.setText(Html.fromHtml(renderTrendHtml(cgmRecord.getTrend()))); textViewIOB.setText(String.format(Locale.getDefault(), "%.2f", pumpStatusRecord.activeInsulin)); + /* + // Open Realm because we're in a different thread + Realm realm = Realm.getDefaultInstance(); + if (MainActivity.mActivePump != null && MainActivity.mActivePump.isValid()) { + PumpInfo pump = MainActivity.mActivePump; + long pumpMac = pump.getPumpMac(); + CgmStatusEvent cgmData = MainActivity.mActivePump.getCgmHistory().last(); + } + realm.close(); + */ + // TODO - waiting for MPAndroidCharts 3.0.0. This will fix: // Date support // Realm v1.0.0 support - // updateChart(results); + //updateChart(results); // Run myself again in 60 seconds; mUiRefreshHandler.postDelayed(this, 60000L); } private void updateChart(RealmResults<CgmStatusEvent> results) { - RealmLineDataSet<CgmStatusEvent> lineDataSet = new RealmLineDataSet<>(results, "sgv", "eventDate"); + RealmLineDataSet<CgmStatusEvent> lineDataSet = new RealmLineDataSet<>(results, "eventDate", "sgv"); lineDataSet.setDrawCircleHole(false); lineDataSet.setColor(ColorTemplate.rgb("#FF5722")); @@ -526,11 +559,11 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc ArrayList<ILineDataSet> dataSets = new ArrayList<ILineDataSet>(); dataSets.add(lineDataSet); - RealmLineData lineData = new RealmLineData(results, "eventDate", dataSets); + LineData lineData = new LineData(dataSets); // set data - //chart.setMinimumHeight(200); - //chart.setData(lineData); + mChart.setMinimumHeight(200); + mChart.setData(lineData); } } @@ -538,12 +571,31 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc @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; + } + + CgmStatusEvent cgmData = null; + + PumpInfo pump = getActivePump(); + + if (pump != null & pump.isValid()) { + long pumpMac = pump.getPumpMac(); + cgmData = pump.getCgmHistory().last(); + } + + if (cgmData != null) { + Log.d(TAG, "It's working yo"); + } + CgmStatusEvent record = mRealm.where(CgmStatusEvent.class) .findAll() .last(); long nextPoll = record.getEventDate().getTime() + MedtronicCnlIntentService.POLL_GRACE_PERIOD_MS + MedtronicCnlIntentService.POLL_PERIOD_MS; - startCgmServicePolling(nextPoll); + startCgmService(nextPoll); Log.d(TAG, "Next Poll at " + new Date(nextPoll).toString()); // Delete invalid or old records from Realm diff --git a/app/src/main/java/info/nightscout/android/medtronic/MedtronicCNLReader.java b/app/src/main/java/info/nightscout/android/medtronic/MedtronicCNLReader.java index 8b56ea37e359278c03f25173894a2bd12f9b5ea0..acd660b2421edb898df25d44174d3d30876992e5 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/MedtronicCNLReader.java +++ b/app/src/main/java/info/nightscout/android/medtronic/MedtronicCNLReader.java @@ -2,11 +2,15 @@ package info.nightscout.android.medtronic; import android.util.Log; +import org.apache.commons.lang3.ArrayUtils; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.math.BigDecimal; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.Locale; import java.util.concurrent.TimeoutException; @@ -31,16 +35,15 @@ import info.nightscout.android.medtronic.message.PumpTimeRequestMessage; import info.nightscout.android.medtronic.message.PumpTimeResponseMessage; import info.nightscout.android.medtronic.message.ReadInfoResponseMessage; import info.nightscout.android.medtronic.message.UnexpectedMessageException; -import info.nightscout.android.medtronic.service.MedtronicCnlIntentService; import info.nightscout.android.model.CgmStatusEvent; import info.nightscout.android.utils.HexDump; /** * Created by lgoedhart on 24/03/2016. */ -public class MedtronicCNLReader implements ContourNextLinkMessageHandler { +public class MedtronicCnlReader implements ContourNextLinkMessageHandler { - private static final String TAG = MedtronicCnlIntentService.class.getSimpleName(); + private static final String TAG = MedtronicCnlReader.class.getSimpleName(); private static final int USB_BLOCKSIZE = 64; private static final int READ_TIMEOUT_MS = 5000; @@ -53,10 +56,31 @@ public class MedtronicCNLReader implements ContourNextLinkMessageHandler { private String mStickSerial = null; - public MedtronicCNLReader(UsbHidDriver device) { + public MedtronicCnlReader(UsbHidDriver device) { mDevice = device; } + private static CgmStatusEvent.TREND fromMessageByte(byte messageByte) { + switch (messageByte) { + case (byte) 0x60: + return CgmStatusEvent.TREND.FLAT; + case (byte) 0xc0: + return CgmStatusEvent.TREND.DOUBLE_UP; + case (byte) 0xa0: + return CgmStatusEvent.TREND.SINGLE_UP; + case (byte) 0x80: + return CgmStatusEvent.TREND.FOURTY_FIVE_UP; + case (byte) 0x40: + return CgmStatusEvent.TREND.FOURTY_FIVE_DOWN; + case (byte) 0x20: + return CgmStatusEvent.TREND.SINGLE_DOWN; + case (byte) 0x00: + return CgmStatusEvent.TREND.DOUBLE_DOWN; + default: + return CgmStatusEvent.TREND.NOT_COMPUTABLE; + } + } + public String getStickSerial() { return mStickSerial; } @@ -70,28 +94,28 @@ public class MedtronicCNLReader implements ContourNextLinkMessageHandler { byte[] responseBuffer = new byte[USB_BLOCKSIZE]; int bytesRead; - int messageSize; + int messageSize = 0; do { bytesRead = mDevice.read(responseBuffer, READ_TIMEOUT_MS); - if (bytesRead == 0) { + if (bytesRead == -1) { throw new TimeoutException("Timeout waiting for response from pump"); + } else if (bytesRead > 0) { + // Validate the header + ByteBuffer header = ByteBuffer.allocate(3); + header.put(responseBuffer, 0, 3); + String headerString = new String(header.array()); + if (!headerString.equals(BAYER_USB_HEADER)) { + throw new IOException("Unexpected header received"); + } + messageSize = responseBuffer[3]; + responseMessage.write(responseBuffer, 4, messageSize); + } else { + Log.w(TAG, "readMessage: got a zero-sized response."); } + } while (bytesRead > 0 && messageSize == 60); - // Validate the header - ByteBuffer header = ByteBuffer.allocate(3); - header.put(responseBuffer, 0, 3); - String headerString = new String(header.array()); - if (!headerString.equals(BAYER_USB_HEADER)) { - throw new IOException("Unexpected header received"); - } - messageSize = responseBuffer[3]; - responseMessage.write(responseBuffer, 4, messageSize); - } while (bytesRead > 0 && (messageSize + 4) == bytesRead); - // TODO - how to deal with messages that finish on the boundary? - - // FIXME - remove debugging String responseString = HexDump.dumpHexString(responseMessage.toByteArray()); Log.d(TAG, "READ: " + responseString); @@ -182,27 +206,6 @@ public class MedtronicCNLReader implements ContourNextLinkMessageHandler { } } - private static CgmStatusEvent.TREND fromMessageByte(byte messageByte) { - switch( messageByte ) { - case (byte) 0x60: - return CgmStatusEvent.TREND.FLAT; - case (byte) 0xc0: - return CgmStatusEvent.TREND.DOUBLE_UP; - case (byte) 0xa0: - return CgmStatusEvent.TREND.SINGLE_UP; - case (byte) 0x80: - return CgmStatusEvent.TREND.FOURTY_FIVE_UP; - case (byte) 0x40: - return CgmStatusEvent.TREND.FOURTY_FIVE_DOWN; - case (byte) 0x20: - return CgmStatusEvent.TREND.SINGLE_DOWN; - case (byte) 0x00: - return CgmStatusEvent.TREND.DOUBLE_DOWN; - default: - return CgmStatusEvent.TREND.NOT_COMPUTABLE; - } - } - public void enterControlMode() throws IOException, TimeoutException, UnexpectedMessageException { boolean doRetry = false; @@ -223,21 +226,26 @@ public class MedtronicCNLReader implements ContourNextLinkMessageHandler { } public void enterPassthroughMode() throws IOException, TimeoutException, UnexpectedMessageException { + Log.d(TAG, "Begin enterPasshtroughMode"); new ContourNextLinkCommandMessage("W|").send(this); checkControlMessage(readMessage(), ASCII.ACK.value); new ContourNextLinkCommandMessage("Q|").send(this); checkControlMessage(readMessage(), ASCII.ACK.value); new ContourNextLinkCommandMessage("1|").send(this); checkControlMessage(readMessage(), ASCII.ACK.value); + Log.d(TAG, "Finished enterPasshtroughMode"); } public void openConnection() throws IOException, TimeoutException { + Log.d(TAG, "Begin openConnection"); new ContourNextLinkBinaryMessage(ContourNextLinkBinaryMessage.CommandType.OPEN_CONNECTION, mPumpSession, mPumpSession.getHMAC()).send(this); // FIXME - We need to care what the response message is - wrong MAC and all that readMessage(); + Log.d(TAG, "Finished openConnection"); } public void requestReadInfo() throws IOException, TimeoutException, EncryptionException, ChecksumException { + Log.d(TAG, "Begin requestReadInfo"); new ContourNextLinkBinaryMessage(ContourNextLinkBinaryMessage.CommandType.READ_INFO, mPumpSession, null).send(this); ContourNextLinkMessage response = ReadInfoResponseMessage.fromBytes(mPumpSession, readMessage()); @@ -251,19 +259,36 @@ public class MedtronicCNLReader implements ContourNextLinkMessageHandler { this.getPumpSession().setLinkMAC(linkMAC); this.getPumpSession().setPumpMAC(pumpMAC); + Log.d(TAG, String.format("Finished requestReadInfo. linkMAC = '%d', pumpMAC = '%d", linkMAC, pumpMAC)); } - public byte negotiateChannel() throws IOException, ChecksumException, TimeoutException { - for (byte channel : RADIO_CHANNELS) { + public byte negotiateChannel(byte lastRadioChannel) throws IOException, ChecksumException, TimeoutException { + ArrayList<Byte> radioChannels = new ArrayList<>(Arrays.asList(ArrayUtils.toObject(RADIO_CHANNELS))); + + if (lastRadioChannel != 0x00) { + // If we know the last channel that was used, shuffle the negotiation order + Byte lastChannel = radioChannels.remove(radioChannels.indexOf(new Byte(lastRadioChannel))); + + if (lastChannel != null) { + radioChannels.add(0, lastChannel); + } + } + + Log.d(TAG, "Begin negotiateChannel"); + for (byte channel : radioChannels) { + Log.d(TAG, String.format("negotiateChannel: trying channel '%d'...", channel)); mPumpSession.setRadioChannel(channel); new ChannelNegotiateMessage(mPumpSession).send(this); // Don't care what the 0x81 response message is at this stage + Log.d(TAG, "negotiateChannel: Reading 0x81 message"); readMessage(); // The 0x80 message + Log.d(TAG, "negotiateChannel: Reading 0x80 message"); ContourNextLinkMessage response = ContourNextLinkBinaryMessage.fromBytes(readMessage()); byte[] responseBytes = response.encode(); + Log.d(TAG, "negotiateChannel: Check response length"); if (responseBytes.length > 46) { // Looks promising, let's check the last byte of the payload to make sure if (responseBytes[76] == mPumpSession.getRadioChannel()) { @@ -276,16 +301,20 @@ public class MedtronicCNLReader implements ContourNextLinkMessageHandler { } } + Log.d(TAG, String.format("Finished negotiateChannel with channel '%d'", mPumpSession.getRadioChannel())); return mPumpSession.getRadioChannel(); } public void beginEHSMSession() throws EncryptionException, IOException, TimeoutException { + Log.d(TAG, "Begin beginEHSMSession"); new BeginEHSMMessage(mPumpSession).send(this); // The Begin EHSM Session only has an 0x81 response readMessage(); + Log.d(TAG, "Finished beginEHSMSession"); } public Date getPumpTime() throws EncryptionException, IOException, ChecksumException, TimeoutException { + Log.d(TAG, "Begin getPumpTime"); // FIXME - throw if not in EHSM mode (add a state machine) Date timeAtCapture = new Date(); @@ -296,8 +325,10 @@ public class MedtronicCNLReader implements ContourNextLinkMessageHandler { // Read the 0x80 ContourNextLinkMessage response = PumpTimeResponseMessage.fromBytes(mPumpSession, readMessage()); - if (response.encode().length < 57) { + if (response.encode().length < (61 + 8)) { // Invalid message. Return an invalid date. + // TODO - deal with this more elegantly + Log.e(TAG, "Invalid message received for getPumpTime"); return new Date(); } @@ -308,10 +339,12 @@ public class MedtronicCNLReader implements ContourNextLinkMessageHandler { long rtc = dateBuffer.getInt(0) & 0x00000000ffffffffL; long offset = dateBuffer.getInt(4); + Log.d(TAG, "Finished getPumpTime with date " + MessageUtils.decodeDateTime(rtc, offset)); return MessageUtils.decodeDateTime(rtc, offset); } public void getPumpStatus(CgmStatusEvent cgmRecord, long pumpTimeOffset) throws IOException, EncryptionException, ChecksumException, TimeoutException { + Log.d(TAG, "Begin getPumpStatus"); // FIXME - throw if not in EHSM mode (add a state machine) new PumpStatusRequestMessage(mPumpSession).send(this); @@ -321,8 +354,10 @@ public class MedtronicCNLReader implements ContourNextLinkMessageHandler { // Read the 0x80 ContourNextLinkMessage response = PumpStatusResponseMessage.fromBytes(mPumpSession, readMessage()); - if (response.encode().length < 57) { + if (response.encode().length < (57 + 96)) { // Invalid message. Don't try and parse it + // TODO - deal with this more elegantly + Log.e(TAG, "Invalid message received for getPumpStatus"); return; } @@ -354,32 +389,42 @@ public class MedtronicCNLReader implements ContourNextLinkMessageHandler { long rawReservoirAmount = statusBuffer.getInt(0x2b); MainActivity.pumpStatusRecord.reservoirAmount = new BigDecimal(rawReservoirAmount / 10000f).setScale(3, BigDecimal.ROUND_HALF_UP); MainActivity.pumpStatusRecord.batteryPercentage = (statusBuffer.get(0x2a)); + + Log.d(TAG, "Finished getPumpStatus"); } public void endEHSMSession() throws EncryptionException, IOException, TimeoutException { + Log.d(TAG, "Begin endEHSMSession"); new EndEHSMMessage(mPumpSession).send(this); // The End EHSM Session only has an 0x81 response readMessage(); + Log.d(TAG, "Finished endEHSMSession"); } public void closeConnection() throws IOException, TimeoutException { + Log.d(TAG, "Begin closeConnection"); new ContourNextLinkBinaryMessage(ContourNextLinkBinaryMessage.CommandType.CLOSE_CONNECTION, mPumpSession, null).send(this); // FIXME - We need to care what the response message is - wrong MAC and all that readMessage(); + Log.d(TAG, "Finished closeConnection"); } public void endPassthroughMode() throws IOException, TimeoutException, UnexpectedMessageException { + Log.d(TAG, "Begin endPassthroughMode"); new ContourNextLinkCommandMessage("W|").send(this); checkControlMessage(readMessage(), ASCII.ACK.value); new ContourNextLinkCommandMessage("Q|").send(this); checkControlMessage(readMessage(), ASCII.ACK.value); new ContourNextLinkCommandMessage("0|").send(this); checkControlMessage(readMessage(), ASCII.ACK.value); + Log.d(TAG, "Finished endPassthroughMode"); } public void endControlMode() throws IOException, TimeoutException, UnexpectedMessageException { + Log.d(TAG, "Begin endControlMode"); new ContourNextLinkCommandMessage(ASCII.EOT.value).send(this); checkControlMessage(readMessage(), ASCII.ENQ.value); + Log.d(TAG, "Finished endControlMode"); } public enum ASCII { diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkBinaryMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkBinaryMessage.java index 1f773efbdfa5e3430cce9e277c412ba3c9487765..33eda925bd298ff4079372cf0189bfc82c1e1d83 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkBinaryMessage.java +++ b/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkBinaryMessage.java @@ -48,7 +48,7 @@ public class ContourNextLinkBinaryMessage extends ContourNextLinkMessage{ payloadBuffer.put((byte) 0x51); payloadBuffer.put((byte) 0x3); - payloadBuffer.put("000000".getBytes()); // Text of Pump serial, but 000000 for 640g + payloadBuffer.put("000000".getBytes()); // Text of PumpInfo serial, but 000000 for 640g byte[] unknownBytes = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; payloadBuffer.put(unknownBytes); payloadBuffer.put(commandType.value); diff --git a/app/src/main/java/info/nightscout/android/medtronic/service/MedtronicCnlAlarmReceiver.java b/app/src/main/java/info/nightscout/android/medtronic/service/MedtronicCnlAlarmReceiver.java new file mode 100644 index 0000000000000000000000000000000000000000..d0d4a41c68b561a7961e63637379f42c567257c9 --- /dev/null +++ b/app/src/main/java/info/nightscout/android/medtronic/service/MedtronicCnlAlarmReceiver.java @@ -0,0 +1,21 @@ +package info.nightscout.android.medtronic.service; + +import android.content.Context; +import android.content.Intent; +import android.support.v4.content.WakefulBroadcastReceiver; +import android.util.Log; + +/** + * Created by lgoedhart on 14/07/2016. + */ +public class MedtronicCnlAlarmReceiver extends WakefulBroadcastReceiver { + private static final String TAG = MedtronicCnlAlarmReceiver.class.getSimpleName(); + + @Override + public void onReceive(final Context context, Intent intent) { + // Start the IntentService + Log.d(TAG, "Received broadcast message"); + Intent service = new Intent(context, MedtronicCnlIntentService.class); + startWakefulService(context, service); + } +} 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 d39f3246dadb395b61d450476ec7e65335f8eca8..f7385e9dac858b8b7ad4aa962757b92d23d57e56 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 @@ -17,13 +17,15 @@ import java.util.concurrent.TimeoutException; import info.nightscout.android.USB.UsbHidDriver; import info.nightscout.android.medtronic.MainActivity; -import info.nightscout.android.medtronic.MedtronicCNLReader; +import info.nightscout.android.medtronic.MedtronicCnlReader; import info.nightscout.android.medtronic.message.ChecksumException; import info.nightscout.android.medtronic.message.EncryptionException; import info.nightscout.android.medtronic.message.MessageUtils; import info.nightscout.android.medtronic.message.UnexpectedMessageException; import info.nightscout.android.model.CgmStatusEvent; import info.nightscout.android.model.medtronicNg.ContourNextLinkInfo; +import info.nightscout.android.model.medtronicNg.PumpInfo; +import info.nightscout.android.model.medtronicNg.PumpStatusEvent; import io.realm.Realm; import io.realm.RealmResults; @@ -89,7 +91,9 @@ public class MedtronicCnlIntentService extends IntentService { if (!hasUsbHostFeature()) { sendStatus("It appears that this device doesn't support USB OTG."); - Log.w(TAG, "Device does not support USB OTG"); + Log.e(TAG, "Device does not support USB OTG"); + MedtronicCnlAlarmReceiver.completeWakefulIntent(intent); + // TODO - throw, don't return return; } @@ -97,26 +101,31 @@ public class MedtronicCnlIntentService extends IntentService { if (cnlStick == null) { sendStatus("USB connection error. Is the Bayer Contour Next Link plugged in?"); Log.w(TAG, "USB connection error. Is the CNL plugged in?"); + MedtronicCnlAlarmReceiver.completeWakefulIntent(intent); + // TODO - throw, don't return 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); - Realm realm = Realm.getDefaultInstance(); - try { mHidDevice.open(); } catch (Exception e) { Log.e(TAG, "Unable to open serial device", e); + MedtronicCnlAlarmReceiver.completeWakefulIntent(intent); + // TODO - throw, don't return return; } - MedtronicCNLReader cnlReader = new MedtronicCNLReader(mHidDevice); + MedtronicCnlReader cnlReader = new MedtronicCnlReader(mHidDevice); + Realm realm = Realm.getDefaultInstance(); realm.beginTransaction(); try { @@ -131,6 +140,7 @@ public class MedtronicCnlIntentService extends IntentService { .findFirst(); if (info == null) { + // TODO - use realm.createObject()? info = new ContourNextLinkInfo(); info.setSerialNumber(cnlReader.getStickSerial()); @@ -145,6 +155,9 @@ public class MedtronicCnlIntentService extends IntentService { realm.commitTransaction(); sendMessage(Constants.ACTION_USB_REGISTER); + realm.close(); + MedtronicCnlAlarmReceiver.completeWakefulIntent(intent); + // TODO - throw, don't return return; } @@ -157,11 +170,26 @@ public class MedtronicCnlIntentService extends IntentService { cnlReader.enterPassthroughMode(); cnlReader.openConnection(); cnlReader.requestReadInfo(); - byte radioChannel = cnlReader.negotiateChannel(); + + long pumpMAC = cnlReader.getPumpSession().getPumpMAC(); + Log.i(TAG, "PumpInfo MAC: " + (pumpMAC & 0xffffff)); + MainActivity.setActivePumpMac(pumpMAC); + PumpInfo activePump = realm + .where(PumpInfo.class) + .equalTo("pumpMac", pumpMAC) + .findFirst(); + + if (activePump == null) { + activePump = realm.createObject(PumpInfo.class); + activePump.setPumpMac(pumpMAC); + } + + byte radioChannel = cnlReader.negotiateChannel(activePump.getLastRadioChannel()); if (radioChannel == 0) { sendStatus("Could not communicate with the 640g. Are you near the pump?"); Log.i(TAG, "Could not communicate with the 640g. Are you near the pump?"); } else { + activePump.setLastRadioChannel(radioChannel); sendStatus(String.format(Locale.getDefault(), "Connected to Contour Next Link on channel %d.", (int) radioChannel)); Log.d(TAG, String.format("Connected to Contour Next Link on channel %d.", (int) radioChannel)); cnlReader.beginEHSMSession(); @@ -170,27 +198,33 @@ public class MedtronicCnlIntentService extends IntentService { CgmStatusEvent cgmRecord = realm.createObject(CgmStatusEvent.class); String deviceName = String.format("medtronic-640g://%s", cnlReader.getStickSerial()); + activePump.setDeviceName(deviceName); + + // TODO - this should not be necessary. We should reverse lookup the device name from PumpInfo cgmRecord.setDeviceName(deviceName); //pumpRecord.setDeviceName(deviceName); + // TODO - legacy. Remove once we've plumbed in pumpRecord. - MainActivity.pumpStatusRecord.setDeviceName(deviceName); + //MainActivity.pumpStatusRecord.setDeviceName(deviceName); - //pumpRecord.setPumpDate(cnlReader.getPumpTime()); long pumpTime = cnlReader.getPumpTime().getTime(); long pumpOffset = pumpTime - System.currentTimeMillis(); + // TODO - send ACTION to MainActivity to show offset between pump and uploader. MainActivity.pumpStatusRecord.pumpDate = new Date(pumpTime - pumpOffset); + //pumpRecord.setPumpDate(cnlReader.getPumpTime()); + cgmRecord.setPumpDate(new Date(pumpTime - pumpOffset)); cnlReader.getPumpStatus(cgmRecord, pumpOffset); + activePump.getCgmHistory().add(cgmRecord); cnlReader.endEHSMSession(); boolean cancelTransaction = true; if (cgmRecord.getSgv() != 0) { // Check that the record doesn't already exist before committing - RealmResults<CgmStatusEvent> checkExistingRecords = realm - .where(CgmStatusEvent.class) + RealmResults<CgmStatusEvent> checkExistingRecords = activePump.getCgmHistory() + .where() .equalTo("eventDate", cgmRecord.getEventDate()) - .equalTo("deviceName", cgmRecord.getDeviceName()) .equalTo("sgv", cgmRecord.getSgv()) .findAll(); @@ -233,13 +267,16 @@ public class MedtronicCnlIntentService extends IntentService { Log.e(TAG, "Could not close connection.", e); sendStatus("Could not close connection: " + e.getMessage()); } finally { - if (realm.isInTransaction()) { - // If we didn't commit the transaction, we've run into an error. Let's roll it back - realm.cancelTransaction(); + 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(); } - } - realm.close(); + MedtronicCnlAlarmReceiver.completeWakefulIntent(intent); + } } private boolean hasUsbHostFeature() {