diff --git a/.gitignore b/.gitignore
index a0722a1d135612b37f3243c980473c0408b138e9..007d64268841d3fb3282a82e2376522174a3d543 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,8 @@
 .gradle
-app/build
-build/generated
 build
-workspace.xml
 fabric.properties
 local.properties
 .idea
 bugfender.properties
+/app/app.iml
+gradle.properties
\ No newline at end of file
diff --git a/640gAndroidUploader.iml b/640gAndroidUploader.iml
index dd11c422309587fbfb2c40dcfbc1c5c64bb0d573..21ad486e1dacdc31dfcf484957078ef9fbc29307 100644
--- a/640gAndroidUploader.iml
+++ b/640gAndroidUploader.iml
@@ -13,7 +13,7 @@
     <content url="file://$MODULE_DIR$">
       <excludeFolder url="file://$MODULE_DIR$/.gradle" />
     </content>
-    <orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
+    <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
   </component>
 </module>
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 321863d615cb36775b630d58e4edd1721a5f7a06..6bfea65e09c18951c4bedbba1cb835ca18935ecb 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -7,7 +7,7 @@ buildscript {
 
     dependencies {
         classpath 'io.fabric.tools:gradle:1.21.6'
-        classpath 'io.realm:realm-gradle-plugin:1.1.1'
+        classpath 'io.realm:realm-gradle-plugin:2.3.1'
         classpath 'org.ajoberstar:grgit:1.5.0'
     }
 }
@@ -28,7 +28,7 @@ apply plugin: 'realm-android'
 def gitVersion() {
     // current dir is <your proj>/app, so it's likely that all your git repo files are in the dir
     // above.
-    ext.repo = Grgit.open(project.file('..'))
+    ext.repo = Grgit.open()
 
     // should result in the same value as running
     // git tag -l | wc -l or git tag -l | grep -c ".*" -
@@ -37,14 +37,15 @@ def gitVersion() {
 }
 
 def gitCommitId() {
-    //def process = ['sh', '-c', 'git tag -l | grep -c ".*" -'].execute().text.trim()
-    //return process.toInteger() + 1
-    //return 42
-    // current dir is <your proj>/app, so it's likely that all your git repo files are in the dir
-    // above.
-    ext.repo = Grgit.open(project.file('..'))
+    ext.repo = Grgit.open()
+
+    return ext.repo.log().first().id.substring(0, 7) //+ " " + ext.repo.branch.current.name
+}
 
-    return ext.repo.log().first().id.substring(0, 7)
+
+def gitBranch() {
+    ext.repo = Grgit.open()
+    return ext.repo.branch.current.name
 }
 
 def getBugfenderApiKey() {
@@ -56,7 +57,7 @@ def getBugfenderApiKey() {
 android {
     compileSdkVersion 23
     buildToolsVersion "23.0.3"
-    // FIXME - replace with URLConnection. This is used in GetHmacAndKeyActivity.
+    // FIXME - replace with URLConnection. This is used in ManageCNLActivity.
     useLibrary 'org.apache.http.legacy'
 
     applicationVariants.all { variant ->
@@ -67,7 +68,7 @@ android {
         applicationId "info.nightscout.android"
         minSdkVersion 14
         targetSdkVersion 23
-        versionName project.properties['version'] + "/" + gitCommitId()
+        versionName project.properties['version'] + "/" + gitCommitId() // + " (" + gitBranch()+")"
         versionCode gitVersion()
         buildConfigField "String", "BUGFENDER_API_KEY", getBugfenderApiKey()
     }
@@ -144,7 +145,7 @@ release {
 
 dependencies {
     compile files('libs/slf4j-api-1.7.2.jar')
-    compile('com.crashlytics.sdk.android:crashlytics:2.6.5@aar') {
+    compile('com.crashlytics.sdk.android:crashlytics:2.6.7@aar') {
         transitive = true;
     }
     compile('com.mikepenz:materialdrawer:5.2.9@aar') {
@@ -154,11 +155,16 @@ dependencies {
     compile 'org.apache.commons:commons-lang3:3.4'
     compile 'com.mikepenz:google-material-typeface:2.2.0.1.original@aar'
     compile 'uk.co.chrisjenx:calligraphy:2.2.0'
-    compile 'com.bugfender.sdk:android:0.6.2'
-    compile 'com.github.PhilJay:MPAndroidChart:v3.0.0-beta1'
-    compile 'com.github.PhilJay:MPAndroidChart-Realm:v1.1.0@aar'
+    compile 'com.bugfender.sdk:android:0.7.2'
+    compile 'com.jjoe64:graphview:4.2.1'
     compile 'com.android.support:support-v4:23.4.0'
     compile 'com.google.code.gson:gson:2.7'
     compile 'com.squareup.retrofit2:retrofit:2.1.0'
     compile 'com.squareup.retrofit2:converter-gson:2.1.0'
+    compile 'com.squareup.okhttp3:okhttp:3.3.1'
+    compile 'com.squareup.okhttp3:logging-interceptor:3.3.1'
+    compile 'com.google.android.gms:play-services-appindexing:8.4.0'
+
+
+
 }
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f7d7185b04799440fc232b3ffd10ef632e76220c..82d30493197be0106b2cf30795d7fed79e1ec57d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -31,11 +31,13 @@
 
 
         <!-- I have set screenOrientation to "portrait" to avoid the restart of AsyncTasks when you rotate the phone -->
+        <!-- configChanges="uiMode" added to avoid restart of AsyncTasks when phone is plugged into charger/dock (for phones that can do usb otg & charging simultaneously -->
         <activity
             android:name=".medtronic.MainActivity"
             android:icon="@drawable/ic_launcher"
             android:label="@string/app_name"
             android:launchMode="singleTask"
+            android:configChanges="uiMode"
             android:screenOrientation="portrait">
 
             <intent-filter android:icon="@drawable/ic_launcher">
@@ -63,10 +65,8 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
-        <activity
-            android:name=".medtronic.GetHmacAndKeyActivity"
-            android:label="@string/title_activity_login"
-            android:theme="@style/SettingsTheme" />
+
+        <activity android:name=".medtronic.ManageCNLActivity"/>
 
         <activity android:name=".medtronic.StatusActivity" />
 
@@ -84,6 +84,11 @@
         <receiver android:name=".upload.nightscout.NightscoutUploadReceiver" />
         <receiver android:name=".xdrip_plus.XDripPlusUploadReceiver" />
 
+
+        <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/USB/USBPower.java b/app/src/main/java/info/nightscout/android/USB/USBPower.java
index 92890b8e0f3577c2a6f4966b2f002da4b65a57ff..cf16a178a2ba8d5b6d8b4da4b13d1138f1391d4d 100644
--- a/app/src/main/java/info/nightscout/android/USB/USBPower.java
+++ b/app/src/main/java/info/nightscout/android/USB/USBPower.java
@@ -1,9 +1,9 @@
 package info.nightscout.android.USB;
 
-import java.io.DataOutputStream;
-
 import android.util.Log;
 
+import java.io.DataOutputStream;
+
 public class USBPower {
 
     private static final String TAG = "USBPower";
diff --git a/app/src/main/java/info/nightscout/android/UploaderApplication.java b/app/src/main/java/info/nightscout/android/UploaderApplication.java
index aa1944885e68507b24370f9bf2f2b75fe774bda1..1bfc87f81ded988434835da9d0af962dce3ff079 100644
--- a/app/src/main/java/info/nightscout/android/UploaderApplication.java
+++ b/app/src/main/java/info/nightscout/android/UploaderApplication.java
@@ -41,7 +41,8 @@ public class UploaderApplication extends Application {
             Bugfender.setDeviceString("NightscoutURL", prefs.getString(getString(R.string.preference_nightscout_url), "Not set"));
         }
 
-        RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(this)
+        Realm.init(this);
+        RealmConfiguration realmConfiguration = new RealmConfiguration.Builder()
                 .deleteRealmIfMigrationNeeded()
                 .build();
 
diff --git a/app/src/main/java/info/nightscout/android/medtronic/GetHmacAndKeyActivity.java b/app/src/main/java/info/nightscout/android/medtronic/GetHmacAndKeyActivity.java
deleted file mode 100644
index dfd43740a7f4ce86fafde82fc746d661476fd109..0000000000000000000000000000000000000000
--- a/app/src/main/java/info/nightscout/android/medtronic/GetHmacAndKeyActivity.java
+++ /dev/null
@@ -1,130 +0,0 @@
-package info.nightscout.android.medtronic;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.annotation.TargetApi;
-import android.app.LoaderManager.LoaderCallbacks;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Loader;
-import android.database.Cursor;
-import android.graphics.Color;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.os.Bundle;
-import android.support.v7.app.AlertDialog;
-import android.support.v7.app.AppCompatActivity;
-import android.text.Html;
-import android.text.TextUtils;
-import android.view.KeyEvent;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.EditText;
-import android.widget.TextView;
-
-import com.mikepenz.google_material_typeface_library.GoogleMaterial;
-import com.mikepenz.iconics.IconicsDrawable;
-
-import org.apache.commons.lang3.ArrayUtils;
-import org.apache.http.HttpResponse;
-import org.apache.http.NameValuePair;
-import org.apache.http.client.ClientProtocolException;
-import org.apache.http.client.entity.UrlEncodedFormEntity;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.entity.ByteArrayEntity;
-import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.http.message.BasicNameValuePair;
-import org.apache.http.util.EntityUtils;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.util.ArrayList;
-import java.util.List;
-
-import info.nightscout.android.R;
-import info.nightscout.android.medtronic.message.MessageUtils;
-import info.nightscout.android.model.medtronicNg.ContourNextLinkInfo;
-import io.realm.Realm;
-import io.realm.RealmResults;
-import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper;
-
-/**
- * A login screen that offers login via username/password.
- */
-public class GetHmacAndKeyActivity extends AppCompatActivity implements LoaderCallbacks<Cursor> {
-
-    // UI references.
-    private TextView mRegisteredStickView;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_login);
-
-        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
-        getSupportActionBar().setTitle("Registered Devices");
-
-        mRegisteredStickView = (TextView) findViewById(R.id.registered_usb_devices);
-
-        showRegisteredSticks();
-    }
-
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        return true;
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        switch (item.getItemId()) {
-            case android.R.id.home:
-                finish();
-                break;
-        }
-        return true;
-    }
-
-    @Override
-    protected void attachBaseContext(Context newBase) {
-        super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase));
-    }
-
-    private void showRegisteredSticks() {
-        Realm realm = Realm.getDefaultInstance();
-
-        RealmResults<ContourNextLinkInfo> results = realm.where(ContourNextLinkInfo.class).findAll();
-
-        String deviceTableHtml = "";
-
-        for (ContourNextLinkInfo info : results) {
-            String longSerial = info.getSerialNumber();
-            String key = info.getKey();
-
-            deviceTableHtml += String.format("<b>Serial Number:</b> %s %s<br/>", longSerial, key == null ? "&#x2718;" : "&#x2714;");
-        }
-
-        mRegisteredStickView.setText(Html.fromHtml(deviceTableHtml));
-    }
-
-    @Override
-    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
-        return null;
-    }
-
-    @Override
-    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
-    }
-
-    @Override
-    public void onLoaderReset(Loader<Cursor> loader) {
-    }
-}
\ 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 d1249934460aebef1968d8ab832c1dcdbb04f5f3..6538ae565c03771d99a6f3eaf7b144dbb0c16c81 100644
--- a/app/src/main/java/info/nightscout/android/medtronic/MainActivity.java
+++ b/app/src/main/java/info/nightscout/android/medtronic/MainActivity.java
@@ -6,11 +6,13 @@ import android.app.PendingIntent;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
 import android.hardware.usb.UsbDevice;
 import android.hardware.usb.UsbManager;
 import android.net.Uri;
@@ -23,9 +25,9 @@ import android.preference.PreferenceManager;
 import android.provider.Settings;
 import android.support.v4.app.TaskStackBuilder;
 import android.support.v4.content.LocalBroadcastManager;
-import android.support.v7.app.AlertDialog;
 import android.support.v7.app.AppCompatActivity;
 import android.support.v7.app.NotificationCompat;
+import android.support.v7.view.menu.ActionMenuItemView;
 import android.support.v7.widget.Toolbar;
 import android.text.Html;
 import android.text.format.DateUtils;
@@ -36,12 +38,16 @@ import android.view.MenuItem;
 import android.view.View;
 import android.widget.TextView;
 import android.widget.TextView.BufferType;
-
-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;
+import android.widget.Toast;
+
+import com.jjoe64.graphview.DefaultLabelFormatter;
+import com.jjoe64.graphview.GraphView;
+import com.jjoe64.graphview.Viewport;
+import com.jjoe64.graphview.series.DataPoint;
+import com.jjoe64.graphview.series.DataPointInterface;
+import com.jjoe64.graphview.series.OnDataPointTapListener;
+import com.jjoe64.graphview.series.PointsGraphSeries;
+import com.jjoe64.graphview.series.Series;
 import com.mikepenz.google_material_typeface_library.GoogleMaterial;
 import com.mikepenz.materialdrawer.AccountHeaderBuilder;
 import com.mikepenz.materialdrawer.Drawer;
@@ -51,7 +57,8 @@ import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
 
 import java.text.DateFormat;
 import java.text.DecimalFormat;
-import java.util.ArrayList;
+import java.text.NumberFormat;
+import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.Locale;
 import java.util.Queue;
@@ -61,28 +68,45 @@ import info.nightscout.android.R;
 import info.nightscout.android.USB.UsbHidDriver;
 import info.nightscout.android.eula.Eula;
 import info.nightscout.android.eula.Eula.OnEulaAgreedTo;
+import info.nightscout.android.medtronic.service.MedtronicCnlAlarmManager;
 import info.nightscout.android.medtronic.service.MedtronicCnlAlarmReceiver;
 import info.nightscout.android.medtronic.service.MedtronicCnlIntentService;
-import info.nightscout.android.model.medtronicNg.ContourNextLinkInfo;
 import info.nightscout.android.model.medtronicNg.PumpInfo;
 import info.nightscout.android.model.medtronicNg.PumpStatusEvent;
 import info.nightscout.android.settings.SettingsActivity;
 import info.nightscout.android.upload.nightscout.NightscoutUploadIntentService;
 import io.realm.Realm;
+import io.realm.RealmChangeListener;
 import io.realm.RealmResults;
 import io.realm.Sort;
 import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper;
 
 public class MainActivity extends AppCompatActivity implements OnSharedPreferenceChangeListener, OnEulaAgreedTo {
     private static final String TAG = MainActivity.class.getSimpleName();
+    public static final float MMOLXLFACTOR = 18.016f;
 
     public static int batLevel = 0;
+    public static boolean reducePollOnPumpAway = false;
+    public static long pollInterval = MedtronicCnlIntentService.POLL_PERIOD_MS;
+    public static long lowBatteryPollInterval = MedtronicCnlIntentService.LOW_BATTERY_POLL_PERIOD_MS;
+
     private static long activePumpMac;
+    private int chartZoom = 3;
+    private boolean hasZoomedChart = false;
+
+    private NumberFormat sgvFormatter;
+    private static boolean mmolxl;
+    private static boolean mmolxlDecimals;
+
+    public static long timeLastGoodSGV = 0;
+    public static short pumpBattery = 0;
+    public static int countUnavailableSGV = 0;
+
     boolean mEnableCgmService = true;
     SharedPreferences prefs = null;
     private PumpInfo mActivePump;
     private TextView mTextViewLog; // This will eventually move to a status page.
-    private LineChart mChart;
+    private GraphView mChart;
     private Intent mNightscoutUploadService;
     private Handler mUiRefreshHandler = new Handler();
     private Runnable mUiRefreshRunnable = new RefreshDisplayRunnable();
@@ -90,8 +114,48 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
     private StatusMessageReceiver statusMessageReceiver = new StatusMessageReceiver();
     private MedtronicCnlAlarmReceiver medtronicCnlAlarmReceiver = new MedtronicCnlAlarmReceiver();
 
-    public static void setActivePumpMac(long pumpMac) {
-        activePumpMac = pumpMac;
+    /**
+     * calculate the next poll timestamp based on last svg event
+     *
+     * @param pumpStatusData
+     * @return timestamp
+     */
+    public static long getNextPoll(PumpStatusEvent pumpStatusData) {
+        long nextPoll = pumpStatusData.getEventDate().getTime() + pumpStatusData.getPumpTimeOffset(),
+            now = System.currentTimeMillis();
+
+        // align to next poll slot
+        if (nextPoll + 2 * 60 * 60 * 1000 < now) { // last event more than 2h old -> could be a calibration
+            nextPoll = System.currentTimeMillis() + 1000;
+        } else {
+            // align to poll interval
+            nextPoll += (((now - nextPoll) / MainActivity.pollInterval)) * MainActivity.pollInterval
+                    + MedtronicCnlIntentService.POLL_GRACE_PERIOD_MS;
+            if (pumpStatusData.getBatteryPercentage() > 25) {
+                // poll every 5 min
+                nextPoll += MainActivity.pollInterval;
+            } else {
+                // if pump battery seems to be empty reduce polling to save battery (every 15 min)
+                //TODO add message & document it
+                nextPoll += MainActivity.lowBatteryPollInterval;
+            }
+        }
+
+        return nextPoll;
+    }
+
+    public static String strFormatSGV(float sgvValue) {
+        if (mmolxl) {
+            NumberFormat sgvFormatter;
+            if (mmolxlDecimals) {
+                sgvFormatter = new DecimalFormat("0.00");
+            } else {
+                sgvFormatter = new DecimalFormat("0.0");
+            }
+            return sgvFormatter.format(sgvValue / MMOLXLFACTOR);
+        } else {
+            return String.valueOf(sgvValue);
+        }
     }
 
     @Override
@@ -111,13 +175,30 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
             stopCgmService();
         }
 
+        // setup preferences
+        MainActivity.pollInterval = Long.parseLong(prefs.getString("pollInterval", Long.toString(MedtronicCnlIntentService.POLL_PERIOD_MS)));
+        MainActivity.lowBatteryPollInterval = Long.parseLong(prefs.getString("lowBatPollInterval", Long.toString(MedtronicCnlIntentService.LOW_BATTERY_POLL_PERIOD_MS)));
+        MainActivity.reducePollOnPumpAway = prefs.getBoolean("doublePollOnPumpAway", false);
+        chartZoom = Integer.parseInt(prefs.getString("chartZoom", "3"));
+        mmolxl = prefs.getBoolean("mmolxl", false);
+        mmolxlDecimals = prefs.getBoolean("mmolDecimals", false);
+
+        if (mmolxl) {
+            if (mmolxlDecimals)
+                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
 
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
             final String packageName = getPackageName();
-            //Log.d(TAG, "Maybe ignoring battery optimization");
             final PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+
             if (!pm.isIgnoringBatteryOptimizations(packageName)) {
                 Log.d(TAG, "Requesting ignore battery optimization");
                 try {
@@ -137,8 +218,8 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
                 statusMessageReceiver,
                 new IntentFilter(MedtronicCnlIntentService.Constants.ACTION_STATUS_MESSAGE));
         LocalBroadcastManager.getInstance(this).registerReceiver(
-                new RefreshDataReceiver(),
-                new IntentFilter(MedtronicCnlIntentService.Constants.ACTION_REFRESH_DATA));
+                new UpdatePumpReceiver(),
+                new IntentFilter(MedtronicCnlIntentService.Constants.ACTION_UPDATE_PUMP));
 
         mEnableCgmService = Eula.show(this, prefs);
 
@@ -221,7 +302,8 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
                             stopCgmService();
                             finish();
                         } else if (drawerItem.equals(itemGetNow)) {
-                            startCgmService();
+                            // It was triggered by user so start reading of data now and not based on last poll.
+                            startCgmService(0);
                         } else if (drawerItem.equals(itemClearLog)) {
                             clearLogText();
                         }
@@ -232,7 +314,54 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
                 .build();
 
         mTextViewLog = (TextView) findViewById(R.id.textview_log);
-        mChart = (LineChart) findViewById(R.id.chart);
+
+        mChart = (GraphView) findViewById(R.id.chart);
+
+        // disable scrolling at the moment
+        mChart.getViewport().setScalable(false);
+        mChart.getViewport().setScrollable(false);
+        mChart.getViewport().setXAxisBoundsManual(true);
+        final long now = System.currentTimeMillis(),
+                left = now - chartZoom * 60 * 60 * 1000;
+
+        mChart.getViewport().setMaxX(now);
+        mChart.getViewport().setMinX(left);
+
+        mChart.getViewport().setOnXAxisBoundsChangedListener(new Viewport.OnXAxisBoundsChangedListener() {
+            @Override
+            public void onXAxisBoundsChanged(double minX, double maxX, Reason reason) {
+                double rightX = mChart.getSeries().get(0).getHighestValueX();
+                hasZoomedChart = (rightX != maxX || rightX - chartZoom * 60 * 60 * 1000 != minX);
+            }
+        });
+
+        mChart.setOnLongClickListener(new View.OnLongClickListener() {
+            @Override
+            public boolean onLongClick(View v) {
+                if (!mChart.getSeries().isEmpty() && !mChart.getSeries().get(0).isEmpty()) {
+                    double rightX = mChart.getSeries().get(0).getHighestValueX();
+                    mChart.getViewport().setMaxX(rightX);
+                    mChart.getViewport().setMinX(rightX - chartZoom * 60 * 60 * 1000);
+                }
+                hasZoomedChart = false;
+                return true;
+            }
+        });
+        mChart.getGridLabelRenderer().setNumHorizontalLabels(6);
+        mChart.getGridLabelRenderer().setHumanRounding(false);
+
+        mChart.getGridLabelRenderer().setLabelFormatter(new DefaultLabelFormatter() {
+            DateFormat mFormat = new SimpleDateFormat("HH:mm");  // 24 hour format forced to fix label overlap
+
+            @Override
+            public String formatLabel(double value, boolean isValueX) {
+                if (isValueX) {
+                    return mFormat.format(new Date((long) value));
+                } else {
+                        return sgvFormatter.format(value);
+                }
+            }}
+        );
     }
 
     @Override
@@ -247,7 +376,7 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
         super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase));
 
         // setup self handling alarm receiver
-        medtronicCnlAlarmReceiver.setContext(getBaseContext());
+        MedtronicCnlAlarmManager.setContext(getBaseContext());
     }
 
     @Override
@@ -269,24 +398,6 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
         return true;
     }
 
-    private boolean hasDetectedCnl() {
-        if (mRealm.where(ContourNextLinkInfo.class).count() == 0) {
-            new AlertDialog.Builder(this, R.style.AppTheme)
-                    .setTitle("No registered Contour Next Link devices")
-                    .setMessage("To register a Contour Next Link you must first plug it in, and get a reading from the pump.")
-                    .setCancelable(false)
-                    .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
-                        public void onClick(DialogInterface dialog, int which) {
-                            dialog.dismiss();
-                        }
-                    })
-                    .setIcon(android.R.drawable.ic_dialog_alert)
-                    .show();
-            return false;
-        }
-        return true;
-    }
-
     private boolean hasUsbPermission() {
         UsbManager usbManager = (UsbManager) this.getSystemService(Context.USB_SERVICE);
         UsbDevice cnlDevice = UsbHidDriver.getUsbDevice(usbManager, MedtronicCnlIntentService.USB_VID, MedtronicCnlIntentService.USB_PID);
@@ -330,7 +441,18 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
     }
 
     private void startCgmService() {
-        startCgmService(System.currentTimeMillis() + 1000);
+        startCgmServiceDelayed(0);
+    }
+
+    private void startCgmServiceDelayed(long delay) {
+        RealmResults<PumpStatusEvent> results = mRealm.where(PumpStatusEvent.class)
+                .findAllSorted("eventDate", Sort.DESCENDING);
+
+        if (results.size() > 0) {
+            startCgmService(getNextPoll(results.first()) + delay);
+        } else {
+            startCgmService(System.currentTimeMillis() + (delay==0?1000:delay));
+        }
     }
 
     private void startCgmService(long initialPoll) {
@@ -340,11 +462,9 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
             return;
         }
 
-        //clearLogText();
-
         // Cancel any existing polling.
         stopCgmService();
-        medtronicCnlAlarmReceiver.setAlarm(initialPoll);
+        MedtronicCnlAlarmManager.setAlarm(initialPoll);
     }
 
     private void uploadCgmData() {
@@ -353,7 +473,7 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
 
     private void stopCgmService() {
         Log.i(TAG, "stopCgmService called");
-        medtronicCnlAlarmReceiver.cancelAlarm();
+        MedtronicCnlAlarmManager.cancelAlarm();
     }
 
     private void showDisconnectionNotification(String title, String message) {
@@ -413,7 +533,29 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
                 mEnableCgmService = true;
                 startCgmService();
             }
-        } else if (key.equals("mmolxl")) {
+        } else if (key.equals("mmolxl") || key.equals("mmolDecimals")) {
+            mmolxl = sharedPreferences.getBoolean("mmolxl", false);
+            mmolxlDecimals = sharedPreferences.getBoolean("mmolDecimals", false);
+            if (mmolxl) {
+                if (mmolxlDecimals)
+                    sgvFormatter = new DecimalFormat("0.00");
+                else
+                    sgvFormatter = new DecimalFormat("0.0");
+            } else {
+                sgvFormatter = new DecimalFormat("0");
+            }
+            refreshDisplay();
+        } else if (key.equals("pollInterval")) {
+            MainActivity.pollInterval = Long.parseLong(sharedPreferences.getString("pollInterval",
+                    Long.toString(MedtronicCnlIntentService.POLL_PERIOD_MS)));
+        } else if (key.equals("lowBatPollInterval")) {
+            MainActivity.lowBatteryPollInterval = Long.parseLong(sharedPreferences.getString("lowBatPollInterval",
+                    Long.toString(MedtronicCnlIntentService.LOW_BATTERY_POLL_PERIOD_MS)));
+        } else if (key.equals("doublePollOnPumpAway")) {
+            MainActivity.reducePollOnPumpAway = sharedPreferences.getBoolean("doublePollOnPumpAway", false);
+        } else if (key.equals("chartZoom")) {
+            chartZoom = Integer.parseInt(sharedPreferences.getString("chartZoom", "3"));
+            hasZoomedChart = false;
             refreshDisplay();
         }
     }
@@ -434,10 +576,8 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
     }
 
     public void openUsbRegistration() {
-        if (hasDetectedCnl()) {
-            Intent loginIntent = new Intent(this, GetHmacAndKeyActivity.class);
-            startActivity(loginIntent);
-        }
+        Intent manageCNLIntent = new Intent(this, ManageCNLActivity.class);
+        startActivity(manageCNLIntent);
     }
 
     private String renderTrendHtml(PumpStatusEvent.CGM_TREND trend) {
@@ -461,9 +601,17 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
         }
     }
 
+    public static void setActivePumpMac(long pumpMac) {
+        activePumpMac = pumpMac;
+    }
+
     private PumpInfo getActivePump() {
         if (activePumpMac != 0L && (mActivePump == null || !mActivePump.isValid() || mActivePump.getPumpMac() != activePumpMac)) {
-            mActivePump = null;
+            if (mActivePump != null) {
+                // remove listener on old pump
+                mActivePump.removeChangeListeners();
+                mActivePump = null;
+            }
 
             PumpInfo pump = mRealm
                     .where(PumpInfo.class)
@@ -472,6 +620,41 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
 
             if (pump != null && pump.isValid()) {
                 mActivePump = pump;
+                mActivePump.addChangeListener(new RealmChangeListener<PumpInfo>() {
+                    long lastQueryTS = 0;
+                    @Override
+                    public void onChange(PumpInfo pump) {
+                        // prevent double updating after deleting old events below
+                        if (pump.getLastQueryTS() == lastQueryTS || !pump.isValid()) {
+                            return;
+                        }
+
+                        lastQueryTS = pump.getLastQueryTS();
+
+                        // 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();
+                                }
+                            });
+                        }
+
+                        // TODO - handle isOffline in NightscoutUploadIntentService?
+                        refreshDisplay();
+                    }
+                });
             }
         }
 
@@ -513,7 +696,7 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
             }
         }
 
-        private Queue<StatusMessage> messages = new ArrayBlockingQueue<>(10);
+        private Queue<StatusMessage> messages = new ArrayBlockingQueue<>(400);
 
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -521,7 +704,7 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
             Log.i(TAG, "Message Receiver: " + message);
 
             synchronized (messages) {
-                while (messages.size() > 8) {
+                while (messages.size() > 398) {
                     messages.poll();
                 }
                 messages.add(new StatusMessage(message));
@@ -553,7 +736,7 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
             TextView textViewBg = (TextView) findViewById(R.id.textview_bg);
             TextView textViewBgTime = (TextView) findViewById(R.id.textview_bg_time);
             TextView textViewUnits = (TextView) findViewById(R.id.textview_units);
-            if (prefs.getBoolean("mmolxl", false)) {
+            if (mmolxl) {
                 textViewUnits.setText(R.string.text_unit_mmolxl);
             } else {
                 textViewUnits.setText(R.string.text_unit_mgxdl);
@@ -566,122 +749,181 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
 
             PumpInfo pump = getActivePump();
 
-            if (pump != null && pump.isValid()) {
+            if (pump != null && pump.isValid() && pump.getPumpHistory().size() > 0) {
                 pumpStatusData = pump.getPumpHistory().last();
             }
 
             // FIXME - grab the last item from the activePump's getPumpHistory
-            RealmResults<PumpStatusEvent> results =
-                    mRealm.where(PumpStatusEvent.class)
-                            .findAllSorted("eventDate", Sort.ASCENDING);
+            updateChart(mRealm.where(PumpStatusEvent.class)
+                    .greaterThan("eventDate", new Date(System.currentTimeMillis() - 1000*60*60*24))
+                    .findAllSorted("eventDate", Sort.ASCENDING));
 
-            if (pumpStatusData == null) {
-                return;
-            }
+            if (pumpStatusData != null) {
 
-            DecimalFormat df;
-            if (prefs.getBoolean("mmolDecimals", false))
-                df = new DecimalFormat("0.00");
-            else
-                df = new DecimalFormat("0.0");
+                String sgvString;
+                if (mmolxl) {
+                    float fBgValue = (float) pumpStatusData.getSgv();
+                    sgvString = sgvFormatter.format(fBgValue / MMOLXLFACTOR);
+                    Log.d(TAG, sgvString + " mmol/L");
+                } else {
+                    sgvString = String.valueOf(pumpStatusData.getSgv());
+                    Log.d(TAG, sgvString + " mg/dL");
+                }
 
-            String sgvString, units;
-            if (prefs.getBoolean("mmolxl", false)) {
-                float fBgValue = (float) pumpStatusData.getSgv();
-                sgvString = df.format(fBgValue / 18.016f);
-                units = "mmol/L";
-                Log.d(TAG, "mmolxl true --> " + sgvString);
+                textViewBg.setText(sgvString);
+                textViewBgTime.setText(DateUtils.getRelativeTimeSpanString(pumpStatusData.getEventDate().getTime()));
+                textViewTrend.setText(Html.fromHtml(renderTrendHtml(pumpStatusData.getCgmTrend())));
+                textViewIOB.setText(String.format(Locale.getDefault(), "%.2f", pumpStatusData.getActiveInsulin()));
+
+                ActionMenuItemView batIcon = ((ActionMenuItemView) findViewById(R.id.status_battery));
+                if (batIcon != null) {
+                    switch (pumpStatusData.getBatteryPercentage()) {
+                        case 0:
+                            batIcon.setTitle("0%");
+                            batIcon.setIcon(getResources().getDrawable(R.drawable.battery_0));
+                            break;
+                        case 25:
+                            batIcon.setTitle("25%");
+                            batIcon.setIcon(getResources().getDrawable(R.drawable.battery_25));
+                            break;
+                        case 50:
+                            batIcon.setTitle("50%");
+                            batIcon.setIcon(getResources().getDrawable(R.drawable.battery_50));
+                            break;
+                        case 75:
+                            batIcon.setTitle("75%");
+                            batIcon.setIcon(getResources().getDrawable(R.drawable.battery_75));
+                            break;
+                        case 100:
+                            batIcon.setTitle("100%");
+                            batIcon.setIcon(getResources().getDrawable(R.drawable.battery_100));
+                            break;
+                        default:
+                            batIcon.setTitle(getResources().getString(R.string.menu_name_status));
+                            batIcon.setIcon(getResources().getDrawable(R.drawable.battery_unknown));
+                    }
+                }
 
-            } else {
-                sgvString = String.valueOf(pumpStatusData.getSgv());
-                units = "mg/dL";
-                Log.d(TAG, "mmolxl false --> " + sgvString);
             }
 
-            textViewBg.setText(sgvString);
-            textViewUnits.setText(units);
-            textViewBgTime.setText(DateUtils.getRelativeTimeSpanString(pumpStatusData.getEventDate().getTime()));
-            textViewTrend.setText(Html.fromHtml(renderTrendHtml(pumpStatusData.getCgmTrend())));
-            textViewIOB.setText(String.format(Locale.getDefault(), "%.2f", pumpStatusData.getActiveInsulin()));
-
-            // TODO - waiting for MPAndroidCharts 3.0.0. This will fix:
-            // Date support
-            // Realm v1.0.0 support
-            //updateChart(results);
-
             // Run myself again in 60 seconds;
             mUiRefreshHandler.postDelayed(this, 60000L);
         }
 
         private void updateChart(RealmResults<PumpStatusEvent> results) {
-            RealmLineDataSet<PumpStatusEvent> lineDataSet = new RealmLineDataSet<>(results, "eventDate", "sgv");
-
-            lineDataSet.setDrawCircleHole(false);
-            lineDataSet.setColor(ColorTemplate.rgb("#FF5722"));
-            lineDataSet.setCircleColor(ColorTemplate.rgb("#FF5722"));
-            lineDataSet.setLineWidth(1.8f);
-            lineDataSet.setCircleSize(3.6f);
-
-            ArrayList<ILineDataSet> dataSets = new ArrayList<ILineDataSet>();
-            dataSets.add(lineDataSet);
 
-            LineData lineData = new LineData(dataSets);
+            mChart.getGridLabelRenderer().setNumHorizontalLabels(6);
 
-            // set data
-            mChart.setMinimumHeight(200);
-            mChart.setData(lineData);
-        }
-    }
+            int size = results.size();
+            if (size == 0) {
+                final long now = System.currentTimeMillis(),
+                        left = now - chartZoom * 60 * 60 * 1000;
 
-    private class RefreshDataReceiver extends BroadcastReceiver {
+                mChart.getViewport().setXAxisBoundsManual(true);
+                mChart.getViewport().setMaxX(now);
+                mChart.getViewport().setMinX(left);
 
-        @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()) {
+                mChart.getViewport().setYAxisBoundsManual(true);
+                if (mmolxl) {
+                    mChart.getViewport().setMinY(80 / MMOLXLFACTOR);
+                    mChart.getViewport().setMaxY(120 / MMOLXLFACTOR);
+                } else {
+                    mChart.getViewport().setMinY(80);
+                    mChart.getViewport().setMaxY(120);
+                }
+                mChart.postInvalidate();
                 return;
             }
 
-            PumpStatusEvent pumpStatusData = null;
+            DataPoint[] entries = new DataPoint[size];
+            final long left = System.currentTimeMillis() - chartZoom * 60 * 60 * 1000;
 
-            PumpInfo pump = getActivePump();
+            int pos = 0;
+            for (PumpStatusEvent pumpStatus: results) {
+                // turn your data into Entry objects
+                int sgv = pumpStatus.getSgv();
 
-            if (pump != null && pump.isValid()) {
-                pumpStatusData = pump.getPumpHistory().last();
-            } else {
-                return;
+                if (mmolxl) {
+                    entries[pos++] = new DataPoint(pumpStatus.getEventDate(), (float) pumpStatus.getSgv() / MMOLXLFACTOR);
+                } else {
+                    entries[pos++] = new DataPoint(pumpStatus.getEventDate(), (float) pumpStatus.getSgv());
+                }
             }
 
-            long nextPoll = pumpStatusData.getEventDate().getTime() + pumpStatusData.getPumpTimeOffset()
-                    + MedtronicCnlIntentService.POLL_GRACE_PERIOD_MS + MedtronicCnlIntentService.POLL_PERIOD_MS;
-            startCgmService(nextPoll);
-
-            // 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() {
+            if (mChart.getSeries().size() == 0) {
+//                long now = System.currentTimeMillis();
+//                entries = new DataPoint[1000];
+//                int j = 0;
+//                for(long i = now - 24*60*60*1000; i < now - 30*60*1000; i+= 5*60*1000) {
+//                    entries[j++] = new DataPoint(i, (float) (Math.random()*200 + 89));
+//                }
+//                entries = Arrays.copyOfRange(entries, 0, j);
+
+                PointsGraphSeries sgvSerie = new PointsGraphSeries(entries);
+//                sgvSerie.setSize(3.6f);
+//                sgvSerie.setColor(Color.LTGRAY);
+
+
+                sgvSerie.setOnDataPointTapListener(new OnDataPointTapListener() {
+                    DateFormat mFormat = DateFormat.getTimeInstance(DateFormat.MEDIUM);
+
                     @Override
-                    public void execute(Realm realm) {
-                        // Delete all matches
-                        Log.d(TAG, "Deleting " + results.size() + " records from realm");
-                        results.deleteAllFromRealm();
+                    public void onTap(Series series, DataPointInterface dataPoint) {
+                        double sgv = dataPoint.getY();
+
+                        StringBuilder sb = new StringBuilder(mFormat.format(new Date((long) dataPoint.getX())) + ": ");
+                        sb.append(sgvFormatter.format(sgv));
+                        Toast.makeText(getBaseContext(), sb.toString(), Toast.LENGTH_SHORT).show();
                     }
                 });
+
+                sgvSerie.setCustomShape(new PointsGraphSeries.CustomShape() {
+                    @Override
+                    public void draw(Canvas canvas, Paint paint, float x, float y, DataPointInterface dataPoint) {
+                        double sgv = dataPoint.getY();
+                        if (sgv < (mmolxl?4.5:80))
+                            paint.setColor(Color.RED);
+                        else if (sgv <= (mmolxl?10:180))
+                            paint.setColor(Color.GREEN);
+                        else if (sgv <= (mmolxl?14:260))
+                            paint.setColor(Color.YELLOW);
+                        else
+                            paint.setColor(Color.RED);
+                        canvas.drawCircle(x, y, 3.6f, paint);
+                    }
+                });
+
+                mChart.getViewport().setYAxisBoundsManual(false);
+                mChart.addSeries(sgvSerie);
+            } else {
+                if (entries.length > 0) {
+                    ((PointsGraphSeries) mChart.getSeries().get(0)).resetData(entries);
+                }
             }
 
-            // TODO - handle isOffline in NightscoutUploadIntentService?
-            uploadCgmData();
+            // set vieport to latest SGV
+            long lastSGVTimestamp = (long) mChart.getSeries().get(0).getHighestValueX();
+            if (!hasZoomedChart) {
+                mChart.getViewport().setMaxX(lastSGVTimestamp);
+                mChart.getViewport().setMinX(lastSGVTimestamp - chartZoom * 60 * 60 * 1000);
+            }
+        }
+    }
 
+    /**
+     * has to be done in MainActivity thread
+     */
+    private class UpdatePumpReceiver extends BroadcastReceiver {
 
-            refreshDisplay();
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // If the MainActivity has already been destroyed (meaning the Realm instance has been closed)
+            // then don't worry about processing this broadcast
+            if (mRealm.isClosed()) {
+                return;
+            }
+            //init local pump listener
+            getActivePump();
         }
     }
 
@@ -703,7 +945,7 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
 
                 if (hasUsbPermission()) {
                     // Give the USB a little time to warm up first
-                    startCgmService(System.currentTimeMillis() + MedtronicCnlIntentService.USB_WARMUP_TIME_MS);
+                    startCgmServiceDelayed(MedtronicCnlIntentService.USB_WARMUP_TIME_MS);
                 } else {
                     Log.d(TAG, "No permission for USB. Waiting.");
                     waitForUsbPermission();
@@ -732,4 +974,5 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
             }
         }
     }
+
 }
diff --git a/app/src/main/java/info/nightscout/android/medtronic/ManageCNLActivity.java b/app/src/main/java/info/nightscout/android/medtronic/ManageCNLActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..b995c0d92f014672185beb3e0f4812da02682aaa
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/ManageCNLActivity.java
@@ -0,0 +1,156 @@
+package info.nightscout.android.medtronic;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.mikepenz.google_material_typeface_library.GoogleMaterial;
+import com.mikepenz.iconics.IconicsDrawable;
+
+import java.util.ArrayList;
+
+import info.nightscout.android.R;
+import info.nightscout.android.model.medtronicNg.ContourNextLinkInfo;
+import io.realm.Realm;
+import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper;
+
+/**
+ * A login screen that offers login via username/password.
+ */
+public class ManageCNLActivity extends AppCompatActivity {
+    private Realm mRealm;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_manage_cnl);
+
+        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+
+        if (toolbar != null) {
+            setSupportActionBar(toolbar);
+            getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+            getSupportActionBar().setHomeAsUpIndicator(
+                    new IconicsDrawable(this)
+                            .icon(GoogleMaterial.Icon.gmd_arrow_back)
+                            .color(Color.WHITE)
+                            .sizeDp(24)
+            );
+            getSupportActionBar().setElevation(0);
+            getSupportActionBar().setTitle("Manage your devices");
+        }
+
+        mRealm = Realm.getDefaultInstance();
+
+        //generate list
+        ArrayList<ContourNextLinkInfo> list = new ArrayList<ContourNextLinkInfo>();
+
+        list.addAll(mRealm.where(ContourNextLinkInfo.class).findAll());
+
+        //instantiate custom adapter
+        CNLAdapter adapter = new CNLAdapter(list, this);
+
+        //handle listview and assign adapter
+        ListView lView = (ListView) findViewById(R.id.cnl_list);
+        lView.setAdapter(adapter);
+        lView.addHeaderView(getLayoutInflater().inflate(R.layout.manage_cnl_listview_header, null));
+        lView.setEmptyView(findViewById(R.id.manage_cnl_listview_empty)); //getLayoutInflater().inflate(R.layout.manage_cnl_listview_empty, null));
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case android.R.id.home:
+                // avoid memory leaks
+                mRealm = null;
+                finish();
+                break;
+        }
+        return true;
+    }
+
+    @Override
+    protected void attachBaseContext(Context newBase) {
+        super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase));
+    }
+
+    private class CNLAdapter extends BaseAdapter implements ListAdapter {
+        private ArrayList<ContourNextLinkInfo> list = new ArrayList<>();
+        private Context context;
+
+        public CNLAdapter(ArrayList<ContourNextLinkInfo> list, Context context) {
+            this.list = list;
+            this.context = context;
+        }
+
+        @Override
+        public int getCount() {
+            return list.size();
+        }
+
+        @Override
+        public Object getItem(int pos) {
+            return list.get(pos);
+        }
+
+        @Override
+        public long getItemId(int pos) {
+            return pos; //list.get(pos).getSerialNumber();
+            //just return 0 if your list items do not have an Id variable.
+        }
+
+        @Override
+        public View getView(final int position, View convertView, ViewGroup parent) {
+            View view = convertView;
+            if (view == null) {
+                LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+                view = inflater.inflate(R.layout.cnl_item, null);
+            }
+
+            //Handle TextView and display string from your list
+            TextView listItemText = (TextView) view.findViewById(R.id.cnl_mac);
+            listItemText.setText(list.get(position).getSerialNumber());
+
+            //Handle buttons and add onClickListeners
+            Button deleteBtn = (Button) view.findViewById(R.id.delete_btn);
+
+            deleteBtn.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    // deleting CNL form database
+                    mRealm.executeTransaction(new Realm.Transaction() {
+                        @Override
+                        public void execute(Realm realm) {
+                            ContourNextLinkInfo cnlToDelete = Realm.getDefaultInstance().where(ContourNextLinkInfo.class).equalTo("serialNumber", list.get(position).getSerialNumber()).findFirst();
+                            cnlToDelete.deleteFromRealm();
+                            list.remove(position);
+                            notifyDataSetChanged();
+                        }
+                    });
+
+                }
+            });
+
+            return view;
+        }
+    }
+}
\ No newline at end of file
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 6dd908f0dd3f57b8d8c585d51690eaee4b82df5b..6763c82d683bcd7daf32ba61f95294216a4a2a70 100644
--- a/app/src/main/java/info/nightscout/android/medtronic/MedtronicCnlReader.java
+++ b/app/src/main/java/info/nightscout/android/medtronic/MedtronicCnlReader.java
@@ -4,87 +4,58 @@ 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.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
-import java.util.Locale;
 import java.util.concurrent.TimeoutException;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 import info.nightscout.android.USB.UsbHidDriver;
 import info.nightscout.android.medtronic.message.BeginEHSMMessage;
-import info.nightscout.android.medtronic.message.ChannelNegotiateMessage;
-import info.nightscout.android.medtronic.message.ChecksumException;
-import info.nightscout.android.medtronic.message.ContourNextLinkBinaryMessage;
+import info.nightscout.android.medtronic.message.ChannelNegotiateRequestMessage;
+import info.nightscout.android.medtronic.message.ChannelNegotiateResponseMessage;
+import info.nightscout.android.medtronic.exception.ChecksumException;
+import info.nightscout.android.medtronic.message.CloseConnectionRequestMessage;
 import info.nightscout.android.medtronic.message.ContourNextLinkCommandMessage;
-import info.nightscout.android.medtronic.message.ContourNextLinkMessage;
-import info.nightscout.android.medtronic.message.ContourNextLinkMessageHandler;
-import info.nightscout.android.medtronic.message.EncryptionException;
+import info.nightscout.android.medtronic.message.DeviceInfoRequestCommandMessage;
+import info.nightscout.android.medtronic.message.DeviceInfoResponseCommandMessage;
+import info.nightscout.android.medtronic.exception.EncryptionException;
 import info.nightscout.android.medtronic.message.EndEHSMMessage;
-import info.nightscout.android.medtronic.message.MedtronicMessage;
-import info.nightscout.android.medtronic.message.MessageUtils;
+import info.nightscout.android.medtronic.message.OpenConnectionRequestMessage;
 import info.nightscout.android.medtronic.message.PumpBasalPatternRequestMessage;
 import info.nightscout.android.medtronic.message.PumpBasalPatternResponseMessage;
 import info.nightscout.android.medtronic.message.PumpStatusRequestMessage;
 import info.nightscout.android.medtronic.message.PumpStatusResponseMessage;
 import info.nightscout.android.medtronic.message.PumpTimeRequestMessage;
 import info.nightscout.android.medtronic.message.PumpTimeResponseMessage;
+import info.nightscout.android.medtronic.message.ReadHistoryInfoRequestMessage;
+import info.nightscout.android.medtronic.message.ReadHistoryInfoResponseMessage;
+import info.nightscout.android.medtronic.message.ReadInfoRequestMessage;
 import info.nightscout.android.medtronic.message.ReadInfoResponseMessage;
+import info.nightscout.android.medtronic.message.RequestLinkKeyRequestMessage;
 import info.nightscout.android.medtronic.message.RequestLinkKeyResponseMessage;
-import info.nightscout.android.medtronic.message.UnexpectedMessageException;
+import info.nightscout.android.medtronic.exception.UnexpectedMessageException;
 import info.nightscout.android.model.medtronicNg.PumpStatusEvent;
-import info.nightscout.android.utils.HexDump;
 
 /**
  * Created by lgoedhart on 24/03/2016.
  */
-public class MedtronicCnlReader implements ContourNextLinkMessageHandler {
-
+public class MedtronicCnlReader {
     private static final String TAG = MedtronicCnlReader.class.getSimpleName();
 
-    private static final int USB_BLOCKSIZE = 64;
-    private static final int READ_TIMEOUT_MS = 5000;
-    private static final String BAYER_USB_HEADER = "ABC";
-
     private static final byte[] RADIO_CHANNELS = {0x14, 0x11, 0x0e, 0x17, 0x1a};
     private UsbHidDriver mDevice;
 
     private MedtronicCnlSession mPumpSession = new MedtronicCnlSession();
-
     private String mStickSerial = null;
 
+    private static final int SLEEP_MS = 500;
+
     public MedtronicCnlReader(UsbHidDriver device) {
         mDevice = device;
     }
 
-    private static PumpStatusEvent.CGM_TREND fromMessageByte(byte messageByte) {
-        switch (messageByte) {
-            case (byte) 0x60:
-                return PumpStatusEvent.CGM_TREND.FLAT;
-            case (byte) 0xc0:
-                return PumpStatusEvent.CGM_TREND.DOUBLE_UP;
-            case (byte) 0xa0:
-                return PumpStatusEvent.CGM_TREND.SINGLE_UP;
-            case (byte) 0x80:
-                return PumpStatusEvent.CGM_TREND.FOURTY_FIVE_UP;
-            case (byte) 0x40:
-                return PumpStatusEvent.CGM_TREND.FOURTY_FIVE_DOWN;
-            case (byte) 0x20:
-                return PumpStatusEvent.CGM_TREND.SINGLE_DOWN;
-            case (byte) 0x00:
-                return PumpStatusEvent.CGM_TREND.DOUBLE_DOWN;
-            default:
-                return PumpStatusEvent.CGM_TREND.NOT_COMPUTABLE;
-        }
-    }
-
     public String getStickSerial() {
         return mStickSerial;
     }
@@ -93,198 +64,75 @@ public class MedtronicCnlReader implements ContourNextLinkMessageHandler {
         return mPumpSession;
     }
 
-    public byte[] readMessage() throws IOException, TimeoutException {
-        ByteArrayOutputStream responseMessage = new ByteArrayOutputStream();
-
-        byte[] responseBuffer = new byte[USB_BLOCKSIZE];
-        int bytesRead;
-        int messageSize = 0;
-
-        do {
-            bytesRead = mDevice.read(responseBuffer, READ_TIMEOUT_MS);
-
-            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);
-
-        String responseString = HexDump.dumpHexString(responseMessage.toByteArray());
-        Log.d(TAG, "READ: " + responseString);
-
-        return responseMessage.toByteArray();
-    }
-
-    @Override
-    public void sendMessage(ContourNextLinkMessage message) throws IOException {
-        sendMessage(message.encode());
-        if (message instanceof ContourNextLinkBinaryMessage) {
-            mPumpSession.incrBayerSequenceNumber();
-        }
+    public void requestDeviceInfo()
+            throws IOException, TimeoutException, UnexpectedMessageException, ChecksumException, EncryptionException {
+        DeviceInfoResponseCommandMessage response = new DeviceInfoRequestCommandMessage().send(mDevice);
 
-        if (message instanceof MedtronicMessage) {
-            mPumpSession.incrMedtronicSequenceNumber();
-        }
+        //TODO - extract more details form the device info.
+        mStickSerial = response.getSerial();
     }
 
-    @Override
-    public ContourNextLinkMessage receiveMessage() {
-        return null;
-    }
+    public void enterControlMode() throws IOException, TimeoutException, UnexpectedMessageException, ChecksumException, EncryptionException {
+        boolean doRetry;
 
-    public void sendMessage(byte[] message) throws IOException {
-
-        int pos = 0;
-
-        while (message.length > pos) {
-            ByteBuffer outputBuffer = ByteBuffer.allocate(USB_BLOCKSIZE);
-            int sendLength = (pos + 60 > message.length) ? message.length - pos : 60;
-            outputBuffer.put(BAYER_USB_HEADER.getBytes());
-            outputBuffer.put((byte) sendLength);
-            outputBuffer.put(message, pos, sendLength);
-
-            mDevice.write(outputBuffer.array(), 200);
-            pos += sendLength;
-
-            String outputString = HexDump.dumpHexString(outputBuffer.array());
-            Log.d(TAG, "WRITE: " + outputString);
-        }
-    }
-
-    // TODO - get rid of this - it should be in a message decoder
-    private void checkControlMessage(byte[] msg, byte controlCharacter) throws IOException, TimeoutException, UnexpectedMessageException {
-        if (msg.length != 1 || msg[0] != controlCharacter) {
-            throw new UnexpectedMessageException(String.format(Locale.getDefault(), "Expected to get control character '%d' Got '%d'.",
-                    (int) controlCharacter, (int) msg[0]));
-        }
-    }
-
-    public void requestDeviceInfo() throws IOException, TimeoutException, UnexpectedMessageException {
-        new ContourNextLinkCommandMessage("X").send(this);
-
-        boolean doRetry = false;
-
-        // TODO - parse this into an ASTM record for the device info.
-        try {
-            // The stick will return either the ASTM message, or the ENQ first. The order can change,
-            // so we need to handle both cases
-            byte[] response1 = readMessage();
-            byte[] response2 = readMessage();
-
-            if (response1[0] == ASCII.EOT.value) {
-                // response 1 is the ASTM message
-                checkControlMessage(response2, ASCII.ENQ.value);
-                extractStickSerial(new String(response1));
-            } else {
-                // response 2 is the ASTM message
-                checkControlMessage(response1, ASCII.ENQ.value);
-                extractStickSerial(new String(response2));
-            }
-        } catch (TimeoutException e) {
-            // Terminate comms with the pump, then try again
-            new ContourNextLinkCommandMessage(ASCII.EOT.value).send(this);
-            doRetry = true;
-        } finally {
-            if (doRetry) {
-                requestDeviceInfo();
-            }
-        }
-    }
-
-    private void extractStickSerial(String astmMessage) {
-        Pattern pattern = Pattern.compile(".*?\\^(\\d{4}-\\d{7})\\^.*");
-        Matcher matcher = pattern.matcher(astmMessage);
-        if (matcher.find()) {
-            mStickSerial = matcher.group(1);
-        }
-    }
-
-    public void enterControlMode() throws IOException, TimeoutException, UnexpectedMessageException {
-        boolean doRetry = false;
-
-        try {
-            new ContourNextLinkCommandMessage(ASCII.NAK.value).send(this);
-            checkControlMessage(readMessage(), ASCII.EOT.value);
-            new ContourNextLinkCommandMessage(ASCII.ENQ.value).send(this);
-            checkControlMessage(readMessage(), ASCII.ACK.value);
-        } catch (UnexpectedMessageException e2) {
-            // Terminate comms with the pump, then try again
-            new ContourNextLinkCommandMessage(ASCII.EOT.value).send(this);
-            doRetry = true;
-        } finally {
-            if (doRetry) {
-                enterControlMode();
+        do {
+            doRetry = false;
+            try {
+                new ContourNextLinkCommandMessage(ContourNextLinkCommandMessage.ASCII.NAK)
+                        .send(mDevice, SLEEP_MS).checkControlMessage(ContourNextLinkCommandMessage.ASCII.EOT);
+                new ContourNextLinkCommandMessage(ContourNextLinkCommandMessage.ASCII.ENQ)
+                        .send(mDevice, SLEEP_MS).checkControlMessage(ContourNextLinkCommandMessage.ASCII.ACK);
+            } catch (UnexpectedMessageException e2) {
+                try {
+                    new ContourNextLinkCommandMessage(ContourNextLinkCommandMessage.ASCII.EOT).send(mDevice);
+                } catch (IOException e) {}
+                finally {
+                    doRetry = true;
+                }
             }
-        }
+        } while (doRetry);
     }
 
-    public void enterPassthroughMode() throws IOException, TimeoutException, UnexpectedMessageException {
+    public void enterPassthroughMode() throws IOException, TimeoutException, UnexpectedMessageException, ChecksumException, EncryptionException {
         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);
+        new ContourNextLinkCommandMessage("W|")
+                .send(mDevice, SLEEP_MS).checkControlMessage(ContourNextLinkCommandMessage.ASCII.ACK);
+        new ContourNextLinkCommandMessage("Q|")
+                .send(mDevice, SLEEP_MS).checkControlMessage(ContourNextLinkCommandMessage.ASCII.ACK);
+        new ContourNextLinkCommandMessage("1|")
+                .send(mDevice, SLEEP_MS).checkControlMessage(ContourNextLinkCommandMessage.ASCII.ACK);
         Log.d(TAG, "Finished enterPasshtroughMode");
     }
 
-    public void openConnection() throws IOException, TimeoutException, NoSuchAlgorithmException {
+    public void openConnection() throws IOException, TimeoutException, NoSuchAlgorithmException, ChecksumException, EncryptionException, UnexpectedMessageException {
         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();
+        new OpenConnectionRequestMessage(mPumpSession, mPumpSession.getHMAC()).send(mDevice);
         Log.d(TAG, "Finished openConnection");
     }
 
-    public void requestReadInfo() throws IOException, TimeoutException, EncryptionException, ChecksumException {
+    public void requestReadInfo() throws IOException, TimeoutException, EncryptionException, ChecksumException, UnexpectedMessageException {
         Log.d(TAG, "Begin requestReadInfo");
-        new ContourNextLinkBinaryMessage(ContourNextLinkBinaryMessage.CommandType.READ_INFO, mPumpSession, null).send(this);
-
-        ContourNextLinkMessage response = ReadInfoResponseMessage.fromBytes(mPumpSession, readMessage());
+        ReadInfoResponseMessage response = new ReadInfoRequestMessage(mPumpSession).send(mDevice);
 
-        // FIXME - this needs to go into ReadInfoResponseMessage
-        ByteBuffer infoBuffer = ByteBuffer.allocate(16);
-        infoBuffer.order(ByteOrder.BIG_ENDIAN);
-        infoBuffer.put(response.encode(), 0x21, 16);
-        long linkMAC = infoBuffer.getLong(0);
-        long pumpMAC = infoBuffer.getLong(8);
+        long linkMAC = response.getLinkMAC();
+        long pumpMAC = response.getPumpMAC();
 
         this.getPumpSession().setLinkMAC(linkMAC);
         this.getPumpSession().setPumpMAC(pumpMAC);
-        Log.d(TAG, String.format("Finished requestReadInfo. linkMAC = '%d', pumpMAC = '%d", linkMAC, pumpMAC));
+        Log.d(TAG, String.format("Finished requestReadInfo. linkMAC = '%s', pumpMAC = '%s'",
+                Long.toHexString(linkMAC), Long.toHexString(pumpMAC)));
     }
 
-    public void requestLinkKey() throws IOException, TimeoutException, EncryptionException, ChecksumException {
+    public void requestLinkKey() throws IOException, TimeoutException, EncryptionException, ChecksumException, UnexpectedMessageException {
         Log.d(TAG, "Begin requestLinkKey");
-        new ContourNextLinkBinaryMessage(ContourNextLinkBinaryMessage.CommandType.REQUEST_LINK_KEY, mPumpSession, null).send(this);
 
-        ContourNextLinkMessage response = RequestLinkKeyResponseMessage.fromBytes(mPumpSession, readMessage());
-
-        // FIXME - this needs to go into RequestLinkKeyResponseMessage
-        ByteBuffer infoBuffer = ByteBuffer.allocate(55);
-        infoBuffer.order(ByteOrder.BIG_ENDIAN);
-        infoBuffer.put(response.encode(), 0x21, 55);
-
-        byte[] packedLinkKey = infoBuffer.array();
-
-        this.getPumpSession().setPackedLinkKey(packedLinkKey);
+        RequestLinkKeyResponseMessage response = new RequestLinkKeyRequestMessage(mPumpSession).send(mDevice);
+        this.getPumpSession().setKey(response.getKey());
 
         Log.d(TAG, String.format("Finished requestLinkKey. linkKey = '%s'", this.getPumpSession().getKey()));
     }
 
-    public byte negotiateChannel(byte lastRadioChannel) throws IOException, ChecksumException, TimeoutException {
+    public byte negotiateChannel(byte lastRadioChannel) throws IOException, ChecksumException, TimeoutException, EncryptionException {
         ArrayList<Byte> radioChannels = new ArrayList<>(Arrays.asList(ArrayUtils.toObject(RADIO_CHANNELS)));
 
         if (lastRadioChannel != 0x00) {
@@ -300,26 +148,14 @@ public class MedtronicCnlReader implements ContourNextLinkMessageHandler {
         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()) {
-                    break;
-                } else {
-                    throw new IOException(String.format(Locale.getDefault(), "Expected to get a message for channel %d. Got %d", mPumpSession.getRadioChannel(), responseBytes[76]));
-                }
+            ChannelNegotiateResponseMessage response = new ChannelNegotiateRequestMessage(mPumpSession).send(mDevice);
+
+            if (response.getRadioChannel() == mPumpSession.getRadioChannel()) {
+                mPumpSession.setRadioRSSI(response.getRadioRSSI());
+                break;
             } else {
-                mPumpSession.setRadioChannel((byte) 0);
+                mPumpSession.setRadioChannel((byte)0);
+                mPumpSession.setRadioRSSI((byte)0);
             }
         }
 
@@ -327,216 +163,101 @@ public class MedtronicCnlReader implements ContourNextLinkMessageHandler {
         return mPumpSession.getRadioChannel();
     }
 
-    public void beginEHSMSession() throws EncryptionException, IOException, TimeoutException {
+    public void beginEHSMSession() throws EncryptionException, IOException, TimeoutException, ChecksumException, UnexpectedMessageException {
         Log.d(TAG, "Begin beginEHSMSession");
-        new BeginEHSMMessage(mPumpSession).send(this);
-        // The Begin EHSM Session only has an 0x81 response
-        readMessage();
+        new BeginEHSMMessage(mPumpSession).send(mDevice);
         Log.d(TAG, "Finished beginEHSMSession");
     }
 
-    public Date getPumpTime() throws EncryptionException, IOException, ChecksumException, TimeoutException {
+    public Date getPumpTime() throws EncryptionException, IOException, ChecksumException, TimeoutException, UnexpectedMessageException {
         Log.d(TAG, "Begin getPumpTime");
-        // FIXME - throw if not in EHSM mode (add a state machine)
-
-        new PumpTimeRequestMessage(mPumpSession).send(this);
-        // Read the 0x81
-        readMessage();
 
-        // Read the 0x80
-        ContourNextLinkMessage response = PumpTimeResponseMessage.fromBytes(mPumpSession, readMessage());
-
-        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();
+        // CNL<-->PUMP comms can have occasional short lived noise causing errors, retrying once catches this
+        try {
+            PumpTimeResponseMessage response = new PumpTimeRequestMessage(mPumpSession).send(mDevice);
+            Log.d(TAG, "Finished getPumpTime with date " + response.getPumpTime());
+            return response.getPumpTime();
+        } catch (UnexpectedMessageException e) {
+            Log.e(TAG, "Unexpected Message", e);
+        } catch (TimeoutException e) {
+            Log.e(TAG, "Timeout communicating with the Contour Next Link.", e);
         }
 
-        // FIXME - this needs to go into PumpTimeResponseMessage
-        ByteBuffer dateBuffer = ByteBuffer.allocate(8);
-        dateBuffer.order(ByteOrder.BIG_ENDIAN);
-        dateBuffer.put(response.encode(), 0x3d, 8);
-        long rtc = dateBuffer.getInt(0) & 0x00000000ffffffffL;
-        long offset = dateBuffer.getInt(4);
+        PumpTimeResponseMessage response = new PumpTimeRequestMessage(mPumpSession).send(mDevice);
 
-        Log.d(TAG, "Finished getPumpTime with date " + MessageUtils.decodeDateTime(rtc, offset));
-        return MessageUtils.decodeDateTime(rtc, offset);
+        Log.d(TAG, "Finished getPumpTime with date " + response.getPumpTime());
+        return response.getPumpTime();
     }
 
-    public void getPumpStatus(PumpStatusEvent pumpRecord, 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);
-        // Read the 0x81
-        readMessage();
-
-        // Read the 0x80
-        ContourNextLinkMessage response = PumpStatusResponseMessage.fromBytes(mPumpSession, readMessage());
-
-        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;
-        }
+    public PumpStatusEvent updatePumpStatus(PumpStatusEvent pumpRecord) throws IOException, EncryptionException, ChecksumException, TimeoutException, UnexpectedMessageException {
+        Log.d(TAG, "Begin updatePumpStatus");
 
-        // FIXME - this needs to go into PumpStatusResponseMessage
-        ByteBuffer statusBuffer = ByteBuffer.allocate(96);
-        statusBuffer.order(ByteOrder.BIG_ENDIAN);
-        statusBuffer.put(response.encode(), 0x39, 96);
-
-        // Status Flags
-        pumpRecord.setSuspended((statusBuffer.get(0x03) & 0x01) != 0x00);
-        pumpRecord.setBolusing((statusBuffer.get(0x03) & 0x02) != 0x00);
-        pumpRecord.setDeliveringInsulin((statusBuffer.get(0x03) & 0x10) != 0x00);
-        pumpRecord.setTempBasalActive((statusBuffer.get(0x03) & 0x20) != 0x00);
-        pumpRecord.setCgmActive((statusBuffer.get(0x03) & 0x40) != 0x00);
-
-        // Active basal pattern
-        pumpRecord.setActiveBasalPattern(statusBuffer.get(0x1a));
-
-        // Normal basal rate
-        long rawNormalBasal = statusBuffer.getInt(0x1b);
-        pumpRecord.setBasalRate(new BigDecimal(rawNormalBasal / 10000f).setScale(3, BigDecimal.ROUND_HALF_UP).floatValue());
-
-        // Temp basal rate
-        // TODO - need to figure this one out
-        //long rawTempBasal = statusBuffer.getShort(0x21) & 0x0000ffff;
-        //pumpRecord.setTempBasalRate(new BigDecimal(rawTempBasal / 10000f).setScale(3, BigDecimal.ROUND_HALF_UP).floatValue());
-
-        // Temp basal percentage
-        pumpRecord.setTempBasalPercentage(statusBuffer.get(0x23));
-
-        // Temp basal minutes remaining
-        pumpRecord.setTempBasalMinutesRemaining((short) (statusBuffer.getShort(0x24) & 0x0000ffff));
-
-        // Units of insulin delivered as basal today
-        // TODO - is this basal? Do we have a total Units delivered elsewhere?
-        pumpRecord.setBasalUnitsDeliveredToday(statusBuffer.getInt(0x26));
-
-        // Pump battery percentage
-        pumpRecord.setBatteryPercentage((statusBuffer.get(0x2a)));
-
-        // Reservoir amount
-        long rawReservoirAmount = statusBuffer.getInt(0x2b);
-        pumpRecord.setReservoirAmount(new BigDecimal(rawReservoirAmount / 10000f).setScale(3, BigDecimal.ROUND_HALF_UP).floatValue());
-
-        // Amount of insulin left in pump (in minutes)
-        byte insulinHours = statusBuffer.get(0x2f);
-        byte insulinMinutes = statusBuffer.get(0x30);
-        pumpRecord.setMinutesOfInsulinRemaining((short) ((insulinHours * 60) + insulinMinutes));
-
-        // Active insulin
-        long rawActiveInsulin = statusBuffer.getShort(0x33) & 0x0000ffff;
-        pumpRecord.setActiveInsulin(new BigDecimal(rawActiveInsulin / 10000f).setScale(3, BigDecimal.ROUND_HALF_UP).floatValue());
-
-        // CGM SGV
-        pumpRecord.setSgv(statusBuffer.getShort(0x35) & 0x0000ffff); // In mg/DL. 0 means no CGM reading
-
-        // SGV Date
-        long rtc;
-        long offset;
-        if ((pumpRecord.getSgv() & 0x200) == 0x200) {
-            // Sensor error. Let's reset. FIXME - solve this more elegantly later
-            pumpRecord.setSgv(0);
-            rtc = 0;
-            offset = 0;
-            pumpRecord.setCgmTrend(PumpStatusEvent.CGM_TREND.NOT_SET);
-        } else {
-            rtc = statusBuffer.getInt(0x37) & 0x00000000ffffffffL;
-            offset = statusBuffer.getInt(0x3b);
-            pumpRecord.setCgmTrend(fromMessageByte(statusBuffer.get(0x40)));
+        // CNL<-->PUMP comms can have occasional short lived noise causing errors, retrying once catches this
+        try {
+            PumpStatusResponseMessage response = new PumpStatusRequestMessage(mPumpSession).send(mDevice);
+            response.updatePumpRecord(pumpRecord);
+            Log.d(TAG, "Finished updatePumpStatus");
+            return pumpRecord;
+        } catch (UnexpectedMessageException e) {
+            Log.e(TAG, "Unexpected Message", e);
+        } catch (TimeoutException e) {
+            Log.e(TAG, "Timeout communicating with the Contour Next Link.", e);
         }
-        // TODO - this should go in the sgvDate, and eventDate should be the time of this poll.
-        pumpRecord.setEventDate(new Date(MessageUtils.decodeDateTime(rtc, offset).getTime() - pumpTimeOffset));
 
-        // Predictive low suspend
-        // TODO - there is more status info in this byte other than just a boolean yes/no
-        pumpRecord.setLowSuspendActive(statusBuffer.get(0x3f) != 0);
+        PumpStatusResponseMessage response = new PumpStatusRequestMessage(mPumpSession).send(mDevice);
+        response.updatePumpRecord(pumpRecord);
 
-        // Recent Bolus Wizard BGL
-        pumpRecord.setRecentBolusWizard(statusBuffer.get(0x48) != 0);
-        pumpRecord.setBolusWizardBGL(statusBuffer.getShort(0x49) & 0x0000ffff); // In mg/DL
+        Log.d(TAG, "Finished updatePumpStatus");
 
-        Log.d(TAG, "Finished getPumpStatus");
+        return pumpRecord;
     }
 
-    public void getBasalPatterns() throws EncryptionException, IOException, ChecksumException, TimeoutException {
+    public void getBasalPatterns() throws EncryptionException, IOException, ChecksumException, TimeoutException, UnexpectedMessageException {
         Log.d(TAG, "Begin getBasalPatterns");
         // FIXME - throw if not in EHSM mode (add a state machine)
 
-        new PumpBasalPatternRequestMessage(mPumpSession).send(this);
-        // Read the 0x81
-        readMessage();
+        PumpBasalPatternResponseMessage response = new PumpBasalPatternRequestMessage(mPumpSession).send(mDevice);
 
-        // Read the 0x80
-        ContourNextLinkMessage response = PumpBasalPatternResponseMessage.fromBytes(mPumpSession, readMessage());
+        Log.d(TAG, "Finished getBasalPatterns");
+    }
 
-        // TODO - determine message validity
-        /*
-        if (response.encode().length < (61 + 8)) {
-            // Invalid message.
-            // TODO - deal with this more elegantly
-            Log.e(TAG, "Invalid message received for getBasalPatterns");
-            return;
-        }
-        */
 
-        // FIXME - this needs to go into PumpBasalPatternResponseMessage
-        ByteBuffer basalRatesBuffer = ByteBuffer.allocate(96);
-        basalRatesBuffer.order(ByteOrder.BIG_ENDIAN);
-        basalRatesBuffer.put(response.encode(), 0x39, 96);
+    public void getHistory() throws EncryptionException, IOException, ChecksumException, TimeoutException, UnexpectedMessageException {
+        Log.d(TAG, "Begin getHistory");
+        // FIXME - throw if not in EHSM mode (add a state machine)
 
-        Log.d(TAG, "Finished getBasalPatterns");
+        ReadHistoryInfoResponseMessage response = new ReadHistoryInfoRequestMessage(mPumpSession).send(mDevice);
+
+        Log.d(TAG, "Finished getHistory");
     }
 
-    public void endEHSMSession() throws EncryptionException, IOException, TimeoutException {
+    public void endEHSMSession() throws EncryptionException, IOException, TimeoutException, ChecksumException, UnexpectedMessageException {
         Log.d(TAG, "Begin endEHSMSession");
-        new EndEHSMMessage(mPumpSession).send(this);
-        // The End EHSM Session only has an 0x81 response
-        readMessage();
+        new EndEHSMMessage(mPumpSession).send(mDevice);
         Log.d(TAG, "Finished endEHSMSession");
     }
 
-    public void closeConnection() throws IOException, TimeoutException {
+    public void closeConnection() throws IOException, TimeoutException, ChecksumException, EncryptionException, NoSuchAlgorithmException, UnexpectedMessageException {
         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();
+        new CloseConnectionRequestMessage(mPumpSession, mPumpSession.getHMAC()).send(mDevice);
         Log.d(TAG, "Finished closeConnection");
     }
 
-    public void endPassthroughMode() throws IOException, TimeoutException, UnexpectedMessageException {
+    public void endPassthroughMode() throws IOException, TimeoutException, UnexpectedMessageException, ChecksumException, EncryptionException {
         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);
+        new ContourNextLinkCommandMessage("W|")
+                .send(mDevice, SLEEP_MS).checkControlMessage(ContourNextLinkCommandMessage.ASCII.ACK);
+        new ContourNextLinkCommandMessage("Q|")
+                .send(mDevice, SLEEP_MS).checkControlMessage(ContourNextLinkCommandMessage.ASCII.ACK);
+        new ContourNextLinkCommandMessage("0|")
+                .send(mDevice, SLEEP_MS).checkControlMessage(ContourNextLinkCommandMessage.ASCII.ACK);
         Log.d(TAG, "Finished endPassthroughMode");
     }
 
-    public void endControlMode() throws IOException, TimeoutException, UnexpectedMessageException {
+    public void endControlMode() throws IOException, TimeoutException, UnexpectedMessageException, ChecksumException, EncryptionException {
         Log.d(TAG, "Begin endControlMode");
-        new ContourNextLinkCommandMessage(ASCII.EOT.value).send(this);
-        checkControlMessage(readMessage(), ASCII.ENQ.value);
+        new ContourNextLinkCommandMessage(ContourNextLinkCommandMessage.ASCII.EOT)
+                .send(mDevice, SLEEP_MS).checkControlMessage(ContourNextLinkCommandMessage.ASCII.ENQ);
         Log.d(TAG, "Finished endControlMode");
     }
-
-    public enum ASCII {
-        STX(0x02),
-        EOT(0x04),
-        ENQ(0x05),
-        ACK(0x06),
-        NAK(0x15);
-
-        private byte value;
-
-        ASCII(int code) {
-            this.value = (byte) code;
-        }
-    }
 }
diff --git a/app/src/main/java/info/nightscout/android/medtronic/MedtronicCnlSession.java b/app/src/main/java/info/nightscout/android/medtronic/MedtronicCnlSession.java
index 215e7f3e6174163da35db8824c928d87f5d454bb..a86820f7b72797aa1a2f7c1c19dc5fb6034db430 100644
--- a/app/src/main/java/info/nightscout/android/medtronic/MedtronicCnlSession.java
+++ b/app/src/main/java/info/nightscout/android/medtronic/MedtronicCnlSession.java
@@ -11,7 +11,6 @@ import java.security.NoSuchAlgorithmException;
 public class MedtronicCnlSession {
     private static final String HMAC_PADDING = "A4BD6CED9A42602564F413123";
 
-    private byte[] HMAC;
     private byte[] key;
 
     private String stickSerial;
@@ -20,13 +19,11 @@ public class MedtronicCnlSession {
     private long pumpMAC;
 
     private byte radioChannel;
+    private byte radioRSSI;
+
     private int bayerSequenceNumber = 1;
     private int medtronicSequenceNumber = 1;
 
-    /*public byte[] getHMAC() {
-        return HMAC;
-    }*/
-
     public byte[] getHMAC() throws NoSuchAlgorithmException {
         String shortSerial = this.stickSerial.replaceAll("\\d+-", "");
         byte[] message = (shortSerial + HMAC_PADDING).getBytes();
@@ -80,6 +77,14 @@ public class MedtronicCnlSession {
         return radioChannel;
     }
 
+    public byte getRadioRSSI() {
+        return radioRSSI;
+    }
+
+    public int getRadioRSSIpercentage() {
+        return (((int) radioRSSI & 0x00FF) * 100) / 0xA8;
+    }
+
     public void incrBayerSequenceNumber() {
         bayerSequenceNumber++;
     }
@@ -92,34 +97,14 @@ public class MedtronicCnlSession {
         this.radioChannel = radioChannel;
     }
 
-    public void setHMAC(byte[] hmac) {
-        this.HMAC = hmac;
+    public void setRadioRSSI(byte radioRSSI) {
+        this.radioRSSI = radioRSSI;
     }
 
     public void setKey(byte[] key) {
         this.key = key;
     }
 
-    public void setPackedLinkKey(byte[] packedLinkKey) {
-        this.key = new byte[16];
-
-        int pos = this.stickSerial.charAt(this.stickSerial.length() - 1) & 7;
-
-        for (int i = 0; i < this.key.length; i++) {
-            if ((packedLinkKey[pos + 1] & 1) == 1) {
-                this.key[i] = (byte) ~packedLinkKey[pos];
-            } else {
-                this.key[i] = packedLinkKey[pos];
-            }
-
-            if (((packedLinkKey[pos + 1] >> 1) & 1) == 0) {
-                pos += 3;
-            } else {
-                pos += 2;
-            }
-        }
-    }
-
     public String getStickSerial() {
         return stickSerial;
     }
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/ChecksumException.java b/app/src/main/java/info/nightscout/android/medtronic/exception/ChecksumException.java
similarity index 77%
rename from app/src/main/java/info/nightscout/android/medtronic/message/ChecksumException.java
rename to app/src/main/java/info/nightscout/android/medtronic/exception/ChecksumException.java
index 1cdfe5341906933a69ece4385c4ea6d91c64bc1a..25418f212f5ddae2f52e3613eb5263f80821e49e 100644
--- a/app/src/main/java/info/nightscout/android/medtronic/message/ChecksumException.java
+++ b/app/src/main/java/info/nightscout/android/medtronic/exception/ChecksumException.java
@@ -1,4 +1,4 @@
-package info.nightscout.android.medtronic.message;
+package info.nightscout.android.medtronic.exception;
 
 /**
  * Created by lgoedhart on 26/03/2016.
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/EncryptionException.java b/app/src/main/java/info/nightscout/android/medtronic/exception/EncryptionException.java
similarity index 77%
rename from app/src/main/java/info/nightscout/android/medtronic/message/EncryptionException.java
rename to app/src/main/java/info/nightscout/android/medtronic/exception/EncryptionException.java
index a86847431c501032a8ed3c9c27bb1087bf0ee288..ccc1cb726fc7605f7b0a946329624101c41b2a4b 100644
--- a/app/src/main/java/info/nightscout/android/medtronic/message/EncryptionException.java
+++ b/app/src/main/java/info/nightscout/android/medtronic/exception/EncryptionException.java
@@ -1,4 +1,4 @@
-package info.nightscout.android.medtronic.message;
+package info.nightscout.android.medtronic.exception;
 
 /**
  * Created by lgoedhart on 26/03/2016.
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/UnexpectedMessageException.java b/app/src/main/java/info/nightscout/android/medtronic/exception/UnexpectedMessageException.java
similarity index 78%
rename from app/src/main/java/info/nightscout/android/medtronic/message/UnexpectedMessageException.java
rename to app/src/main/java/info/nightscout/android/medtronic/exception/UnexpectedMessageException.java
index 71ec46969f14e8caf316b27ed30c3666144df880..4d2daeb207cfed052e0dc76c14b57fed7fb4a701 100644
--- a/app/src/main/java/info/nightscout/android/medtronic/message/UnexpectedMessageException.java
+++ b/app/src/main/java/info/nightscout/android/medtronic/exception/UnexpectedMessageException.java
@@ -1,4 +1,4 @@
-package info.nightscout.android.medtronic.message;
+package info.nightscout.android.medtronic.exception;
 
 /**
  * Created by lgoedhart on 26/03/2016.
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/BeginEHSMMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/BeginEHSMMessage.java
index e2ee643a1a0d9f7de0a0c566c6d2d147ad6e3e19..3902beb03e6e87cdb322871d28fddb1ab80f3a85 100644
--- a/app/src/main/java/info/nightscout/android/medtronic/message/BeginEHSMMessage.java
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/BeginEHSMMessage.java
@@ -1,12 +1,18 @@
 package info.nightscout.android.medtronic.message;
 
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+import info.nightscout.android.USB.UsbHidDriver;
 import info.nightscout.android.medtronic.MedtronicCnlSession;
+import info.nightscout.android.medtronic.exception.ChecksumException;
+import info.nightscout.android.medtronic.exception.EncryptionException;
 
 /**
  * Created by lgoedhart on 26/03/2016.
  */
-public class BeginEHSMMessage extends MedtronicSendMessage {
-    public BeginEHSMMessage(MedtronicCnlSession pumpSession) throws EncryptionException {
+public class BeginEHSMMessage extends EHSMMessage {
+    public BeginEHSMMessage(MedtronicCnlSession pumpSession) throws EncryptionException, ChecksumException {
         super(SendMessageType.BEGIN_EHSM_SESSION, pumpSession, buildPayload());
     }
 
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/ChannelNegotiateMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/ChannelNegotiateMessage.java
deleted file mode 100644
index eda3ce6f6f514f3045396216dc658b65e825e5fc..0000000000000000000000000000000000000000
--- a/app/src/main/java/info/nightscout/android/medtronic/message/ChannelNegotiateMessage.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package info.nightscout.android.medtronic.message;
-
-import info.nightscout.android.medtronic.MedtronicCnlSession;
-
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-/**
- * Created by lgoedhart on 26/03/2016.
- */
-public class ChannelNegotiateMessage extends MedtronicMessage {
-    public ChannelNegotiateMessage(MedtronicCnlSession pumpSession) {
-        super(CommandType.SEND_MESSAGE, CommandAction.CHANNEL_NEGOTIATE, pumpSession, buildPayload(pumpSession));
-    }
-
-    protected static byte[] buildPayload( MedtronicCnlSession pumpSession ) {
-        ByteBuffer payload = ByteBuffer.allocate(26);
-        payload.order(ByteOrder.LITTLE_ENDIAN);
-        // The MedtronicMessage sequence number is always sent as 1 for this message,
-        // even though the sequence should keep incrementing as normal
-        payload.put((byte) 1);
-        payload.put(pumpSession.getRadioChannel());
-        byte[] unknownBytes = {0, 0, 0, 0x07, 0x07, 0, 0, 0x02};
-        payload.put(unknownBytes);
-        payload.putLong(pumpSession.getLinkMAC());
-        payload.putLong(pumpSession.getPumpMAC());
-
-        return payload.array();
-    }
-}
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/ChannelNegotiateRequestMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/ChannelNegotiateRequestMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..84dc029782c0306a1f86e70c19b467fc3e7bea11
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/ChannelNegotiateRequestMessage.java
@@ -0,0 +1,58 @@
+package info.nightscout.android.medtronic.message;
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.concurrent.TimeoutException;
+
+import info.nightscout.android.USB.UsbHidDriver;
+import info.nightscout.android.medtronic.MedtronicCnlSession;
+import info.nightscout.android.medtronic.exception.ChecksumException;
+import info.nightscout.android.medtronic.exception.EncryptionException;
+
+/**
+ * Created by lgoedhart on 26/03/2016.
+ */
+public class ChannelNegotiateRequestMessage extends MedtronicRequestMessage<ChannelNegotiateResponseMessage> {
+    private static final String TAG = ChannelNegotiateRequestMessage.class.getSimpleName();
+
+    public ChannelNegotiateRequestMessage(MedtronicCnlSession pumpSession) throws ChecksumException {
+        super(CommandType.SEND_MESSAGE, CommandAction.CHANNEL_NEGOTIATE, pumpSession, buildPayload(pumpSession));
+    }
+
+    @Override
+    public ChannelNegotiateResponseMessage send(UsbHidDriver mDevice) throws IOException, TimeoutException, ChecksumException, EncryptionException {
+        sendMessage(mDevice);
+
+        // Don't care what the 0x81 response message is at this stage
+        Log.d(TAG, "negotiateChannel: Reading 0x81 message");
+        readMessage(mDevice);
+        // The 0x80 message
+        Log.d(TAG, "negotiateChannel: Reading 0x80 message");
+        ChannelNegotiateResponseMessage response = this.getResponse(readMessage(mDevice));
+
+        return response;
+    }
+
+    @Override
+    protected ChannelNegotiateResponseMessage getResponse(byte[] payload) throws ChecksumException, EncryptionException, IOException {
+        return new ChannelNegotiateResponseMessage(mPumpSession, payload);
+    }
+
+    protected static byte[] buildPayload( MedtronicCnlSession pumpSession ) {
+        ByteBuffer payload = ByteBuffer.allocate(26);
+        payload.order(ByteOrder.LITTLE_ENDIAN);
+        // The MedtronicMessage sequence number is always sent as 1 for this message,
+        // even though the sequence should keep incrementing as normal
+        payload.put((byte) 1);
+        payload.put(pumpSession.getRadioChannel());
+        byte[] unknownBytes = {0, 0, 0, 0x07, 0x07, 0, 0, 0x02};
+        payload.put(unknownBytes);
+        payload.putLong(pumpSession.getLinkMAC());
+        payload.putLong(pumpSession.getPumpMAC());
+
+        return payload.array();
+    }
+}
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/ChannelNegotiateResponseMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/ChannelNegotiateResponseMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..54cff417066ebfb51163b38a485f447d1e55ae64
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/ChannelNegotiateResponseMessage.java
@@ -0,0 +1,46 @@
+package info.nightscout.android.medtronic.message;
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.Locale;
+
+import info.nightscout.android.medtronic.MedtronicCnlSession;
+import info.nightscout.android.medtronic.exception.ChecksumException;
+import info.nightscout.android.medtronic.exception.EncryptionException;
+
+/**
+ * Created by lgoedhart on 27/03/2016.
+ */
+public class ChannelNegotiateResponseMessage extends ContourNextLinkBinaryResponseMessage {
+    private static final String TAG = ChannelNegotiateResponseMessage.class.getSimpleName();
+
+    private byte radioChannel = 0;
+    private byte radioRSSI = 0;
+
+    protected ChannelNegotiateResponseMessage(MedtronicCnlSession pumpSession, byte[] payload) throws EncryptionException, ChecksumException, IOException {
+        super(payload);
+
+        byte[] responseBytes = this.encode();
+
+        Log.d(TAG, "negotiateChannel: Check response length");
+        if (responseBytes.length > 46) {
+            radioChannel = responseBytes[76];
+            radioRSSI = responseBytes[59];
+            if (responseBytes[76] != pumpSession.getRadioChannel()) {
+                throw new IOException(String.format(Locale.getDefault(), "Expected to get a message for channel %d. Got %d", pumpSession.getRadioChannel(), responseBytes[76]));
+            }
+        } else {
+            radioChannel = ((byte) 0);
+            radioRSSI = ((byte) 0);
+        }
+    }
+
+    public byte getRadioChannel() {
+        return radioChannel;
+    }
+
+    public byte getRadioRSSI() {
+        return radioRSSI;
+    }
+}
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/CloseConnectionRequestMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/CloseConnectionRequestMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..bc656cbd18a4b0c3177e339567f0d5c42d94d74a
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/CloseConnectionRequestMessage.java
@@ -0,0 +1,24 @@
+package info.nightscout.android.medtronic.message;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+import info.nightscout.android.USB.UsbHidDriver;
+import info.nightscout.android.medtronic.MedtronicCnlSession;
+import info.nightscout.android.medtronic.exception.ChecksumException;
+import info.nightscout.android.medtronic.exception.EncryptionException;
+
+/**
+ * Created by volker on 10.12.2016.
+ */
+
+public class CloseConnectionRequestMessage extends ContourNextLinkBinaryRequestMessage<CloseConnectionResponseMessage> {
+    public CloseConnectionRequestMessage(MedtronicCnlSession pumpSession, byte[] payload) throws ChecksumException {
+        super(CommandType.CLOSE_CONNECTION, pumpSession, payload);
+    }
+
+    @Override
+    protected CloseConnectionResponseMessage getResponse(byte[] payload) throws ChecksumException, EncryptionException, IOException {
+        return new CloseConnectionResponseMessage(payload);
+    }
+}
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/CloseConnectionResponseMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/CloseConnectionResponseMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..d1d101e925945018f5b3d87b5a2215acf99cbb14
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/CloseConnectionResponseMessage.java
@@ -0,0 +1,15 @@
+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;
+
+/**
+ * Created by lgoedhart on 10/05/2016.
+ */
+public class CloseConnectionResponseMessage extends ContourNextLinkBinaryResponseMessage {
+    protected CloseConnectionResponseMessage(byte[] payload) throws ChecksumException, EncryptionException {
+        super(payload);
+    }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkBinaryMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkBinaryRequestMessage.java
similarity index 58%
rename from app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkBinaryMessage.java
rename to app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkBinaryRequestMessage.java
index 95e94baa56ef73718e3db0d2d13e272f8b48a058..96e72ed8559b0dfbb6d472cc490b7db683dd7a06 100644
--- a/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkBinaryMessage.java
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkBinaryRequestMessage.java
@@ -1,47 +1,49 @@
 package info.nightscout.android.medtronic.message;
 
-import info.nightscout.android.medtronic.MedtronicCnlSession;
-
+import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.Locale;
 
+import info.nightscout.android.USB.UsbHidDriver;
+import info.nightscout.android.medtronic.MedtronicCnlSession;
+import info.nightscout.android.medtronic.exception.ChecksumException;
+
 /**
  * Created by lgoedhart on 26/03/2016.
  */
-public class ContourNextLinkBinaryMessage extends ContourNextLinkMessage{
-    //protected ByteBuffer mBayerEnvelope;
-    //protected ByteBuffer mBayerPayload;
-    //protected MedtronicCNLSession mPumpSession;
-    //protected CommandType mCommandType = CommandType.NO_TYPE;
-
-    static int ENVELOPE_SIZE = 33;
-
-    public enum CommandType {
-        NO_TYPE(0x0),
-        OPEN_CONNECTION(0x10),
-        CLOSE_CONNECTION(0x11),
-        SEND_MESSAGE(0x12),
-        READ_INFO(0x14),
-        REQUEST_LINK_KEY(0x16),
-        SEND_LINK_KEY(0x17),
-        RECEIVE_MESSAGE(0x80),
-        SEND_MESSAGE_RESPONSE(0x81),
-        REQUEST_LINK_KEY_RESPONSE(0x86);
-
-        private byte value;
-
-        CommandType(int commandType) {
-            value = (byte) commandType;
-        }
+public abstract class ContourNextLinkBinaryRequestMessage<T> extends ContourNextLinkRequestMessage<T> {
+    private final static int ENVELOPE_SIZE = 33;
+
+    protected CommandType mCommandType = CommandType.NO_TYPE;
+    protected MedtronicCnlSession mPumpSession;
+
+    public ContourNextLinkBinaryRequestMessage(CommandType commandType, MedtronicCnlSession pumpSession, byte[] payload) throws ChecksumException {
+        super(buildPayload(commandType, pumpSession, payload));
+
+        this.mPumpSession = pumpSession;
+        this.mCommandType = commandType;
+
+        // Validate checksum
+        // FIXME - this is not needed. Because we're setting the checksum in buildPayload, we know it's
+        // going to be okay. However, this check does need to be done when reading a message.
+        byte messageChecksum = this.mPayload.get(32);
+        byte calculatedChecksum = (byte) (MessageUtils.oneByteSum(this.mPayload.array()) - messageChecksum);
 
-        public int getValue() {
-            return value;
+        if (messageChecksum != calculatedChecksum) {
+            throw new ChecksumException(String.format(Locale.getDefault(), "Expected to get %d. Got %d", (int) calculatedChecksum, (int) messageChecksum));
         }
     }
 
-    public ContourNextLinkBinaryMessage(CommandType commandType, MedtronicCnlSession pumpSession, byte[] payload) {
-        super(buildPayload(commandType, pumpSession, payload));
+    /**
+     * Handle incrementing sequence number
+     *
+     * @param mDevice
+     * @throws IOException
+     */
+    protected void sendMessage(UsbHidDriver mDevice) throws IOException {
+        super.sendMessage(mDevice);
+        mPumpSession.incrBayerSequenceNumber();
     }
 
     protected static byte[] buildPayload(CommandType commandType, MedtronicCnlSession pumpSession, byte[] payload) {
@@ -55,7 +57,7 @@ public class ContourNextLinkBinaryMessage extends ContourNextLinkMessage{
         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);
+        payloadBuffer.put(commandType.getValue());
         payloadBuffer.putInt(pumpSession.getBayerSequenceNumber());
         byte[] unknownBytes2 = {0, 0, 0, 0, 0};
         payloadBuffer.put(unknownBytes2);
@@ -73,17 +75,4 @@ public class ContourNextLinkBinaryMessage extends ContourNextLinkMessage{
         return payloadBuffer.array();
     }
 
-    public static ContourNextLinkMessage fromBytes(byte[] bytes) throws ChecksumException {
-        ContourNextLinkMessage message = new ContourNextLinkMessage(bytes);
-
-        // Validate checksum
-        byte messageChecksum = message.mPayload.get(32);
-        byte calculatedChecksum = (byte) (MessageUtils.oneByteSum(message.mPayload.array()) - messageChecksum);
-
-        if (messageChecksum != calculatedChecksum) {
-            throw new ChecksumException(String.format(Locale.getDefault(), "Expected to get %d. Got %d", (int) calculatedChecksum, (int) messageChecksum));
-        }
-
-        return message;
-    }
 }
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkBinaryResponseMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkBinaryResponseMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..66a9a8b749f5ef47b1a6472f398a1bf9605665dc
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkBinaryResponseMessage.java
@@ -0,0 +1,13 @@
+package info.nightscout.android.medtronic.message;
+
+import info.nightscout.android.medtronic.exception.ChecksumException;
+
+/**
+ * Created by lgoedhart on 26/03/2016.
+ */
+public class ContourNextLinkBinaryResponseMessage extends ContourNextLinkResponseMessage {
+
+    public ContourNextLinkBinaryResponseMessage(byte[] payload) throws ChecksumException {
+        super(payload);
+    }
+}
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkCommandMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkCommandMessage.java
index ad114c682a2eeac28d4e1e19a0f42a1131a72c26..36b7530d61104c8c87bd63a8f604c8a6718874fd 100644
--- a/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkCommandMessage.java
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkCommandMessage.java
@@ -1,9 +1,21 @@
 package info.nightscout.android.medtronic.message;
 
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+import info.nightscout.android.USB.UsbHidDriver;
+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 26/03/2016.
  */
-public class ContourNextLinkCommandMessage extends ContourNextLinkMessage {
+public class ContourNextLinkCommandMessage extends ContourNextLinkRequestMessage<ContourNextLinkCommandResponse> {
+    public ContourNextLinkCommandMessage(ASCII command) {
+        super(new byte[]{command.getValue()});
+    }
+
     public ContourNextLinkCommandMessage(byte command) {
         super(new byte[]{command});
     }
@@ -11,4 +23,10 @@ public class ContourNextLinkCommandMessage extends ContourNextLinkMessage {
     public ContourNextLinkCommandMessage(String command) {
         super(command.getBytes());
     }
+
+    @Override
+    protected ContourNextLinkCommandResponse getResponse(byte[] payload) throws ChecksumException {
+        return new ContourNextLinkCommandResponse(payload);
+    }
+
 }
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkCommandResponse.java b/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkCommandResponse.java
new file mode 100644
index 0000000000000000000000000000000000000000..db4839fab75f46b5e6b0277c02782150d0ddaab3
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkCommandResponse.java
@@ -0,0 +1,13 @@
+package info.nightscout.android.medtronic.message;
+
+import info.nightscout.android.medtronic.exception.ChecksumException;
+
+/**
+ * Created by volker on 10.12.2016.
+ */
+public class ContourNextLinkCommandResponse extends ContourNextLinkBinaryResponseMessage {
+
+    public ContourNextLinkCommandResponse(byte[] payload) throws ChecksumException {
+        super(payload);
+    }
+}
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkMessage.java
index 2a44cfdeede51c0ec2d21d6b6128aa17a04cbd10..8c340cb9ef425cdd34e8bf322e2eef4aaa791926 100644
--- a/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkMessage.java
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkMessage.java
@@ -1,32 +1,214 @@
 package info.nightscout.android.medtronic.message;
 
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.util.concurrent.TimeoutException;
+
+import info.nightscout.android.USB.UsbHidDriver;
+import info.nightscout.android.medtronic.MainActivity;
+import info.nightscout.android.utils.HexDump;
 
 /**
  * Created by lgoedhart on 26/03/2016.
  */
-public class ContourNextLinkMessage {
+public abstract class ContourNextLinkMessage {
+    private static final String TAG = ContourNextLinkMessage.class.getSimpleName();
+
+    private static final int USB_BLOCKSIZE = 64;
+    private static final int READ_TIMEOUT_MS = 15000; //ASTM standard is 15 seconds (note was previously set at 10 seconds)
+    private static final String BAYER_USB_HEADER = "ABC";
+
     protected ByteBuffer mPayload;
 
-    public ContourNextLinkMessage(byte[] bytes) {
-        if (bytes != null) {
-            this.mPayload = ByteBuffer.allocate(bytes.length);
-            this.mPayload.put(bytes);
+    public enum CommandAction {
+        NO_TYPE(0x0),
+        CHANNEL_NEGOTIATE(0x03),
+        PUMP_REQUEST(0x05),
+        PUMP_RESPONSE(0x55);
+
+        private byte value;
+
+        CommandAction(int commandAction) {
+            value = (byte) commandAction;
+        }
+
+        public byte getValue() {
+            return value;
+        }
+
+        public boolean equals(byte value) {
+            return this.value == value;
         }
     }
 
-    public byte[] encode() {
-        return mPayload.array();
+    public enum CommandType {
+        OPEN_CONNECTION(0x10),
+        CLOSE_CONNECTION(0x11),
+        SEND_MESSAGE(0x12),
+        READ_INFO(0x14),
+        REQUEST_LINK_KEY(0x16),
+        SEND_LINK_KEY(0x17),
+        RECEIVE_MESSAGE(0x80),
+        SEND_MESSAGE_RESPONSE(0x81),
+        REQUEST_LINK_KEY_RESPONSE(0x86),
+
+        NO_TYPE(0x0);
+
+        private byte value;
+
+        CommandType(int commandType) {
+            value = (byte) commandType;
+        }
+
+        public byte getValue() {
+            return value;
+        }
+
+        public boolean equals(byte value) {
+            return this.value == value;
+        }
     }
 
-    public void send(ContourNextLinkMessageHandler handler) throws IOException {
-        handler.sendMessage(this);
+    protected ContourNextLinkMessage(byte[] bytes) {
+        setPayload(bytes);
+    }
+
+    public byte[] encode() {
+        return mPayload.array();
     }
 
     // FIXME - get rid of this - make a Builder instead
     protected void setPayload(byte[] payload) {
-        mPayload = ByteBuffer.allocate(payload.length);
-        mPayload.put(payload);
+        if (payload != null) {
+            mPayload = ByteBuffer.allocate(payload.length);
+            mPayload.put(payload);
+        }
+    }
+
+    protected void sendMessage(UsbHidDriver mDevice) throws IOException {
+        int pos = 0;
+        byte[] message = this.encode();
+
+        while (message.length > pos) {
+            ByteBuffer outputBuffer = ByteBuffer.allocate(USB_BLOCKSIZE);
+            int sendLength = (pos + 60 > message.length) ? message.length - pos : 60;
+            outputBuffer.put(BAYER_USB_HEADER.getBytes());
+            outputBuffer.put((byte) sendLength);
+            outputBuffer.put(message, pos, sendLength);
+
+            mDevice.write(outputBuffer.array(), 200);
+            pos += sendLength;
+
+            String outputString = HexDump.dumpHexString(outputBuffer.array());
+            Log.d(TAG, "WRITE: " + outputString);
+        }
+    }
+
+    protected byte[] readMessage(UsbHidDriver mDevice) throws IOException, TimeoutException {
+        ByteArrayOutputStream responseMessage = new ByteArrayOutputStream();
+
+        byte[] responseBuffer = new byte[USB_BLOCKSIZE];
+        int bytesRead;
+        int messageSize = 0;
+
+        do {
+            bytesRead = mDevice.read(responseBuffer, READ_TIMEOUT_MS);
+
+            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);
+
+        String responseString = HexDump.dumpHexString(responseMessage.toByteArray());
+        Log.d(TAG, "READ: " + responseString);
+
+        return responseMessage.toByteArray();
+    }
+
+    // safety check to make sure a expected 0x81 response is received before next expected 0x80 response
+    // very infrequent as clearMessage catches most issues but very important to save a CNL error situation
+
+    protected int readMessage_0x81(UsbHidDriver mDevice) throws IOException, TimeoutException {
+
+        int responseSize = 0;
+        boolean doRetry;
+        do {
+            byte[] responseBytes = readMessage(mDevice);
+            if (responseBytes[18] != (byte) 0x81) {
+                doRetry = true;
+                Log.d(TAG, "readMessage0x81: did not get 0x81 response, got " + responseBytes[18]);
+            } else {
+                doRetry = false;
+                responseSize = responseBytes.length;
+            }
+
+        } while (doRetry);
+
+        return responseSize;
+    }
+
+    // intercept unexpected messages from the CNL
+    // these usually come from pump requests as it can occasionally resend message responses several times (possibly due to a missed CNL ACK during CNL-PUMP comms?)
+    // mostly noted on the higher radio channels, channel 26 shows this the most
+    // if these messages are not cleared the CNL will likely error needing to be unplugged to reset as it expects them to be read before any further commands are sent
+
+    protected int clearMessage(UsbHidDriver mDevice) throws IOException {
+
+        byte[] responseBuffer = new byte[USB_BLOCKSIZE];
+        int bytesRead;
+        int bytesClear = 0;
+
+        do {
+            bytesRead = mDevice.read(responseBuffer, 2000);
+            if (bytesRead > 0) {
+                bytesClear += bytesRead;
+                String responseString = HexDump.dumpHexString(responseBuffer);
+                Log.d(TAG, "READ: " + responseString);
+            }
+        } while (bytesRead > 0);
+
+        if (bytesClear > 0) {
+            Log.d(TAG, "clearMessage: message stream cleared bytes: " + bytesClear);
+        }
+
+        return bytesClear;
+    }
+
+
+    public enum ASCII {
+        STX(0x02),
+        EOT(0x04),
+        ENQ(0x05),
+        ACK(0x06),
+        NAK(0x15);
+
+        protected byte value;
+
+        ASCII(int code) {
+            this.value = (byte) code;
+        }
+
+        public byte getValue() {
+            return value;
+        }
+
+        public boolean equals(byte value) {
+            return this.value == value;
+        }
     }
 }
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkMessageHandler.java b/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkMessageHandler.java
deleted file mode 100644
index 1d669f67b84cd9118e11c91966db05ff95f8738c..0000000000000000000000000000000000000000
--- a/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkMessageHandler.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package info.nightscout.android.medtronic.message;
-
-import java.io.IOException;
-
-/**
- * Created by lgoedhart on 26/03/2016.
- */
-public interface ContourNextLinkMessageHandler {
-    void sendMessage( ContourNextLinkMessage message ) throws IOException;
-    ContourNextLinkMessage receiveMessage();
-}
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkRequestMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkRequestMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..cd25feda28afca655cdb4eb0cc69dad0304e4c58
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkRequestMessage.java
@@ -0,0 +1,47 @@
+package info.nightscout.android.medtronic.message;
+
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+import info.nightscout.android.USB.UsbHidDriver;
+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 12.12.2016.
+ */
+
+public abstract class ContourNextLinkRequestMessage<T> extends ContourNextLinkMessage {
+    private static final String TAG = ContourNextLinkRequestMessage.class.getSimpleName();
+
+    protected ContourNextLinkRequestMessage(byte[] bytes) {
+        super(bytes);
+    }
+
+    public T send(UsbHidDriver mDevice) throws IOException, TimeoutException, EncryptionException, ChecksumException, UnexpectedMessageException {
+        return send(mDevice, 0);
+    }
+
+    public T send(UsbHidDriver mDevice, int millis) throws UnexpectedMessageException, EncryptionException, TimeoutException, ChecksumException, IOException {
+        sendMessage(mDevice);
+        if (millis > 0) {
+            try {
+                Log.d(TAG, "waiting " + millis +" ms");
+                Thread.sleep(millis);
+            } catch (InterruptedException e) {
+            }
+        }
+
+        T response = this.getResponse(readMessage(mDevice)); //new ContourNextLinkCommandResponse();
+
+        // FIXME - We need to care what the response message is - wrong MAC and all that
+        return response;
+    }
+
+    protected abstract <T> T getResponse(byte[] payload) throws ChecksumException, EncryptionException, IOException, UnexpectedMessageException, TimeoutException;
+
+}
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkResponseMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkResponseMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..d20a5613e3345ccdc13a7499852074372490b249
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkResponseMessage.java
@@ -0,0 +1,30 @@
+package info.nightscout.android.medtronic.message;
+
+import java.io.IOException;
+import java.util.Locale;
+import java.util.concurrent.TimeoutException;
+
+import info.nightscout.android.medtronic.exception.ChecksumException;
+import info.nightscout.android.medtronic.exception.UnexpectedMessageException;
+
+/**
+ * Created by lgoedhart on 26/03/2016.
+ */
+public abstract class ContourNextLinkResponseMessage extends ContourNextLinkMessage {
+
+    public ContourNextLinkResponseMessage(byte[] payload) throws ChecksumException {
+        super(payload);
+    }
+
+
+    public void checkControlMessage(ASCII controlCharacter) throws UnexpectedMessageException {
+        checkControlMessage(mPayload.array(), controlCharacter);
+    }
+
+    public void checkControlMessage(byte[] msg, ASCII controlCharacter) throws UnexpectedMessageException {
+        if (msg.length != 1 || !controlCharacter.equals(msg[0])) {
+            throw new UnexpectedMessageException(String.format(Locale.getDefault(), "Expected to get control character '%d' Got '%d'.",
+                    (int) controlCharacter.getValue(), (int) msg[0]));
+        }
+    }
+}
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/DeviceInfoRequestCommandMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/DeviceInfoRequestCommandMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..38a1bd6b03d1747bf353677df2b956516ca09871
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/DeviceInfoRequestCommandMessage.java
@@ -0,0 +1,67 @@
+package info.nightscout.android.medtronic.message;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+import info.nightscout.android.USB.UsbHidDriver;
+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.
+ */
+
+public class DeviceInfoRequestCommandMessage extends ContourNextLinkRequestMessage<DeviceInfoResponseCommandMessage> {
+    public DeviceInfoRequestCommandMessage() {
+        super("X".getBytes());
+    }
+
+    @Override
+    public DeviceInfoResponseCommandMessage send(UsbHidDriver mDevice, int millis) throws IOException, TimeoutException, EncryptionException, ChecksumException, UnexpectedMessageException {
+        sendMessage(mDevice);
+
+        if (millis > 0) {
+            try {
+                Thread.sleep(millis);
+            } catch (InterruptedException e) {
+            }
+        }
+        byte[] response1 = readMessage(mDevice);
+        if (millis > 0) {
+            try {
+                Thread.sleep(millis);
+            } catch (InterruptedException e) {
+            }
+        }
+        byte[] response2 = readMessage(mDevice);
+
+        boolean doRetry = false;
+        DeviceInfoResponseCommandMessage response = null;
+
+        do {
+            try {
+                if (ASCII.EOT.equals(response1[0])) {
+                    // response 1 is the ASTM message
+                    response = this.getResponse(response1);
+                    // ugly....
+                    response.checkControlMessage(response2, ASCII.ENQ);
+                } else {
+                    // response 2 is the ASTM message
+                    response = this.getResponse(response2);
+                    // ugly, too....
+                    response.checkControlMessage(response1, ASCII.ENQ);
+                }
+            } catch (TimeoutException e) {
+                doRetry = true;
+            }
+        } while (doRetry);
+
+        return response;
+    }
+
+    @Override
+    protected DeviceInfoResponseCommandMessage getResponse(byte[] payload) throws ChecksumException, EncryptionException, IOException, UnexpectedMessageException, TimeoutException {
+        return new DeviceInfoResponseCommandMessage(payload);
+    }
+}
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/DeviceInfoResponseCommandMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/DeviceInfoResponseCommandMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..4147ad8bad97129f068c847228b9a7b79a4ab579
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/DeviceInfoResponseCommandMessage.java
@@ -0,0 +1,37 @@
+package info.nightscout.android.medtronic.message;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+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 DeviceInfoResponseCommandMessage extends ContourNextLinkResponseMessage {
+    private String serial = "";
+    private final Pattern pattern = Pattern.compile(".*?\\^(\\d{4}-\\d{7})\\^.*");
+
+    protected DeviceInfoResponseCommandMessage(byte[] payload)
+            throws ChecksumException, EncryptionException, TimeoutException, UnexpectedMessageException, IOException {
+        super(payload);
+
+        extractStickSerial(new String(payload));
+    }
+
+    public String getSerial() {
+        return serial;
+    }
+
+    private void extractStickSerial(String astmMessage) {
+        Matcher matcher = pattern.matcher(astmMessage);
+        if (matcher.find()) {
+            serial = matcher.group(1);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/EHSMMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/EHSMMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..0e064536b650efd76e2ee8dad5867a787671339f
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/EHSMMessage.java
@@ -0,0 +1,47 @@
+package info.nightscout.android.medtronic.message;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+import info.nightscout.android.USB.UsbHidDriver;
+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 22.12.2016.
+ */
+
+public class EHSMMessage extends  MedtronicSendMessageRequestMessage<ContourNextLinkResponseMessage>{
+    protected EHSMMessage(SendMessageType sendMessageType, MedtronicCnlSession pumpSession, byte[] payload) throws EncryptionException, ChecksumException {
+        super(sendMessageType, pumpSession, payload);
+    }
+
+    @Override
+    public ContourNextLinkResponseMessage send(UsbHidDriver mDevice, int millis) throws IOException, TimeoutException, UnexpectedMessageException {
+
+        // clear unexpected incoming messages
+        clearMessage(mDevice);
+
+        sendMessage(mDevice);
+        if (millis > 0) {
+            try {
+                Thread.sleep(millis);
+            } catch (InterruptedException e) {
+            }
+        }
+
+        // The End EHSM Session only has an 0x81 response
+        if (readMessage_0x81(mDevice) != 48) {
+            throw new UnexpectedMessageException("length of EHSMMessage response does not match");
+        }
+/*
+        readMessage(mDevice);
+        if (this.encode().length != 54) {
+            throw new UnexpectedMessageException("length of EHSMMessage response does not match");
+        }
+*/
+        return null;
+    }
+}
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/EndEHSMMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/EndEHSMMessage.java
index 0364110375aec3d9c5f6b9c940f500d363758e4f..5335d4cf3b5b48fef9231bc060f011026f5f1f8c 100644
--- a/app/src/main/java/info/nightscout/android/medtronic/message/EndEHSMMessage.java
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/EndEHSMMessage.java
@@ -1,12 +1,19 @@
 package info.nightscout.android.medtronic.message;
 
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+import info.nightscout.android.USB.UsbHidDriver;
 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 26/03/2016.
  */
-public class EndEHSMMessage extends MedtronicSendMessage {
-    public EndEHSMMessage(MedtronicCnlSession pumpSession) throws EncryptionException {
+public class EndEHSMMessage extends EHSMMessage {
+    public EndEHSMMessage(MedtronicCnlSession pumpSession) throws EncryptionException, ChecksumException {
         super(SendMessageType.END_EHSM_SESSION, pumpSession, buildPayload());
     }
 
@@ -14,4 +21,5 @@ public class EndEHSMMessage extends MedtronicSendMessage {
         // Not sure what the payload byte means, but it's the same every time.
         return new byte[] { 0x01 };
     }
+
 }
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicPumpMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicPumpMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..6485bd71e6a5d1dd4db717095b5c6999ea4f382a
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicPumpMessage.java
@@ -0,0 +1,14 @@
+package info.nightscout.android.medtronic.message;
+
+import info.nightscout.android.medtronic.MedtronicCnlSession;
+
+/**
+ * Created by volker on 15.12.2016.
+ */
+
+public class MedtronicPumpMessage extends ContourNextLinkMessage {
+
+    protected MedtronicPumpMessage(MedtronicCnlSession pumpSession, byte[] bytes) {
+        super(bytes);
+    }
+}
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicReceiveMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicReceiveMessage.java
deleted file mode 100644
index c2f4a7d58cf10d70925e3d2ab15e3358a4ff3a3f..0000000000000000000000000000000000000000
--- a/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicReceiveMessage.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package info.nightscout.android.medtronic.message;
-
-import info.nightscout.android.medtronic.MedtronicCnlSession;
-
-import java.nio.ByteBuffer;
-
-/**
- * Created by lgoedhart on 26/03/2016.
- */
-public class MedtronicReceiveMessage extends MedtronicMessage {
-    static int ENVELOPE_SIZE = 22;
-    static int ENCRYPTED_ENVELOPE_SIZE = 3;
-    static int CRC_SIZE = 2;
-
-    protected MedtronicReceiveMessage(CommandType commandType, CommandAction commandAction, MedtronicCnlSession pumpSession, byte[] payload) {
-        super(commandType, commandAction, pumpSession, payload);
-    }
-
-    public enum ReceiveMessageType {
-        NO_TYPE(0x0),
-        TIME_RESPONSE(0x407);
-
-        private short value;
-
-        ReceiveMessageType(int messageType) {
-            value = (short) messageType;
-        }
-    }
-
-    /**
-     * MedtronicReceiveMessage:
-     * +------------------+-----------------+-----------------+---------------------------------+-------------------+--------------------------------+
-     * | LE short unknown | LE long pumpMAC | LE long linkMAC | byte[3] responseSequenceNumber? | byte Payload size | byte[] Encrypted Payload bytes |
-     * +------------------+-----------------+-----------------+---------------------------------+-------------------+--------------------------------+
-     * <p/>
-     * MedtronicReceiveMessage (decrypted payload):
-     * +----------------------------+-----------------------------+----------------------+--------------------+
-     * | byte receiveSequenceNumber | BE short receiveMessageType | byte[] Payload bytes | BE short CCITT CRC |
-     * +----------------------------+-----------------------------+----------------------+--------------------+
-     */
-    public static ContourNextLinkMessage fromBytes(MedtronicCnlSession pumpSession, byte[] bytes) throws ChecksumException, EncryptionException {
-        // TODO - turn this into a factory
-        ContourNextLinkMessage message = MedtronicMessage.fromBytes(bytes);
-
-        // TODO - Validate the message, inner CCITT, serial numbers, etc
-
-        // If there's not 57 bytes, then we got back a bad message. Not sure how to process these yet.
-        // Also, READ_INFO and REQUEST_LINK_KEY are not encrypted
-        if (bytes.length >= 57 &&
-                (bytes[18] != CommandType.READ_INFO.getValue()) &&
-                (bytes[18] != CommandType.REQUEST_LINK_KEY_RESPONSE.getValue())) {
-            // Replace the encrypted bytes by their decrypted equivalent (same block size)
-            byte encryptedPayloadSize = bytes[56];
-
-            ByteBuffer encryptedPayload = ByteBuffer.allocate(encryptedPayloadSize);
-            encryptedPayload.put(bytes, 57, encryptedPayloadSize);
-            byte[] decryptedPayload = decrypt(pumpSession.getKey(), pumpSession.getIV(), encryptedPayload.array());
-
-            // 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...
-            message.mPayload.position(57);
-            message.mPayload.put(decryptedPayload);
-        }
-        return message;
-    }
-}
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicRequestMessage.java
similarity index 61%
rename from app/src/main/java/info/nightscout/android/medtronic/message/MedtronicMessage.java
rename to app/src/main/java/info/nightscout/android/medtronic/message/MedtronicRequestMessage.java
index 25588505ab2e817307d0428108a0e2bb98ef4eb7..9963dd32e133b884e589777d9d2705813df11c5f 100644
--- a/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicMessage.java
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicRequestMessage.java
@@ -1,7 +1,6 @@
 package info.nightscout.android.medtronic.message;
 
-import info.nightscout.android.medtronic.MedtronicCnlSession;
-
+import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 
@@ -9,27 +8,19 @@ import javax.crypto.Cipher;
 import javax.crypto.spec.IvParameterSpec;
 import javax.crypto.spec.SecretKeySpec;
 
+import info.nightscout.android.USB.UsbHidDriver;
+import info.nightscout.android.medtronic.MedtronicCnlSession;
+import info.nightscout.android.medtronic.exception.ChecksumException;
+import info.nightscout.android.medtronic.exception.EncryptionException;
+
 /**
  * Created by lgoedhart on 26/03/2016.
  */
-public class MedtronicMessage extends ContourNextLinkBinaryMessage {
+public abstract class MedtronicRequestMessage<T> extends ContourNextLinkBinaryRequestMessage<T> {
     static int ENVELOPE_SIZE = 2;
     static int CRC_SIZE = 2;
 
-    public enum CommandAction {
-        NO_TYPE(0x0),
-        CHANNEL_NEGOTIATE(0x03),
-        PUMP_REQUEST(0x05),
-        PUMP_RESPONSE(0x55);
-
-        private byte value;
-
-        CommandAction(int commandAction) {
-            value = (byte) commandAction;
-        }
-    }
-
-    protected MedtronicMessage(CommandType commandType, CommandAction commandAction, MedtronicCnlSession pumpSession, byte[] payload) {
+    protected MedtronicRequestMessage(CommandType commandType, CommandAction commandAction, MedtronicCnlSession pumpSession, byte[] payload) throws ChecksumException {
         super(commandType, pumpSession, buildPayload(commandAction, payload));
     }
 
@@ -45,7 +36,7 @@ public class MedtronicMessage extends ContourNextLinkBinaryMessage {
         ByteBuffer payloadBuffer = ByteBuffer.allocate(ENVELOPE_SIZE + payloadLength + CRC_SIZE);
         payloadBuffer.order(ByteOrder.LITTLE_ENDIAN);
 
-        payloadBuffer.put(commandAction.value);
+        payloadBuffer.put(commandAction.getValue());
         payloadBuffer.put((byte) (ENVELOPE_SIZE + payloadLength));
         if (payloadLength != 0) {
             payloadBuffer.put(payload != null ? payload : new byte[0]);
@@ -56,12 +47,6 @@ public class MedtronicMessage extends ContourNextLinkBinaryMessage {
         return payloadBuffer.array();
     }
 
-    public static ContourNextLinkMessage fromBytes(byte[] bytes) throws ChecksumException {
-        ContourNextLinkMessage message = ContourNextLinkBinaryMessage.fromBytes(bytes);
-
-        // TODO - Validate the CCITT
-        return message;
-    }
 
     // TODO - maybe move the SecretKeySpec, IvParameterSpec and Cipher construction into the PumpSession?
     protected static byte[] encrypt(byte[] key, byte[] iv, byte[] clear) throws EncryptionException {
@@ -79,18 +64,8 @@ public class MedtronicMessage extends ContourNextLinkBinaryMessage {
         return encrypted;
     }
 
-    protected static byte[] decrypt(byte[] key, byte[] iv, byte[] encrypted) throws EncryptionException {
-        SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
-        IvParameterSpec ivSpec = new IvParameterSpec(iv);
-        byte[] decrypted;
-
-        try {
-            Cipher cipher = Cipher.getInstance("AES/CFB/NoPadding");
-            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivSpec);
-            decrypted = cipher.doFinal(encrypted);
-        } catch (Exception e ) {
-            throw new EncryptionException( "Could not decrypt Medtronic Message" );
-        }
-        return decrypted;
+    protected void sendMessage(UsbHidDriver mDevice) throws IOException {
+        super.sendMessage(mDevice);
+        mPumpSession.incrMedtronicSequenceNumber();
     }
 }
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
new file mode 100644
index 0000000000000000000000000000000000000000..47c33dcf6a980e1a93dff4fb3ebefc40ecae3767
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicResponseMessage.java
@@ -0,0 +1,127 @@
+package info.nightscout.android.medtronic.message;
+
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import info.nightscout.android.BuildConfig;
+import info.nightscout.android.medtronic.MedtronicCnlSession;
+import info.nightscout.android.medtronic.exception.ChecksumException;
+import info.nightscout.android.medtronic.exception.EncryptionException;
+import info.nightscout.android.utils.HexDump;
+
+/**
+ * Created by lgoedhart on 26/03/2016.
+ */
+public class MedtronicResponseMessage extends ContourNextLinkResponseMessage {
+    private static final String TAG = MedtronicResponseMessage.class.getSimpleName();
+
+    static int ENVELOPE_SIZE = 22;
+    static int ENCRYPTED_ENVELOPE_SIZE = 3;
+    static int CRC_SIZE = 2;
+
+    protected MedtronicCnlSession mPumpSession;
+
+    protected MedtronicResponseMessage(MedtronicCnlSession pumpSession, byte[] payload) throws EncryptionException, ChecksumException {
+        super(payload);
+
+        mPumpSession = pumpSession;
+
+        // TODO - Validate the message, inner CCITT, serial numbers, etc
+        // If there's not 57 bytes, then we got back a bad message. Not sure how to process these yet.
+        // Also, READ_INFO and REQUEST_LINK_KEY are not encrypted
+        if (payload.length >= 57 &&
+                (payload[18] != CommandType.READ_INFO.getValue()) &&
+                (payload[18] != CommandType.REQUEST_LINK_KEY_RESPONSE.getValue())) {
+            // Replace the encrypted bytes by their decrypted equivalent (same block size)
+            byte encryptedPayloadSize = payload[56];
+
+            ByteBuffer encryptedPayload = ByteBuffer.allocate(encryptedPayloadSize);
+            encryptedPayload.put(payload, 57, encryptedPayloadSize);
+            byte[] decryptedPayload = decrypt(pumpSession.getKey(), pumpSession.getIV(), encryptedPayload.array());
+
+            // 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...
+            this.mPayload.position(57);
+            this.mPayload.put(decryptedPayload);
+
+            if (BuildConfig.DEBUG) {
+                String outputString = HexDump.dumpHexString(this.mPayload.array());
+                Log.d(TAG, "DECRYPTED: " + outputString);
+            }
+        }
+    }
+
+    public enum ReceiveMessageType {
+        NO_TYPE(0x0),
+        TIME_RESPONSE(0x407);
+
+        private short value;
+
+        ReceiveMessageType(int messageType) {
+            value = (short) messageType;
+        }
+    }
+
+    /**
+     * MedtronicResponseMessage:
+     * +------------------+-----------------+-----------------+---------------------------------+-------------------+--------------------------------+
+     * | LE short unknown | LE long pumpMAC | LE long linkMAC | byte[3] responseSequenceNumber? | byte Payload size | byte[] Encrypted Payload bytes |
+     * +------------------+-----------------+-----------------+---------------------------------+-------------------+--------------------------------+
+     * <p/>
+     * MedtronicResponseMessage (decrypted payload):
+     * +----------------------------+-----------------------------+----------------------+--------------------+
+     * | byte receiveSequenceNumber | BE short receiveMessageType | byte[] Payload bytes | BE short CCITT CRC |
+     * +----------------------------+-----------------------------+----------------------+--------------------+
+     */
+    public static ContourNextLinkMessage fromBytes(MedtronicCnlSession pumpSession, byte[] bytes) throws ChecksumException, EncryptionException {
+        // TODO - turn this into a factory
+
+        return new MedtronicResponseMessage(pumpSession, bytes);
+        /*
+        ContourNextLinkMessage message = MedtronicMessage.fromBytes(bytes);
+
+
+        // TODO - Validate the message, inner CCITT, serial numbers, etc
+
+        // If there's not 57 bytes, then we got back a bad message. Not sure how to process these yet.
+        // Also, READ_INFO and REQUEST_LINK_KEY are not encrypted
+        if (bytes.length >= 57 &&
+                (bytes[18] != CommandType.READ_INFO.getValue()) &&
+                (bytes[18] != CommandType.REQUEST_LINK_KEY_RESPONSE.getValue())) {
+            // Replace the encrypted bytes by their decrypted equivalent (same block size)
+            byte encryptedPayloadSize = bytes[56];
+
+            ByteBuffer encryptedPayload = ByteBuffer.allocate(encryptedPayloadSize);
+            encryptedPayload.put(bytes, 57, encryptedPayloadSize);
+            byte[] decryptedPayload = decrypt(pumpSession.getKey(), pumpSession.getIV(), encryptedPayload.array());
+
+            // 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...
+            message.mPayload.position(57);
+            message.mPayload.put(decryptedPayload);
+        }
+        return message;*/
+    }
+
+    protected static byte[] decrypt(byte[] key, byte[] iv, byte[] encrypted) throws EncryptionException {
+        SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
+        IvParameterSpec ivSpec = new IvParameterSpec(iv);
+        byte[] decrypted;
+
+        try {
+            Cipher cipher = Cipher.getInstance("AES/CFB/NoPadding");
+            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivSpec);
+            decrypted = cipher.doFinal(encrypted);
+        } catch (Exception e ) {
+            throw new EncryptionException( "Could not decrypt Medtronic Message" );
+        }
+        return decrypted;
+    }
+}
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicSendMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicSendMessageRequestMessage.java
similarity index 73%
rename from app/src/main/java/info/nightscout/android/medtronic/message/MedtronicSendMessage.java
rename to app/src/main/java/info/nightscout/android/medtronic/message/MedtronicSendMessageRequestMessage.java
index 7bba4ded16da96428fd056f5b985ce821ee9e63e..50aee6a666fbba3edb5733bf3e629b17bce98cfe 100644
--- a/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicSendMessage.java
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicSendMessageRequestMessage.java
@@ -1,24 +1,28 @@
 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;
 
+import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
-
 /**
- * Created by lgoedhart on 26/03/2016.
+ * Created by volker on 18.12.2016.
  */
-public class MedtronicSendMessage extends MedtronicMessage {
+
+public abstract class MedtronicSendMessageRequestMessage<T>  extends MedtronicRequestMessage<T> {
     static int ENVELOPE_SIZE = 11;
     static int ENCRYPTED_ENVELOPE_SIZE = 3;
     static int CRC_SIZE = 2;
 
     public enum SendMessageType {
         NO_TYPE(0x0),
-        BEGIN_EHSM_SESSION(0x412),
+        BEGIN_EHSM_SESSION(0x0412),
         TIME_REQUEST(0x0403),
         READ_PUMP_STATUS_REQUEST(0x0112),
-        READ_BASAL_PATTERN_REQUEST(0x0112),
+        READ_BASAL_PATTERN_REQUEST(0x0116),
         END_EHSM_SESSION(0x412);
 
         private short value;
@@ -28,10 +32,15 @@ public class MedtronicSendMessage extends MedtronicMessage {
         }
     }
 
-    protected MedtronicSendMessage(SendMessageType sendMessageType, MedtronicCnlSession pumpSession, byte[] payload) throws EncryptionException {
+    protected MedtronicSendMessageRequestMessage(SendMessageType sendMessageType, MedtronicCnlSession pumpSession, byte[] payload) throws EncryptionException, ChecksumException {
         super(CommandType.SEND_MESSAGE, CommandAction.PUMP_REQUEST, pumpSession, buildPayload(sendMessageType, pumpSession, payload));
     }
 
+    @Override
+    protected ContourNextLinkResponseMessage getResponse(byte[] payload) throws ChecksumException, EncryptionException, IOException, UnexpectedMessageException {
+        return null;
+    }
+
     /**
      * MedtronicSendMessage:
      * +-----------------+------------------------------+--------------+-------------------+--------------------------------+
@@ -39,9 +48,9 @@ public class MedtronicSendMessage extends MedtronicMessage {
      * +-----------------+------------------------------+--------------+-------------------+--------------------------------+
      * <p/>
      * MedtronicSendMessage (decrypted payload):
-     * +-------------------------+----------------------+----------------------+--------------------+
+     * +-------------------------+--------------------------+----------------------+--------------------+
      * | byte sendSequenceNumber | BE short sendMessageType | byte[] Payload bytes | BE short CCITT CRC |
-     * +-------------------------+----------------------+----------------------+--------------------+
+     * +-------------------------+--------------------------+----------------------+--------------------+
      */
     protected static byte[] buildPayload(SendMessageType sendMessageType, MedtronicCnlSession pumpSession, byte[] payload) throws EncryptionException {
         byte payloadLength = (byte) (payload == null ? 0 : payload.length);
@@ -69,6 +78,7 @@ public class MedtronicSendMessage extends MedtronicMessage {
         return payloadBuffer.array();
     }
 
+    // TODO - This should be dynamically incremented in the Session object
     protected static byte sendSequenceNumber(SendMessageType sendMessageType) {
         switch (sendMessageType) {
             case BEGIN_EHSM_SESSION:
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicSendMessageResponseMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicSendMessageResponseMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..48820677d9619a21d925c4e795a4ec4b2e195505
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicSendMessageResponseMessage.java
@@ -0,0 +1,15 @@
+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;
+
+/**
+ * Created by volker on 18.12.2016.
+ */
+
+public class MedtronicSendMessageResponseMessage extends MedtronicResponseMessage {
+    protected MedtronicSendMessageResponseMessage(MedtronicCnlSession pumpSession, byte[] payload) throws EncryptionException, ChecksumException {
+        super(pumpSession, payload);
+    }
+}
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/OpenConnectionRequestMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/OpenConnectionRequestMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..85869867aaf3e5ce37ef1a05fdb76880ca04900b
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/OpenConnectionRequestMessage.java
@@ -0,0 +1,24 @@
+package info.nightscout.android.medtronic.message;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+import info.nightscout.android.USB.UsbHidDriver;
+import info.nightscout.android.medtronic.MedtronicCnlSession;
+import info.nightscout.android.medtronic.exception.ChecksumException;
+import info.nightscout.android.medtronic.exception.EncryptionException;
+
+/**
+ * Created by volker on 10.12.2016.
+ */
+
+public class OpenConnectionRequestMessage extends ContourNextLinkBinaryRequestMessage<OpenConnectionResponseMessage> {
+    public OpenConnectionRequestMessage(MedtronicCnlSession pumpSession, byte[] payload) throws ChecksumException {
+        super(CommandType.OPEN_CONNECTION, pumpSession, payload);
+    }
+
+    @Override
+    protected OpenConnectionResponseMessage getResponse(byte[] payload) throws ChecksumException, EncryptionException {
+        return new OpenConnectionResponseMessage(payload);
+    }
+}
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/OpenConnectionResponseMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/OpenConnectionResponseMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..1f8e6df122f3efffe51a1a71a2f9a02e28e0407b
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/OpenConnectionResponseMessage.java
@@ -0,0 +1,15 @@
+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;
+
+/**
+ * Created by lgoedhart on 10/05/2016.
+ */
+public class OpenConnectionResponseMessage extends ContourNextLinkBinaryResponseMessage {
+    protected OpenConnectionResponseMessage(byte[] payload) throws ChecksumException, EncryptionException {
+        super(payload);
+    }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/PumpBasalPatternRequestMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/PumpBasalPatternRequestMessage.java
index d31eb36a229654efedd6b9849fac749c1b748254..aa71df206607d8ef811415d199576042c063db3b 100644
--- a/app/src/main/java/info/nightscout/android/medtronic/message/PumpBasalPatternRequestMessage.java
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/PumpBasalPatternRequestMessage.java
@@ -1,12 +1,24 @@
 package info.nightscout.android.medtronic.message;
 
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+import info.nightscout.android.USB.UsbHidDriver;
 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 26/03/2016.
  */
-public class PumpBasalPatternRequestMessage extends MedtronicSendMessage {
-    public PumpBasalPatternRequestMessage(MedtronicCnlSession pumpSession) throws EncryptionException {
+public class PumpBasalPatternRequestMessage extends MedtronicSendMessageRequestMessage<PumpBasalPatternResponseMessage> {
+    public PumpBasalPatternRequestMessage(MedtronicCnlSession pumpSession) throws EncryptionException, ChecksumException {
         super(SendMessageType.READ_BASAL_PATTERN_REQUEST, pumpSession, null);
     }
+
+    @Override
+    protected PumpBasalPatternResponseMessage getResponse(byte[] payload) throws ChecksumException, EncryptionException, IOException, UnexpectedMessageException {
+        return new PumpBasalPatternResponseMessage(mPumpSession, payload);
+    }
 }
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/PumpBasalPatternResponseMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/PumpBasalPatternResponseMessage.java
index d4b4ef97d55422a5d5be2a5179879569a59cfcad..7bc6cf93671f90167dc59210188bc8c96613d621 100644
--- a/app/src/main/java/info/nightscout/android/medtronic/message/PumpBasalPatternResponseMessage.java
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/PumpBasalPatternResponseMessage.java
@@ -1,21 +1,48 @@
 package info.nightscout.android.medtronic.message;
 
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import info.nightscout.android.BuildConfig;
 import info.nightscout.android.medtronic.MedtronicCnlSession;
+import info.nightscout.android.medtronic.exception.ChecksumException;
+import info.nightscout.android.medtronic.exception.EncryptionException;
+import info.nightscout.android.model.medtronicNg.PumpInfo;
+import info.nightscout.android.utils.HexDump;
 
 /**
  * Created by lgoedhart on 27/03/2016.
  */
-public class PumpBasalPatternResponseMessage extends MedtronicReceiveMessage {
-    protected PumpBasalPatternResponseMessage(CommandType commandType, CommandAction commandAction, MedtronicCnlSession pumpSession, byte[] payload) {
-        super(commandType, commandAction, pumpSession, payload);
-    }
+public class PumpBasalPatternResponseMessage extends MedtronicSendMessageResponseMessage {
+    private static final String TAG = PumpBasalPatternResponseMessage.class.getSimpleName();
+
+    protected PumpBasalPatternResponseMessage(MedtronicCnlSession pumpSession, byte[] payload) throws EncryptionException, ChecksumException {
+        super(pumpSession, payload);
 
-    public static ContourNextLinkMessage fromBytes(MedtronicCnlSession pumpSession, byte[] bytes) throws ChecksumException, EncryptionException {
-        // TODO - turn this into a factory
-        ContourNextLinkMessage message = MedtronicReceiveMessage.fromBytes(pumpSession, bytes);
+        // TODO - determine message validity
+        /*
+        if (response.encode().length < (61 + 8)) {
+            // Invalid message.
+            // TODO - deal with this more elegantly
+            Log.e(TAG, "Invalid message received for getBasalPatterns");
+            return;
+        }
+        */
 
-        // TODO - Validate the MessageType
 
-        return message;
+        byte bufferSize = (byte) (this.encode()[0x38] - 2); // TODO - getting the size should be part of the superclass.
+        ByteBuffer basalBuffer = ByteBuffer.allocate(bufferSize);
+        basalBuffer.order(ByteOrder.BIG_ENDIAN);
+        basalBuffer.put(this.encode(), 0x39, bufferSize);
+
+        if (BuildConfig.DEBUG) {
+            String outputString = HexDump.dumpHexString(basalBuffer.array());
+            Log.d(TAG, "BASAL PAYLOAD: " + outputString);
+        }
+    }
+
+    public void updateBasalPatterns(PumpInfo pumpInfo) {
     }
 }
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 1162bfe139ca65e06fe1eb95571f1ff67520f78c..163f42775f2161bcf92ae7e743c2a0435ba99165 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
@@ -1,12 +1,55 @@
 package info.nightscout.android.medtronic.message;
 
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+import info.nightscout.android.USB.UsbHidDriver;
 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 26/03/2016.
  */
-public class PumpStatusRequestMessage extends MedtronicSendMessage {
-    public PumpStatusRequestMessage(MedtronicCnlSession pumpSession) throws EncryptionException {
+public class PumpStatusRequestMessage extends MedtronicSendMessageRequestMessage<PumpStatusResponseMessage> {
+    private static final String TAG = PumpStatusRequestMessage.class.getSimpleName();
+
+    public PumpStatusRequestMessage(MedtronicCnlSession pumpSession) throws EncryptionException, ChecksumException {
         super(SendMessageType.READ_PUMP_STATUS_REQUEST, pumpSession, null);
     }
+
+    // TODO - this needs refactoring
+    public PumpStatusResponseMessage send(UsbHidDriver mDevice, int millis) throws IOException, TimeoutException, ChecksumException, EncryptionException, UnexpectedMessageException {
+        sendMessage(mDevice);
+        if (millis > 0) {
+            try {
+                Log.d(TAG, "waiting " + millis +" ms");
+                Thread.sleep(millis);
+            } catch (InterruptedException e) {
+            }
+        }
+        // Read the 0x81
+        readMessage_0x81(mDevice);
+        if (millis > 0) {
+            try {
+                Log.d(TAG, "waiting " + millis +" ms");
+                Thread.sleep(millis);
+            } catch (InterruptedException e) {
+            }
+        }
+        PumpStatusResponseMessage response = this.getResponse(readMessage(mDevice));
+
+        // clear unexpected incoming messages
+        clearMessage(mDevice);
+
+        return response;
+    }
+
+    @Override
+    protected PumpStatusResponseMessage getResponse(byte[] payload) throws ChecksumException, EncryptionException, IOException, UnexpectedMessageException {
+        return new PumpStatusResponseMessage(mPumpSession, payload);
+    }
 }
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 06e9a0f3357d2456f64a5eb7c84ac3218d1e35ee..f97a73934acf42bcb5ffffb6f778a440af158563 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
@@ -1,21 +1,202 @@
 package info.nightscout.android.medtronic.message;
 
+import android.util.Log;
+
+import java.math.BigDecimal;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Date;
+import java.util.Locale;
+
+import info.nightscout.android.BuildConfig;
 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;
+import info.nightscout.android.model.medtronicNg.PumpStatusEvent;
+import info.nightscout.android.utils.HexDump;
 
 /**
  * Created by lgoedhart on 27/03/2016.
  */
-public class PumpStatusResponseMessage extends MedtronicReceiveMessage {
-    protected PumpStatusResponseMessage(CommandType commandType, CommandAction commandAction, MedtronicCnlSession pumpSession, byte[] payload) {
-        super(commandType, commandAction, pumpSession, payload);
+public class PumpStatusResponseMessage extends MedtronicSendMessageResponseMessage {
+    private static final String TAG = PumpStatusResponseMessage.class.getSimpleName();
+
+    // Data from the Medtronic Pump Status message
+    private boolean suspended;
+    private boolean bolusing;
+    private boolean deliveringInsulin;
+    private boolean tempBasalActive;
+    private boolean cgmActive;
+    private byte activeBasalPattern;
+    private float basalRate;
+    private float tempBasalRate;
+    private byte tempBasalPercentage;
+    private short tempBasalMinutesRemaining;
+    private float basalUnitsDeliveredToday;
+    private short batteryPercentage;
+    private float reservoirAmount;
+    private short minutesOfInsulinRemaining; // 25h == "more than 1 day"
+    private float activeInsulin;
+    private int sgv;
+    private Date sgvDate;
+    private boolean lowSuspendActive;
+    private PumpStatusEvent.CGM_TREND cgmTrend;
+
+    private boolean recentBolusWizard; // Whether a bolus wizard has been run recently
+    private int bolusWizardBGL; // in mg/dL. 0 means no recent bolus wizard reading.
+
+    private long rtc;
+    private long offset;
+
+    protected PumpStatusResponseMessage(MedtronicCnlSession pumpSession, byte[] payload) throws EncryptionException, ChecksumException, UnexpectedMessageException {
+        super(pumpSession, payload);
+
+        if (this.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 updatePumpStatus");
+            throw new UnexpectedMessageException("Invalid message received for updatePumpStatus");
+        }
+
+        byte bufferSize = (byte) (this.encode()[0x38] - 2); // TODO - getting the size should be part of the superclass.
+        ByteBuffer statusBuffer = ByteBuffer.allocate(bufferSize);
+        statusBuffer.order(ByteOrder.BIG_ENDIAN);
+        statusBuffer.put(this.encode(), 0x39, bufferSize);
+
+        if (BuildConfig.DEBUG) {
+            String outputString = HexDump.dumpHexString(statusBuffer.array());
+            Log.d(TAG, "PAYLOAD: " + outputString);
+        }
+        // Status Flags
+        suspended = (statusBuffer.get(0x03) & 0x01) != 0x00;
+        bolusing = (statusBuffer.get(0x03) & 0x02) != 0x00;
+        deliveringInsulin = (statusBuffer.get(0x03) & 0x10) != 0x00;
+        tempBasalActive = (statusBuffer.get(0x03) & 0x20) != 0x00;
+        cgmActive = (statusBuffer.get(0x03) & 0x40) != 0x00;
+
+        // Active basal pattern
+        activeBasalPattern = statusBuffer.get(0x1a);
+
+        // Normal basal rate
+        long rawNormalBasal = statusBuffer.getInt(0x1b);
+        basalRate = new BigDecimal(rawNormalBasal / 10000f).setScale(3, BigDecimal.ROUND_HALF_UP).floatValue();
+
+        // Temp basal rate
+        long rawTempBasal = statusBuffer.getShort(0x21) & 0x0000ffff;
+        tempBasalRate = new BigDecimal(rawTempBasal / 10000f).setScale(3, BigDecimal.ROUND_HALF_UP).floatValue();
+
+        // Temp basal percentage
+        tempBasalPercentage = statusBuffer.get(0x23);
+
+        // Temp basal minutes remaining
+        tempBasalMinutesRemaining = (short) (statusBuffer.getShort(0x24) & 0x0000ffff);
+
+        // Units of insulin delivered as basal today
+        // TODO - is this basal? Do we have a total Units delivered elsewhere?
+        basalUnitsDeliveredToday = statusBuffer.getInt(0x26);
+
+        // Pump battery percentage
+        batteryPercentage = statusBuffer.get(0x2a);
+
+        // Reservoir amount
+        long rawReservoirAmount = statusBuffer.getInt(0x2b);
+        reservoirAmount = new BigDecimal(rawReservoirAmount / 10000f).setScale(3, BigDecimal.ROUND_HALF_UP).floatValue();
+
+        // Amount of insulin left in pump (in minutes)
+        byte insulinHours = statusBuffer.get(0x2f);
+        byte insulinMinutes = statusBuffer.get(0x30);
+        minutesOfInsulinRemaining = (short) ((insulinHours * 60) + insulinMinutes);
+
+        // Active insulin
+        long rawActiveInsulin = statusBuffer.getInt(0x31);
+        activeInsulin = new BigDecimal(rawActiveInsulin / 10000f).setScale(3, BigDecimal.ROUND_HALF_UP).floatValue();
+
+        // CGM SGV
+        sgv = (statusBuffer.getShort(0x35) & 0x0000ffff); // In mg/DL. 0 means no CGM reading
+        if ((sgv & 0x200) == 0x200) {
+            // Sensor error. Let's reset. FIXME - solve this more elegantly later
+            sgv = 0;
+            rtc = 0;
+            offset = 0;
+            cgmTrend = PumpStatusEvent.CGM_TREND.NOT_SET;
+        } else {
+            rtc = statusBuffer.getInt(0x37) & 0x00000000ffffffffL;
+            offset = statusBuffer.getInt(0x3b);
+            cgmTrend = PumpStatusEvent.CGM_TREND.fromMessageByte(statusBuffer.get(0x40));
+        }
+
+        // SGV Date
+        // TODO - this should go in the sgvDate, and eventDate should be the time of this poll.
+        sgvDate = MessageUtils.decodeDateTime(rtc, offset);
+        Log.d(TAG, "original sgv date: " + sgvDate);
+
+        // Predictive low suspend
+        // TODO - there is more status info in this byte other than just a boolean yes/no
+        lowSuspendActive = statusBuffer.get(0x3f) != 0;
+
+        // Recent Bolus Wizard BGL
+        recentBolusWizard = statusBuffer.get(0x48) != 0;
+        bolusWizardBGL = statusBuffer.getShort(0x49) & 0x0000ffff; // In mg/DL
     }
 
-    public static ContourNextLinkMessage fromBytes(MedtronicCnlSession pumpSession, byte[] bytes) throws ChecksumException, EncryptionException {
-        // TODO - turn this into a factory
-        ContourNextLinkMessage message = MedtronicReceiveMessage.fromBytes(pumpSession, bytes);
+    /**
+     * update pumpRecord with data read from pump
+     *
+     * @param pumpRecord
+     */
+    public void updatePumpRecord(PumpStatusEvent pumpRecord) {
+        // Status Flags
+        pumpRecord.setSuspended(suspended);
+        pumpRecord.setBolusing(bolusing);
+        pumpRecord.setDeliveringInsulin(deliveringInsulin);
+        pumpRecord.setTempBasalActive(tempBasalActive);
+        pumpRecord.setCgmActive(cgmActive);
+
+        // Active basal pattern
+        pumpRecord.setActiveBasalPattern(activeBasalPattern);
+
+        // Normal basal rate
+        pumpRecord.setBasalRate(basalRate);
+
+        // Temp basal rate
+        pumpRecord.setTempBasalRate(tempBasalRate);
+
+        // Temp basal percentage
+        pumpRecord.setTempBasalPercentage(tempBasalPercentage);
+
+        // Temp basal minutes remaining
+        pumpRecord.setTempBasalMinutesRemaining(tempBasalMinutesRemaining);
+
+        // Units of insulin delivered as basal today
+        pumpRecord.setBasalUnitsDeliveredToday(basalUnitsDeliveredToday);
+
+        // Pump battery percentage
+        pumpRecord.setBatteryPercentage(batteryPercentage);
+
+        // Reservoir amount
+        pumpRecord.setReservoirAmount(reservoirAmount);
+
+        // Amount of insulin left in pump (in minutes)
+        pumpRecord.setMinutesOfInsulinRemaining(minutesOfInsulinRemaining);
+
+        // Active insulin
+        pumpRecord.setActiveInsulin(activeInsulin);
+
+        // CGM SGV
+        pumpRecord.setSgv(sgv);
+        pumpRecord.setSgvDate(new Date(sgvDate.getTime() - pumpRecord.getPumpTimeOffset()));
+
+        // SGV Date
+        pumpRecord.setCgmTrend(cgmTrend);
+        pumpRecord.setEventDate(new Date(sgvDate.getTime() - pumpRecord.getPumpTimeOffset()));
 
-        // TODO - Validate the MessageType
+        // Predictive low suspend
+        // TODO - there is more status info in this byte other than just a boolean yes/no
+        pumpRecord.setLowSuspendActive(lowSuspendActive);
 
-        return message;
+        // Recent Bolus Wizard BGL
+        pumpRecord.setRecentBolusWizard(recentBolusWizard);
+        pumpRecord.setBolusWizardBGL(bolusWizardBGL); // In mg/DL
     }
 }
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 89305b0a9b3873d3ec4079eeacebf8d740443ffe..592800406888cb5c6ce3860642c785b274ce79a2 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
@@ -1,12 +1,51 @@
 package info.nightscout.android.medtronic.message;
 
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+import info.nightscout.android.USB.UsbHidDriver;
 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 26/03/2016.
  */
-public class PumpTimeRequestMessage extends MedtronicSendMessage {
-    public PumpTimeRequestMessage(MedtronicCnlSession pumpSession) throws EncryptionException {
+public class PumpTimeRequestMessage extends MedtronicSendMessageRequestMessage<PumpTimeResponseMessage> {
+    public PumpTimeRequestMessage(MedtronicCnlSession pumpSession) throws EncryptionException, ChecksumException {
         super(SendMessageType.TIME_REQUEST, pumpSession, null);
     }
-}
+
+    @Override
+    public PumpTimeResponseMessage send(UsbHidDriver mDevice, int millis) throws IOException, TimeoutException, ChecksumException, EncryptionException, UnexpectedMessageException {
+
+        sendMessage(mDevice);
+        if (millis > 0) {
+            try {
+                Thread.sleep(millis);
+            } catch (InterruptedException e) {
+            }
+        }
+        // Read the 0x81
+        readMessage_0x81(mDevice);
+        if (millis > 0) {
+            try {
+                Thread.sleep(millis);
+            } catch (InterruptedException e) {
+            }
+        }
+        // Read the 0x80
+        PumpTimeResponseMessage response = this.getResponse(readMessage(mDevice));
+
+        // Pump sends additional 0x80 message when not using EHSM, lets clear this and any unexpected incoming messages
+        clearMessage(mDevice);
+
+        return response;
+    }
+
+    @Override
+    protected PumpTimeResponseMessage getResponse(byte[] payload) throws ChecksumException, EncryptionException, IOException, UnexpectedMessageException {
+        return new PumpTimeResponseMessage(mPumpSession, payload);
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/PumpTimeResponseMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/PumpTimeResponseMessage.java
index 3437dd69c65e9adfe188966ea14cbc1a6f8afb18..2005880b7929d3dbd808d1f7be6e6592f07b1948 100644
--- a/app/src/main/java/info/nightscout/android/medtronic/message/PumpTimeResponseMessage.java
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/PumpTimeResponseMessage.java
@@ -1,21 +1,51 @@
 package info.nightscout.android.medtronic.message;
 
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Date;
+
+import info.nightscout.android.BuildConfig;
 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;
+import info.nightscout.android.utils.HexDump;
 
 /**
  * Created by lgoedhart on 27/03/2016.
  */
-public class PumpTimeResponseMessage extends MedtronicReceiveMessage {
-    protected PumpTimeResponseMessage(CommandType commandType, CommandAction commandAction, MedtronicCnlSession pumpSession, byte[] payload) {
-        super(commandType, commandAction, pumpSession, payload);
-    }
+public class PumpTimeResponseMessage extends MedtronicSendMessageResponseMessage {
+    private static final String TAG = PumpTimeResponseMessage.class.getSimpleName();
+
+    private Date pumpTime;
 
-    public static ContourNextLinkMessage fromBytes(MedtronicCnlSession pumpSession, byte[] bytes) throws ChecksumException, EncryptionException {
-        // TODO - turn this into a factory
-        ContourNextLinkMessage message = MedtronicReceiveMessage.fromBytes(pumpSession, bytes);
+    protected PumpTimeResponseMessage(MedtronicCnlSession pumpSession, byte[] payload) throws EncryptionException, ChecksumException, UnexpectedMessageException {
+        super(pumpSession, payload);
 
-        // TODO - Validate the MessageType
+        if (this.encode().length < (61 + 8)) {
+            // Invalid message. Return an invalid date.
+            // TODO - deal with this more elegantly
+            Log.e(TAG, "Invalid message received for getPumpTime");
+            throw new UnexpectedMessageException("Invalid message received for getPumpTime");
+        } else {
+            ByteBuffer dateBuffer = ByteBuffer.allocate(8);
+            dateBuffer.order(ByteOrder.BIG_ENDIAN);
+            dateBuffer.put(this.encode(), 0x3d, 8);
+
+            if (BuildConfig.DEBUG) {
+                String outputString = HexDump.dumpHexString(dateBuffer.array());
+                Log.d(TAG, "PAYLOAD: " + outputString);
+            }
+
+            long rtc = dateBuffer.getInt(0) & 0x00000000ffffffffL;
+            long offset = dateBuffer.getInt(4);
+            pumpTime = MessageUtils.decodeDateTime(rtc, offset);
+        }
+    }
 
-        return message;
+    public Date getPumpTime() {
+        return pumpTime;
     }
 }
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/ReadHistoryInfoRequestMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/ReadHistoryInfoRequestMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..ff7a2f692eee0d339f577e30c8059d84d5421dcf
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/ReadHistoryInfoRequestMessage.java
@@ -0,0 +1,37 @@
+package info.nightscout.android.medtronic.message;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+import info.nightscout.android.USB.UsbHidDriver;
+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 26/03/2016.
+ */
+public class ReadHistoryInfoRequestMessage extends MedtronicSendMessageRequestMessage<ReadHistoryInfoResponseMessage> {
+    public ReadHistoryInfoRequestMessage(MedtronicCnlSession pumpSession) throws EncryptionException, ChecksumException {
+        super(SendMessageType.READ_BASAL_PATTERN_REQUEST, pumpSession, new byte[] {
+                2,
+                3,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0
+        });
+    }
+
+    @Override
+    protected ReadHistoryInfoResponseMessage getResponse(byte[] payload) throws ChecksumException, EncryptionException, IOException, UnexpectedMessageException {
+        return new ReadHistoryInfoResponseMessage(mPumpSession, payload);
+    }
+}
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/ReadHistoryInfoResponseMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/ReadHistoryInfoResponseMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..f77fc57a69cd621854f4712cecb57ab2b7d0b11d
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/ReadHistoryInfoResponseMessage.java
@@ -0,0 +1,48 @@
+package info.nightscout.android.medtronic.message;
+
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import info.nightscout.android.BuildConfig;
+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;
+import info.nightscout.android.utils.HexDump;
+
+/**
+ * Created by lgoedhart on 27/03/2016.
+ */
+public class ReadHistoryInfoResponseMessage extends MedtronicSendMessageResponseMessage {
+    private static final String TAG = ReadHistoryInfoResponseMessage.class.getSimpleName();
+
+    protected ReadHistoryInfoResponseMessage(MedtronicCnlSession pumpSession, byte[] payload) throws EncryptionException, ChecksumException, UnexpectedMessageException {
+        super(pumpSession, payload);
+
+
+        if (this.encode().length < 32) {
+            // Invalid message.
+            // TODO - deal with this more elegantly
+            Log.e(TAG, "Invalid message received for ReadHistoryInfo");
+            throw new UnexpectedMessageException("Invalid message received for ReadHistoryInfo");
+        } else {
+
+            ByteBuffer basalRatesBuffer = ByteBuffer.allocate(payload.length);
+            basalRatesBuffer.order(ByteOrder.BIG_ENDIAN);
+            basalRatesBuffer.put(this.encode());
+
+            if (BuildConfig.DEBUG) {
+                String outputString = HexDump.dumpHexString(basalRatesBuffer.array());
+                Log.d(TAG, "PAYLOAD: " + outputString);
+            }
+            String responseString = HexDump.dumpHexString(basalRatesBuffer.array());
+            Log.d(TAG, "ReadHistoryInfo: " + responseString);
+            Log.d(TAG, "ReadHistoryInfo-length: " + basalRatesBuffer.getLong(28));
+        }
+
+
+    }
+
+}
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/ReadInfoRequestMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/ReadInfoRequestMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..c357cd5f25ac84be95988ea702987df8ed059deb
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/ReadInfoRequestMessage.java
@@ -0,0 +1,24 @@
+package info.nightscout.android.medtronic.message;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+import info.nightscout.android.USB.UsbHidDriver;
+import info.nightscout.android.medtronic.MedtronicCnlSession;
+import info.nightscout.android.medtronic.exception.ChecksumException;
+import info.nightscout.android.medtronic.exception.EncryptionException;
+
+/**
+ * Created by volker on 10.12.2016.
+ */
+
+public class ReadInfoRequestMessage extends ContourNextLinkBinaryRequestMessage<ReadInfoResponseMessage> {
+    public ReadInfoRequestMessage(MedtronicCnlSession pumpSession) throws ChecksumException {
+        super(ContourNextLinkBinaryRequestMessage.CommandType.READ_INFO, pumpSession, null);
+    }
+
+    @Override
+    protected ReadInfoResponseMessage getResponse(byte[] payload) throws ChecksumException, EncryptionException, IOException {
+        return new ReadInfoResponseMessage(mPumpSession, payload);
+    }
+}
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/ReadInfoResponseMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/ReadInfoResponseMessage.java
index 68aab2df7977d4235e1c445b4b8ac0692d16e88b..ad350653318d5caba0c4a8b687fe473173a32ac6 100644
--- a/app/src/main/java/info/nightscout/android/medtronic/message/ReadInfoResponseMessage.java
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/ReadInfoResponseMessage.java
@@ -1,21 +1,34 @@
 package info.nightscout.android.medtronic.message;
 
+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;
 
 /**
  * Created by lgoedhart on 10/05/2016.
  */
-public class ReadInfoResponseMessage extends MedtronicReceiveMessage {
-    protected ReadInfoResponseMessage(CommandType commandType, CommandAction commandAction, MedtronicCnlSession pumpSession, byte[] payload) {
-        super(commandType, commandAction, pumpSession, payload);
-    }
+public class ReadInfoResponseMessage extends MedtronicResponseMessage {
+    private long linkMAC;
+    private long pumpMAC;
 
-    public static ContourNextLinkMessage fromBytes(MedtronicCnlSession pumpSession, byte[] bytes) throws ChecksumException, EncryptionException {
-        // TODO - turn this into a factory
-        ContourNextLinkMessage message = MedtronicReceiveMessage.fromBytes(pumpSession, bytes);
+    protected ReadInfoResponseMessage(MedtronicCnlSession pumpSession, byte[] payload) throws ChecksumException, EncryptionException {
+        super(pumpSession, payload);
 
-        // TODO - Validate the MessageType
+        ByteBuffer infoBuffer = ByteBuffer.allocate(16);
+        infoBuffer.order(ByteOrder.BIG_ENDIAN);
+        infoBuffer.put(this.encode(), 0x21, 16);
+        linkMAC = infoBuffer.getLong(0);
+        pumpMAC = infoBuffer.getLong(8);
+    }
+
+    public long getLinkMAC() {
+        return linkMAC;
+    }
 
-        return message;
+    public long getPumpMAC() {
+        return pumpMAC;
     }
 }
\ No newline at end of file
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
new file mode 100644
index 0000000000000000000000000000000000000000..bc2d01869d3c7a47d2080d03bd134837d74fc618
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/RequestLinkKeyRequestMessage.java
@@ -0,0 +1,24 @@
+package info.nightscout.android.medtronic.message;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+import info.nightscout.android.USB.UsbHidDriver;
+import info.nightscout.android.medtronic.MedtronicCnlSession;
+import info.nightscout.android.medtronic.exception.ChecksumException;
+import info.nightscout.android.medtronic.exception.EncryptionException;
+
+/**
+ * Created by volker on 10.12.2016.
+ */
+
+public class RequestLinkKeyRequestMessage extends ContourNextLinkBinaryRequestMessage<RequestLinkKeyResponseMessage> {
+    public RequestLinkKeyRequestMessage(MedtronicCnlSession pumpSession) throws ChecksumException {
+        super(CommandType.REQUEST_LINK_KEY, pumpSession, null);
+    }
+
+    @Override
+    protected RequestLinkKeyResponseMessage getResponse(byte[] payload) throws ChecksumException, EncryptionException {
+        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 423e40fb8ab61296fc9c26995837a56932715a7e..e5bb5ce76b280c6d8dd3a8508653b289feac251f 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,21 +1,50 @@
 package info.nightscout.android.medtronic.message;
 
+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;
 
 /**
  * Created by lgoedhart on 10/05/2016.
  */
-public class RequestLinkKeyResponseMessage extends MedtronicReceiveMessage {
-    protected RequestLinkKeyResponseMessage(CommandType commandType, CommandAction commandAction, MedtronicCnlSession pumpSession, byte[] payload) {
-        super(commandType, commandAction, pumpSession, payload);
+public class RequestLinkKeyResponseMessage extends MedtronicResponseMessage {
+
+    private byte[] key;
+
+    protected RequestLinkKeyResponseMessage(MedtronicCnlSession pumpSession, byte[] payload) throws EncryptionException, ChecksumException {
+        super(pumpSession, payload);
+
+        ByteBuffer infoBuffer = ByteBuffer.allocate(55);
+        infoBuffer.order(ByteOrder.BIG_ENDIAN);
+        infoBuffer.put(this.encode(), 0x21, 55);
+
+        setPackedLinkKey(infoBuffer.array());
+    }
+
+    public byte[] getKey() {
+        return key;
     }
 
-    public static ContourNextLinkMessage fromBytes(MedtronicCnlSession pumpSession, byte[] bytes) throws ChecksumException, EncryptionException {
-        // TODO - turn this into a factory
-        ContourNextLinkMessage message = MedtronicReceiveMessage.fromBytes(pumpSession, bytes);
+    private void setPackedLinkKey(byte[] packedLinkKey) {
+        this.key = new byte[16];
+
+        int pos = mPumpSession.getStickSerial().charAt(mPumpSession.getStickSerial().length() - 1) & 7;
 
-        // TODO - Validate the MessageType
+        for (int i = 0; i < this.key.length; i++) {
+            if ((packedLinkKey[pos + 1] & 1) == 1) {
+                this.key[i] = (byte) ~packedLinkKey[pos];
+            } else {
+                this.key[i] = packedLinkKey[pos];
+            }
 
-        return message;
+            if (((packedLinkKey[pos + 1] >> 1) & 1) == 0) {
+                pos += 3;
+            } else {
+                pos += 2;
+            }
+        }
     }
 }
\ No newline at end of file
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
new file mode 100644
index 0000000000000000000000000000000000000000..08737107d34864e8164ba6e04f25f4b327ed4c5a
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/service/MedtronicCnlAlarmManager.java
@@ -0,0 +1,95 @@
+package info.nightscout.android.medtronic.service;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.support.v4.content.WakefulBroadcastReceiver;
+import android.util.Log;
+
+import java.util.Date;
+
+import info.nightscout.android.medtronic.MainActivity;
+
+/**
+ * Created by lgoedhart on 14/07/2016.
+ */
+public class MedtronicCnlAlarmManager {
+    private static final String TAG = MedtronicCnlAlarmManager.class.getSimpleName();
+    private static final int ALARM_ID = 102; // Alarm id
+
+    private static PendingIntent pendingIntent = null;
+    private static AlarmManager alarmManager = null;
+    private static long nextAlarm = Long.MAX_VALUE;
+
+    public static void setContext(Context context) {
+        cancelAlarm();
+
+        alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+        Intent intent = new Intent(context, MedtronicCnlAlarmReceiver.class);
+        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
+     *
+     * @param inFuture number of millin in the future
+     */
+    public static void setAlarmAfterMillis(long inFuture) {
+        setAlarm(System.currentTimeMillis() + inFuture);
+    }
+
+    // Setting the alarm to call onRecieve
+    public static void setAlarm(long millis) {
+        if (alarmManager == null || pendingIntent == null)
+            return;
+
+        Log.d(TAG, "request to set Alarm at " + new Date(millis));
+
+        long now = System.currentTimeMillis();
+        // don't trigger the past
+        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
+            alarmManager.setExact(AlarmManager.RTC_WAKEUP, millis, pendingIntent);
+        } else {
+            alarmManager.set(AlarmManager.RTC_WAKEUP, millis, pendingIntent);
+        }
+    }
+
+    // restarting the alarm after MedtronicCnlIntentService.POLL_PERIOD_MS from now
+    public static void restartAlarm() {
+        //setAlarmAfterMillis(MainActivity.pollInterval + MedtronicCnlIntentService.POLL_GRACE_PERIOD_MS);
+        setAlarmAfterMillis(MainActivity.pollInterval); // grace already accounted for when using current intent time to set default restart
+    }
+
+    // Cancel the alarm.
+    public static void cancelAlarm() {
+        if (alarmManager == null || pendingIntent == null)
+            return;
+
+        alarmManager.cancel(pendingIntent);
+    }
+
+}
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 49e648c68cc37df7ca643e632c9c76b1457b32f0..d86076cc3f01cf4a433bb3a9cd29196707bbe0c8 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,15 +1,20 @@
 package info.nightscout.android.medtronic.service;
 
+import android.app.Activity;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.os.Build;
+import android.preference.PreferenceManager;
 import android.support.v4.content.WakefulBroadcastReceiver;
 import android.util.Log;
 
 import java.util.Date;
 
+import info.nightscout.android.medtronic.MainActivity;
+
 /**
  * Created by lgoedhart on 14/07/2016.
  */
@@ -17,8 +22,9 @@ 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 pendingIntent = null;
-    private static AlarmManager alarmManager = null;
+    public MedtronicCnlAlarmReceiver() {
+        super();
+    }
 
     @Override
     public void onReceive(final Context context, Intent intent) {
@@ -26,53 +32,6 @@ public class MedtronicCnlAlarmReceiver extends WakefulBroadcastReceiver {
         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) {
-        cancelAlarm();
-
-        alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
-        Intent intent = new Intent(context, MedtronicCnlAlarmReceiver.class);
-        pendingIntent = PendingIntent.getBroadcast(context, ALARM_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT);
-    }
-
-    // Setting the alarm in 15 seconds from now
-    public void setAlarm() {
-        setAlarm(System.currentTimeMillis());
-    }
-
-    // Setting the alarm to call onRecieve
-    public void setAlarm(long millis) {
-        if (alarmManager == null || pendingIntent == null)
-            return;
-
-        cancelAlarm();
-
-        // don't trigger the past and at least 30 sec away
-        if (millis < System.currentTimeMillis())
-            millis = System.currentTimeMillis();
-
-        Log.d(TAG, "AlarmManager 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) {
-            alarmManager.setExact(AlarmManager.RTC_WAKEUP, millis, pendingIntent);
-        } else
-            alarmManager.set(AlarmManager.RTC_WAKEUP, millis, pendingIntent);
-    }
-
-    // restarting the alarm after MedtronicCnlIntentService.POLL_PERIOD_MS from now
-    public void restartAlarm() {
-        setAlarm(System.currentTimeMillis() + MedtronicCnlIntentService.POLL_PERIOD_MS + MedtronicCnlIntentService.POLL_GRACE_PERIOD_MS);
+        MedtronicCnlAlarmManager.restartAlarm();
     }
-
-    // Cancel the alarm.
-    public void cancelAlarm() {
-        if (alarmManager == null || pendingIntent == null)
-            return;
-
-        alarmManager.cancel(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 b958a2c3ac004079815a46121313dc9e74cf097a..3d9e0eb7cea5e6e3fbdf9b866e6a1ad59b32ad8e 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
@@ -20,15 +20,17 @@ import java.security.NoSuchAlgorithmException;
 import java.util.Date;
 import java.util.Locale;
 import java.util.concurrent.TimeoutException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
 
 import info.nightscout.android.R;
 import info.nightscout.android.USB.UsbHidDriver;
 import info.nightscout.android.medtronic.MainActivity;
 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.exception.ChecksumException;
+import info.nightscout.android.medtronic.exception.EncryptionException;
+import info.nightscout.android.medtronic.exception.UnexpectedMessageException;
 import info.nightscout.android.medtronic.message.MessageUtils;
-import info.nightscout.android.medtronic.message.UnexpectedMessageException;
 import info.nightscout.android.model.medtronicNg.ContourNextLinkInfo;
 import info.nightscout.android.model.medtronicNg.PumpInfo;
 import info.nightscout.android.model.medtronicNg.PumpStatusEvent;
@@ -42,6 +44,7 @@ public class MedtronicCnlIntentService extends IntentService {
     public final static int USB_PID = 0x6210;
     public final static long USB_WARMUP_TIME_MS = 5000L;
     public final static long POLL_PERIOD_MS = 300000L;
+    public final static long LOW_BATTERY_POLL_PERIOD_MS = 900000L;
     // Number of additional seconds to wait after the next expected CGM poll, so that we don't interfere with CGM radio comms.
     public final static long POLL_GRACE_PERIOD_MS = 30000L;
     private static final String TAG = MedtronicCnlIntentService.class.getSimpleName();
@@ -97,6 +100,25 @@ public class MedtronicCnlIntentService extends IntentService {
     protected void onHandleIntent(Intent intent) {
         Log.d(TAG, "onHandleIntent called");
 
+        long timePollStarted = System.currentTimeMillis();
+        long timePollExpected = timePollStarted;
+        if (MainActivity.timeLastGoodSGV != 0) {
+            timePollExpected = MainActivity.timeLastGoodSGV + POLL_PERIOD_MS + POLL_GRACE_PERIOD_MS + (POLL_PERIOD_MS * ((timePollStarted - 1000L - (MainActivity.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 = MainActivity.pollInterval;
+        if ((MainActivity.pumpBattery > 0) && (MainActivity.pumpBattery <= 25)) {
+            pollInterval = MainActivity.lowBatteryPollInterval;
+        }
+
         if (!hasUsbHostFeature()) {
             sendStatus("It appears that this device doesn't support USB OTG.");
             Log.e(TAG, "Device does not support USB OTG");
@@ -134,14 +156,16 @@ public class MedtronicCnlIntentService extends IntentService {
             return;
         }
 
+        DateFormat df = new SimpleDateFormat("HH:mm:ss");
+
         MedtronicCnlReader cnlReader = new MedtronicCnlReader(mHidDevice);
 
         Realm realm = Realm.getDefaultInstance();
         realm.beginTransaction();
 
         try {
-            sendStatus("Connecting to the Contour Next Link...");
-            Log.d(TAG, "Connecting to the Contour Next Link.");
+            sendStatus("Connecting to Contour Next Link");
+            Log.d(TAG, "Connecting to Contour Next Link");
             cnlReader.requestDeviceInfo();
 
             // Is the device already configured?
@@ -151,11 +175,7 @@ public class MedtronicCnlIntentService extends IntentService {
                     .findFirst();
 
             if (info == null) {
-                // TODO - use realm.createObject()?
-                info = new ContourNextLinkInfo();
-                info.setSerialNumber(cnlReader.getStickSerial());
-
-                info = realm.copyToRealm(info);
+                info = realm.createObject(ContourNextLinkInfo.class, cnlReader.getStickSerial());
             }
 
             cnlReader.getPumpSession().setStickSerial(info.getSerialNumber());
@@ -165,6 +185,7 @@ public class MedtronicCnlIntentService extends IntentService {
             try {
                 cnlReader.enterPassthroughMode();
                 cnlReader.openConnection();
+
                 cnlReader.requestReadInfo();
 
                 String key = info.getKey();
@@ -180,27 +201,29 @@ public class MedtronicCnlIntentService extends IntentService {
 
                 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);
+                    activePump = realm.createObject(PumpInfo.class, pumpMAC);
                 }
 
+                activePump.updateLastQueryTS();
+
                 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?");
+                    pollInterval = MainActivity.pollInterval / (MainActivity.reducePollOnPumpAway?2L:1L); // reduce polling interval to half until pump is available
                 } else {
+                    setActivePumpMac(pumpMAC);
                     activePump.setLastRadioChannel(radioChannel);
-                    sendStatus(String.format(Locale.getDefault(), "Connected to Contour Next Link on channel %d.", (int) 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));
-                    cnlReader.beginEHSMSession();
 
+                    // read pump status
                     PumpStatusEvent pumpRecord = realm.createObject(PumpStatusEvent.class);
 
                     String deviceName = String.format("medtronic-640g://%s", cnlReader.getStickSerial());
@@ -216,45 +239,68 @@ public class MedtronicCnlIntentService extends IntentService {
                     // TODO - send ACTION to MainActivity to show offset between pump and uploader.
                     pumpRecord.setPumpTimeOffset(pumpOffset);
                     pumpRecord.setPumpDate(new Date(pumpTime - pumpOffset));
-                    cnlReader.getPumpStatus(pumpRecord, pumpOffset);
-                    activePump.getPumpHistory().add(pumpRecord);
+                    cnlReader.updatePumpStatus(pumpRecord);
 
-                    cnlReader.endEHSMSession();
-
-                    boolean cancelTransaction = true;
                     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
+
+                        // Check if pump sent old event when new expected and schedule a re-poll
+                        if (((pumpRecord.getEventDate().getTime() - MainActivity.timeLastGoodSGV) < 5000L) && ((timePollExpected - timePollStarted) < 5000L)) {
+                            pollInterval = 90000L; // polling interval set to 90 seconds
+                            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
+                        MainActivity.countUnavailableSGV = 0; // reset unavailable sgv count
+
                         // Check that the record doesn't already exist before committing
                         RealmResults<PumpStatusEvent> checkExistingRecords = activePump.getPumpHistory()
                                 .where()
-                                .equalTo("eventDate", pumpRecord.getEventDate())
+                                .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("sgv", pumpRecord.getSgv())
                                 .findAll();
 
                         // There should be the 1 record we've already added in this transaction.
-                        if (checkExistingRecords.size() <= 1) {
-                            realm.commitTransaction();
-                            cancelTransaction = false;
+                        if (checkExistingRecords.size() == 0) {
+                            activePump.getPumpHistory().add(pumpRecord);
                         }
 
-                        // Tell the Main Activity we have new data
-                        sendMessage(Constants.ACTION_REFRESH_DATA);
+                        Log.d(TAG, "history reading size: " + activePump.getPumpHistory().size());
+                        Log.d(TAG, "history reading date: " + activePump.getPumpHistory().last().getEventDate());
+                    } else {
+                        sendStatus("SGV: unavailable from pump");
+                        MainActivity.countUnavailableSGV ++; // poll clash detection
                     }
 
-                    if (cancelTransaction) {
-                        realm.cancelTransaction();
-                    }
+                    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 = MainActivity.pollInterval / (MainActivity.reducePollOnPumpAway?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 = MainActivity.pollInterval / (MainActivity.reducePollOnPumpAway?2L:1L);
             } catch (NoSuchAlgorithmException e) {
                 Log.e(TAG, "Could not determine CNL HMAC", e);
                 sendStatus("Error connecting to Contour Next Link: Hashing error.");
             } finally {
-                //TODO : 05.11.2016 has the close to be here?
-                cnlReader.closeConnection();
-                cnlReader.endPassthroughMode();
-                cnlReader.endControlMode();
+                try {
+                    cnlReader.closeConnection();
+                    cnlReader.endPassthroughMode();
+                    cnlReader.endControlMode();
+                } catch (NoSuchAlgorithmException e) {}
+
             }
         } catch (IOException e) {
             Log.e(TAG, "Error connecting to Contour Next Link.", e);
@@ -279,14 +325,42 @@ public class MedtronicCnlIntentService extends IntentService {
                 }
                 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 (MainActivity.timeLastGoodSGV > 0) {
+                lastActualPollTime = MainActivity.timeLastGoodSGV + POLL_GRACE_PERIOD_MS + (POLL_PERIOD_MS * ((System.currentTimeMillis() - (MainActivity.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 (MainActivity.countUnavailableSGV > 0) {
+                if (MainActivity.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 (MainActivity.countUnavailableSGV > 2) {
+                    sendStatus("Warning: No SGV available from pump for " + MainActivity.countUnavailableSGV + " attempts");
+                    nextRequestedPollTime += ((long) ((MainActivity.countUnavailableSGV - 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));
+
             MedtronicCnlAlarmReceiver.completeWakefulIntent(intent);
         }
     }
 
+    private void setActivePumpMac(long pumpMAC) {
+        MainActivity.setActivePumpMac(pumpMAC);
+    }
+
     // reliable wake alarm manager wake up for all android versions
     public static void wakeUpIntent(Context context, long wakeTime, PendingIntent pendingIntent) {
         final AlarmManager alarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
@@ -326,6 +400,7 @@ public class MedtronicCnlIntentService extends IntentService {
         public static final String ACTION_USB_PERMISSION = "info.nightscout.android.medtronic.USB_PERMISSION";
         public static final String ACTION_REFRESH_DATA = "info.nightscout.android.medtronic.service.CGM_DATA";
         public static final String ACTION_USB_REGISTER = "info.nightscout.android.medtronic.USB_REGISTER";
+        public static final String ACTION_UPDATE_PUMP = "info.nightscout.android.medtronic.UPDATE_PUMP";
 
         public static final String EXTENDED_DATA = "info.nightscout.android.medtronic.service.DATA";
     }
diff --git a/app/src/main/java/info/nightscout/android/model/medtronicNg/BasalRate.java b/app/src/main/java/info/nightscout/android/model/medtronicNg/BasalRate.java
new file mode 100644
index 0000000000000000000000000000000000000000..25c17b251658ddbb585c3cb91179e776be96a039
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/model/medtronicNg/BasalRate.java
@@ -0,0 +1,28 @@
+package info.nightscout.android.model.medtronicNg;
+
+import io.realm.RealmObject;
+
+/**
+ * Created by lennart on 22/1/17.
+ */
+
+public class BasalRate extends RealmObject {
+    private long start;
+    private float rate;
+
+    public long getStart() {
+        return start;
+    }
+
+    public void setStart(long start) {
+        this.start = start;
+    }
+
+    public float getRate() {
+        return rate;
+    }
+
+    public void setRate(float rate) {
+        this.rate = rate;
+    }
+}
diff --git a/app/src/main/java/info/nightscout/android/model/medtronicNg/BasalSchedule.java b/app/src/main/java/info/nightscout/android/model/medtronicNg/BasalSchedule.java
new file mode 100644
index 0000000000000000000000000000000000000000..a151a35de02466504c3d8be7f1b1c6e3e7481ce4
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/model/medtronicNg/BasalSchedule.java
@@ -0,0 +1,59 @@
+package info.nightscout.android.model.medtronicNg;
+
+import io.realm.RealmList;
+import io.realm.RealmObject;
+import io.realm.annotations.Index;
+import io.realm.annotations.PrimaryKey;
+
+/**
+ * Created by lennart on 22/1/17.
+ */
+
+public class BasalSchedule extends RealmObject {
+    @PrimaryKey
+    private byte scheduleNumber;
+    private RealmList<BasalRate> schedule;
+
+    @Index
+    private boolean uploaded = false;
+
+    public byte getScheduleNumber() {
+        return scheduleNumber;
+    }
+
+    public void setScheduleNumber(byte scheduleNumber) {
+        this.scheduleNumber = scheduleNumber;
+    }
+
+    public String getName() {
+        // TODO - internationalise
+        String[] patternNames = {
+                "Pattern 1",
+                "Pattern 2",
+                "Pattern 3",
+                "Pattern 4",
+                "Pattern 5",
+                "Workday",
+                "Day Off",
+                "Sick Day",
+
+        };
+        return patternNames[this.scheduleNumber - 1];
+    }
+
+    public RealmList<BasalRate> getSchedule() {
+        return schedule;
+    }
+
+    public void setSchedule(RealmList<BasalRate> schedule) {
+        this.schedule = schedule;
+    }
+
+    public boolean isUploaded() {
+        return uploaded;
+    }
+
+    public void setUploaded(boolean uploaded) {
+        this.uploaded = uploaded;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/android/model/medtronicNg/PumpInfo.java b/app/src/main/java/info/nightscout/android/model/medtronicNg/PumpInfo.java
index 3b25a51612206e6ffc265a1b6e1e18190b056d71..456c6f139830618d580de258f78ac517d3b9258e 100644
--- a/app/src/main/java/info/nightscout/android/model/medtronicNg/PumpInfo.java
+++ b/app/src/main/java/info/nightscout/android/model/medtronicNg/PumpInfo.java
@@ -1,5 +1,7 @@
 package info.nightscout.android.model.medtronicNg;
 
+import android.util.Log;
+
 import io.realm.RealmList;
 import io.realm.RealmObject;
 import io.realm.annotations.PrimaryKey;
@@ -12,14 +14,16 @@ public class PumpInfo extends RealmObject {
     private long pumpMac;
     private String deviceName;
     private byte lastRadioChannel;
+    private long lastQueryTS = 0;
     private RealmList<ContourNextLinkInfo> associatedCnls;
-    private RealmList<PumpStatusEvent> pumpHistory;
+    private RealmList<PumpStatusEvent> pumpHistory = new RealmList<>();
+    private RealmList<BasalSchedule> basalSchedules;
 
     public long getPumpMac() {
         return pumpMac;
     }
 
-    public void setPumpMac(long pumpMac) {
+    private void setPumpMac(long pumpMac) {
         this.pumpMac = pumpMac;
     }
 
@@ -58,4 +62,38 @@ public class PumpInfo extends RealmObject {
     public long getPumpSerial() {
         return pumpMac & 0xffffff;
     }
+
+    public long getLastQueryTS() {
+        return lastQueryTS;
+    }
+
+    public void updateLastQueryTS() {
+        lastQueryTS = System.currentTimeMillis();
+    }
+
+    public RealmList<BasalSchedule> getBasalSchedules() {
+        return basalSchedules;
+    }
+
+    public void setBasalSchedules(RealmList<BasalSchedule> basalSchedules) {
+        this.basalSchedules = basalSchedules;
+    }
+
+    public boolean checkBasalRatesMatch(PumpStatusEvent pumpRecord) {
+        byte activeBasal = pumpRecord.getActiveBasalPattern();
+
+        BasalSchedule schedule = basalSchedules
+                .where()
+                .equalTo("scheduleNumber", activeBasal)
+                .findFirst();
+
+        if(schedule == null) {
+            Log.d("Schedule Check", "Didn't find a matching schedule for " + activeBasal);
+            return false;
+        } else {
+            Log.d("Schedule Check", "Found a schedule for " + activeBasal + " with name " + schedule.getName());
+            return true;
+        }
+    }
+
 }
diff --git a/app/src/main/java/info/nightscout/android/model/medtronicNg/PumpStatusEvent.java b/app/src/main/java/info/nightscout/android/model/medtronicNg/PumpStatusEvent.java
index ab95d50f6d346d506d3a6f03a992e53cfaa00804..87adfa0667c404458262d91ce4f503e29dc9e0a1 100644
--- a/app/src/main/java/info/nightscout/android/model/medtronicNg/PumpStatusEvent.java
+++ b/app/src/main/java/info/nightscout/android/model/medtronicNg/PumpStatusEvent.java
@@ -9,7 +9,7 @@ import io.realm.annotations.Index;
 /**
  * Created by lgoedhart on 4/06/2016.
  */
-public class PumpStatusEvent extends RealmObject {
+public class PumpStatusEvent extends RealmObject  {
     @Index
     private Date eventDate; // The actual time of the event (assume the capture device eventDate/time is accurate)
     private Date pumpDate; // The eventDate/time on the pump at the time of the event
@@ -81,8 +81,15 @@ public class PumpStatusEvent extends RealmObject {
         return CGM_TREND.valueOf(cgmTrend);
     }
 
+    public String getCgmTrendString() {
+        return cgmTrend;
+    }
+
     public void setCgmTrend(CGM_TREND cgmTrend) {
-        this.cgmTrend = cgmTrend.name();
+        if (cgmTrend != null)
+            this.cgmTrend = cgmTrend.name();
+        else
+            this.cgmTrend = CGM_TREND.NOT_SET.name();
     }
 
     public void setCgmTrend(String cgmTrend) {
@@ -272,6 +279,27 @@ public class PumpStatusEvent extends RealmObject {
         DOUBLE_DOWN,
         NOT_COMPUTABLE,
         RATE_OUT_OF_RANGE,
-        NOT_SET
+        NOT_SET;
+
+        public static CGM_TREND fromMessageByte(byte messageByte) {
+            switch (messageByte) {
+                case (byte) 0x60:
+                    return PumpStatusEvent.CGM_TREND.FLAT;
+                case (byte) 0xc0:
+                    return PumpStatusEvent.CGM_TREND.DOUBLE_UP;
+                case (byte) 0xa0:
+                    return PumpStatusEvent.CGM_TREND.SINGLE_UP;
+                case (byte) 0x80:
+                    return PumpStatusEvent.CGM_TREND.FOURTY_FIVE_UP;
+                case (byte) 0x40:
+                    return PumpStatusEvent.CGM_TREND.FOURTY_FIVE_DOWN;
+                case (byte) 0x20:
+                    return PumpStatusEvent.CGM_TREND.SINGLE_DOWN;
+                case (byte) 0x00:
+                    return PumpStatusEvent.CGM_TREND.DOUBLE_DOWN;
+                default:
+                    return PumpStatusEvent.CGM_TREND.NOT_COMPUTABLE;
+            }
+        }
     }
 }
diff --git a/app/src/main/java/info/nightscout/android/settings/SettingsFragment.java b/app/src/main/java/info/nightscout/android/settings/SettingsFragment.java
index 7519a5c7f75cc4e177012ca37b2ec08d272d0b8a..cbabb6a4ec7e9a497f487642f0fd8f1f06ccf3da 100644
--- a/app/src/main/java/info/nightscout/android/settings/SettingsFragment.java
+++ b/app/src/main/java/info/nightscout/android/settings/SettingsFragment.java
@@ -25,11 +25,50 @@ public class SettingsFragment extends PreferenceFragment implements OnSharedPref
         for (int i = 0; i < getPreferenceScreen().getPreferenceCount(); i++) {
             initSummary(getPreferenceScreen().getPreference(i));
         }
+
+        setMinBatPollIntervall((ListPreference) findPreference("pollInterval"), (ListPreference) findPreference("lowBatPollInterval"));
     }
 
     @Override
     public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
-        updatePrefSummary(findPreference(key));
+        Preference pref = findPreference(key);
+
+
+        if ("pollInterval".equals(key)) {
+            setMinBatPollIntervall((ListPreference) pref, (ListPreference) findPreference("lowBatPollInterval"));
+        }
+        updatePrefSummary(pref);
+    }
+
+    //
+
+    /**
+     * set lowBatPollInterval to normal poll interval at least
+     * and adapt the selectable values
+     *
+     * @param pollIntervalPref
+     * @param lowBatPollIntervalPref
+     */
+    private void setMinBatPollIntervall(ListPreference pollIntervalPref, ListPreference lowBatPollIntervalPref) {
+        final String currentValue = lowBatPollIntervalPref.getValue();
+        final int pollIntervalPos = (pollIntervalPref.findIndexOfValue(pollIntervalPref.getValue()) >= 0?pollIntervalPref.findIndexOfValue(pollIntervalPref.getValue()):0),
+                length = pollIntervalPref.getEntries().length;
+
+        CharSequence[] entries = new String[length - pollIntervalPos],
+                entryValues = new String[length - pollIntervalPos];
+
+        // generate temp Entries and EntryValues
+        for(int i = pollIntervalPos; i < length; i++) {
+            entries[i - pollIntervalPos] = pollIntervalPref.getEntries()[i];
+            entryValues[i - pollIntervalPos] = pollIntervalPref.getEntryValues()[i];
+        }
+        lowBatPollIntervalPref.setEntries(entries);
+        lowBatPollIntervalPref.setEntryValues(entryValues);
+
+        // and set the correct one
+        if (lowBatPollIntervalPref.findIndexOfValue(currentValue) == -1) {
+            lowBatPollIntervalPref.setValueIndex(0);
+        }
     }
 
     @Override
diff --git a/app/src/main/java/info/nightscout/android/upload/nightscout/NightScoutUpload.java b/app/src/main/java/info/nightscout/android/upload/nightscout/NightScoutUpload.java
new file mode 100644
index 0000000000000000000000000000000000000000..4fe14f2a8542bba2bbd0d346a6faf3663204e003
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/upload/nightscout/NightScoutUpload.java
@@ -0,0 +1,181 @@
+package info.nightscout.android.upload.nightscout;
+
+import android.util.Log;
+
+import java.io.UnsupportedEncodingException;
+import java.math.BigDecimal;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import info.nightscout.android.model.medtronicNg.PumpStatusEvent;
+import info.nightscout.android.upload.nightscout.serializer.EntriesSerializer;
+
+import android.support.annotation.NonNull;
+
+import info.nightscout.api.UploadApi;
+import info.nightscout.api.GlucoseEndpoints;
+import info.nightscout.api.BolusEndpoints.BolusEntry;
+import info.nightscout.api.GlucoseEndpoints.GlucoseEntry;
+import info.nightscout.api.BolusEndpoints;
+import info.nightscout.api.DeviceEndpoints;
+import info.nightscout.api.DeviceEndpoints.Iob;
+import info.nightscout.api.DeviceEndpoints.Battery;
+import info.nightscout.api.DeviceEndpoints.PumpStatus;
+import info.nightscout.api.DeviceEndpoints.PumpInfo;
+import info.nightscout.api.DeviceEndpoints.DeviceStatus;
+import okhttp3.ResponseBody;
+import retrofit2.Response;
+import retrofit2.Retrofit;
+
+class NightScoutUpload {
+
+    private static final String TAG = NightscoutUploadIntentService.class.getSimpleName();
+    private static final SimpleDateFormat ISO8601_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault());
+
+    NightScoutUpload() {
+
+    }
+
+    Boolean doRESTUpload(String url,
+                         String secret,
+                         int uploaderBatteryLevel,
+                         List<PumpStatusEvent> records) {
+        Boolean success = false;
+        try {
+            success = isUploaded(records, url, secret, uploaderBatteryLevel);
+        } catch (Exception e) {
+            Log.e(TAG, "Unable to do REST API Upload to: " + url, e);
+        }
+        return success;
+    }
+
+
+    private boolean isUploaded(List<PumpStatusEvent> records,
+                               String baseURL,
+                               String secret,
+                               int uploaderBatteryLevel) throws Exception {
+
+        UploadApi uploadApi = new UploadApi(baseURL, formToken(secret));
+
+        boolean eventsUploaded = uploadEvents(uploadApi.getGlucoseEndpoints(),
+                uploadApi.getBolusEndpoints(),
+                records);
+
+        boolean deviceStatusUploaded = uploadDeviceStatus(uploadApi.getDeviceEndpoints(),
+                uploaderBatteryLevel, records);
+
+        return eventsUploaded && deviceStatusUploaded;
+    }
+
+    private boolean uploadEvents(GlucoseEndpoints glucoseEndpoints,
+                                 BolusEndpoints bolusEndpoints,
+                                 List<PumpStatusEvent> records) throws Exception {
+
+
+        List<GlucoseEntry> glucoseEntries = new ArrayList<>();
+        List<BolusEntry> bolusEntries = new ArrayList<>();
+
+        for (PumpStatusEvent record : records) {
+
+            GlucoseEntry glucoseEntry = new GlucoseEntry();
+
+            glucoseEntry.setType("sgv");
+            glucoseEntry.setDirection(EntriesSerializer.getDirectionStringStatus(record.getCgmTrend()));
+            glucoseEntry.setDevice(record.getDeviceName());
+            glucoseEntry.setSgv(record.getSgv());
+            glucoseEntry.setDate(record.getSgvDate().getTime());
+            glucoseEntry.setDateString(record.getSgvDate().toString());
+
+            glucoseEntries.add(glucoseEntry);
+
+            if (record.getBolusWizardBGL() != 0) {
+                BolusEntry bolusEntry = new BolusEntry();
+
+                bolusEntry.setType("mbg");
+                bolusEntry.setDate(record.getEventDate().getTime());
+                bolusEntry.setDateString(record.getEventDate().toString());
+                bolusEntry.setDevice(record.getDeviceName());
+                bolusEntry.setMbg(record.getBolusWizardBGL());
+
+                bolusEntries.add(bolusEntry);
+            }
+
+        }
+
+        boolean uploaded = true;
+        if (glucoseEntries.size() > 0) {
+            Response<ResponseBody> result = glucoseEndpoints.sendEntries(glucoseEntries).execute();
+            uploaded = uploaded && result.isSuccessful();
+        }
+        if (bolusEntries.size() > 0) {
+            Response<ResponseBody> result = bolusEndpoints.sendEntries(bolusEntries).execute();
+            uploaded = uploaded && result.isSuccessful();
+        }
+        return uploaded;
+    }
+
+    private boolean uploadDeviceStatus(DeviceEndpoints deviceEndpoints,
+                                       int uploaderBatteryLevel,
+                                       List<PumpStatusEvent> records) throws Exception {
+
+
+        List<DeviceStatus> deviceEntries = new ArrayList<>();
+        for (PumpStatusEvent record : records) {
+
+            Iob iob = new Iob(record.getPumpDate(), record.getActiveInsulin());
+            Battery battery = new Battery(record.getBatteryPercentage());
+            PumpStatus pumpstatus;
+            if (record.isBolusing()) {
+                pumpstatus = new PumpStatus(true, false, "");
+
+            } else if (record.isSuspended()) {
+                pumpstatus = new PumpStatus(false, true, "");
+            } else {
+                pumpstatus = new PumpStatus(false, false, "normal");
+            }
+
+            PumpInfo pumpInfo = new PumpInfo(
+                    ISO8601_DATE_FORMAT.format(record.getPumpDate()),
+                    new BigDecimal(record.getReservoirAmount()).setScale(3, BigDecimal.ROUND_HALF_UP),
+                    iob,
+                    battery,
+                    pumpstatus
+            );
+
+            DeviceStatus deviceStatus = new DeviceStatus(
+                    uploaderBatteryLevel,
+                    record.getDeviceName(),
+                    ISO8601_DATE_FORMAT.format(record.getPumpDate()),
+                    pumpInfo
+            );
+
+            deviceEntries.add(deviceStatus);
+        }
+
+        boolean uploaded = true;
+        for (DeviceStatus status : deviceEntries) {
+            Response<ResponseBody> result = deviceEndpoints.sendDeviceStatus(status).execute();
+            uploaded = uploaded && result.isSuccessful();
+        }
+
+        return uploaded;
+    }
+
+    @NonNull
+    private String formToken(String secret) throws NoSuchAlgorithmException, UnsupportedEncodingException {
+        MessageDigest digest = MessageDigest.getInstance("SHA-1");
+        byte[] bytes = secret.getBytes("UTF-8");
+        digest.update(bytes, 0, bytes.length);
+        bytes = digest.digest();
+        StringBuilder sb = new StringBuilder(bytes.length * 2);
+        for (byte b : bytes) {
+            sb.append(String.format("%02x", b & 0xff));
+        }
+        return sb.toString();
+    }
+
+}
diff --git a/app/src/main/java/info/nightscout/android/upload/nightscout/NightscoutApi.java b/app/src/main/java/info/nightscout/android/upload/nightscout/NightscoutApi.java
index c613dfbb5112dad61676c85cd04ebe520c9b56af..07cba2e8fdbe2f7bc0ae100d436fdb619b78d910 100644
--- a/app/src/main/java/info/nightscout/android/upload/nightscout/NightscoutApi.java
+++ b/app/src/main/java/info/nightscout/android/upload/nightscout/NightscoutApi.java
@@ -2,7 +2,6 @@ package info.nightscout.android.upload.nightscout;
 
 import retrofit2.Call;
 import retrofit2.http.GET;
-
 /**
  * Created by lgoedhart on 26/06/2016.
  */
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 6ac7129878dfdcf45118b3f1f4cf83538766de24..51174e3cfe59108f0b24fc827e0f620f6363e424 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
@@ -10,42 +10,19 @@ import android.preference.PreferenceManager;
 import android.support.v4.content.LocalBroadcastManager;
 import android.util.Log;
 
-import org.apache.http.HttpResponse;
-import org.apache.http.StatusLine;
-import org.apache.http.client.ResponseHandler;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.client.BasicResponseHandler;
-import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.http.params.BasicHttpParams;
-import org.apache.http.params.HttpConnectionParams;
-import org.apache.http.params.HttpParams;
-import org.json.JSONArray;
-import org.json.JSONObject;
-
-import java.math.BigDecimal;
-import java.net.URL;
-import java.security.MessageDigest;
-import java.text.SimpleDateFormat;
-import java.util.Locale;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
 import info.nightscout.android.R;
 import info.nightscout.android.medtronic.MainActivity;
 import info.nightscout.android.model.medtronicNg.PumpStatusEvent;
-import info.nightscout.android.upload.nightscout.serializer.EntriesSerializer;
 import io.realm.Realm;
 import io.realm.RealmResults;
 
 public class NightscoutUploadIntentService extends IntentService {
 
     private static final String TAG = NightscoutUploadIntentService.class.getSimpleName();
-    private static final SimpleDateFormat ISO8601_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault());
-    private static final int SOCKET_TIMEOUT = 60 * 1000;
-    private static final int CONNECTION_TIMEOUT = 30 * 1000;
-    Context mContext;
+
+    private Context mContext;
     private Realm mRealm;
+    private NightScoutUpload mNightScoutUpload;
 
     public NightscoutUploadIntentService() {
         super(NightscoutUploadIntentService.class.getName());
@@ -64,6 +41,9 @@ public class NightscoutUploadIntentService extends IntentService {
 
         Log.i(TAG, "onCreate called");
         mContext = this.getBaseContext();
+
+        mNightScoutUpload = new NightScoutUpload();
+
     }
 
     @Override
@@ -85,7 +65,18 @@ public class NightscoutUploadIntentService extends IntentService {
                 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);
+                    String urlSetting = prefs.getString(mContext.getString(R.string.preference_nightscout_url), "");
+                    String secretSetting = prefs.getString(mContext.getString(R.string.preference_api_secret), "YOURAPISECRET");
+                    int uploaderBatteryLevel = MainActivity.batLevel;
+                    Boolean uploadSuccess = mNightScoutUpload.doRESTUpload(urlSetting,
+                            secretSetting, uploaderBatteryLevel, records);
+                    if (uploadSuccess) {
+                        mRealm.beginTransaction();
+                        for (PumpStatusEvent updateRecord : records) {
+                            updateRecord.setUploaded(true);
+                        }
+                        mRealm.commitTransaction();
+                    }
                     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) {
@@ -94,221 +85,11 @@ public class NightscoutUploadIntentService extends IntentService {
         } else {
             Log.i(TAG, "No records has to be uploaded");
         }
+        mRealm.close();
 
         NightscoutUploadReceiver.completeWakefulIntent(intent);
     }
 
-    private void doRESTUpload(SharedPreferences prefs, RealmResults<PumpStatusEvent> records) {
-        String apiScheme = "https://";
-        String apiUrl = "";
-        String apiSecret = prefs.getString(mContext.getString(R.string.preference_api_secret), "YOURAPISECRET");
-
-        // TODO - this code needs to go to the Settings Activity.
-        // Add the extra match for "KEY@" to support the previous single field
-        Pattern p = Pattern.compile("(.*\\/\\/)?(.*@)?([^\\/]*)(.*)");
-        Matcher m = p.matcher(prefs.getString(mContext.getString(R.string.preference_nightscout_url), ""));
-
-        if (m.find()) {
-            apiUrl = m.group(3);
-
-            // Only override apiSecret from URL (the "old" way), if the API secret preference is empty
-            if (apiSecret.equals("YOURAPISECRET") || apiSecret.equals("")) {
-                apiSecret = (m.group(2) == null) ? "" : m.group(2).replace("@", "");
-            }
-
-            // Override the URI scheme if it's been provided in the preference)
-            if (m.group(1) != null && !m.group(1).equals("")) {
-                apiScheme = m.group(1);
-            }
-        }
-
-        // Update the preferences to match what we expect. Only really used from converting from the
-        // old format to the new format. Aren't we nice for managing backward compatibility?
-        prefs.edit().putString(mContext.getString(R.string.preference_api_secret), apiSecret).apply();
-        prefs.edit().putString(mContext.getString(R.string.preference_nightscout_url), String.format("%s%s", apiScheme, apiUrl)).apply();
-
-        String uploadUrl = String.format("%s%s@%s/api/v1/", apiScheme, apiSecret, apiUrl);
-
-        try {
-            doRESTUploadTo(uploadUrl, records);
-        } catch (Exception e) {
-            Log.e(TAG, "Unable to do REST API Upload to: " + uploadUrl, e);
-        }
-    }
-
-    private void doRESTUploadTo(String baseURI, RealmResults<PumpStatusEvent> records) {
-        try {
-            String baseURL;
-            String secret = null;
-            String[] uriParts = baseURI.split("@");
-
-            if (uriParts.length == 1) {
-                throw new Exception("Starting with API v1, a pass phase is required");
-            } else if (uriParts.length == 2) {
-                secret = uriParts[0];
-                baseURL = uriParts[1];
-
-                // new format URL!
-                if (secret.contains("http")) {
-                    if (secret.contains("https")) {
-                        baseURL = "https://" + baseURL;
-                    } else {
-                        baseURL = "http://" + baseURL;
-                    }
-                    String[] uriParts2 = secret.split("//");
-                    secret = uriParts2[1];
-                }
-            } else {
-                throw new Exception(String.format("Unexpected baseURI: %s, uriParts.length: %s", baseURI, uriParts.length));
-            }
-
-            JSONArray devicestatusBody = new JSONArray();
-            JSONArray entriesBody = new JSONArray();
-
-            for (PumpStatusEvent record : records) {
-                addDeviceStatus(devicestatusBody, record);
-                addSgvEntry(entriesBody, record);
-                addMbgEntry(entriesBody, record);
-            }
-
-            boolean isUploaded = uploadToNightscout(new URL(baseURL + "/entries"), secret, entriesBody);
-
-            for(int i = 0; isUploaded && i < devicestatusBody.length(); i++) {
-                isUploaded &= uploadToNightscout(new URL(baseURL + "/devicestatus"), secret, devicestatusBody.getJSONObject(i));
-            }
-
-            if (isUploaded) {
-                // Yay! We uploaded. Tell Realm
-                // FIXME - check the upload succeeded!
-                mRealm.beginTransaction();
-                for (PumpStatusEvent updateRecord : records) {
-                    updateRecord.setUploaded(true);
-                }
-                mRealm.commitTransaction();
-            }
-
-        } catch (Exception e) {
-            Log.e(TAG, "Unable to post data", e);
-        }
-    }
-
-    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());
-
-        if (secret == null || secret.isEmpty()) {
-            throw new Exception("Starting with API v1, a pass phase is required");
-        } else {
-            MessageDigest digest = MessageDigest.getInstance("SHA-1");
-            byte[] bytes = secret.getBytes("UTF-8");
-            digest.update(bytes, 0, bytes.length);
-            bytes = digest.digest();
-            StringBuilder sb = new StringBuilder(bytes.length * 2);
-            for (byte b : bytes) {
-                sb.append(String.format("%02x", b & 0xff));
-            }
-            String token = sb.toString();
-            post.setHeader("api-secret", token);
-        }
-
-        HttpParams params = new BasicHttpParams();
-        HttpConnectionParams.setSoTimeout(params, SOCKET_TIMEOUT);
-        HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT);
-
-        DefaultHttpClient httpclient = new DefaultHttpClient(params);
-
-        Log.i(TAG, "Upload JSON: " + httpBody);
-
-        try {
-            StringEntity se = new StringEntity(httpBody);
-            post.setEntity(se);
-            post.setHeader("Accept", "application/json");
-            post.setHeader("Content-type", "application/json");
-
-            ResponseHandler responseHandler = new BasicResponseHandler();
-            httpclient.execute(post, responseHandler);
-        } catch (Exception e) {
-            Log.w(TAG, "Unable to post data to: '" + post.getURI().toString() + "'", e);
-            return false;
-        }
-
-        return true;
-    }
-
-    private void addDeviceStatus(JSONArray devicestatusArray, PumpStatusEvent record) throws Exception {
-        JSONObject json = new JSONObject();
-        json.put("uploaderBattery", MainActivity.batLevel);
-        json.put("device", record.getDeviceName());
-        json.put("created_at", ISO8601_DATE_FORMAT.format(record.getPumpDate()));
-
-        JSONObject pumpInfo = new JSONObject();
-        pumpInfo.put("clock", ISO8601_DATE_FORMAT.format(record.getPumpDate()));
-        pumpInfo.put("reservoir", new BigDecimal(record.getReservoirAmount()).setScale(3, BigDecimal.ROUND_HALF_UP));
-
-        JSONObject iob = new JSONObject();
-        iob.put("timestamp", record.getPumpDate());
-        iob.put("bolusiob", record.getActiveInsulin());
-
-        JSONObject status = new JSONObject();
-        if (record.isBolusing()) {
-            status.put("bolusing", true);
-        } else if (record.isSuspended()) {
-            status.put("suspended", true);
-        } else {
-            status.put("status", "normal");
-        }
-
-        JSONObject battery = new JSONObject();
-        battery.put("percent", record.getBatteryPercentage());
-
-        pumpInfo.put("iob", iob);
-        pumpInfo.put("battery", battery);
-        pumpInfo.put("status", status);
-
-        json.put("pump", pumpInfo);
-        String jsonString = json.toString();
-        Log.i(TAG, "Device Status JSON: " + jsonString);
-
-        devicestatusArray.put(json);
-    }
-
-    private void addSgvEntry(JSONArray entriesArray, PumpStatusEvent pumpRecord) throws Exception {
-        JSONObject json = new JSONObject();
-        // TODO replace with Retrofit/EntriesSerializer
-        json.put("sgv", pumpRecord.getSgv());
-        json.put("direction", EntriesSerializer.getDirectionString(pumpRecord.getCgmTrend()));
-        json.put("device", pumpRecord.getDeviceName());
-        json.put("type", "sgv");
-        json.put("date", pumpRecord.getEventDate().getTime());
-        json.put("dateString", pumpRecord.getEventDate());
-
-        entriesArray.put(json);
-    }
-
-    private void addMbgEntry(JSONArray entriesArray, PumpStatusEvent pumpRecord) throws Exception {
-        if (pumpRecord.hasRecentBolusWizard()) {
-            JSONObject json = new JSONObject();
-
-            // TODO replace with Retrofit/EntriesSerializer
-            json.put("type", "mbg");
-            json.put("mbg", pumpRecord.getBolusWizardBGL());
-            json.put("device", pumpRecord.getDeviceName());
-            json.put("date", pumpRecord.getEventDate().getTime());
-            json.put("dateString", pumpRecord.getEventDate());
-
-            entriesArray.put(json);
-        }
-    }
-
     private boolean isOnline() {
         ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
         NetworkInfo netInfo = cm.getActiveNetworkInfo();
@@ -320,4 +101,6 @@ public class NightscoutUploadIntentService extends IntentService {
 
         public static final String EXTENDED_DATA = "info.nightscout.android.upload.nightscout.DATA";
     }
+
+
 }
diff --git a/app/src/main/java/info/nightscout/android/upload/nightscout/serializer/EntriesSerializer.java b/app/src/main/java/info/nightscout/android/upload/nightscout/serializer/EntriesSerializer.java
index 260b8c3b2acab0a24e81dd6c35dee9f9872f4e21..ce79ab7900ef741ecb3c8f35550eebd019f34d69 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
@@ -42,6 +42,35 @@ public class EntriesSerializer implements JsonSerializer<PumpStatusEvent> {
         }
     }
 
+    public static String getDirectionStringStatus(PumpStatusEvent.CGM_TREND trend) {
+        switch( trend ) {
+            case NONE:
+                return "NONE";
+            case DOUBLE_UP:
+                return "DoubleUp";
+            case SINGLE_UP:
+                return "SingleUp";
+            case FOURTY_FIVE_UP:
+                return "FortyFiveUp";
+            case FLAT:
+                return "Flat";
+            case FOURTY_FIVE_DOWN:
+                return "FortyFiveDown";
+            case SINGLE_DOWN:
+                return "SingleDown";
+            case DOUBLE_DOWN:
+                return "DoubleDown";
+            case NOT_COMPUTABLE:
+                return "NOT COMPUTABLE";
+            case RATE_OUT_OF_RANGE:
+                return "RATE OUT OF RANGE";
+            case NOT_SET:
+                return "NONE";
+            default:
+                return "NOT COMPUTABLE"; // TODO - should this be something else?
+        }
+    }
+
     @Override
     public JsonElement serialize(PumpStatusEvent src, Type typeOfSrc, JsonSerializationContext context) {
         final JsonObject jsonObject = new JsonObject();
diff --git a/app/src/main/java/info/nightscout/android/utils/HexDump.java b/app/src/main/java/info/nightscout/android/utils/HexDump.java
index 1e01ce78ed71eaf094579ec3f171fdcab1779ef8..ff441ced24ba8bc11f04cc1a57f7ec99a032302a 100644
--- a/app/src/main/java/info/nightscout/android/utils/HexDump.java
+++ b/app/src/main/java/info/nightscout/android/utils/HexDump.java
@@ -35,7 +35,12 @@ public class HexDump {
         byte[] line = new byte[16];
         int lineIndex = 0;
 
+        result.append("\n          ");
+        for (int i = 0; i < Math.min(16, array.length); i++) {
+            result.append(" ?" + HEX_DIGITS[i]);
+        }
         result.append("\n0x");
+
         result.append(toHexString(offset));
 
         for (int i = offset; i < offset + length; i++) {
@@ -63,19 +68,17 @@ public class HexDump {
             line[lineIndex++] = b;
         }
 
-        if (lineIndex != 16) {
-            int count = (16 - lineIndex) * 3;
-            count++;
-            for (int i = 0; i < count; i++) {
-                result.append(" ");
-            }
+        int count = (16 - lineIndex) * 3;
+        count++;
+        for (int i = 0; i < count; i++) {
+            result.append(" ");
+        }
 
-            for (int i = 0; i < lineIndex; i++) {
-                if (line[i] > ' ' && line[i] < '~') {
-                    result.append(new String(line, i, 1));
-                } else {
-                    result.append(".");
-                }
+        for (int i = 0; i < lineIndex; i++) {
+            if (line[i] > ' ' && line[i] < '~') {
+                result.append(new String(line, i, 1));
+            } else {
+                result.append(".");
             }
         }
 
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 6cc1a6f91b54362685926b06cb6f9072b78704bd..eef010f25bcd95a441250f3090c01fd3d367e4e3 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
@@ -70,6 +70,7 @@ public class XDripPlusUploadIntentService extends IntentService {
             List<PumpStatusEvent> records = all_records.subList(0, 1);
             doXDripUpload(records);
         }
+        mRealm.close();
         XDripPlusUploadReceiver.completeWakefulIntent(intent);
     }
 
@@ -85,10 +86,12 @@ public class XDripPlusUploadIntentService extends IntentService {
                 addMbgEntry(entriesBody, record);
             }
 
-            if (entriesBody.length() > 0) sendBundle(mContext, "add", "entries", entriesBody);
-            if (devicestatusBody.length() > 0)
+            if (entriesBody.length() > 0) {
+                sendBundle(mContext, "add", "entries", entriesBody);
+            }
+            if (devicestatusBody.length() > 0) {
                 sendBundle(mContext, "add", "devicestatus", devicestatusBody);
-
+            }
         } catch (Exception e) {
             Log.e(TAG, "Unable to send bundle: " + e);
         }
@@ -104,8 +107,10 @@ public class XDripPlusUploadIntentService extends IntentService {
         context.sendBroadcast(intent);
         List<ResolveInfo> receivers = context.getPackageManager().queryBroadcastReceivers(intent, 0);
         if (receivers.size() < 1) {
-            Log.e(TAG, "No receivers");
-        } else Log.e(TAG, receivers.size() + " receivers");
+            Log.w(TAG, "No xDrip receivers found. ");
+        } else {
+            Log.d(TAG, receivers.size() + " xDrip receivers");
+        }
     }
 
 
diff --git a/app/src/main/java/info/nightscout/api/BolusEndpoints.java b/app/src/main/java/info/nightscout/api/BolusEndpoints.java
new file mode 100644
index 0000000000000000000000000000000000000000..a1c2a56e35eacaa534b5e5ede7de4fca577672c8
--- /dev/null
+++ b/app/src/main/java/info/nightscout/api/BolusEndpoints.java
@@ -0,0 +1,74 @@
+package info.nightscout.api;
+
+import java.util.List;
+
+import okhttp3.ResponseBody;
+import retrofit2.Call;
+import retrofit2.http.Body;
+import retrofit2.http.Headers;
+import retrofit2.http.POST;
+
+public interface BolusEndpoints {
+
+    class BolusEntry {
+        String type;
+        String dateString;
+        long date;
+        int mbg;
+        String device;
+
+        public BolusEntry() {  }
+
+        public String getType() {
+            return type;
+        }
+
+        public void setType(String type) {
+            this.type = type;
+        }
+
+        public String getDateString() {
+            return dateString;
+        }
+
+        public void setDateString(String dateString) {
+            this.dateString = dateString;
+        }
+
+        public long getDate() {
+            return date;
+        }
+
+        public void setDate(long date) {
+            this.date = date;
+        }
+
+        public int getMbg() {
+            return mbg;
+        }
+
+        public void setMbg(int mbg) {
+            this.mbg = mbg;
+        }
+
+        public String getDevice() {
+            return device;
+        }
+
+        public void setDevice(String device) {
+            this.device = device;
+        }
+
+    }
+
+    @Headers({
+            "Accept: application/json",
+            "Content-type: application/json"
+    })
+    @POST("/api/v1/entries")
+    Call<ResponseBody> sendEntries(@Body List<BolusEntry> entries);
+
+}
+
+
+
diff --git a/app/src/main/java/info/nightscout/api/DeviceEndpoints.java b/app/src/main/java/info/nightscout/api/DeviceEndpoints.java
new file mode 100644
index 0000000000000000000000000000000000000000..1bf66b24336e790d69c464ed1acb419c029d433d
--- /dev/null
+++ b/app/src/main/java/info/nightscout/api/DeviceEndpoints.java
@@ -0,0 +1,97 @@
+package info.nightscout.api;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+import okhttp3.ResponseBody;
+import retrofit2.Call;
+import retrofit2.http.Body;
+import retrofit2.http.Header;
+import retrofit2.http.Headers;
+import retrofit2.http.POST;
+
+public interface DeviceEndpoints {
+
+    class Iob {
+        final Date timestamp;
+        final float bolusiob;
+        public Iob (Date timestamp,
+             float bolusiob) {
+            this.timestamp = timestamp;
+            this.bolusiob = bolusiob;
+        }
+    }
+
+    class Battery {
+        final short percent;
+        public Battery(short percent) {
+            this.percent = percent;
+        }
+    }
+
+    class PumpStatus {
+        final Boolean bolusing;
+        final Boolean suspended;
+        final String status;
+        public PumpStatus(
+                Boolean bolusing,
+                Boolean suspended,
+                String status
+
+        ) {
+            this.bolusing = bolusing;
+            this.suspended = suspended;
+            this.status = status;
+
+        }
+    }
+
+    class PumpInfo {
+        final String clock;
+        final BigDecimal reservoir;
+        final Iob iob;
+        final Battery battery;
+        final PumpStatus status;
+
+        public PumpInfo(String clock,
+                 BigDecimal reservoir,
+                 Iob iob,
+                 Battery battery,
+                 PumpStatus status) {
+            this.clock = clock;
+            this.reservoir = reservoir;
+            this.iob = iob;
+            this.battery = battery;
+            this.status = status;
+
+        }
+    }
+
+    class DeviceStatus {
+        final Integer uploaderBattery;
+        final String device;
+        final String created_at;
+        final PumpInfo pump;
+
+        public DeviceStatus(Integer uploaderBattery,
+                     String device,
+                     String created_at,
+                     PumpInfo pump) {
+            this.uploaderBattery = uploaderBattery;
+            this.device = device;
+            this.created_at = created_at;
+            this.pump = pump;
+        }
+    }
+
+    @Headers({
+            "Accept: application/json",
+            "Content-type: application/json"
+    })
+    @POST("/api/v1/devicestatus")
+    Call<ResponseBody> sendDeviceStatus(@Body DeviceStatus deviceStatus);
+
+}
+
+
+
diff --git a/app/src/main/java/info/nightscout/api/GlucoseEndpoints.java b/app/src/main/java/info/nightscout/api/GlucoseEndpoints.java
new file mode 100644
index 0000000000000000000000000000000000000000..d34c5e7abdae5efadc346bc560eb54aee252dacb
--- /dev/null
+++ b/app/src/main/java/info/nightscout/api/GlucoseEndpoints.java
@@ -0,0 +1,83 @@
+package info.nightscout.api;
+
+import java.util.List;
+
+import okhttp3.ResponseBody;
+import retrofit2.Call;
+import retrofit2.http.Body;
+import retrofit2.http.Headers;
+import retrofit2.http.POST;
+
+public interface GlucoseEndpoints {
+
+    class GlucoseEntry {
+
+        String type;
+        String dateString;
+        long date;
+        int sgv;
+        String direction;
+        String device;
+
+        public String getType() {
+            return type;
+        }
+
+        public void setType(String type) {
+            this.type = type;
+        }
+
+        public String getDateString() {
+            return dateString;
+        }
+
+        public void setDateString(String dateString) {
+            this.dateString = dateString;
+        }
+
+        public long getDate() {
+            return date;
+        }
+
+        public void setDate(long date) {
+            this.date = date;
+        }
+
+        public int getSgv() {
+            return sgv;
+        }
+
+        public void setSgv(int sgv) {
+            this.sgv = sgv;
+        }
+
+        public String getDirection() {
+            return direction;
+        }
+
+        public void setDirection(String direction) {
+            this.direction = direction;
+        }
+
+        public String getDevice() {
+            return device;
+        }
+
+        public void setDevice(String device) {
+            this.device = device;
+        }
+
+        public GlucoseEntry() {  }
+    }
+
+    @Headers({
+            "Accept: application/json",
+            "Content-type: application/json"
+    })
+    @POST("/api/v1/entries")
+    Call<ResponseBody> sendEntries(@Body List<GlucoseEntry> entries);
+
+}
+
+
+
diff --git a/app/src/main/java/info/nightscout/api/UploadApi.java b/app/src/main/java/info/nightscout/api/UploadApi.java
new file mode 100644
index 0000000000000000000000000000000000000000..165203fc7f6f8101b78dc9130f0f5447d5e10f9c
--- /dev/null
+++ b/app/src/main/java/info/nightscout/api/UploadApi.java
@@ -0,0 +1,77 @@
+package info.nightscout.api;
+
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import okhttp3.Interceptor;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.logging.HttpLoggingInterceptor;
+import retrofit2.Retrofit;
+import retrofit2.converter.gson.GsonConverterFactory;
+
+public class UploadApi {
+    private Retrofit retrofit;
+    private GlucoseEndpoints glucoseEndpoints;
+    private BolusEndpoints bolusEndpoints;
+    private DeviceEndpoints deviceEndpoints;
+
+    public GlucoseEndpoints getGlucoseEndpoints() {
+        return glucoseEndpoints;
+    }
+
+    public BolusEndpoints getBolusEndpoints() {
+        return bolusEndpoints;
+    }
+
+    public DeviceEndpoints getDeviceEndpoints() {
+        return deviceEndpoints;
+    }
+
+    public UploadApi(String baseURL, String token) {
+
+        class AddAuthHeader implements Interceptor {
+
+            private String token;
+
+            public AddAuthHeader(String token) {
+                this.token = token;
+            }
+
+            @Override
+            public Response intercept(Interceptor.Chain chain) throws IOException {
+                Request original = chain.request();
+
+                Request.Builder requestBuilder = original.newBuilder()
+                        .header("api-secret", token)
+                        .method( original.method(), original.body());
+
+                Request request = requestBuilder.build();
+                return chain.proceed(request);
+            }
+        };
+
+        OkHttpClient.Builder okHttpClient = new OkHttpClient().newBuilder()
+                .connectTimeout(30, TimeUnit.SECONDS)
+                .readTimeout(60, TimeUnit.SECONDS)
+                .writeTimeout(60, TimeUnit.SECONDS);
+
+        okHttpClient.addInterceptor(new AddAuthHeader(token));
+
+        HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
+        logging.setLevel(HttpLoggingInterceptor.Level.BODY);
+        okHttpClient.addInterceptor(logging);
+
+        retrofit = new Retrofit.Builder()
+                .baseUrl(baseURL)
+                .client(okHttpClient.build())
+                .addConverterFactory(GsonConverterFactory.create())
+                .build();
+
+        glucoseEndpoints = retrofit.create(GlucoseEndpoints.class);
+        bolusEndpoints = retrofit.create(BolusEndpoints.class);
+        deviceEndpoints = retrofit.create(DeviceEndpoints.class);
+    }
+}
diff --git a/app/src/main/res/drawable-hdpi/battery_0.png b/app/src/main/res/drawable-hdpi/battery_0.png
new file mode 100644
index 0000000000000000000000000000000000000000..c1bc1e661db06dca60a1bfa56459b1341bcc1a7d
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/battery_0.png differ
diff --git a/app/src/main/res/drawable-hdpi/battery_100.png b/app/src/main/res/drawable-hdpi/battery_100.png
new file mode 100644
index 0000000000000000000000000000000000000000..6f76257ee2f3faa17e9133b74644a21e205e7a7b
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/battery_100.png differ
diff --git a/app/src/main/res/drawable-hdpi/battery_25.png b/app/src/main/res/drawable-hdpi/battery_25.png
new file mode 100644
index 0000000000000000000000000000000000000000..ea92908aff0ce281fd9bc844ac1005f389954071
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/battery_25.png differ
diff --git a/app/src/main/res/drawable-hdpi/battery_50.png b/app/src/main/res/drawable-hdpi/battery_50.png
new file mode 100644
index 0000000000000000000000000000000000000000..691875ac35fe3a599785ad4b138ddae5788d9e79
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/battery_50.png differ
diff --git a/app/src/main/res/drawable-hdpi/battery_75.png b/app/src/main/res/drawable-hdpi/battery_75.png
new file mode 100644
index 0000000000000000000000000000000000000000..b118fc431c19d8edde4b0a254a218c1718d33847
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/battery_75.png differ
diff --git a/app/src/main/res/drawable-hdpi/battery_unknown.png b/app/src/main/res/drawable-hdpi/battery_unknown.png
new file mode 100644
index 0000000000000000000000000000000000000000..b5eb90f1dc573cd34e7a1cebc036b47c56677d8a
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/battery_unknown.png differ
diff --git a/app/src/main/res/drawable-mdpi/battery_0.png b/app/src/main/res/drawable-mdpi/battery_0.png
new file mode 100644
index 0000000000000000000000000000000000000000..b75d354afde7ab209622e5ec3c864a5973e1add7
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/battery_0.png differ
diff --git a/app/src/main/res/drawable-mdpi/battery_100.png b/app/src/main/res/drawable-mdpi/battery_100.png
new file mode 100644
index 0000000000000000000000000000000000000000..8d54af3c89b52434f2d4e8d216338a56c9a1f9b5
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/battery_100.png differ
diff --git a/app/src/main/res/drawable-mdpi/battery_25.png b/app/src/main/res/drawable-mdpi/battery_25.png
new file mode 100644
index 0000000000000000000000000000000000000000..86347a621bb7cee942aab6bed8854f4ff0e397fd
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/battery_25.png differ
diff --git a/app/src/main/res/drawable-mdpi/battery_50.png b/app/src/main/res/drawable-mdpi/battery_50.png
new file mode 100644
index 0000000000000000000000000000000000000000..7668c659db0ace4ec891e5badf9a00d3ac61fb72
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/battery_50.png differ
diff --git a/app/src/main/res/drawable-mdpi/battery_75.png b/app/src/main/res/drawable-mdpi/battery_75.png
new file mode 100644
index 0000000000000000000000000000000000000000..d928b5c1c26fbd620e8fd10090a65b82d4778e06
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/battery_75.png differ
diff --git a/app/src/main/res/drawable-mdpi/battery_unknown.png b/app/src/main/res/drawable-mdpi/battery_unknown.png
new file mode 100644
index 0000000000000000000000000000000000000000..8bc91464617a716f8fa5f9aa19a79c214644b324
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/battery_unknown.png differ
diff --git a/app/src/main/res/drawable-xhdpi/battery_0.png b/app/src/main/res/drawable-xhdpi/battery_0.png
new file mode 100644
index 0000000000000000000000000000000000000000..2c552d7aaf30fd5235aab8551e1e831b1caa4f98
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/battery_0.png differ
diff --git a/app/src/main/res/drawable-xhdpi/battery_100.png b/app/src/main/res/drawable-xhdpi/battery_100.png
new file mode 100644
index 0000000000000000000000000000000000000000..ca9172be7ace10190853e0d6b460e3bf034c38a5
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/battery_100.png differ
diff --git a/app/src/main/res/drawable-xhdpi/battery_25.png b/app/src/main/res/drawable-xhdpi/battery_25.png
new file mode 100644
index 0000000000000000000000000000000000000000..2bfce679ddb28ee2da3bf13763ca82965fec2bb5
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/battery_25.png differ
diff --git a/app/src/main/res/drawable-xhdpi/battery_50.png b/app/src/main/res/drawable-xhdpi/battery_50.png
new file mode 100644
index 0000000000000000000000000000000000000000..6e0d3b3186fdfad8e1aaadf1bb95e0d2c523bd95
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/battery_50.png differ
diff --git a/app/src/main/res/drawable-xhdpi/battery_75.png b/app/src/main/res/drawable-xhdpi/battery_75.png
new file mode 100644
index 0000000000000000000000000000000000000000..3cf8966e9f7d08719dee7a8c6febd2ce3557802d
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/battery_75.png differ
diff --git a/app/src/main/res/drawable-xhdpi/battery_unknown.png b/app/src/main/res/drawable-xhdpi/battery_unknown.png
new file mode 100644
index 0000000000000000000000000000000000000000..368a8527f8736ae47b26f61fb7bb0d27dde17c39
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/battery_unknown.png differ
diff --git a/app/src/main/res/drawable/battery_0.png b/app/src/main/res/drawable/battery_0.png
new file mode 100644
index 0000000000000000000000000000000000000000..03c55826d4bab007dafd9ff69e4a30738e78f9ab
Binary files /dev/null and b/app/src/main/res/drawable/battery_0.png differ
diff --git a/app/src/main/res/drawable/battery_100.png b/app/src/main/res/drawable/battery_100.png
new file mode 100644
index 0000000000000000000000000000000000000000..3447a8f32435312acd6c002214716e14c41a62a3
Binary files /dev/null and b/app/src/main/res/drawable/battery_100.png differ
diff --git a/app/src/main/res/drawable/battery_25.png b/app/src/main/res/drawable/battery_25.png
new file mode 100644
index 0000000000000000000000000000000000000000..1b8e10542a6b0c132f7ff4eb918bc2cef68043e1
Binary files /dev/null and b/app/src/main/res/drawable/battery_25.png differ
diff --git a/app/src/main/res/drawable/battery_50.png b/app/src/main/res/drawable/battery_50.png
new file mode 100644
index 0000000000000000000000000000000000000000..d516ea0cfe6949fa3b0d518a4a65d846f7337294
Binary files /dev/null and b/app/src/main/res/drawable/battery_50.png differ
diff --git a/app/src/main/res/drawable/battery_75.png b/app/src/main/res/drawable/battery_75.png
new file mode 100644
index 0000000000000000000000000000000000000000..d2e1ac09daee07c0b0493fcb7ab765798ebba524
Binary files /dev/null and b/app/src/main/res/drawable/battery_75.png differ
diff --git a/app/src/main/res/drawable/battery_unknown.png b/app/src/main/res/drawable/battery_unknown.png
new file mode 100644
index 0000000000000000000000000000000000000000..4c8036c84d3e64b4c6ce5e75c4fb18a00367489c
Binary files /dev/null and b/app/src/main/res/drawable/battery_unknown.png differ
diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml
deleted file mode 100644
index 84cc07b2b8603da6a558869fe53fa029ae140bcd..0000000000000000000000000000000000000000
--- a/app/src/main/res/layout/activity_login.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:gravity="center_horizontal"
-    android:orientation="vertical"
-    android:paddingBottom="@dimen/activity_vertical_margin"
-    android:paddingLeft="@dimen/activity_horizontal_margin"
-    android:paddingRight="@dimen/activity_horizontal_margin"
-    android:paddingTop="@dimen/activity_vertical_margin"
-    tools:context=".medtronic.GetHmacAndKeyActivity">
-
-    <!-- Login progress -->
-
-    <ScrollView
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-
-        <LinearLayout
-            android:orientation="vertical"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content">
-
-            <TextView
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:id="@+id/registered_usb_devices" />
-
-        </LinearLayout>
-    </ScrollView>
-
-</LinearLayout>
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index af14d6804fd460697687fa207ac50f610a4bf518..3dfa26586c1b258eca12ab5d0ba8e3f142d02d82 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -119,28 +119,30 @@
 
     </LinearLayout>
 
-    <com.github.mikephil.charting.charts.LineChart
-        android:id="@+id/chart"
+    <com.jjoe64.graphview.GraphView
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:visibility="invisible" />
+        android:layout_height="100dip"
+        android:id="@+id/chart" />
 
     <ScrollView
         android:id="@+id/scrollView"
         android:layout_width="match_parent"
         android:layout_height="fill_parent">
+        android:gravity="bottom"
 
         <LinearLayout
             android:orientation="vertical"
             android:layout_width="match_parent"
             android:layout_height="wrap_content">
+            android:gravity="bottom"
 
             <TextView
                 android:id="@+id/textview_log"
                 android:layout_width="fill_parent"
                 android:layout_height="wrap_content"
                 android:layout_margin="10sp"
-                android:maxLines="20"
+                android:maxLines="800"
+                android:gravity="bottom"
                 android:text="" />
         </LinearLayout>
     </ScrollView>
diff --git a/app/src/main/res/layout/activity_manage_cnl.xml b/app/src/main/res/layout/activity_manage_cnl.xml
new file mode 100644
index 0000000000000000000000000000000000000000..41cbee62fa6656797f97d05135d44ba4d57341ab
--- /dev/null
+++ b/app/src/main/res/layout/activity_manage_cnl.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+
+    tools:context=".medtronic.ManageCNLActivity">
+
+    <android.support.design.widget.AppBarLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
+
+        <android.support.v7.widget.Toolbar
+            android:id="@+id/toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
+            app:layout_scrollFlags="scroll|enterAlways"
+            app:popupTheme="@style/ThemeOverlay.AppCompat.Light">
+
+        </android.support.v7.widget.Toolbar>
+
+    </android.support.design.widget.AppBarLayout>
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="10sp"
+        android:layout_marginTop="10sp"
+        android:baselineAligned="true"
+        android:gravity="bottom"
+        android:orientation="vertical">
+
+        <ListView
+            android:id="@+id/cnl_list"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+    </LinearLayout>
+
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/manage_cnl_listview_empty"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:orientation="vertical"
+        android:layout_marginLeft="10sp"
+        android:layout_marginRight="10sp"
+        android:visibility="gone">
+
+        <TextView
+            android:id="@+id/manage_cnl_listview_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/no_registered_contour_next_link_devices"
+            android:layout_marginBottom="10sp"
+            android:textSize="@dimen/materialize_typography_headline"
+            android:textStyle="bold" />
+
+        <TextView
+            android:id="@+id/manage_cnl_listview_text2"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/to_register_a_contour_next_link_you_must_first_plug_it_in_and_get_a_reading_from_the_pump" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/app/src/main/res/layout/activity_status.xml b/app/src/main/res/layout/activity_status.xml
index eff1081d8b651ab4bebcaae0eab44e4fb08dea67..5a7529d14946eef094b6b350374771d6c953c670 100644
--- a/app/src/main/res/layout/activity_status.xml
+++ b/app/src/main/res/layout/activity_status.xml
@@ -1,9 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
+
     tools:context="info.nightscout.android.medtronic.StatusActivity"
     android:orientation="vertical">
 
@@ -28,7 +29,7 @@
         android:layout_width="fill_parent"
         android:layout_height="fill_parent"
         android:id="@+id/status_scroll_view"
-        android:fillViewport="true" >
+        android:fillViewport="true">
 
         <LinearLayout
             android:orientation="vertical"
diff --git a/app/src/main/res/layout/cnl_item.xml b/app/src/main/res/layout/cnl_item.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5ce73b2c268c0bfb9e233f7504435b64cc1c06d1
--- /dev/null
+++ b/app/src/main/res/layout/cnl_item.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <TextView
+        android:id="@+id/cnl_mac"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_alignParentLeft="true"
+        android:paddingLeft="8dp"
+        android:textSize="18sp"
+        android:textStyle="bold" />
+
+    <Button
+        android:id="@+id/delete_btn"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentRight="true"
+        android:layout_centerVertical="true"
+        android:layout_marginRight="5dp"
+        android:text="Delete" />
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/manage_cnl_listview_header.xml b/app/src/main/res/layout/manage_cnl_listview_header.xml
new file mode 100644
index 0000000000000000000000000000000000000000..163219fc423a3780d9375dfe9ad022b088b5600e
--- /dev/null
+++ b/app/src/main/res/layout/manage_cnl_listview_header.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/manage_cnl_listview_header"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical" >
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="10sp"
+        android:text="@string/serial_number"
+        android:layout_marginLeft="10sp" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu.xml b/app/src/main/res/menu/menu.xml
index 3a4948d2788551963d7d0670e34f0e6c67aa5944..a1da6693ab352108fe3a1f7a42105a3d839f00dd 100644
--- a/app/src/main/res/menu/menu.xml
+++ b/app/src/main/res/menu/menu.xml
@@ -1,6 +1,14 @@
 <?xml version="1.0" encoding="utf-8"?>
 <menu xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <!-- battery icons by Oxygen Team http://www.oxygen-icons.org -->
+    <item
+        android:id="@+id/status_battery"
+        android:icon="@drawable/battery_unknown"
+        android:orderInCategory="99"
+        app:showAsAction="always"
+        android:title="@string/menu_name_battery_status"/>
     <item
         android:id="@+id/action_menu_status"
         android:icon="@drawable/ic_launcher"
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
new file mode 100644
index 0000000000000000000000000000000000000000..cfb3458e897140a9308cac32f15041128630caad
--- /dev/null
+++ b/app/src/main/res/values/arrays.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <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>
+        <item>60 min</item>
+        <!--item>off</item-->
+    </string-array>
+
+    <string-array name="poll_interval_millis">
+        <item>300000</item>
+        <item>600000</item>
+        <item>720000</item>
+        <item>900000</item>
+        <item>1200000</item>
+        <item>1800000</item>
+        <item>3600000</item>
+        <!--item>0</item-->
+    </string-array>
+
+    <string-array name="chart_zoom">
+        <item>1 hour</item>
+        <item>3 hours</item>
+        <item>6 hours</item>
+        <item>12 hours</item>
+        <item>24 hours</item>
+    </string-array>
+
+    <string-array name="chart_zoom_hours">
+        <item>1</item>
+        <item>3</item>
+        <item>6</item>
+        <item>12</item>
+        <item>24</item>
+    </string-array>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 443d000163d74d1afe620bd3cb389b7d005b04b1..b403419f414cf3f94125d7f47ae87e0e7f14e5b7 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -16,7 +16,7 @@
         <item>Info</item>
         <item>Debug</item>
     </string-array>
-    <string name="title_activity_login">CareLink login</string>
+    <string name="title_activity_manage_cnl">Manage your Contour Next Link devices</string>
 
     <!-- Strings related to login -->
     <string name="prompt_username">CareLink Username</string>
@@ -25,9 +25,11 @@
     <string name="action_sign_in_short">Retrieve keys</string>
     <string name="error_invalid_password">Password is required</string>
     <string name="error_incorrect_password">The Username or password is incorrect</string>
+    <string name="error_client_protocol_exception">Could not communicate with server.</string>
+    <string name="error_io_exception">Could not connect to server.</string>
+    <string name="error_class_not_found_exception">Application code error.</string>
+    <string name="error_http_response">Server responded with error. Could be username or password problem.</string>
     <string name="error_field_required">This field is required</string>
-    <string name="preference_nightscout_url">Nightscout URL</string>
-    <string name="preference_api_secret">API SECRET</string>
     <string name="prompt_carelink_username_password">Please enter your CareLink details.\nThey will not be stored.</string>
     <string name="close">Close</string>
     <string name="register_contour_next_link">Registered Devices</string>
@@ -50,4 +52,15 @@
     <string name="dummy_button">Dummy Button</string>
     <string name="dummy_content">DUMMY\nCONTENT</string>
     <string name="menu_name_status">Status</string>
+    <string name="menu_name_battery_status">unknown</string>
+    <string name="preference_api_secret">YOUR.API.SECRET</string>
+    <string name="preference_nightscout_url">YOUR.NIGHTSCOUT.URL</string>
+
+    <string name="preferences_poll_interval">Poll interval</string>
+    <string name="preferences_low_battery_poll_interval">Poll interval on low pump battery</string>
+    <string name="no_registered_contour_next_link_devices">No registered Contour Next Link devices</string>
+    <string name="to_register_a_contour_next_link_you_must_first_plug_it_in_and_get_a_reading_from_the_pump">To register a Contour Next Link you must first plug it in, and get a reading from the pump.</string>
+    <string name="serial_number">Serial number</string>
+    <string name="preferences_chart_interval">Chart Zoom</string>
+
 </resources>
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 9e6793c85ec60101ef37b053d3af5d9caefd74b1..de93838a97ceafac7a7701ecfbb6ee914ed9c5c3 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -18,6 +18,37 @@
             android:switchTextOff="1"
             android:switchTextOn="2"
             android:title="Decimals"/>
+        <ListPreference
+            android:key="pollInterval"
+            android:defaultValue="300000"
+            android:title="@string/preferences_poll_interval"
+            android:summary="%s"
+            android:entries="@array/poll_interval"
+            android:entryValues="@array/poll_interval_millis"/>
+        <info.nightscout.android.utils.CustomSwitchPreference
+            android:disableDependentsState="false"
+            android:key="doublePollOnPumpAway"
+            android:summaryOff="Normal polling if pump is away"
+            android:summaryOn="Double polling if pump is away"
+            android:switchTextOff="off"
+            android:switchTextOn="on"
+            android:title="Polling interval if pump is away"/>
+        <ListPreference
+            android:key="lowBatPollInterval"
+            android:defaultValue="900000"
+            android:title="@string/preferences_low_battery_poll_interval"
+            android:summary="%s"
+            android:entries="@array/poll_interval"
+            android:entryValues="@array/poll_interval_millis"/>
+    </PreferenceCategory>
+    <PreferenceCategory android:title="Display">
+    <ListPreference
+        android:key="chartZoom"
+        android:defaultValue="3"
+        android:title="@string/preferences_chart_interval"
+        android:summary="%s"
+        android:entries="@array/chart_zoom"
+        android:entryValues="@array/chart_zoom_hours"/>
     </PreferenceCategory>
     <PreferenceCategory android:title="Sharing">
         <CheckBoxPreference
diff --git a/build.gradle b/build.gradle
index 9da42b0b994170c75de5ee547bfabc0007cf8dd7..fa343792dd739dcc26b1521ca4931cdda922476f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -8,7 +8,7 @@ buildscript {
         classpath 'com.android.tools.build:gradle:2.2.3'
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
-        classpath "io.realm:realm-gradle-plugin:1.0.0"
+        classpath 'io.realm:realm-gradle-plugin:2.2.2'
     }
 }