diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b489f118313ad1ce9be62bdb83b6d268bc008815..9f14c5b58bb70a546b4920237d9960ba062fe9e5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -33,6 +33,7 @@ android:label="@string/app_name" android:launchMode="singleTask" android:screenOrientation="portrait"> + <intent-filter android:icon="@drawable/ic_launcher"> <category android:name="android.intent.category.DEFAULT" /> @@ -71,9 +72,11 @@ android:name=".medtronic.service.MedtronicCnlIntentService" android:icon="@drawable/ic_launcher" /> - <activity android:name=".medtronic.StatusActivity"></activity> + <activity android:name=".medtronic.StatusActivity" /> + + <receiver android:name=".medtronic.service.MedtronicCnlAlarmReceiver" /> + <receiver android:name=".upload.nightscout.NightscoutUploadReceiver" /> - <receiver android:name=".medtronic.service.MedtronicCnlAlarmReceiver"></receiver> </application> </manifest> \ No newline at end of file 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 b8b62dd5106c883e7e09b14c33b8d887f94a4707..6905665497f5f23e3f18498ffd4eff166fbbf795 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/MainActivity.java +++ b/app/src/main/java/info/nightscout/android/medtronic/MainActivity.java @@ -45,10 +45,13 @@ import com.mikepenz.materialdrawer.DrawerBuilder; import com.mikepenz.materialdrawer.model.PrimaryDrawerItem; import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; +import java.text.DateFormat; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Date; import java.util.Locale; +import java.util.Queue; +import java.util.concurrent.ArrayBlockingQueue; import info.nightscout.android.R; import info.nightscout.android.USB.UsbHidDriver; @@ -80,6 +83,8 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc private Handler mUiRefreshHandler = new Handler(); private Runnable mUiRefreshRunnable = new RefreshDisplayRunnable(); private Realm mRealm; + private StatusMessageReceiver statusMessageReceiver = new StatusMessageReceiver(); + private MedtronicCnlAlarmReceiver medtronicCnlAlarmReceiver = new MedtronicCnlAlarmReceiver(); public static void setActivePumpMac(long pumpMac) { activePumpMac = pumpMac; @@ -103,7 +108,7 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc } LocalBroadcastManager.getInstance(this).registerReceiver( - new StatusMessageReceiver(), + statusMessageReceiver, new IntentFilter(MedtronicCnlIntentService.Constants.ACTION_STATUS_MESSAGE)); LocalBroadcastManager.getInstance(this).registerReceiver( new RefreshDataReceiver(), @@ -214,6 +219,9 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc @Override protected void attachBaseContext(Context newBase) { super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase)); + + // setup self handling alarm receiver + medtronicCnlAlarmReceiver.setContext(getBaseContext()); } @Override @@ -284,7 +292,8 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc } private void clearLogText() { - mTextViewLog.setText("", BufferType.EDITABLE); + statusMessageReceiver.clearMessages(); + //mTextViewLog.setText("", BufferType.EDITABLE); } private void startDisplayRefreshLoop() { @@ -296,7 +305,7 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc } private void startCgmService() { - startCgmService(0L); + startCgmService(System.currentTimeMillis()); } private void startCgmService(long initialPoll) { @@ -306,22 +315,11 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc return; } - if (initialPoll == 0) { - initialPoll = SystemClock.currentThreadTimeMillis(); - } - - Log.d(TAG, "startCgmService set to fire at " + new Date(initialPoll)); - clearLogText(); + //clearLogText(); // Cancel any existing polling. stopCgmService(); - - AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - 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); + medtronicCnlAlarmReceiver.setAlarm(initialPoll); } private void uploadCgmData() { @@ -330,12 +328,7 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc private void stopCgmService() { Log.i(TAG, "stopCgmService called"); - - AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - Intent receiverIntent = new Intent(this, MedtronicCnlAlarmReceiver.class); - PendingIntent pending = PendingIntent.getBroadcast(this, 0, receiverIntent, 0); - - alarmManager.cancel(pending); + medtronicCnlAlarmReceiver.cancelAlarm(); } private void showDisconnectionNotification(String title, String message) { @@ -461,18 +454,78 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc } private class StatusMessageReceiver extends BroadcastReceiver { + private class StatusMessage { + private long timestamp; + private String message; + + public StatusMessage(String message) { + this(System.currentTimeMillis(), message); + } + + public StatusMessage(long timestamp, String message) { + this.timestamp = timestamp; + this.message = message; + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String toString() { + return DateFormat.getTimeInstance(DateFormat.MEDIUM).format(timestamp) + ": " + message; + } + } + + private Queue<StatusMessage> messages = new ArrayBlockingQueue<>(10); + @Override public void onReceive(Context context, Intent intent) { String message = intent.getStringExtra(MedtronicCnlIntentService.Constants.EXTENDED_DATA); - Log.i(TAG, "Message Receiver: " + message); - mTextViewLog.setText(mTextViewLog.getText() + "\n" + message, BufferType.EDITABLE); + + synchronized (messages) { + while (messages.size() > 8) { + messages.poll(); + } + messages.add(new StatusMessage(message)); + } + + StringBuilder sb = new StringBuilder(); + for (StatusMessage msg : messages) { + if (sb.length() > 0) + sb.append("\n"); + sb.append(msg); + } + + mTextViewLog.setText(sb.toString(), BufferType.EDITABLE); + } + + public void clearMessages() { + synchronized (messages) { + messages.clear(); + } + + mTextViewLog.setText("", BufferType.EDITABLE); } } private class RefreshDisplayRunnable implements Runnable { @Override public void run() { + Log.d(TAG, "NOW " + new Date(System.currentTimeMillis()).toString()); + // UI elements - TODO do these need to be members? TextView textViewBg = (TextView) findViewById(R.id.textview_bg); TextView textViewBgTime = (TextView) findViewById(R.id.textview_bg_time); @@ -579,6 +632,8 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc long nextPoll = pumpStatusData.getEventDate().getTime() + MedtronicCnlIntentService.POLL_GRACE_PERIOD_MS + MedtronicCnlIntentService.POLL_PERIOD_MS; startCgmService(nextPoll); + Log.d(TAG, "Local time " + new Date()); + Log.d(TAG, "Last event was " + new Date(pumpStatusData.getEventDate().getTime())); 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 1eab92da74ecea4548ab9afdf0aed8f19839a223..505f6414814beaee557686a4cb9d194848767da7 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/MedtronicCNLReader.java +++ b/app/src/main/java/info/nightscout/android/medtronic/MedtronicCNLReader.java @@ -43,9 +43,9 @@ 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 = MedtronicCnlReader.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; @@ -58,7 +58,7 @@ public class MedtronicCnlReader implements ContourNextLinkMessageHandler { private String mStickSerial = null; - public MedtronicCnlReader(UsbHidDriver device) { + public MedtronicCNLReader(UsbHidDriver device) { mDevice = device; } 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 index d0d4a41c68b561a7961e63637379f42c567257c9..32933be7001e4ae1db1551e718fdb108284b14c0 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/service/MedtronicCnlAlarmReceiver.java +++ b/app/src/main/java/info/nightscout/android/medtronic/service/MedtronicCnlAlarmReceiver.java @@ -1,21 +1,65 @@ package info.nightscout.android.medtronic.service; +import android.app.AlarmManager; +import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.support.v4.content.WakefulBroadcastReceiver; import android.util.Log; +import java.util.Date; + /** * Created by lgoedhart on 14/07/2016. */ public class MedtronicCnlAlarmReceiver extends WakefulBroadcastReceiver { private static final String TAG = MedtronicCnlAlarmReceiver.class.getSimpleName(); + private static final int ALARM_ID = 102; // Alarm id + + private static PendingIntent pi = null; + private static AlarmManager am = null; @Override public void onReceive(final Context context, Intent intent) { // Start the IntentService - Log.d(TAG, "Received broadcast message"); + Log.d(TAG, "Received broadcast message at " + new Date(System.currentTimeMillis())); Intent service = new Intent(context, MedtronicCnlIntentService.class); startWakefulService(context, service); + restartAlarm(); + } + + public void setContext(Context context) { + if (am != null && pi != null) { + // here is an old alarm. So we cancel it before + cancelAlarm(); + } + am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + Intent intent = new Intent(context, MedtronicCnlAlarmReceiver.class); + pi = PendingIntent.getBroadcast(context, ALARM_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT); } + + // Setting the alarm to call onRecieve every _REFRESH_INTERVAL seconds + public void setAlarm() { + setAlarm(System.currentTimeMillis()); + } + + // Setting the alarm to call onRecieve + public void setAlarm(long millis) { + try { + am.cancel(pi); + } catch (Exception ignored) {} + Log.d(TAG, "AlarmManager set to fire at " + new Date(millis)); + am.setExact(AlarmManager.RTC_WAKEUP, millis, pi); + } + + // restarting the alarm after MedtronicCnlIntentService.POLL_PERIOD_MS from now + public void restartAlarm() { + setAlarm(System.currentTimeMillis() + MedtronicCnlIntentService.POLL_PERIOD_MS); + } + + // Cancel the alarm. + public void cancelAlarm() { + am.cancel(pi); + } + } 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 bf3aadca466b5d48d49cdfb0670e1264a946991b..1a1f246f4f3e4cd643b14ee0c4cf1b3e9484f6e7 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 @@ -19,7 +19,7 @@ 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; @@ -103,6 +103,9 @@ 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?"); + + // TODO - set status if offline or Nightscout not reachable + uploadToNightscout(); MedtronicCnlAlarmReceiver.completeWakefulIntent(intent); // TODO - throw, don't return return; @@ -125,7 +128,7 @@ public class MedtronicCnlIntentService extends IntentService { return; } - MedtronicCnlReader cnlReader = new MedtronicCnlReader(mHidDevice); + MedtronicCNLReader cnlReader = new MedtronicCNLReader(mHidDevice); Realm realm = Realm.getDefaultInstance(); realm.beginTransaction(); @@ -237,12 +240,12 @@ public class MedtronicCnlIntentService extends IntentService { realm.cancelTransaction(); } } - - cnlReader.closeConnection(); } catch (UnexpectedMessageException e) { Log.e(TAG, "Unexpected Message", e); sendStatus("Communication Error: " + e.getMessage()); } finally { + //TODO : 05.11.2016 has the close to be here? + cnlReader.closeConnection(); cnlReader.endPassthroughMode(); cnlReader.endControlMode(); } @@ -279,8 +282,9 @@ public class MedtronicCnlIntentService extends IntentService { private void uploadToNightscout() { AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); Intent receiverIntent = new Intent(this, NightscoutUploadReceiver.class); - PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, receiverIntent, 0); - alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 1000L, pendingIntent); + final long timestamp = System.currentTimeMillis() + 1000L; + PendingIntent pendingIntent = PendingIntent.getBroadcast(this, (int)timestamp, receiverIntent, PendingIntent.FLAG_ONE_SHOT); + alarmManager.set(AlarmManager.RTC_WAKEUP, timestamp, pendingIntent); } private boolean hasUsbHostFeature() { diff --git a/app/src/main/java/info/nightscout/android/upload/nightscout/NightscoutUploadIntentService.java b/app/src/main/java/info/nightscout/android/upload/nightscout/NightscoutUploadIntentService.java index b5c067e21475aade67dea92a2f61a74b323453b2..3fcefde12a486c2e2b5d71f48634cd60d1058fac 100644 --- a/app/src/main/java/info/nightscout/android/upload/nightscout/NightscoutUploadIntentService.java +++ b/app/src/main/java/info/nightscout/android/upload/nightscout/NightscoutUploadIntentService.java @@ -31,6 +31,7 @@ import java.util.regex.Pattern; import info.nightscout.android.R; import info.nightscout.android.medtronic.MainActivity; +import info.nightscout.android.medtronic.service.MedtronicCnlIntentService; import info.nightscout.android.model.medtronicNg.PumpStatusEvent; import info.nightscout.android.upload.nightscout.serializer.EntriesSerializer; import io.realm.Realm; @@ -75,18 +76,22 @@ public class NightscoutUploadIntentService extends IntentService { .notEqualTo("sgv", 0) .findAll(); - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext); + if (records.size() > 0) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext); - Boolean enableRESTUpload = prefs.getBoolean("EnableRESTUpload", false); - try { - if (enableRESTUpload) { - long start = System.currentTimeMillis(); - Log.i(TAG, String.format("Starting upload of %s record using a REST API", records.size())); - doRESTUpload(prefs, records); - Log.i(TAG, String.format("Finished upload of %s record using a REST API in %s ms", records.size(), System.currentTimeMillis() - start)); + Boolean enableRESTUpload = prefs.getBoolean("EnableRESTUpload", false); + try { + if (enableRESTUpload) { + long start = System.currentTimeMillis(); + Log.i(TAG, String.format("Starting upload of %s record using a REST API", records.size())); + doRESTUpload(prefs, records); + Log.i(TAG, String.format("Finished upload of %s record using a REST API in %s ms", records.size(), System.currentTimeMillis() - start)); + } + } catch (Exception e) { + Log.e(TAG, "ERROR uploading data!!!!!", e); } - } catch (Exception e) { - Log.e(TAG, "ERROR uploading data!!!!!", e); + } else { + Log.i(TAG, "No records has to be uploaded"); } NightscoutUploadReceiver.completeWakefulIntent(intent); @@ -160,14 +165,16 @@ public class NightscoutUploadIntentService extends IntentService { JSONArray entriesBody = new JSONArray(); for (PumpStatusEvent record : records) { - addDeviceStatus(devicestatusBody, record); addSgvEntry(entriesBody, record); addMbgEntry(entriesBody, record); } uploadToNightscout(new URL(baseURL + "/entries"), secret, entriesBody); - uploadToNightscout(new URL(baseURL + "/devicestatus"), secret, devicestatusBody); + + for(int i = 0; i < devicestatusBody.length(); i++) { + uploadToNightscout(new URL(baseURL + "/devicestatus"), secret, devicestatusBody.getJSONObject(i)); + } // Yay! We uploaded. Tell Realm // FIXME - check the upload succeeded! @@ -184,7 +191,15 @@ public class NightscoutUploadIntentService extends IntentService { } } + private boolean uploadToNightscout(URL endpoint, String secret, JSONObject httpBody) throws Exception { + return uploadToNightscout(endpoint, secret, httpBody.toString()); + } + private boolean uploadToNightscout(URL endpoint, String secret, JSONArray httpBody) throws Exception { + return uploadToNightscout(endpoint, secret, httpBody.toString()); + } + + private boolean uploadToNightscout(URL endpoint, String secret, String httpBody) throws Exception { Log.i(TAG, "postURL: " + endpoint.toString()); HttpPost post = new HttpPost(endpoint.toString()); @@ -210,12 +225,10 @@ public class NightscoutUploadIntentService extends IntentService { DefaultHttpClient httpclient = new DefaultHttpClient(params); - String jsonString = httpBody.toString(); - - Log.i(TAG, "Upload JSON: " + jsonString); + Log.i(TAG, "Upload JSON: " + httpBody); try { - StringEntity se = new StringEntity(jsonString); + StringEntity se = new StringEntity(httpBody); post.setEntity(se); post.setHeader("Accept", "application/json"); post.setHeader("Content-type", "application/json");