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
deleted file mode 100644
index dd11c422309587fbfb2c40dcfbc1c5c64bb0d573..0000000000000000000000000000000000000000
--- a/640gAndroidUploader.iml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module external.linked.project.id="640gAndroidUploader" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
-  <component name="FacetManager">
-    <facet type="java-gradle" name="Java-Gradle">
-      <configuration>
-        <option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" />
-        <option name="BUILDABLE" value="false" />
-      </configuration>
-    </facet>
-  </component>
-  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="true">
-    <exclude-output />
-    <content url="file://$MODULE_DIR$">
-      <excludeFolder url="file://$MODULE_DIR$/.gradle" />
-    </content>
-    <orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
-    <orderEntry type="sourceFolder" forTests="false" />
-  </component>
-</module>
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2346e1e45da68fc2d530770a87cd25ad3eea5b02..b5c94546d6e2e01b5d16df7d4f2c716deb630dbd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,6 +21,6 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 ### Added
 - Initial public beta release
 
-[Unreleased]: https://github.com/pazaan/640gAndroidUploader/compare/v0.2.0...HEAD
-[v0.2.0]: https://github.com/pazaan/640gAndroidUploader/compare/v0.1.1...v0.2.0
-[v0.1.1]: https://github.com/pazaan/640gAndroidUploader/compare/v0.1.0...v0.1.1
+[Unreleased]: https://github.com/pazaan/600SeriesAndroidUploader/compare/v0.2.0...HEAD
+[v0.2.0]: https://github.com/pazaan/600SeriesAndroidUploader/compare/v0.1.1...v0.2.0
+[v0.1.1]: https://github.com/pazaan/600SeriesAndroidUploader/compare/v0.1.0...v0.1.1
diff --git a/app/640gUploader.iml b/app/640gUploader.iml
deleted file mode 100644
index 45575b43f07bf114a0d826ad3cb7d363481e01df..0000000000000000000000000000000000000000
--- a/app/640gUploader.iml
+++ /dev/null
@@ -1,120 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module external.linked.project.id=":640gUploader" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="Medtronic640gUploader" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
-  <component name="FacetManager">
-    <facet type="android-gradle" name="Android-Gradle">
-      <configuration>
-        <option name="GRADLE_PROJECT_PATH" value=":640gUploader" />
-      </configuration>
-    </facet>
-    <facet type="android" name="Android">
-      <configuration>
-        <option name="SELECTED_BUILD_VARIANT" value="debug" />
-        <option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
-        <option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
-        <option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
-        <afterSyncTasks>
-          <task>generateDebugSources</task>
-        </afterSyncTasks>
-        <option name="ALLOW_USER_CONFIGURATION" value="false" />
-        <option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
-        <option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
-        <option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
-        <option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
-      </configuration>
-    </facet>
-  </component>
-  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false">
-    <output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
-    <output-test url="file://$MODULE_DIR$/build/intermediates/classes/test/debug" />
-    <exclude-output />
-    <content url="file://$MODULE_DIR$">
-      <sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
-      <sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
-      <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
-      <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
-      <sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
-      <sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" />
-      <sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" />
-      <sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
-      <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
-      <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
-      <sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" />
-      <sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/debug" type="java-test-resource" />
-      <sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
-      <sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
-      <sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
-      <sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
-      <sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
-      <sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
-      <sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
-      <sourceFolder url="file://$MODULE_DIR$/src/debug/shaders" isTestSource="false" />
-      <sourceFolder url="file://$MODULE_DIR$/src/testDebug/res" type="java-test-resource" />
-      <sourceFolder url="file://$MODULE_DIR$/src/testDebug/resources" type="java-test-resource" />
-      <sourceFolder url="file://$MODULE_DIR$/src/testDebug/assets" type="java-test-resource" />
-      <sourceFolder url="file://$MODULE_DIR$/src/testDebug/aidl" isTestSource="true" />
-      <sourceFolder url="file://$MODULE_DIR$/src/testDebug/java" isTestSource="true" />
-      <sourceFolder url="file://$MODULE_DIR$/src/testDebug/jni" isTestSource="true" />
-      <sourceFolder url="file://$MODULE_DIR$/src/testDebug/rs" isTestSource="true" />
-      <sourceFolder url="file://$MODULE_DIR$/src/testDebug/shaders" isTestSource="true" />
-      <sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
-      <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
-      <sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
-      <sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
-      <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
-      <sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
-      <sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
-      <sourceFolder url="file://$MODULE_DIR$/src/main/shaders" isTestSource="false" />
-      <sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
-      <sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
-      <sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
-      <sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
-      <sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
-      <sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
-      <sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
-      <sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" />
-      <sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
-      <sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
-      <sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
-      <sourceFolder url="file://$MODULE_DIR$/src/test/aidl" isTestSource="true" />
-      <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
-      <sourceFolder url="file://$MODULE_DIR$/src/test/jni" isTestSource="true" />
-      <sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
-      <sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" />
-      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
-      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
-      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/builds" />
-      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
-      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
-      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
-      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/21.0.3/jars" />
-      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/21.0.3/jars" />
-      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
-      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-classes" />
-      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-runtime-classes" />
-      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-safeguard" />
-      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-verifier" />
-      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant-run-support" />
-      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" />
-      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
-      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" />
-      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/reload-dex" />
-      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
-      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/restart-dex" />
-      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
-      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/shaders" />
-      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
-      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/transforms" />
-      <excludeFolder url="file://$MODULE_DIR$/build/outputs" />
-      <excludeFolder url="file://$MODULE_DIR$/build/tmp" />
-    </content>
-    <orderEntry type="jdk" jdkName="Android API 21 Platform" jdkType="Android SDK" />
-    <orderEntry type="sourceFolder" forTests="false" />
-    <orderEntry type="library" exported="" name="appcompat-v7-21.0.3" level="project" />
-    <orderEntry type="library" exported="" name="mongo-java-driver-3.0.2" level="project" />
-    <orderEntry type="library" exported="" name="commons-lang3-3.4" level="project" />
-    <orderEntry type="library" exported="" name="support-v4-21.0.3" level="project" />
-    <orderEntry type="library" exported="" name="support-annotations-21.0.3" level="project" />
-    <orderEntry type="library" exported="" name="slf4j-api-1.7.2" level="project" />
-    <orderEntry type="library" exported="" name="logback-android-1.1.1-3" level="project" />
-  </component>
-</module>
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 321863d615cb36775b630d58e4edd1721a5f7a06..b4d22ec6eb4fc031d5e71d600bcd294eb7fa58ad 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -2,17 +2,18 @@ import org.ajoberstar.grgit.Grgit
 
 buildscript {
     repositories {
+        jcenter()
         maven { url 'https://maven.fabric.io/public' }
     }
 
     dependencies {
-        classpath 'io.fabric.tools:gradle:1.21.6'
-        classpath 'io.realm:realm-gradle-plugin:1.1.1'
+        classpath 'io.fabric.tools:gradle:1.22.2'
+        classpath 'io.realm:realm-gradle-plugin:3.4.0'
         classpath 'org.ajoberstar:grgit:1.5.0'
     }
 }
 plugins {
-    id 'net.researchgate.release' version '2.3.4'
+    id 'net.researchgate.release' version '2.6.0'
 }
 
 apply plugin: 'com.android.application'
@@ -28,7 +29,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,26 +38,27 @@ 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() {
+static def getBugfenderApiKey() {
     Properties properties = new Properties()
     properties.load(new FileInputStream("app/bugfender.properties"))
     return "\"" + properties.getProperty("apiKey", "") + "\""
 }
 
 android {
-    compileSdkVersion 23
-    buildToolsVersion "23.0.3"
-    // FIXME - replace with URLConnection. This is used in GetHmacAndKeyActivity.
+    compileSdkVersion 25
+    buildToolsVersion '25.0.2'
+    // FIXME - replace with URLConnection. This is used in ManageCNLActivity.
     useLibrary 'org.apache.http.legacy'
 
     applicationVariants.all { variant ->
@@ -66,8 +68,8 @@ android {
     defaultConfig {
         applicationId "info.nightscout.android"
         minSdkVersion 14
-        targetSdkVersion 23
-        versionName project.properties['version'] + "/" + gitCommitId()
+        targetSdkVersion 25
+        versionName project.properties['version'] + "/" + gitCommitId() // + " (" + gitBranch()+")"
         versionCode gitVersion()
         buildConfigField "String", "BUGFENDER_API_KEY", getBugfenderApiKey()
     }
@@ -86,52 +88,56 @@ android {
     }
 }
 
-task signRelease << {
-    def command = [
-            'jarsigner',
-            '-verbose',
-            '-sigalg',
-            'SHA1withRSA',
-            '-digestalg',
-            'SHA1',
-            '-keystore',
-            '/Users/lennart/keystores/nightscout_android.jks',
-            'app/build/outputs/apk/app-release-unsigned.apk',
-            'nightscoutandroidkey'
-    ]
-
-    def proc = new ProcessBuilder(command)
-            .redirectOutput(ProcessBuilder.Redirect.INHERIT)
-            .redirectInput(ProcessBuilder.Redirect.INHERIT)
-            .redirectError(ProcessBuilder.Redirect.INHERIT)
-            .start()
-
-    proc.waitFor()
-
-    if (0 != proc.exitValue()) {
-        throw new RuntimeException("Could not sign APK.")
+task signRelease {
+    doLast {
+        def command = [
+                'jarsigner',
+                '-verbose',
+                '-sigalg',
+                'SHA1withRSA',
+                '-digestalg',
+                'SHA1',
+                '-keystore',
+                '/Users/lennart/keystores/nightscout_android.jks',
+                'app/build/outputs/apk/app-release-unsigned.apk',
+                'nightscoutandroidkey'
+        ]
+
+        def proc = new ProcessBuilder(command)
+                .redirectOutput(ProcessBuilder.Redirect.INHERIT)
+                .redirectInput(ProcessBuilder.Redirect.INHERIT)
+                .redirectError(ProcessBuilder.Redirect.INHERIT)
+                .start()
+
+        proc.waitFor()
+
+        if (0 != proc.exitValue()) {
+            throw new RuntimeException("Could not sign APK.")
+        }
     }
 }
 
-task zipalignRelease << {
-    def command = [
-            '/Users/lennart/Library/Android/sdk/build-tools/23.0.3/zipalign',
-            '-v',
-            '4',
-            'app/build/outputs/apk/app-release-unsigned.apk',
-            'app/build/outputs/apk/640g-android-uploader.apk'
-    ]
-
-    def proc = new ProcessBuilder(command)
-            .redirectOutput(ProcessBuilder.Redirect.INHERIT)
-            .redirectInput(ProcessBuilder.Redirect.INHERIT)
-            .redirectError(ProcessBuilder.Redirect.INHERIT)
-            .start()
-
-    proc.waitFor()
-
-    if (0 != proc.exitValue()) {
-        throw new RuntimeException("Could not align APK.")
+task zipalignRelease {
+    doLast {
+        def command = [
+                '/Users/lennart/Library/Android/sdk/build-tools/25.0.2/zipalign',
+                '-v',
+                '4',
+                'app/build/outputs/apk/app-release-unsigned.apk',
+                'app/build/outputs/apk/600-series-uploader.apk'
+        ]
+
+        def proc = new ProcessBuilder(command)
+                .redirectOutput(ProcessBuilder.Redirect.INHERIT)
+                .redirectInput(ProcessBuilder.Redirect.INHERIT)
+                .redirectError(ProcessBuilder.Redirect.INHERIT)
+                .start()
+
+        proc.waitFor()
+
+        if (0 != proc.exitValue()) {
+            throw new RuntimeException("Could not align APK.")
+        }
     }
 }
 
@@ -144,21 +150,34 @@ release {
 
 dependencies {
     compile files('libs/slf4j-api-1.7.2.jar')
-    compile('com.crashlytics.sdk.android:crashlytics:2.6.5@aar') {
-        transitive = true;
-    }
-    compile('com.mikepenz:materialdrawer:5.2.9@aar') {
-        transitive = true
-    }
-    compile 'com.android.support:appcompat-v7:23.4.0'
+
+    compile 'com.android.support:support-v13:25.3.1'
+    compile 'com.android.support:design:25.3.1'
+    compile 'com.android.support:appcompat-v7:25.3.1'
+    compile 'com.android.support:recyclerview-v7:25.3.1'
+    compile 'com.android.support:cardview-v7:25.3.1'
     compile 'org.apache.commons:commons-lang3:3.4'
     compile 'com.mikepenz:google-material-typeface:2.2.0.1.original@aar'
     compile '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.android.support:support-v4:23.4.0'
+    compile 'com.bugfender.sdk:android:0.7.2'
+    compile 'com.jjoe64:graphview:4.0.1'
     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.crashlytics.sdk.android:crashlytics:2.6.8@aar') {
+        transitive = true;
+    }
+
+    // The version of okhttp3 *must* be the same as the version in AppUpdater
+    compile 'com.squareup.okhttp3:okhttp:3.6.0'
+    compile 'com.squareup.okhttp3:logging-interceptor:3.6.0'
+
+    compile('com.crashlytics.sdk.android:crashlytics:2.6.7@aar') {
+        transitive = true;
+    }
+    compile('com.mikepenz:materialdrawer:5.2.9@aar') {
+        transitive = true
+    }
+    compile 'com.github.javiersantos:AppUpdater:2.6.1'
 }
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f7d7185b04799440fc232b3ffd10ef632e76220c..3e5115f5ee899354c561c7788101e4fb9bd054ee 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" />
 
diff --git a/app/src/main/java/com/google/zxing/integration/android/IntentIntegrator.java b/app/src/main/java/com/google/zxing/integration/android/IntentIntegrator.java
new file mode 100644
index 0000000000000000000000000000000000000000..3924ee0bbf610fd9a72a20c66dc31ca6509cb742
--- /dev/null
+++ b/app/src/main/java/com/google/zxing/integration/android/IntentIntegrator.java
@@ -0,0 +1,506 @@
+/*
+ * Copyright 2009 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.integration.android;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Fragment;
+import android.content.ActivityNotFoundException;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * <p>A utility class which helps ease integration with Barcode Scanner via {@link Intent}s. This is a simple
+ * way to invoke barcode scanning and receive the result, without any need to integrate, modify, or learn the
+ * project's source code.</p>
+ *
+ * <h2>Initiating a barcode scan</h2>
+ *
+ * <p>To integrate, create an instance of {@code IntentIntegrator} and call {@link #initiateScan()} and wait
+ * for the result in your app.</p>
+ *
+ * <p>It does require that the Barcode Scanner (or work-alike) application is installed. The
+ * {@link #initiateScan()} method will prompt the user to download the application, if needed.</p>
+ *
+ * <p>There are a few steps to using this integration. First, your {@link Activity} must implement
+ * the method {@link Activity#onActivityResult(int, int, Intent)} and include a line of code like this:</p>
+ *
+ * <pre>{@code
+ * public void onActivityResult(int requestCode, int resultCode, Intent intent) {
+ *   IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
+ *   if (scanResult != null) {
+ *     // handle scan result
+ *   }
+ *   // else continue with any other code you need in the method
+ *   ...
+ * }
+ * }</pre>
+ *
+ * <p>This is where you will handle a scan result.</p>
+ *
+ * <p>Second, just call this in response to a user action somewhere to begin the scan process:</p>
+ *
+ * <pre>{@code
+ * IntentIntegrator integrator = new IntentIntegrator(yourActivity);
+ * integrator.initiateScan();
+ * }</pre>
+ *
+ * <p>Note that {@link #initiateScan()} returns an {@link AlertDialog} which is non-null if the
+ * user was prompted to download the application. This lets the calling app potentially manage the dialog.
+ * In particular, ideally, the app dismisses the dialog if it's still active in its {@link Activity#onPause()}
+ * method.</p>
+ * 
+ * <p>You can use {@link #setTitle(String)} to customize the title of this download prompt dialog (or, use
+ * {@link #setTitleByID(int)} to set the title by string resource ID.) Likewise, the prompt message, and
+ * yes/no button labels can be changed.</p>
+ *
+ * <p>Finally, you can use {@link #addExtra(String, Object)} to add more parameters to the Intent used
+ * to invoke the scanner. This can be used to set additional options not directly exposed by this
+ * simplified API.</p>
+ * 
+ * <p>By default, this will only allow applications that are known to respond to this intent correctly
+ * do so. The apps that are allowed to response can be set with {@link #setTargetApplications(List)}.
+ * For example, set to {@link #TARGET_BARCODE_SCANNER_ONLY} to only target the Barcode Scanner app itself.</p>
+ *
+ * <h2>Sharing text via barcode</h2>
+ *
+ * <p>To share text, encoded as a QR Code on-screen, similarly, see {@link #shareText(CharSequence)}.</p>
+ *
+ * <p>Some code, particularly download integration, was contributed from the Anobiit application.</p>
+ *
+ * <h2>Enabling experimental barcode formats</h2>
+ *
+ * <p>Some formats are not enabled by default even when scanning with {@link #ALL_CODE_TYPES}, such as
+ * PDF417. Use {@link #initiateScan(Collection)} with
+ * a collection containing the names of formats to scan for explicitly, like "PDF_417", to use such
+ * formats.</p>
+ *
+ * @author Sean Owen
+ * @author Fred Lin
+ * @author Isaac Potoczny-Jones
+ * @author Brad Drehmer
+ * @author gcstang
+ */
+public class IntentIntegrator {
+
+  public static final int REQUEST_CODE = 0x0000c0de; // Only use bottom 16 bits
+  private static final String TAG = IntentIntegrator.class.getSimpleName();
+
+  public static final String DEFAULT_TITLE = "Install Barcode Scanner?";
+  public static final String DEFAULT_MESSAGE =
+      "This application requires Barcode Scanner. Would you like to install it?";
+  public static final String DEFAULT_YES = "Yes";
+  public static final String DEFAULT_NO = "No";
+
+  private static final String BS_PACKAGE = "com.google.zxing.client.android";
+  private static final String BSPLUS_PACKAGE = "com.srowen.bs.android";
+
+  // supported barcode formats
+  public static final Collection<String> PRODUCT_CODE_TYPES = list("UPC_A", "UPC_E", "EAN_8", "EAN_13", "RSS_14");
+  public static final Collection<String> ONE_D_CODE_TYPES =
+      list("UPC_A", "UPC_E", "EAN_8", "EAN_13", "CODE_39", "CODE_93", "CODE_128",
+           "ITF", "RSS_14", "RSS_EXPANDED");
+  public static final Collection<String> QR_CODE_TYPES = Collections.singleton("QR_CODE");
+  public static final Collection<String> DATA_MATRIX_TYPES = Collections.singleton("DATA_MATRIX");
+
+  public static final Collection<String> ALL_CODE_TYPES = null;
+  
+  public static final List<String> TARGET_BARCODE_SCANNER_ONLY = Collections.singletonList(BS_PACKAGE);
+  public static final List<String> TARGET_ALL_KNOWN = list(
+          BSPLUS_PACKAGE,             // Barcode Scanner+
+          BSPLUS_PACKAGE + ".simple", // Barcode Scanner+ Simple
+          BS_PACKAGE                  // Barcode Scanner          
+          // What else supports this intent?
+      );
+  
+  private final Activity activity;
+  private final Fragment fragment;
+
+  private String title;
+  private String message;
+  private String buttonYes;
+  private String buttonNo;
+  private List<String> targetApplications;
+  private final Map<String,Object> moreExtras = new HashMap<>(3);
+
+  /**
+   * @param activity {@link Activity} invoking the integration
+   */
+  public IntentIntegrator(Activity activity) {
+    this.activity = activity;
+    this.fragment = null;
+    initializeConfiguration();
+  }
+
+  /**
+   * @param fragment {@link Fragment} invoking the integration.
+   *  {@link #startActivityForResult(Intent, int)} will be called on the {@link Fragment} instead
+   *  of an {@link Activity}
+   */
+  public IntentIntegrator(Fragment fragment) {
+    this.activity = fragment.getActivity();
+    this.fragment = fragment;
+    initializeConfiguration();
+  }
+
+  private void initializeConfiguration() {
+    title = DEFAULT_TITLE;
+    message = DEFAULT_MESSAGE;
+    buttonYes = DEFAULT_YES;
+    buttonNo = DEFAULT_NO;
+    targetApplications = TARGET_ALL_KNOWN;
+  }
+  
+  public String getTitle() {
+    return title;
+  }
+  
+  public void setTitle(String title) {
+    this.title = title;
+  }
+
+  public void setTitleByID(int titleID) {
+    title = activity.getString(titleID);
+  }
+
+  public String getMessage() {
+    return message;
+  }
+
+  public void setMessage(String message) {
+    this.message = message;
+  }
+
+  public void setMessageByID(int messageID) {
+    message = activity.getString(messageID);
+  }
+
+  public String getButtonYes() {
+    return buttonYes;
+  }
+
+  public void setButtonYes(String buttonYes) {
+    this.buttonYes = buttonYes;
+  }
+
+  public void setButtonYesByID(int buttonYesID) {
+    buttonYes = activity.getString(buttonYesID);
+  }
+
+  public String getButtonNo() {
+    return buttonNo;
+  }
+
+  public void setButtonNo(String buttonNo) {
+    this.buttonNo = buttonNo;
+  }
+
+  public void setButtonNoByID(int buttonNoID) {
+    buttonNo = activity.getString(buttonNoID);
+  }
+  
+  public Collection<String> getTargetApplications() {
+    return targetApplications;
+  }
+  
+  public final void setTargetApplications(List<String> targetApplications) {
+    if (targetApplications.isEmpty()) {
+      throw new IllegalArgumentException("No target applications");
+    }
+    this.targetApplications = targetApplications;
+  }
+  
+  public void setSingleTargetApplication(String targetApplication) {
+    this.targetApplications = Collections.singletonList(targetApplication);
+  }
+
+  public Map<String,?> getMoreExtras() {
+    return moreExtras;
+  }
+
+  public final void addExtra(String key, Object value) {
+    moreExtras.put(key, value);
+  }
+
+  /**
+   * Initiates a scan for all known barcode types with the default camera.
+   *
+   * @return the {@link AlertDialog} that was shown to the user prompting them to download the app
+   *   if a prompt was needed, or null otherwise.
+   */
+  public final AlertDialog initiateScan() {
+    return initiateScan(ALL_CODE_TYPES, -1);
+  }
+  
+  /**
+   * Initiates a scan for all known barcode types with the specified camera.
+   *
+   * @param cameraId camera ID of the camera to use. A negative value means "no preference".
+   * @return the {@link AlertDialog} that was shown to the user prompting them to download the app
+   *   if a prompt was needed, or null otherwise.
+   */
+  public final AlertDialog initiateScan(int cameraId) {
+    return initiateScan(ALL_CODE_TYPES, cameraId);
+  }
+
+  /**
+   * Initiates a scan, using the default camera, only for a certain set of barcode types, given as strings corresponding
+   * to their names in ZXing's {@code BarcodeFormat} class like "UPC_A". You can supply constants
+   * like {@link #PRODUCT_CODE_TYPES} for example.
+   *
+   * @param desiredBarcodeFormats names of {@code BarcodeFormat}s to scan for
+   * @return the {@link AlertDialog} that was shown to the user prompting them to download the app
+   *   if a prompt was needed, or null otherwise.
+   */
+  public final AlertDialog initiateScan(Collection<String> desiredBarcodeFormats) {
+    return initiateScan(desiredBarcodeFormats, -1);
+  }
+  
+  /**
+   * Initiates a scan, using the specified camera, only for a certain set of barcode types, given as strings corresponding
+   * to their names in ZXing's {@code BarcodeFormat} class like "UPC_A". You can supply constants
+   * like {@link #PRODUCT_CODE_TYPES} for example.
+   *
+   * @param desiredBarcodeFormats names of {@code BarcodeFormat}s to scan for
+   * @param cameraId camera ID of the camera to use. A negative value means "no preference".
+   * @return the {@link AlertDialog} that was shown to the user prompting them to download the app
+   *   if a prompt was needed, or null otherwise
+   */
+  public final AlertDialog initiateScan(Collection<String> desiredBarcodeFormats, int cameraId) {
+    Intent intentScan = new Intent(BS_PACKAGE + ".SCAN");
+    intentScan.addCategory(Intent.CATEGORY_DEFAULT);
+
+    // check which types of codes to scan for
+    if (desiredBarcodeFormats != null) {
+      // set the desired barcode types
+      StringBuilder joinedByComma = new StringBuilder();
+      for (String format : desiredBarcodeFormats) {
+        if (joinedByComma.length() > 0) {
+          joinedByComma.append(',');
+        }
+        joinedByComma.append(format);
+      }
+      intentScan.putExtra("SCAN_FORMATS", joinedByComma.toString());
+    }
+
+    // check requested camera ID
+    if (cameraId >= 0) {
+      intentScan.putExtra("SCAN_CAMERA_ID", cameraId);
+    }
+
+    String targetAppPackage = findTargetAppPackage(intentScan);
+    if (targetAppPackage == null) {
+      return showDownloadDialog();
+    }
+    intentScan.setPackage(targetAppPackage);
+    intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+    intentScan.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+    attachMoreExtras(intentScan);
+    startActivityForResult(intentScan, REQUEST_CODE);
+    return null;
+  }
+
+  /**
+   * Start an activity. This method is defined to allow different methods of activity starting for
+   * newer versions of Android and for compatibility library.
+   *
+   * @param intent Intent to start.
+   * @param code Request code for the activity
+   * @see Activity#startActivityForResult(Intent, int)
+   * @see Fragment#startActivityForResult(Intent, int)
+   */
+  protected void startActivityForResult(Intent intent, int code) {
+    if (fragment == null) {
+      activity.startActivityForResult(intent, code);
+    } else {
+      fragment.startActivityForResult(intent, code);
+    }
+  }
+  
+  private String findTargetAppPackage(Intent intent) {
+    PackageManager pm = activity.getPackageManager();
+    List<ResolveInfo> availableApps = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+    if (availableApps != null) {
+      for (String targetApp : targetApplications) {
+        if (contains(availableApps, targetApp)) {
+          return targetApp;
+        }
+      }
+    }
+    return null;
+  }
+  
+  private static boolean contains(Iterable<ResolveInfo> availableApps, String targetApp) {
+    for (ResolveInfo availableApp : availableApps) {
+      String packageName = availableApp.activityInfo.packageName;
+      if (targetApp.equals(packageName)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private AlertDialog showDownloadDialog() {
+    AlertDialog.Builder downloadDialog = new AlertDialog.Builder(activity);
+    downloadDialog.setTitle(title);
+    downloadDialog.setMessage(message);
+    downloadDialog.setPositiveButton(buttonYes, new DialogInterface.OnClickListener() {
+      @Override
+      public void onClick(DialogInterface dialogInterface, int i) {
+        String packageName;
+        if (targetApplications.contains(BS_PACKAGE)) {
+          // Prefer to suggest download of BS if it's anywhere in the list
+          packageName = BS_PACKAGE;
+        } else {
+          // Otherwise, first option:
+          packageName = targetApplications.get(0);
+        }
+        Uri uri = Uri.parse("market://details?id=" + packageName);
+        Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+        try {
+          if (fragment == null) {
+            activity.startActivity(intent);
+          } else {
+            fragment.startActivity(intent);
+          }
+        } catch (ActivityNotFoundException anfe) {
+          // Hmm, market is not installed
+          Log.w(TAG, "Google Play is not installed; cannot install " + packageName);
+        }
+      }
+    });
+    downloadDialog.setNegativeButton(buttonNo, null);
+    downloadDialog.setCancelable(true);
+    return downloadDialog.show();
+  }
+
+
+  /**
+   * <p>Call this from your {@link Activity}'s
+   * {@link Activity#onActivityResult(int, int, Intent)} method.</p>
+   *
+   * @param requestCode request code from {@code onActivityResult()}
+   * @param resultCode result code from {@code onActivityResult()}
+   * @param intent {@link Intent} from {@code onActivityResult()}
+   * @return null if the event handled here was not related to this class, or
+   *  else an {@link IntentResult} containing the result of the scan. If the user cancelled scanning,
+   *  the fields will be null.
+   */
+  public static IntentResult parseActivityResult(int requestCode, int resultCode, Intent intent) {
+    if (requestCode == REQUEST_CODE) {
+      if (resultCode == Activity.RESULT_OK) {
+        String contents = intent.getStringExtra("SCAN_RESULT");
+        String formatName = intent.getStringExtra("SCAN_RESULT_FORMAT");
+        byte[] rawBytes = intent.getByteArrayExtra("SCAN_RESULT_BYTES");
+        int intentOrientation = intent.getIntExtra("SCAN_RESULT_ORIENTATION", Integer.MIN_VALUE);
+        Integer orientation = intentOrientation == Integer.MIN_VALUE ? null : intentOrientation;
+        String errorCorrectionLevel = intent.getStringExtra("SCAN_RESULT_ERROR_CORRECTION_LEVEL");
+        return new IntentResult(contents,
+                                formatName,
+                                rawBytes,
+                                orientation,
+                                errorCorrectionLevel);
+      }
+      return new IntentResult();
+    }
+    return null;
+  }
+
+
+  /**
+   * Defaults to type "TEXT_TYPE".
+   *
+   * @param text the text string to encode as a barcode
+   * @return the {@link AlertDialog} that was shown to the user prompting them to download the app
+   *   if a prompt was needed, or null otherwise
+   * @see #shareText(CharSequence, CharSequence)
+   */
+  public final AlertDialog shareText(CharSequence text) {
+    return shareText(text, "TEXT_TYPE");
+  }
+
+  /**
+   * Shares the given text by encoding it as a barcode, such that another user can
+   * scan the text off the screen of the device.
+   *
+   * @param text the text string to encode as a barcode
+   * @param type type of data to encode. See {@code com.google.zxing.client.android.Contents.Type} constants.
+   * @return the {@link AlertDialog} that was shown to the user prompting them to download the app
+   *   if a prompt was needed, or null otherwise
+   */
+  public final AlertDialog shareText(CharSequence text, CharSequence type) {
+    Intent intent = new Intent();
+    intent.addCategory(Intent.CATEGORY_DEFAULT);
+    intent.setAction(BS_PACKAGE + ".ENCODE");
+    intent.putExtra("ENCODE_TYPE", type);
+    intent.putExtra("ENCODE_DATA", text);
+    String targetAppPackage = findTargetAppPackage(intent);
+    if (targetAppPackage == null) {
+      return showDownloadDialog();
+    }
+    intent.setPackage(targetAppPackage);
+    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+    attachMoreExtras(intent);
+    if (fragment == null) {
+      activity.startActivity(intent);
+    } else {
+      fragment.startActivity(intent);
+    }
+    return null;
+  }
+  
+  private static List<String> list(String... values) {
+    return Collections.unmodifiableList(Arrays.asList(values));
+  }
+
+  private void attachMoreExtras(Intent intent) {
+    for (Map.Entry<String,Object> entry : moreExtras.entrySet()) {
+      String key = entry.getKey();
+      Object value = entry.getValue();
+      // Kind of hacky
+      if (value instanceof Integer) {
+        intent.putExtra(key, (Integer) value);
+      } else if (value instanceof Long) {
+        intent.putExtra(key, (Long) value);
+      } else if (value instanceof Boolean) {
+        intent.putExtra(key, (Boolean) value);
+      } else if (value instanceof Double) {
+        intent.putExtra(key, (Double) value);
+      } else if (value instanceof Float) {
+        intent.putExtra(key, (Float) value);
+      } else if (value instanceof Bundle) {
+        intent.putExtra(key, (Bundle) value);
+      } else {
+        intent.putExtra(key, value.toString());
+      }
+    }
+  }
+
+}
diff --git a/app/src/main/java/com/google/zxing/integration/android/IntentResult.java b/app/src/main/java/com/google/zxing/integration/android/IntentResult.java
new file mode 100644
index 0000000000000000000000000000000000000000..15b2e961cdfbb848218261f985dd1edbe76e8ddb
--- /dev/null
+++ b/app/src/main/java/com/google/zxing/integration/android/IntentResult.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2009 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.integration.android;
+
+/**
+ * <p>Encapsulates the result of a barcode scan invoked through {@link IntentIntegrator}.</p>
+ *
+ * @author Sean Owen
+ */
+public final class IntentResult {
+
+  private final String contents;
+  private final String formatName;
+  private final byte[] rawBytes;
+  private final Integer orientation;
+  private final String errorCorrectionLevel;
+
+  IntentResult() {
+    this(null, null, null, null, null);
+  }
+
+  IntentResult(String contents,
+               String formatName,
+               byte[] rawBytes,
+               Integer orientation,
+               String errorCorrectionLevel) {
+    this.contents = contents;
+    this.formatName = formatName;
+    this.rawBytes = rawBytes;
+    this.orientation = orientation;
+    this.errorCorrectionLevel = errorCorrectionLevel;
+  }
+
+  /**
+   * @return raw content of barcode
+   */
+  public String getContents() {
+    return contents;
+  }
+
+  /**
+   * @return name of format, like "QR_CODE", "UPC_A". See {@code BarcodeFormat} for more format names.
+   */
+  public String getFormatName() {
+    return formatName;
+  }
+
+  /**
+   * @return raw bytes of the barcode content, if applicable, or null otherwise
+   */
+  public byte[] getRawBytes() {
+    return rawBytes;
+  }
+
+  /**
+   * @return rotation of the image, in degrees, which resulted in a successful scan. May be null.
+   */
+  public Integer getOrientation() {
+    return orientation;
+  }
+
+  /**
+   * @return name of the error correction level used in the barcode, if applicable
+   */
+  public String getErrorCorrectionLevel() {
+    return errorCorrectionLevel;
+  }
+  
+  @Override
+  public String toString() {
+    int rawBytesLength = rawBytes == null ? 0 : rawBytes.length;
+    return "Format: " + formatName + '\n' +
+        "Contents: " + contents + '\n' +
+        "Raw bytes: (" + rawBytesLength + " bytes)\n" +
+        "Orientation: " + orientation + '\n' +
+        "EC level: " + errorCorrectionLevel + '\n';
+  }
+
+}
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..373489b351c88f124b26921b5d3b02bba0f35699 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,11 +25,10 @@ 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;
 import android.util.Log;
 import android.view.Menu;
@@ -36,12 +37,17 @@ 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.github.javiersantos.appupdater.AppUpdater;
+import com.github.javiersantos.appupdater.enums.UpdateFrom;
+import com.jjoe64.graphview.DefaultLabelFormatter;
+import com.jjoe64.graphview.GraphView;
+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,37 +68,69 @@ import info.nightscout.android.R;
 import info.nightscout.android.USB.UsbHidDriver;
 import info.nightscout.android.eula.Eula;
 import info.nightscout.android.eula.Eula.OnEulaAgreedTo;
-import info.nightscout.android.medtronic.service.MedtronicCnlAlarmReceiver;
+import info.nightscout.android.medtronic.service.MedtronicCnlAlarmManager;
 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 info.nightscout.android.utils.ConfigurationStore;
+import info.nightscout.android.utils.DataStore;
 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 int USB_DISCONNECT_NOFICATION_ID = 1;
+    public static final float MMOLXLFACTOR = 18.016f;
+
+    private DataStore dataStore = DataStore.getInstance();
+    private ConfigurationStore configurationStore = ConfigurationStore.getInstance();
+
+    private int chartZoom = 3;
+    private boolean hasZoomedChart = false;
 
-    public static int batLevel = 0;
-    private static long activePumpMac;
-    boolean mEnableCgmService = true;
-    SharedPreferences prefs = null;
+    private boolean mEnableCgmService = true;
+    private SharedPreferences prefs = null;
     private PumpInfo mActivePump;
     private TextView mTextViewLog; // This will eventually move to a status page.
-    private LineChart mChart;
-    private Intent mNightscoutUploadService;
+    private GraphView mChart;
     private Handler mUiRefreshHandler = new Handler();
     private Runnable mUiRefreshRunnable = new RefreshDisplayRunnable();
     private Realm mRealm;
     private StatusMessageReceiver statusMessageReceiver = new StatusMessageReceiver();
-    private MedtronicCnlAlarmReceiver medtronicCnlAlarmReceiver = new MedtronicCnlAlarmReceiver();
 
-    public static void setActivePumpMac(long pumpMac) {
-        activePumpMac = pumpMac;
+    /**
+     * calculate the next poll timestamp based on last svg event
+     *
+     * @param pumpStatusData
+     * @return timestamp
+     */
+    public static long getNextPoll(PumpStatusEvent pumpStatusData) {
+        long nextPoll = pumpStatusData.getSgvDate().getTime() + pumpStatusData.getPumpTimeOffset(),
+                now = System.currentTimeMillis(),
+                pollInterval = ConfigurationStore.getInstance().getPollInterval();
+
+        // 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) / pollInterval)) * pollInterval
+                    + MedtronicCnlIntentService.POLL_GRACE_PERIOD_MS;
+            if (pumpStatusData.getBatteryPercentage() > 25) {
+                // poll every 5 min
+                nextPoll += pollInterval;
+            } else {
+                // if pump battery seems to be empty reduce polling to save battery (every 15 min)
+                //TODO add message & document it
+                nextPoll += ConfigurationStore.getInstance().getLowBatteryPollInterval();
+            }
+        }
+
+        return nextPoll;
     }
 
     @Override
@@ -100,7 +139,11 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
         super.onCreate(savedInstanceState);
 
         mRealm = Realm.getDefaultInstance();
-        mNightscoutUploadService = new Intent(this, NightscoutUploadIntentService.class);
+
+        RealmResults<PumpStatusEvent> data = mRealm.where(PumpStatusEvent.class)
+                .findAllSorted("eventDate", Sort.DESCENDING);
+        if (data.size() > 0)
+            dataStore.setLastPumpStatus(data.first());
 
         setContentView(R.layout.activity_main);
 
@@ -111,13 +154,22 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
             stopCgmService();
         }
 
+        // setup preferences
+        configurationStore.setPollInterval(Long.parseLong(prefs.getString("pollInterval", Long.toString(MedtronicCnlIntentService.POLL_PERIOD_MS))));
+        configurationStore.setLowBatteryPollInterval(Long.parseLong(prefs.getString("lowBatPollInterval", Long.toString(MedtronicCnlIntentService.LOW_BATTERY_POLL_PERIOD_MS))));
+        configurationStore.setReducePollOnPumpAway(prefs.getBoolean("doublePollOnPumpAway", false));
+
+        chartZoom = Integer.parseInt(prefs.getString("chartZoom", "3"));
+        configurationStore.setMmolxl(prefs.getBoolean("mmolxl", false));
+        configurationStore.setMmolxlDecimals(prefs.getBoolean("mmolDecimals", false));
+
         // 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 +189,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);
 
@@ -175,22 +227,31 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
                 .withIcon(GoogleMaterial.Icon.gmd_settings)
                 .withSelectable(false);
         final PrimaryDrawerItem itemRegisterUsb = new PrimaryDrawerItem()
-                .withName("Registered Devices")
+                .withName("Registered devices")
                 .withIcon(GoogleMaterial.Icon.gmd_usb)
                 .withSelectable(false);
         final PrimaryDrawerItem itemStopCollecting = new PrimaryDrawerItem()
                 .withName("Stop collecting data")
-                .withIcon(GoogleMaterial.Icon.gmd_stop)
+                .withIcon(GoogleMaterial.Icon.gmd_power_settings_new)
                 .withSelectable(false);
         final PrimaryDrawerItem itemGetNow = new PrimaryDrawerItem()
                 .withName("Read data now")
-                .withIcon(GoogleMaterial.Icon.gmd_play_arrow)
+                .withIcon(GoogleMaterial.Icon.gmd_refresh)
+                .withSelectable(false);
+        final PrimaryDrawerItem itemUpdateProfile = new PrimaryDrawerItem()
+                .withName("Update pump profile")
+                .withIcon(GoogleMaterial.Icon.gmd_insert_chart)
                 .withSelectable(false);
         final PrimaryDrawerItem itemClearLog = new PrimaryDrawerItem()
-                .withName("Clear Log")
+                .withName("Clear log")
                 .withIcon(GoogleMaterial.Icon.gmd_clear_all)
                 .withSelectable(false);
+        final PrimaryDrawerItem itemCheckForUpdate = new PrimaryDrawerItem()
+                .withName("Check for App update")
+                .withIcon(GoogleMaterial.Icon.gmd_update)
+                .withSelectable(false);
 
+        assert toolbar != null;
         new DrawerBuilder()
                 .withActivity(this)
                 .withAccountHeader(new AccountHeaderBuilder()
@@ -204,10 +265,12 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
                 .withSelectedItem(-1)
                 .addDrawerItems(
                         itemSettings,
+                        //itemUpdateProfile, // TODO - re-add when we to add Basal Profile Upload
                         itemRegisterUsb,
-                        itemStopCollecting,
+                        itemCheckForUpdate,
+                        itemClearLog,
                         itemGetNow,
-                        itemClearLog
+                        itemStopCollecting
                 )
                 .withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
                     @Override
@@ -221,9 +284,12 @@ 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();
+                        } else if (drawerItem.equals(itemCheckForUpdate)) {
+                            checkForUpdateNow();
                         }
 
                         return false;
@@ -232,7 +298,66 @@ 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);
+
+// due to bug in GraphView v4.2.1 using setNumHorizontalLabels reverted to using v4.0.1 and setOnXAxisBoundsChangedListener is n/a in this version
+/*
+        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);
+
+// due to bug in GraphView v4.2.1 using setNumHorizontalLabels reverted to using v4.0.1 and setHumanRounding is n/a in this version
+//        mChart.getGridLabelRenderer().setHumanRounding(false);
+
+        mChart.getGridLabelRenderer().setLabelFormatter(
+                new DefaultLabelFormatter() {
+                    DateFormat mFormat = new SimpleDateFormat("HH:mm", Locale.US);  // 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 MainActivity.strFormatSGV(value);
+                        }
+                    }
+                }
+        );
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        checkForUpdateBackground(5);
     }
 
     @Override
@@ -247,7 +372,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
@@ -262,31 +387,14 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
     public boolean onOptionsItemSelected(MenuItem item) {
         switch (item.getItemId()) {
             case R.id.action_menu_status:
-                Intent intent = new Intent(this, StatusActivity.class);
-                startActivity(intent);
+                // TODO - remove when we want to re-add the status menu item
+                //Intent intent = new Intent(this, StatusActivity.class);
+                //startActivity(intent);
                 break;
         }
         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);
@@ -318,7 +426,22 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
 
     private void clearLogText() {
         statusMessageReceiver.clearMessages();
-        //mTextViewLog.setText("", BufferType.EDITABLE);
+    }
+
+    private void checkForUpdateNow() {
+        new AppUpdater(this)
+                .setUpdateFrom(UpdateFrom.JSON)
+                .setUpdateJSON("https://raw.githubusercontent.com/pazaan/600SeriesAndroidUploader/master/app/update.json")
+                .showAppUpdated(true) // Show a dialog, even if there isn't an update
+                .start();
+    }
+
+    private void checkForUpdateBackground(int checkEvery) {
+        new AppUpdater(this)
+                .setUpdateFrom(UpdateFrom.JSON)
+                .setUpdateJSON("https://raw.githubusercontent.com/pazaan/600SeriesAndroidUploader/master/app/update.json")
+                .showEvery(checkEvery) // Only check for an update every `checkEvery` invocations
+                .start();
     }
 
     private void startDisplayRefreshLoop() {
@@ -330,7 +453,19 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
     }
 
     private void startCgmService() {
-        startCgmService(System.currentTimeMillis() + 1000);
+        startCgmServiceDelayed(0);
+    }
+
+    private void startCgmServiceDelayed(long delay) {
+        if (!mRealm.isClosed()) {
+            RealmResults<PumpStatusEvent> results = mRealm.where(PumpStatusEvent.class)
+                    .findAllSorted("eventDate", Sort.DESCENDING);
+            if (results.size() > 0) {
+                startCgmService(getNextPoll(results.first()) + delay);
+                return;
+            }
+        }
+        startCgmService(System.currentTimeMillis() + (delay == 0 ? 1000 : delay));
     }
 
     private void startCgmService(long initialPoll) {
@@ -340,25 +475,17 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
             return;
         }
 
-        //clearLogText();
-
         // Cancel any existing polling.
         stopCgmService();
-        medtronicCnlAlarmReceiver.setAlarm(initialPoll);
-    }
-
-    private void uploadCgmData() {
-        startService(mNightscoutUploadService);
+        MedtronicCnlAlarmManager.setAlarm(initialPoll);
     }
 
     private void stopCgmService() {
         Log.i(TAG, "stopCgmService called");
-        medtronicCnlAlarmReceiver.cancelAlarm();
+        MedtronicCnlAlarmManager.cancelAlarm();
     }
 
     private void showDisconnectionNotification(String title, String message) {
-        int notifyId = 1;
-
         NotificationCompat.Builder mBuilder =
                 (NotificationCompat.Builder) new NotificationCompat.Builder(this)
                         .setPriority(NotificationCompat.PRIORITY_MAX)
@@ -383,8 +510,12 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
         mBuilder.setContentIntent(resultPendingIntent);
         NotificationManager mNotificationManager =
                 (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
-        // notifyId allows you to update the notification later on.
-        mNotificationManager.notify(notifyId, mBuilder.build());
+        mNotificationManager.notify(USB_DISCONNECT_NOFICATION_ID, mBuilder.build());
+    }
+
+    private void clearDisconnectionNotification() {
+        NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+        notificationManager.cancel(MainActivity.USB_DISCONNECT_NOFICATION_ID);
     }
 
     @Override
@@ -413,7 +544,21 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
                 mEnableCgmService = true;
                 startCgmService();
             }
-        } else if (key.equals("mmolxl")) {
+        } else if (key.equals("mmolxl") || key.equals("mmolDecimals")) {
+            configurationStore.setMmolxl(sharedPreferences.getBoolean("mmolxl", false));
+            configurationStore.setMmolxlDecimals(sharedPreferences.getBoolean("mmolDecimals", false));
+            refreshDisplay();
+        } else if (key.equals("pollInterval")) {
+            configurationStore.setPollInterval(Long.parseLong(sharedPreferences.getString("pollInterval",
+                    Long.toString(MedtronicCnlIntentService.POLL_PERIOD_MS))));
+        } else if (key.equals("lowBatPollInterval")) {
+            configurationStore.setLowBatteryPollInterval(Long.parseLong(sharedPreferences.getString("lowBatPollInterval",
+                    Long.toString(MedtronicCnlIntentService.LOW_BATTERY_POLL_PERIOD_MS))));
+        } else if (key.equals("doublePollOnPumpAway")) {
+            configurationStore.setReducePollOnPumpAway(sharedPreferences.getBoolean("doublePollOnPumpAway", false));
+        } else if (key.equals("chartZoom")) {
+            chartZoom = Integer.parseInt(sharedPreferences.getString("chartZoom", "3"));
+            hasZoomedChart = false;
             refreshDisplay();
         }
     }
@@ -434,77 +579,118 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
     }
 
     public void openUsbRegistration() {
-        if (hasDetectedCnl()) {
-            Intent loginIntent = new Intent(this, GetHmacAndKeyActivity.class);
-            startActivity(loginIntent);
-        }
-    }
-
-    private String renderTrendHtml(PumpStatusEvent.CGM_TREND trend) {
-        switch (trend) {
-            case DOUBLE_UP:
-                return "&#x21c8;";
-            case SINGLE_UP:
-                return "&#x2191;";
-            case FOURTY_FIVE_UP:
-                return "&#x2197;";
-            case FLAT:
-                return "&#x2192;";
-            case FOURTY_FIVE_DOWN:
-                return "&#x2198;";
-            case SINGLE_DOWN:
-                return "&#x2193;";
-            case DOUBLE_DOWN:
-                return "&#x21ca;";
-            default:
-                return "&mdash;";
-        }
+        Intent manageCNLIntent = new Intent(this, ManageCNLActivity.class);
+        startActivity(manageCNLIntent);
     }
 
     private PumpInfo getActivePump() {
+        long activePumpMac = dataStore.getActivePumpMac();
         if (activePumpMac != 0L && (mActivePump == null || !mActivePump.isValid() || mActivePump.getPumpMac() != activePumpMac)) {
-            mActivePump = null;
+            if (mActivePump != null) {
+                // remove listener on old pump
+                mActivePump.removeAllChangeListeners();
+                mActivePump = null;
+            }
 
             PumpInfo pump = mRealm
                     .where(PumpInfo.class)
-                    .equalTo("pumpMac", MainActivity.activePumpMac)
+                    .equalTo("pumpMac", activePumpMac)
                     .findFirst();
 
             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();
+                    }
+                });
             }
         }
 
         return mActivePump;
     }
 
+
+    public static String strFormatSGV(double sgvValue) {
+        ConfigurationStore configurationStore = ConfigurationStore.getInstance();
+
+        NumberFormat sgvFormatter;
+        if (configurationStore.isMmolxl()) {
+            if (configurationStore.isMmolxlDecimals()) {
+                sgvFormatter = new DecimalFormat("0.00");
+            } else {
+                sgvFormatter = new DecimalFormat("0.0");
+            }
+            return sgvFormatter.format(sgvValue / MMOLXLFACTOR);
+        } else {
+            sgvFormatter = new DecimalFormat("0");
+            return sgvFormatter.format(sgvValue);
+        }
+    }
+
+    public static String renderTrendSymbol(PumpStatusEvent.CGM_TREND trend) {
+        switch (trend) {
+            case DOUBLE_UP:
+                return "\u21c8";
+            case SINGLE_UP:
+                return "\u2191";
+            case FOURTY_FIVE_UP:
+                return "\u2197";
+            case FLAT:
+                return "\u2192";
+            case FOURTY_FIVE_DOWN:
+                return "\u2198";
+            case SINGLE_DOWN:
+                return "\u2193";
+            case DOUBLE_DOWN:
+                return "\u21ca";
+            default:
+                return "\u2014";
+        }
+    }
+
     private class StatusMessageReceiver extends BroadcastReceiver {
         private class StatusMessage {
             private long timestamp;
             private String message;
 
-            public StatusMessage(String message) {
+            StatusMessage(String message) {
                 this(System.currentTimeMillis(), message);
             }
 
-            public StatusMessage(long timestamp, String message) {
-                this.timestamp = timestamp;
-                this.message = message;
-            }
-
-            public long getTimestamp() {
-                return timestamp;
-            }
-
-            public void setTimestamp(long timestamp) {
+            StatusMessage(long timestamp, String message) {
                 this.timestamp = timestamp;
-            }
-
-            public String getMessage() {
-                return message;
-            }
-
-            public void setMessage(String message) {
                 this.message = message;
             }
 
@@ -513,7 +699,7 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
             }
         }
 
-        private Queue<StatusMessage> messages = new ArrayBlockingQueue<>(10);
+        private final Queue<StatusMessage> messages = new ArrayBlockingQueue<>(400);
 
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -521,7 +707,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));
@@ -549,11 +735,10 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
     private class RefreshDisplayRunnable implements Runnable {
         @Override
         public void run() {
-            // UI elements - TODO do these need to be members?
             TextView textViewBg = (TextView) findViewById(R.id.textview_bg);
             TextView textViewBgTime = (TextView) findViewById(R.id.textview_bg_time);
             TextView textViewUnits = (TextView) findViewById(R.id.textview_units);
-            if (prefs.getBoolean("mmolxl", false)) {
+            if (configurationStore.isMmolxl()) {
                 textViewUnits.setText(R.string.text_unit_mmolxl);
             } else {
                 textViewUnits.setText(R.string.text_unit_mgxdl);
@@ -564,130 +749,180 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
             // Get the most recently written CGM record for the active pump.
             PumpStatusEvent pumpStatusData = null;
 
-            PumpInfo pump = getActivePump();
-
-            if (pump != null && pump.isValid()) {
-                pumpStatusData = pump.getPumpHistory().last();
+            if (dataStore.getLastPumpStatus().getEventDate().getTime() > 0) {
+                pumpStatusData = dataStore.getLastPumpStatus();
             }
 
-            // FIXME - grab the last item from the activePump's getPumpHistory
-            RealmResults<PumpStatusEvent> results =
-                    mRealm.where(PumpStatusEvent.class)
-                            .findAllSorted("eventDate", Sort.ASCENDING);
-
-            if (pumpStatusData == null) {
-                return;
-            }
-
-            DecimalFormat df;
-            if (prefs.getBoolean("mmolDecimals", false))
-                df = new DecimalFormat("0.00");
-            else
-                df = new DecimalFormat("0.0");
+            updateChart(mRealm.where(PumpStatusEvent.class)
+                    .greaterThan("sgvDate", new Date(System.currentTimeMillis() - 1000 * 60 * 60 * 24))
+                    .findAllSorted("sgvDate", Sort.ASCENDING));
+
+            if (pumpStatusData != null) {
+                String sgvString;
+                if (pumpStatusData.isCgmActive()) {
+                    sgvString = MainActivity.strFormatSGV(pumpStatusData.getSgv());
+                    if (configurationStore.isMmolxl()) {
+                        Log.d(TAG, sgvString + " mmol/L");
+                    } else {
+                        Log.d(TAG, sgvString + " mg/dL");
+                    }
+                } else {
+                    sgvString = "\u2014"; // &mdash;
+                }
 
-            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.getSgvDate().getTime()));
+
+                textViewTrend.setText(MainActivity.renderTrendSymbol(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);
+            mChart.getGridLabelRenderer().setNumHorizontalLabels(6);
 
-            ArrayList<ILineDataSet> dataSets = new ArrayList<ILineDataSet>();
-            dataSets.add(lineDataSet);
+            int size = results.size();
+            if (size == 0) {
+                final long now = System.currentTimeMillis(),
+                        left = now - chartZoom * 60 * 60 * 1000;
 
-            LineData lineData = new LineData(dataSets);
+                mChart.getViewport().setXAxisBoundsManual(true);
+                mChart.getViewport().setMaxX(now);
+                mChart.getViewport().setMinX(left);
 
-            // set data
-            mChart.setMinimumHeight(200);
-            mChart.setData(lineData);
-        }
-    }
-
-    private class RefreshDataReceiver extends BroadcastReceiver {
+                mChart.getViewport().setYAxisBoundsManual(true);
+                mChart.getViewport().setMinY(80);
+                mChart.getViewport().setMaxY(120);
 
-        @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.postInvalidate();
                 return;
             }
 
-            PumpStatusEvent pumpStatusData = null;
-
-            PumpInfo pump = getActivePump();
+            DataPoint[] entries = new DataPoint[size];
 
-            if (pump != null && pump.isValid()) {
-                pumpStatusData = pump.getPumpHistory().last();
-            } else {
-                return;
+            int pos = 0;
+            for (PumpStatusEvent pumpStatus : results) {
+                // turn your data into Entry objects
+                entries[pos++] = new DataPoint(pumpStatus.getSgvDate(), (double) pumpStatus.getSgv());
             }
 
-            long nextPoll = pumpStatusData.getEventDate().getTime() + pumpStatusData.getPumpTimeOffset()
-                    + MedtronicCnlIntentService.POLL_GRACE_PERIOD_MS + MedtronicCnlIntentService.POLL_PERIOD_MS;
-            startCgmService(nextPoll);
+            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);
 
-            // 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();
+                PointsGraphSeries sgvSerie = new PointsGraphSeries(entries);
+//                sgvSerie.setSize(3.6f);
+//                sgvSerie.setColor(Color.LTGRAY);
 
-            if (results.size() > 0) {
-                mRealm.executeTransaction(new Realm.Transaction() {
+
+                sgvSerie.setOnDataPointTapListener(new OnDataPointTapListener() {
+                    DateFormat mFormat = DateFormat.getTimeInstance(DateFormat.MEDIUM);
+
+                    @Override
+                    public void onTap(Series series, DataPointInterface dataPoint) {
+                        double sgv = dataPoint.getY();
+
+                        StringBuilder sb = new StringBuilder(mFormat.format(new Date((long) dataPoint.getX())) + ": ");
+                        sb.append(MainActivity.strFormatSGV(sgv));
+                        Toast.makeText(getBaseContext(), sb.toString(), Toast.LENGTH_SHORT).show();
+                    }
+                });
+
+                sgvSerie.setCustomShape(new PointsGraphSeries.CustomShape() {
                     @Override
-                    public void execute(Realm realm) {
-                        // Delete all matches
-                        Log.d(TAG, "Deleting " + results.size() + " records from realm");
-                        results.deleteAllFromRealm();
+                    public void draw(Canvas canvas, Paint paint, float x, float y, DataPointInterface dataPoint) {
+                        double sgv = dataPoint.getY();
+                        if (sgv < 80)
+                            paint.setColor(Color.RED);
+                        else if (sgv <= 180)
+                            paint.setColor(Color.GREEN);
+                        else if (sgv <= 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 viewport 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();
         }
     }
 
     private class UsbReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
+            // TODO move this somewhere else ... wherever it belongs
+            // realm might be closed ... sometimes occurs when USB is disconnected and replugged ...
+            if (mRealm.isClosed()) mRealm = Realm.getDefaultInstance();
             String action = intent.getAction();
             if (MedtronicCnlIntentService.Constants.ACTION_USB_PERMISSION.equals(action)) {
                 boolean permissionGranted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);
@@ -700,10 +935,13 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
                 }
             } else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
                 Log.d(TAG, "USB plugged in");
+                if (mEnableCgmService) {
+                    clearDisconnectionNotification();
+                }
 
                 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();
@@ -728,8 +966,9 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc
             if (arg1.getAction().equalsIgnoreCase(Intent.ACTION_BATTERY_LOW)
                     || arg1.getAction().equalsIgnoreCase(Intent.ACTION_BATTERY_CHANGED)
                     || arg1.getAction().equalsIgnoreCase(Intent.ACTION_BATTERY_OKAY)) {
-                batLevel = arg1.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
+                dataStore.setUploaderBatteryLevel(arg1.getIntExtra(BatteryManager.EXTRA_LEVEL, 0));
             }
         }
     }
+
 }
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..78d55479c0627ff3ebf470d7e5e01700bfe6dfe4
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/ManageCNLActivity.java
@@ -0,0 +1,154 @@
+package info.nightscout.android.medtronic;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.os.Bundle;
+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<>();
+
+        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.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));
+        lView.setAdapter(adapter);
+    }
+
+    @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, parent, false);
+            }
+
+            //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..9c2080c5b70aab426520174927c1b0a67b6b9cfe 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();
-        }
-
-        if (message instanceof MedtronicMessage) {
-            mPumpSession.incrMedtronicSequenceNumber();
-        }
-    }
-
-    @Override
-    public ContourNextLinkMessage receiveMessage() {
-        return null;
-    }
-
-    public void sendMessage(byte[] message) throws IOException {
+    public void requestDeviceInfo()
+            throws IOException, TimeoutException, UnexpectedMessageException, ChecksumException, EncryptionException {
+        DeviceInfoResponseCommandMessage response = new DeviceInfoRequestCommandMessage().send(mDevice);
 
-        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 - extract more details form the device info.
+        mStickSerial = response.getSerial();
     }
 
-    // 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;
+    public void enterControlMode() throws IOException, TimeoutException, UnexpectedMessageException, ChecksumException, EncryptionException {
+        boolean doRetry;
 
-        // 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();
+        RequestLinkKeyResponseMessage response = new RequestLinkKeyRequestMessage(mPumpSession).send(mDevice);
+        this.getPumpSession().setKey(response.getKey());
 
-        this.getPumpSession().setPackedLinkKey(packedLinkKey);
-
-        Log.d(TAG, String.format("Finished requestLinkKey. linkKey = '%s'", this.getPumpSession().getKey()));
+        Log.d(TAG, String.format("Finished requestLinkKey. linkKey = '%s'", (Object) 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) {
@@ -293,6 +141,7 @@ public class MedtronicCnlReader implements ContourNextLinkMessageHandler {
 
             if (lastChannel != null) {
                 radioChannels.add(0, lastChannel);
+                radioChannels.add(5, lastChannel);  // retry last used channel again, this allows for transient noise if missed on first attempt when pump is in range
             }
         }
 
@@ -300,26 +149,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 +164,77 @@ 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());
+        PumpTimeResponseMessage response = new PumpTimeRequestMessage(mPumpSession).send(mDevice);
 
-        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();
-        }
-
-        // 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);
-
-        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)
+    public PumpStatusEvent updatePumpStatus(PumpStatusEvent pumpRecord) throws IOException, EncryptionException, ChecksumException, TimeoutException, UnexpectedMessageException {
+        Log.d(TAG, "Begin updatePumpStatus");
 
-        new PumpStatusRequestMessage(mPumpSession).send(this);
-        // Read the 0x81
-        readMessage();
+        PumpStatusResponseMessage response = new PumpStatusRequestMessage(mPumpSession).send(mDevice);
+        response.updatePumpRecord(pumpRecord);
 
-        // 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;
-        }
-
-        // 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)));
-        }
-        // 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);
-
-        // Recent Bolus Wizard BGL
-        pumpRecord.setRecentBolusWizard(statusBuffer.get(0x48) != 0);
-        pumpRecord.setBolusWizardBGL(statusBuffer.getShort(0x49) & 0x0000ffff); // In mg/DL
-
-        Log.d(TAG, "Finished getPumpStatus");
+        Log.d(TAG, "Finished updatePumpStatus");
+        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);
+
+        Log.d(TAG, "Finished getBasalPatterns");
+    }
 
-        // Read the 0x80
-        ContourNextLinkMessage response = PumpBasalPatternResponseMessage.fromBytes(mPumpSession, readMessage());
 
-        // 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;
-        }
-        */
+    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)
 
-        // FIXME - this needs to go into PumpBasalPatternResponseMessage
-        ByteBuffer basalRatesBuffer = ByteBuffer.allocate(96);
-        basalRatesBuffer.order(ByteOrder.BIG_ENDIAN);
-        basalRatesBuffer.put(response.encode(), 0x39, 96);
+        ReadHistoryInfoResponseMessage response = new ReadHistoryInfoRequestMessage(mPumpSession).send(mDevice);
 
-        Log.d(TAG, "Finished getBasalPatterns");
+        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..918e794d9e3388d3c565561f1e9a77af519c38cf 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,12 +19,10 @@ public class MedtronicCnlSession {
     private long pumpMAC;
 
     private byte radioChannel;
-    private int bayerSequenceNumber = 1;
-    private int medtronicSequenceNumber = 1;
+    private byte radioRSSI;
 
-    /*public byte[] getHMAC() {
-        return HMAC;
-    }*/
+    private int cnlSequenceNumber = 1;
+    private int medtronicSequenceNumber = 1;
 
     public byte[] getHMAC() throws NoSuchAlgorithmException {
         String shortSerial = this.stickSerial.replaceAll("\\d+-", "");
@@ -68,8 +65,8 @@ public class MedtronicCnlSession {
         this.pumpMAC = pumpMAC;
     }
 
-    public int getBayerSequenceNumber() {
-        return bayerSequenceNumber;
+    public int getCnlSequenceNumber() {
+        return cnlSequenceNumber;
     }
 
     public int getMedtronicSequenceNumber() {
@@ -80,8 +77,16 @@ public class MedtronicCnlSession {
         return radioChannel;
     }
 
-    public void incrBayerSequenceNumber() {
-        bayerSequenceNumber++;
+    public byte getRadioRSSI() {
+        return radioRSSI;
+    }
+
+    public int getRadioRSSIpercentage() {
+        return (((int) radioRSSI & 0x00FF) * 100) / 0xA8;
+    }
+
+    public void incrCnlSequenceNumber() {
+        cnlSequenceNumber++;
     }
 
     public void incrMedtronicSequenceNumber() {
@@ -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..7a2d11bb6045345401faefb8eca06641ce6faae7 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,14 @@
 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 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..abf23486fbd627c1bd271301f14584f17a507cb9
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/ChannelNegotiateRequestMessage.java
@@ -0,0 +1,57 @@
+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");
+
+        return this.getResponse(readMessage(mDevice));
+    }
+
+    @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..711728a5978a6e1938adc3b8719fcf29f4200e0d
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/CloseConnectionRequestMessage.java
@@ -0,0 +1,42 @@
+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 10.12.2016.
+ */
+
+public class CloseConnectionRequestMessage extends ContourNextLinkBinaryRequestMessage<CloseConnectionResponseMessage> {
+    public CloseConnectionRequestMessage(MedtronicCnlSession pumpSession, byte[] payload) throws ChecksumException {
+        super(CommandType.CLOSE_CONNECTION, pumpSession, payload);
+    }
+
+    @Override
+    public CloseConnectionResponseMessage send(UsbHidDriver mDevice, int millis) throws IOException, TimeoutException, ChecksumException, EncryptionException, UnexpectedMessageException {
+
+        // clear unexpected incoming messages
+        clearMessage(mDevice);
+
+        sendMessage(mDevice);
+        if (millis > 0) {
+            try {
+                Thread.sleep(millis);
+            } catch (InterruptedException e) {
+            }
+        }
+
+        return this.getResponse(readMessage(mDevice));
+    }
+
+    @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..7f189e33fd89431d2b4bd6c89269fe1b12801698
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/CloseConnectionResponseMessage.java
@@ -0,0 +1,14 @@
+package info.nightscout.android.medtronic.message;
+
+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 55%
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..fadfec17187f3a7ade746ea7d48473471a8208e8 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.incrCnlSequenceNumber();
     }
 
     protected static byte[] buildPayload(CommandType commandType, MedtronicCnlSession pumpSession, byte[] payload) {
@@ -52,11 +54,11 @@ public class ContourNextLinkBinaryMessage extends ContourNextLinkMessage{
 
         payloadBuffer.put((byte) 0x51);
         payloadBuffer.put((byte) 0x3);
-        payloadBuffer.put("000000".getBytes()); // Text of PumpInfo serial, but 000000 for 640g
+        payloadBuffer.put("000000".getBytes()); // Text of PumpInfo serial, but 000000 for 600 Series pumps
         byte[] unknownBytes = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
         payloadBuffer.put(unknownBytes);
-        payloadBuffer.put(commandType.value);
-        payloadBuffer.putInt(pumpSession.getBayerSequenceNumber());
+        payloadBuffer.put(commandType.getValue());
+        payloadBuffer.putInt(pumpSession.getCnlSequenceNumber());
         byte[] unknownBytes2 = {0, 0, 0, 0, 0};
         payloadBuffer.put(unknownBytes2);
         payloadBuffer.putInt(payloadLength);
@@ -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..7a948f02fe07a27c2ef6c386ae62a172686a1197 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,15 @@
 package info.nightscout.android.medtronic.message;
 
+import info.nightscout.android.medtronic.exception.ChecksumException;
+
 /**
  * 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 +17,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..0cbe120e2b998dd89680d16f0c5087b6f53e5dc8 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,213 @@
 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.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 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(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(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..681bd909cc388bfe2671bd809d7e3f18c0cbfd95
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkRequestMessage.java
@@ -0,0 +1,45 @@
+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) {
+            }
+        }
+
+        // FIXME - We need to care what the response message is - wrong MAC and all that
+        return this.getResponse(readMessage(mDevice));
+    }
+
+    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..614a2c4f33dba239941183fda7557d21932353b8
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkResponseMessage.java
@@ -0,0 +1,28 @@
+package info.nightscout.android.medtronic.message;
+
+import java.util.Locale;
+
+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..bb681200de437f12f126839a11379b2e130b3c3e 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,14 @@
 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 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 +16,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/MessageUtils.java b/app/src/main/java/info/nightscout/android/medtronic/message/MessageUtils.java
index 5b800808e7ab294f9f115f66589d971ac8dffa32..f5a80e07d2fb8bb3263f92683155c82affc48241 100644
--- a/app/src/main/java/info/nightscout/android/medtronic/message/MessageUtils.java
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/MessageUtils.java
@@ -66,7 +66,6 @@ public class MessageUtils {
         // However, the time the pump *means* is Fri, 13 May 2016 21:07:48 in our own timezone
         long offsetFromUTC = currentTz.getOffset(Calendar.getInstance().getTimeInMillis());
 
-        Date pumpDate = new Date((( baseTime + rtc + offset ) * 1000 ) - offsetFromUTC );
-        return pumpDate;
+        return new Date((( baseTime + rtc + offset ) * 1000 ) - offsetFromUTC );
     }
 }
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..808324743c26b2f71255e00bd48c59b1247356d5
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/OpenConnectionRequestMessage.java
@@ -0,0 +1,20 @@
+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 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..d20c421e301c92e6903720b03b51badc367d45d2
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/OpenConnectionResponseMessage.java
@@ -0,0 +1,14 @@
+package info.nightscout.android.medtronic.message;
+
+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..18be6188ba743957c83cfd328d4e5d5e0e276033 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,22 @@
 package info.nightscout.android.medtronic.message;
 
+import java.io.IOException;
+
 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..6fbe8768dadc0ece6003a59b3cdad7dcc88d32a6 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,56 @@
 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) {
+            }
+        }
+        // Read the 0x80
+        byte[] payload = readMessage(mDevice);
+
+        // clear unexpected incoming messages
+        clearMessage(mDevice);
+
+        return this.getResponse(payload);
+    }
+
+    @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..6a70ee7ed1b6d3248102202e43f49d88e26bca86 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 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.DataStore;
+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.
+
+    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
+        long rtc;
+        long offset;
+        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
+        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 data
+        pumpRecord.setSgv(sgv);
+        pumpRecord.setSgvDate(new Date(sgvDate.getTime() - pumpRecord.getPumpTimeOffset()));
+        pumpRecord.setCgmTrend(cgmTrend);
 
-        // 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);
+        // there is a BolusWizard usage & the IOB increased
+        if (activeInsulin > DataStore.getInstance().getLastPumpStatus().getActiveInsulin()) {
+            pumpRecord.setBolusWizardBGL(bolusWizardBGL); // In mg/DL
+        } else {
+            pumpRecord.setBolusWizardBGL(0); // 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..7332a48e0c88c609cd9f2db4a7687b6f6c6195d3 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
+        byte[] payload = readMessage(mDevice);
+
+        // Pump sends additional 0x80 message when not using EHSM, lets clear this and any unexpected incoming messages
+        clearMessage(mDevice);
+
+        return this.getResponse(payload);
+    }
+
+    @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..c64ba271f7cf9b10a53fa5f125f980b0b062423a
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/ReadHistoryInfoRequestMessage.java
@@ -0,0 +1,35 @@
+package info.nightscout.android.medtronic.message;
+
+import java.io.IOException;
+
+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..01c63ce23d0263e2ba3ff4ed30f313eb441362ea
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/ReadInfoRequestMessage.java
@@ -0,0 +1,22 @@
+package info.nightscout.android.medtronic.message;
+
+import java.io.IOException;
+
+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..1814fe87fa8df4322fdeead228477434986ca466
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/RequestLinkKeyRequestMessage.java
@@ -0,0 +1,20 @@
+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 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..b58d702ce647bf2c1014af714954c15107661788
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/service/MedtronicCnlAlarmManager.java
@@ -0,0 +1,81 @@
+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.util.Log;
+
+import java.util.Date;
+
+import info.nightscout.android.utils.ConfigurationStore;
+
+/**
+ * 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;
+
+    private static PendingIntent pendingIntent = null;
+    private static AlarmManager alarmManager = null;
+
+    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);
+    }
+
+    /**
+     * 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 onReceive
+    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;
+
+        cancelAlarm();
+
+        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 and 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(ConfigurationStore.getInstance().getPollInterval()); // 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..ad908713062d88a4c902ab0f933e69354ffd88fe 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,10 +1,7 @@
 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;
 
@@ -17,8 +14,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 +24,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();
+        MedtronicCnlAlarmManager.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);
-    }
-
-    // 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..1ef2deaa0780b4b08c679af3365a0c1009c7cf32 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,19 +20,23 @@ 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;
 import info.nightscout.android.upload.nightscout.NightscoutUploadReceiver;
+import info.nightscout.android.utils.ConfigurationStore;
+import info.nightscout.android.utils.DataStore;
 import info.nightscout.android.xdrip_plus.XDripPlusUploadReceiver;
 import io.realm.Realm;
 import io.realm.RealmResults;
@@ -42,13 +46,19 @@ 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();
+
     private UsbHidDriver mHidDevice;
     private Context mContext;
     private NotificationManagerCompat nm;
     private UsbManager mUsbManager;
+    private DataStore dataStore = DataStore.getInstance();
+    private ConfigurationStore configurationStore = ConfigurationStore.getInstance();
+    private DateFormat dateFormatter = new SimpleDateFormat("HH:mm:ss", Locale.US);
+
 
     public MedtronicCnlIntentService() {
         super(MedtronicCnlIntentService.class.getName());
@@ -96,195 +106,284 @@ public class MedtronicCnlIntentService extends IntentService {
 
     protected void onHandleIntent(Intent intent) {
         Log.d(TAG, "onHandleIntent called");
+        try {
+            // TODO use of ConfigurationStore is confusinng if pollInterval uses the CS, which
+            // uses the POLL_PERIOD_MS, while the latter constant is also used directly.
+
+            // Note that the variable pollInterval refers to the poll we'd like to make to the pump,
+            // based on settings and battery level, while POLL_PERIOD_MS is used to calculate
+            // when the pump is going to poll data from the transmitter again.
+            // Thus POLL_PERIOD_MS is important to calculate times we'd be clashing with transmitter
+            // to pump transmissions, which are then checked against the time the uploader would
+            // like to poll, which is calculated using the pollInterval variable.
+            // TODO find better variable names to make this distinction clearer and/or if possible
+            // do more method extraction refactorings to make this method easier to grasp
+
+            final long timePollStarted = System.currentTimeMillis();
+            final long timeLastGoodSGV = dataStore.getLastPumpStatus().getSgvDate().getTime();
+
+            final long timePollExpected;
+            if (timeLastGoodSGV != 0) {
+                timePollExpected = timeLastGoodSGV + POLL_PERIOD_MS + POLL_GRACE_PERIOD_MS + (POLL_PERIOD_MS * ((timePollStarted - 1000L - (timeLastGoodSGV + POLL_GRACE_PERIOD_MS)) / POLL_PERIOD_MS));
+            } else {
+                timePollExpected = timePollStarted;
+            }
 
-        if (!hasUsbHostFeature()) {
-            sendStatus("It appears that this device doesn't support USB OTG.");
-            Log.e(TAG, "Device does not support USB OTG");
-            MedtronicCnlAlarmReceiver.completeWakefulIntent(intent);
-            // TODO - throw, don't return
-            return;
-        }
+            // avoid polling when too close to sensor-pump comms
+            if (((timePollExpected - timePollStarted) > 5000L) && ((timePollExpected - timePollStarted) < (POLL_GRACE_PERIOD_MS + 45000L))) {
+                sendStatus("Please wait: Poll due in " + ((timePollExpected - timePollStarted) / 1000L) + " seconds");
+                MedtronicCnlAlarmManager.setAlarm(timePollExpected);
+                return;
+            }
 
-        UsbDevice cnlStick = UsbHidDriver.getUsbDevice(mUsbManager, USB_VID, USB_PID);
-        if (cnlStick == null) {
-            sendStatus("USB connection error. Is the Bayer Contour Next Link plugged in?");
-            Log.w(TAG, "USB connection error. Is the CNL plugged in?");
+            final short pumpBatteryLevel = dataStore.getLastPumpStatus().getBatteryPercentage();
+            long pollInterval = configurationStore.getPollInterval();
+            if ((pumpBatteryLevel > 0) && (pumpBatteryLevel <= 25)) {
+                pollInterval = configurationStore.getLowBatteryPollInterval();
+            }
 
-            // TODO - set status if offline or Nightscout not reachable
-            uploadToNightscout();
-            MedtronicCnlAlarmReceiver.completeWakefulIntent(intent);
             // TODO - throw, don't return
-            return;
-        }
+            if (!openUsbDevice())
+                return;
 
-        if (!mUsbManager.hasPermission(UsbHidDriver.getUsbDevice(mUsbManager, USB_VID, USB_PID))) {
-            sendMessage(Constants.ACTION_NO_USB_PERMISSION);
-            MedtronicCnlAlarmReceiver.completeWakefulIntent(intent);
-            // TODO - throw, don't return
-            return;
-        }
-        mHidDevice = UsbHidDriver.acquire(mUsbManager, cnlStick);
+            MedtronicCnlReader cnlReader = new MedtronicCnlReader(mHidDevice);
 
-        try {
-            mHidDevice.open();
-        } catch (Exception e) {
-            Log.e(TAG, "Unable to open serial device", e);
-            MedtronicCnlAlarmReceiver.completeWakefulIntent(intent);
-            // TODO - throw, don't return
-            return;
-        }
+            Realm realm = Realm.getDefaultInstance();
+            realm.beginTransaction();
 
-        MedtronicCnlReader cnlReader = new MedtronicCnlReader(mHidDevice);
+            try {
+                sendStatus("Connecting to Contour Next Link");
+                Log.d(TAG, "Connecting to Contour Next Link");
+                cnlReader.requestDeviceInfo();
+
+                // Is the device already configured?
+                ContourNextLinkInfo info = realm
+                        .where(ContourNextLinkInfo.class)
+                        .equalTo("serialNumber", cnlReader.getStickSerial())
+                        .findFirst();
 
-        Realm realm = Realm.getDefaultInstance();
-        realm.beginTransaction();
+                if (info == null) {
+                    info = realm.createObject(ContourNextLinkInfo.class, cnlReader.getStickSerial());
+                }
 
-        try {
-            sendStatus("Connecting to the Contour Next Link...");
-            Log.d(TAG, "Connecting to the Contour Next Link.");
-            cnlReader.requestDeviceInfo();
-
-            // Is the device already configured?
-            ContourNextLinkInfo info = realm
-                    .where(ContourNextLinkInfo.class)
-                    .equalTo("serialNumber", cnlReader.getStickSerial())
-                    .findFirst();
-
-            if (info == null) {
-                // TODO - use realm.createObject()?
-                info = new ContourNextLinkInfo();
-                info.setSerialNumber(cnlReader.getStickSerial());
-
-                info = realm.copyToRealm(info);
-            }
+                cnlReader.getPumpSession().setStickSerial(info.getSerialNumber());
 
-            cnlReader.getPumpSession().setStickSerial(info.getSerialNumber());
+                cnlReader.enterControlMode();
 
-            cnlReader.enterControlMode();
+                try {
+                    cnlReader.enterPassthroughMode();
+                    cnlReader.openConnection();
 
-            try {
-                cnlReader.enterPassthroughMode();
-                cnlReader.openConnection();
-                cnlReader.requestReadInfo();
+                    cnlReader.requestReadInfo();
 
-                String key = info.getKey();
+                    String key = info.getKey();
 
-                if (key == null) {
-                    cnlReader.requestLinkKey();
+                    if (key == null) {
+                        cnlReader.requestLinkKey();
 
-                    info.setKey(MessageUtils.byteArrayToHexString(cnlReader.getPumpSession().getKey()));
-                    key = info.getKey();
-                }
+                        info.setKey(MessageUtils.byteArrayToHexString(cnlReader.getPumpSession().getKey()));
+                        key = info.getKey();
+                    }
 
-                cnlReader.getPumpSession().setKey(MessageUtils.hexStringToByteArray(key));
+                    cnlReader.getPumpSession().setKey(MessageUtils.hexStringToByteArray(key));
 
-                long pumpMAC = cnlReader.getPumpSession().getPumpMAC();
-                Log.i(TAG, "PumpInfo MAC: " + (pumpMAC & 0xffffff));
-                MainActivity.setActivePumpMac(pumpMAC);
-                PumpInfo activePump = realm
-                        .where(PumpInfo.class)
-                        .equalTo("pumpMac", pumpMAC)
-                        .findFirst();
+                    long pumpMAC = cnlReader.getPumpSession().getPumpMAC();
+                    Log.i(TAG, "PumpInfo MAC: " + (pumpMAC & 0xffffff));
+                    PumpInfo activePump = realm
+                            .where(PumpInfo.class)
+                            .equalTo("pumpMac", pumpMAC)
+                            .findFirst();
 
-                if (activePump == null) {
-                    activePump = realm.createObject(PumpInfo.class);
-                    activePump.setPumpMac(pumpMAC);
-                }
+                    if (activePump == null) {
+                        activePump = realm.createObject(PumpInfo.class, pumpMAC);
+                    }
 
-                byte radioChannel = cnlReader.negotiateChannel(activePump.getLastRadioChannel());
-                if (radioChannel == 0) {
-                    sendStatus("Could not communicate with the 640g. Are you near the pump?");
-                    Log.i(TAG, "Could not communicate with the 640g. Are you near the pump?");
-                } else {
-                    activePump.setLastRadioChannel(radioChannel);
-                    sendStatus(String.format(Locale.getDefault(), "Connected to Contour Next Link on channel %d.", (int) radioChannel));
-                    Log.d(TAG, String.format("Connected to Contour Next Link on channel %d.", (int) radioChannel));
-                    cnlReader.beginEHSMSession();
+                    activePump.updateLastQueryTS();
 
-                    PumpStatusEvent pumpRecord = realm.createObject(PumpStatusEvent.class);
+                    byte radioChannel = cnlReader.negotiateChannel(activePump.getLastRadioChannel());
+                    if (radioChannel == 0) {
+                        sendStatus("Could not communicate with the pump. Is it nearby?");
+                        Log.i(TAG, "Could not communicate with the pump. Is it nearby?");
+                        pollInterval = configurationStore.getPollInterval() / (configurationStore.isReducePollOnPumpAway() ? 2L : 1L); // reduce polling interval to half until pump is available
+                    } else if (cnlReader.getPumpSession().getRadioRSSIpercentage() < 5) {
+                        sendStatus(String.format(Locale.getDefault(), "Connected on channel %d  RSSI: %d%%", (int) radioChannel, cnlReader.getPumpSession().getRadioRSSIpercentage()));
+                        sendStatus("Warning: pump signal too weak. Is it nearby?");
+                        Log.i(TAG, "Warning: pump signal too weak. Is it nearby?");
+                        pollInterval = configurationStore.getPollInterval() / (configurationStore.isReducePollOnPumpAway() ? 2L : 1L); // reduce polling interval to half until pump is available
+                    } else {
+                        dataStore.setActivePumpMac(pumpMAC);
 
-                    String deviceName = String.format("medtronic-640g://%s", cnlReader.getStickSerial());
-                    activePump.setDeviceName(deviceName);
+                        activePump.setLastRadioChannel(radioChannel);
+                        sendStatus(String.format(Locale.getDefault(), "Connected on channel %d  RSSI: %d%%", (int) radioChannel, cnlReader.getPumpSession().getRadioRSSIpercentage()));
+                        Log.d(TAG, String.format("Connected to Contour Next Link on channel %d.", (int) radioChannel));
 
-                    // TODO - this should not be necessary. We should reverse lookup the device name from PumpInfo
-                    pumpRecord.setDeviceName(deviceName);
+                        // read pump status
+                        PumpStatusEvent pumpRecord = realm.createObject(PumpStatusEvent.class);
 
-                    long pumpTime = cnlReader.getPumpTime().getTime();
-                    long pumpOffset = pumpTime - System.currentTimeMillis();
-                    Log.d(TAG, "Time offset between pump and device: " + pumpOffset + " millis.");
+                        String deviceName = String.format("medtronic-600://%s", cnlReader.getStickSerial());
+                        activePump.setDeviceName(deviceName);
 
-                    // 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);
+                        // TODO - this should not be necessary. We should reverse lookup the device name from PumpInfo
+                        pumpRecord.setDeviceName(deviceName);
 
-                    cnlReader.endEHSMSession();
+                        long pumpTime = cnlReader.getPumpTime().getTime();
+                        long pumpOffset = pumpTime - System.currentTimeMillis();
+                        Log.d(TAG, "Time offset between pump and device: " + pumpOffset + " millis.");
+
+                        // TODO - send ACTION to MainActivity to show offset between pump and uploader.
+                        pumpRecord.setPumpTimeOffset(pumpOffset);
+                        pumpRecord.setPumpDate(new Date(pumpTime - pumpOffset));
+                        cnlReader.updatePumpStatus(pumpRecord);
 
-                    boolean cancelTransaction = true;
                     if (pumpRecord.getSgv() != 0) {
+                        String offsetSign = "";
+                        if (pumpOffset > 0) {
+                            offsetSign = "+";
+                        }
+                        sendStatus("SGV: " + MainActivity.strFormatSGV(pumpRecord.getSgv()) + "  At: " + dateFormatter.format(pumpRecord.getSgvDate().getTime()) + "  Pump: " + offsetSign + (pumpOffset / 1000L) + "sec");  //note: event time is currently stored with offset
+
+                        // Check if pump sent old event when new expected
+                        if (dataStore.getLastPumpStatus() != null &&
+                                dataStore.getLastPumpStatus().getSgvDate() != null &&
+                                pumpRecord.getSgvDate().getTime() - dataStore.getLastPumpStatus().getSgvDate().getTime() < 5000L &&
+                                timePollExpected - timePollStarted < 5000L) {
+                            sendStatus("Pump sent old SGV event");
+                        }
+
+                        dataStore.clearUnavailableSGVCount(); // reset unavailable sgv count
+
                         // Check that the record doesn't already exist before committing
                         RealmResults<PumpStatusEvent> checkExistingRecords = activePump.getPumpHistory()
                                 .where()
-                                .equalTo("eventDate", pumpRecord.getEventDate())
+                                .equalTo("sgvDate", pumpRecord.getSgvDate())
                                 .equalTo("sgv", pumpRecord.getSgv())
                                 .findAll();
 
                         // There should be the 1 record we've already added in this transaction.
-                        if (checkExistingRecords.size() <= 1) {
-                            realm.commitTransaction();
-                            cancelTransaction = false;
+                        if (checkExistingRecords.size() == 0) {
+                            activePump.getPumpHistory().add(pumpRecord);
+                            dataStore.setLastPumpStatus(pumpRecord);
                         }
 
+                    } else {
+                        sendStatus("SGV: unavailable from pump");
+                        dataStore.incUnavailableSGVCount(); // poll clash detection
+                    }
+
+                        realm.commitTransaction();
                         // Tell the Main Activity we have new data
-                        sendMessage(Constants.ACTION_REFRESH_DATA);
+                        sendMessage(Constants.ACTION_UPDATE_PUMP);
                     }
 
-                    if (cancelTransaction) {
-                        realm.cancelTransaction();
+                } catch (UnexpectedMessageException e) {
+                    Log.e(TAG, "Unexpected Message", e);
+                    sendStatus("Communication Error: " + e.getMessage());
+                    pollInterval = 60000L; // retry once during this poll period, this allows for transient radio noise
+                } catch (TimeoutException e) {
+                    Log.e(TAG, "Timeout communicating with the Contour Next Link.", e);
+                    sendStatus("Timeout communicating with the Contour Next Link.");
+                    pollInterval = 60000L; // retry once during this poll period, this allows for transient radio noise
+                } catch (NoSuchAlgorithmException e) {
+                    Log.e(TAG, "Could not determine CNL HMAC", e);
+                    sendStatus("Error connecting to Contour Next Link: Hashing error.");
+                } finally {
+                    try {
+                        cnlReader.closeConnection();
+                        cnlReader.endPassthroughMode();
+                        cnlReader.endControlMode();
+                    } catch (NoSuchAlgorithmException e) {
                     }
+
                 }
+            } catch (IOException e) {
+                Log.e(TAG, "Error connecting to Contour Next Link.", e);
+                sendStatus("Error connecting to Contour Next Link.");
+            } catch (ChecksumException e) {
+                Log.e(TAG, "Checksum error getting message from the Contour Next Link.", e);
+                sendStatus("Checksum error getting message from the Contour Next Link.");
+            } catch (EncryptionException e) {
+                Log.e(TAG, "Error decrypting messages from Contour Next Link.", e);
+                sendStatus("Error decrypting messages from Contour Next Link.");
+            } catch (TimeoutException e) {
+                Log.e(TAG, "Timeout communicating with the Contour Next Link.", e);
+                sendStatus("Timeout communicating with the Contour Next Link.");
             } catch (UnexpectedMessageException e) {
-                Log.e(TAG, "Unexpected Message", e);
-                sendStatus("Communication Error: " + e.getMessage());
-            } catch (NoSuchAlgorithmException e) {
-                Log.e(TAG, "Could not determine CNL HMAC", e);
-                sendStatus("Error connecting to Contour Next Link: Hashing error.");
+                Log.e(TAG, "Could not close connection.", e);
+                sendStatus("Could not close connection: " + e.getMessage());
             } finally {
-                //TODO : 05.11.2016 has the close to be here?
-                cnlReader.closeConnection();
-                cnlReader.endPassthroughMode();
-                cnlReader.endControlMode();
+                if (!realm.isClosed()) {
+                    if (realm.isInTransaction()) {
+                        // If we didn't commit the transaction, we've run into an error. Let's roll it back
+                        realm.cancelTransaction();
+                    }
+                    realm.close();
+                }
+
+                uploadPollResults();
+                scheduleNextPoll(timePollStarted, timeLastGoodSGV, pollInterval);
             }
-        } catch (IOException e) {
-            Log.e(TAG, "Error connecting to Contour Next Link.", e);
-            sendStatus("Error connecting to Contour Next Link.");
-        } catch (ChecksumException e) {
-            Log.e(TAG, "Checksum error getting message from the Contour Next Link.", e);
-            sendStatus("Checksum error getting message from the Contour Next Link.");
-        } catch (EncryptionException e) {
-            Log.e(TAG, "Error decrypting messages from Contour Next Link.", e);
-            sendStatus("Error decrypting messages from Contour Next Link.");
-        } catch (TimeoutException e) {
-            Log.e(TAG, "Timeout communicating with the Contour Next Link.", e);
-            sendStatus("Timeout communicating with the Contour Next Link.");
-        } catch (UnexpectedMessageException e) {
-            Log.e(TAG, "Could not close connection.", e);
-            sendStatus("Could not close connection: " + e.getMessage());
         } finally {
-            if (!realm.isClosed()) {
-                if (realm.isInTransaction()) {
-                    // If we didn't commit the transaction, we've run into an error. Let's roll it back
-                    realm.cancelTransaction();
-                }
-                realm.close();
+            MedtronicCnlAlarmReceiver.completeWakefulIntent(intent);
+        }
+    }
+
+    private void scheduleNextPoll(long timePollStarted, long timeLastGoodSGV, long pollInterval) {
+        // smart polling and pump-sensor poll clash detection
+        long lastActualPollTime = timePollStarted;
+        if (timeLastGoodSGV > 0) {
+            lastActualPollTime = timeLastGoodSGV + POLL_GRACE_PERIOD_MS + (POLL_PERIOD_MS * ((System.currentTimeMillis() - (timeLastGoodSGV + POLL_GRACE_PERIOD_MS)) / POLL_PERIOD_MS));
+        }
+        long nextActualPollTime = lastActualPollTime + POLL_PERIOD_MS;
+        long nextRequestedPollTime = lastActualPollTime + pollInterval;
+        if ((nextRequestedPollTime - System.currentTimeMillis()) < 10000L) {
+            nextRequestedPollTime = nextActualPollTime;
+        }
+        // extended unavailable SGV may be due to clash with the current polling time
+        // while we wait for a good SGV event, polling is auto adjusted by offsetting the next poll based on miss count
+        if (dataStore.getUnavailableSGVCount() > 0) {
+            if (timeLastGoodSGV == 0) {
+                nextRequestedPollTime += POLL_PERIOD_MS / 5L; // if there is a uploader/sensor poll clash on startup then this will push the next attempt out by 60 seconds
+            } else if (dataStore.getUnavailableSGVCount() > 2) {
+                sendStatus("Warning: No SGV available from pump for " + dataStore.getUnavailableSGVCount() + " attempts");
+                nextRequestedPollTime += ((long) ((dataStore.getUnavailableSGVCount() - 2) % 5)) * (POLL_PERIOD_MS / 10L); // adjust poll time in 1/10 steps to avoid potential poll clash (max adjustment at 5/10)
             }
+        }
+        MedtronicCnlAlarmManager.setAlarm(nextRequestedPollTime);
+        sendStatus("Next poll due at: " + dateFormatter.format(nextRequestedPollTime));
+    }
 
-            // TODO - set status if offline or Nightscout not reachable
-            sendToXDrip();
-            uploadToNightscout();
-            MedtronicCnlAlarmReceiver.completeWakefulIntent(intent);
+    /**
+     * @return if device acquisition was successful
+     */
+    private boolean openUsbDevice() {
+        if (!hasUsbHostFeature()) {
+            sendStatus("It appears that this device doesn't support USB OTG.");
+            Log.e(TAG, "Device does not support USB OTG");
+            return false;
+        }
+
+        UsbDevice cnlStick = UsbHidDriver.getUsbDevice(mUsbManager, USB_VID, USB_PID);
+        if (cnlStick == null) {
+            sendStatus("USB connection error. Is the Contour Next Link plugged in?");
+            Log.w(TAG, "USB connection error. Is the CNL plugged in?");
+            return false;
+        }
+
+        if (!mUsbManager.hasPermission(UsbHidDriver.getUsbDevice(mUsbManager, USB_VID, USB_PID))) {
+            sendMessage(Constants.ACTION_NO_USB_PERMISSION);
+            return false;
         }
+        mHidDevice = UsbHidDriver.acquire(mUsbManager, cnlStick);
+
+        try {
+            mHidDevice.open();
+        } catch (Exception e) {
+            sendStatus("Unable to open USB device");
+            Log.e(TAG, "Unable to open serial device", e);
+            return false;
+        }
+
+        return true;
     }
 
     // reliable wake alarm manager wake up for all android versions
@@ -298,6 +397,11 @@ public class MedtronicCnlIntentService extends IntentService {
             alarm.set(AlarmManager.RTC_WAKEUP, wakeTime, pendingIntent);
     }
 
+    private void uploadPollResults() {
+        sendToXDrip();
+        uploadToNightscout();
+    }
+
     private void sendToXDrip() {
         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
         if (prefs.getBoolean(getString(R.string.preference_enable_xdrip_plus), false)) {
@@ -310,6 +414,7 @@ public class MedtronicCnlIntentService extends IntentService {
     }
 
     private void uploadToNightscout() {
+        // TODO - set status if offline or Nightscout not reachable
         Intent receiverIntent = new Intent(this, NightscoutUploadReceiver.class);
         final long timestamp = System.currentTimeMillis() + 1000L;
         final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, (int) timestamp, receiverIntent, PendingIntent.FLAG_ONE_SHOT);
@@ -324,8 +429,8 @@ public class MedtronicCnlIntentService extends IntentService {
         public static final String ACTION_STATUS_MESSAGE = "info.nightscout.android.medtronic.service.STATUS_MESSAGE";
         public static final String ACTION_NO_USB_PERMISSION = "info.nightscout.android.medtronic.service.NO_USB_PERMISSION";
         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..75847c83ce358679ce2ce14bcc0ae728675d4b13 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
@@ -45,13 +45,16 @@ public class PumpStatusEvent extends RealmObject {
     @Index
     private boolean uploaded = false;
 
+    public PumpStatusEvent() {
+        // The the eventDate to now.
+        this.eventDate = new Date();
+    }
+
     public Date getEventDate() {
         return eventDate;
     }
 
-    public void setEventDate(Date eventDate) {
-        this.eventDate = eventDate;
-    }
+    // No EventDate setter. The eventDate is set at the time that the PumpStatusEvent is created.
 
     public Date getPumpDate() {
         return pumpDate;
@@ -78,17 +81,28 @@ public class PumpStatusEvent extends RealmObject {
     }
 
     public CGM_TREND getCgmTrend() {
-        return CGM_TREND.valueOf(cgmTrend);
-    }
-
-    public void setCgmTrend(CGM_TREND cgmTrend) {
-        this.cgmTrend = cgmTrend.name();
+        if (cgmTrend == null || !this.isCgmActive()) {
+            return CGM_TREND.NOT_SET;
+        } else {
+            return CGM_TREND.valueOf(cgmTrend);
+        }
     }
 
     public void setCgmTrend(String cgmTrend) {
         this.cgmTrend = cgmTrend;
     }
 
+    public String getCgmTrendString() {
+        return cgmTrend;
+    }
+
+    public void setCgmTrend(CGM_TREND cgmTrend) {
+        if (cgmTrend != null)
+            this.cgmTrend = cgmTrend.name();
+        else
+            this.cgmTrend = CGM_TREND.NOT_SET.name();
+    }
+
     public float getActiveInsulin() {
         return activeInsulin;
     }
@@ -261,6 +275,38 @@ public class PumpStatusEvent extends RealmObject {
         this.pumpTimeOffset = pumpTimeOffset;
     }
 
+    @Override
+    public String toString() {
+        return "PumpStatusEvent{" +
+                "eventDate=" + eventDate +
+                ", pumpDate=" + pumpDate +
+                ", deviceName='" + deviceName + '\'' +
+                ", suspended=" + suspended +
+                ", bolusing=" + bolusing +
+                ", deliveringInsulin=" + deliveringInsulin +
+                ", tempBasalActive=" + tempBasalActive +
+                ", cgmActive=" + cgmActive +
+                ", activeBasalPattern=" + activeBasalPattern +
+                ", basalRate=" + basalRate +
+                ", tempBasalRate=" + tempBasalRate +
+                ", tempBasalPercentage=" + tempBasalPercentage +
+                ", tempBasalMinutesRemaining=" + tempBasalMinutesRemaining +
+                ", basalUnitsDeliveredToday=" + basalUnitsDeliveredToday +
+                ", batteryPercentage=" + batteryPercentage +
+                ", reservoirAmount=" + reservoirAmount +
+                ", minutesOfInsulinRemaining=" + minutesOfInsulinRemaining +
+                ", activeInsulin=" + activeInsulin +
+                ", sgv=" + sgv +
+                ", sgvDate=" + sgvDate +
+                ", lowSuspendActive=" + lowSuspendActive +
+                ", cgmTrend='" + cgmTrend + '\'' +
+                ", recentBolusWizard=" + recentBolusWizard +
+                ", bolusWizardBGL=" + bolusWizardBGL +
+                ", pumpTimeOffset=" + pumpTimeOffset +
+                ", uploaded=" + uploaded +
+                '}';
+    }
+
     public enum CGM_TREND {
         NONE,
         DOUBLE_UP,
@@ -272,6 +318,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..b42a10614fb9f13c13dc06e9d87fb1459bfd166e 100644
--- a/app/src/main/java/info/nightscout/android/settings/SettingsFragment.java
+++ b/app/src/main/java/info/nightscout/android/settings/SettingsFragment.java
@@ -1,5 +1,6 @@
 package info.nightscout.android.settings;
 
+import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.os.Bundle;
@@ -9,14 +10,25 @@ import android.preference.MultiSelectListPreference;
 import android.preference.Preference;
 import android.preference.PreferenceCategory;
 import android.preference.PreferenceFragment;
+import android.util.Log;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+import com.google.zxing.integration.android.IntentIntegrator;
+import com.google.zxing.integration.android.IntentResult;
+
+import java.net.MalformedURLException;
+import java.net.URL;
 
 import info.nightscout.android.R;
 
 public class SettingsFragment extends PreferenceFragment implements OnSharedPreferenceChangeListener {
+    private static final String TAG = SettingsFragment.class.getSimpleName();
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        final SettingsFragment that = this;
 
         /* set preferences */
         addPreferencesFromResource(R.xml.preferences);
@@ -25,11 +37,60 @@ 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"));
+
+        Preference button = findPreference("scanButton");
+        button.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+            @Override
+            public boolean onPreferenceClick(Preference preference) {
+                IntentIntegrator integrator = new IntentIntegrator(that);
+                integrator.initiateScan();
+
+                return true;
+            }
+        });
     }
 
     @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
@@ -71,4 +132,55 @@ public class SettingsFragment extends PreferenceFragment implements OnSharedPref
             p.setSummary(editTextPref.getText());
         }
     }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+
+        if (requestCode==IntentIntegrator.REQUEST_CODE)
+        {
+            IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
+            if (scanResult != null)
+            {
+                Log.d(TAG, "scanResult returns: " + scanResult.toString());
+
+                JsonParser json = new JsonParser();
+                String resultContents = scanResult.getContents() == null ? "" : scanResult.getContents();
+                JsonElement jsonElement = json.parse(resultContents);
+                if (jsonElement != null && jsonElement.isJsonObject()) {
+                    jsonElement = (jsonElement.getAsJsonObject()).get("rest");
+                    if (jsonElement != null && jsonElement.isJsonObject()) {
+                        jsonElement = (jsonElement.getAsJsonObject()).get("endpoint");
+                        if (jsonElement != null && jsonElement.isJsonArray() && jsonElement.getAsJsonArray().size() > 0) {
+                            String endpoint = jsonElement.getAsJsonArray().get(0).getAsString();
+                            Log.d(TAG, "endpoint: " + endpoint);
+
+                            try {
+                                URL uri = new URL(endpoint);
+
+                                StringBuilder url = new StringBuilder(uri.getProtocol())
+                                        .append("://").append(uri.getHost());
+                                if (uri.getPort() > -1)
+                                    url.append(":").append(uri.getPort());
+
+                                EditTextPreference editPref = (EditTextPreference) findPreference(getString(R.string.preference_nightscout_url));
+                                editPref.setText(url.toString());
+                                updatePrefSummary(editPref);
+
+                                editPref = (EditTextPreference) findPreference(getString(R.string.preference_api_secret));
+                                editPref.setText(uri.getUserInfo());
+                                updatePrefSummary(editPref);
+                            } catch (MalformedURLException e) {
+                                Log.w (TAG, e.getMessage());
+                            }
+
+                        }
+                    }
+                }
+            }
+            else
+            {
+                Log.d(TAG, "scanResult is null.");
+            }
+        }
+    }
 }
\ No newline at end of file
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..819f82e3923c37694a2ba17447dac2d82543ee0b
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/upload/nightscout/NightScoutUpload.java
@@ -0,0 +1,180 @@
+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;
+
+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 = 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..038218934c13ccb0c2e99389977a5106ec8b536a 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,18 @@ 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 info.nightscout.android.utils.DataStore;
 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 Realm mRealm;
+
+    private Context mContext;
+    private NightScoutUpload mNightScoutUpload;
 
     public NightscoutUploadIntentService() {
         super(NightscoutUploadIntentService.class.getName());
@@ -64,12 +40,15 @@ public class NightscoutUploadIntentService extends IntentService {
 
         Log.i(TAG, "onCreate called");
         mContext = this.getBaseContext();
+
+        mNightScoutUpload = new NightScoutUpload();
+
     }
 
     @Override
     protected void onHandleIntent(Intent intent) {
         Log.d(TAG, "onHandleIntent called");
-        mRealm = Realm.getDefaultInstance();
+        Realm mRealm = Realm.getDefaultInstance();
 
         RealmResults<PumpStatusEvent> records = mRealm
                 .where(PumpStatusEvent.class)
@@ -85,7 +64,17 @@ 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");
+                    Boolean uploadSuccess = mNightScoutUpload.doRESTUpload(urlSetting,
+                            secretSetting, DataStore.getInstance().getUploaderBatteryLevel(), 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 +83,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 +99,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..1f40ab015abd146de2c596efb414ad798df58594 100644
--- a/app/src/main/java/info/nightscout/android/upload/nightscout/serializer/EntriesSerializer.java
+++ b/app/src/main/java/info/nightscout/android/upload/nightscout/serializer/EntriesSerializer.java
@@ -42,6 +42,37 @@ 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?
+        }
+    }
+
+    // TODO currentnly unused, see info.nightscout.android.xdrip_plus.XDripPlusUploadIntentService.addSgvEntry()
+    // TODO also, proper method name
     @Override
     public JsonElement serialize(PumpStatusEvent src, Type typeOfSrc, JsonSerializationContext context) {
         final JsonObject jsonObject = new JsonObject();
@@ -49,8 +80,8 @@ public class EntriesSerializer implements JsonSerializer<PumpStatusEvent> {
         jsonObject.addProperty("direction", getDirectionString(src.getCgmTrend()));
         jsonObject.addProperty("device", src.getDeviceName());
         jsonObject.addProperty("type", "sgv");
-        jsonObject.addProperty("date", src.getEventDate().getTime());
-        jsonObject.addProperty("dateString", String.valueOf(src.getEventDate()));
+        jsonObject.addProperty("date", src.getSgvDate().getTime());
+        jsonObject.addProperty("dateString", String.valueOf(src.getSgvDate()));
 
         return jsonObject;
     }
diff --git a/app/src/main/java/info/nightscout/android/utils/ConfigurationStore.java b/app/src/main/java/info/nightscout/android/utils/ConfigurationStore.java
new file mode 100644
index 0000000000000000000000000000000000000000..a45c63f40b50e8771371de624cb05ffc7e8dabd5
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/utils/ConfigurationStore.java
@@ -0,0 +1,66 @@
+package info.nightscout.android.utils;
+
+
+import info.nightscout.android.medtronic.service.MedtronicCnlIntentService;
+
+/**
+ * Created by volker on 30.03.2017.
+ */
+
+public class ConfigurationStore {
+    private static ConfigurationStore instance;
+
+    private boolean reducePollOnPumpAway = false;
+    private long pollInterval = MedtronicCnlIntentService.POLL_PERIOD_MS;
+    private long lowBatteryPollInterval = MedtronicCnlIntentService.LOW_BATTERY_POLL_PERIOD_MS;
+    private boolean mmolxl;
+    private boolean mmolxlDecimals;
+
+    public static ConfigurationStore getInstance() {
+        if (ConfigurationStore.instance == null) {
+            instance = new ConfigurationStore();
+        }
+
+        return instance;
+    }
+
+    public boolean isReducePollOnPumpAway() {
+        return reducePollOnPumpAway;
+    }
+
+    public void setReducePollOnPumpAway(boolean reducePollOnPumpAway) {
+        this.reducePollOnPumpAway = reducePollOnPumpAway;
+    }
+
+    public long getPollInterval() {
+        return pollInterval;
+    }
+
+    public void setPollInterval(long pollInterval) {
+        this.pollInterval = pollInterval;
+    }
+
+    public long getLowBatteryPollInterval() {
+        return lowBatteryPollInterval;
+    }
+
+    public void setLowBatteryPollInterval(long lowBatteryPollInterval) {
+        this.lowBatteryPollInterval = lowBatteryPollInterval;
+    }
+
+    public boolean isMmolxl() {
+        return mmolxl;
+    }
+
+    public void setMmolxl(boolean mmolxl) {
+        this.mmolxl = mmolxl;
+    }
+
+    public boolean isMmolxlDecimals() {
+        return mmolxlDecimals;
+    }
+
+    public void setMmolxlDecimals(boolean mmolxlDecimals) {
+        this.mmolxlDecimals = mmolxlDecimals;
+    }
+}
diff --git a/app/src/main/java/info/nightscout/android/utils/DataStore.java b/app/src/main/java/info/nightscout/android/utils/DataStore.java
new file mode 100644
index 0000000000000000000000000000000000000000..39fe120d971acda594d91203e040fd2125c2a3de
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/utils/DataStore.java
@@ -0,0 +1,79 @@
+package info.nightscout.android.utils;
+
+
+import java.util.Date;
+
+import info.nightscout.android.model.medtronicNg.PumpStatusEvent;
+import io.realm.Realm;
+
+/**
+ * Created by volker on 30.03.2017.
+ */
+
+public class DataStore {
+    private static DataStore instance;
+
+    private PumpStatusEvent lastPumpStatus;
+    private int uploaderBatteryLevel = 0;
+    private int unavailableSGVCount = 0;
+    private long activePumpMac = 0;
+
+    private DataStore() {}
+
+    public static DataStore getInstance() {
+        if (DataStore.instance == null) {
+            instance = new DataStore();
+
+            // set some initial dummy values
+            PumpStatusEvent dummyStatus = new PumpStatusEvent();
+            dummyStatus.setSgvDate(new Date());
+
+            // bypass setter to avoid dealing with a real Realm object
+            instance.lastPumpStatus = dummyStatus;
+        }
+
+        return instance;
+    }
+
+    public PumpStatusEvent getLastPumpStatus() {
+        return lastPumpStatus;
+    }
+
+    public void setLastPumpStatus(PumpStatusEvent lastPumpStatus) {
+        Realm realm = Realm.getDefaultInstance();
+
+        this.lastPumpStatus = realm.copyFromRealm(lastPumpStatus);
+        if (!realm.isClosed()) realm.close();
+    }
+
+    public int getUploaderBatteryLevel() {
+        return uploaderBatteryLevel;
+    }
+
+    public void setUploaderBatteryLevel(int uploaderBatteryLevel) {
+        this.uploaderBatteryLevel = uploaderBatteryLevel;
+    }
+
+    public int getUnavailableSGVCount() {
+        return unavailableSGVCount;
+    }
+
+    public int incUnavailableSGVCount() {
+        return unavailableSGVCount++;
+    }
+
+    public void clearUnavailableSGVCount() {
+        this.unavailableSGVCount = 0;
+    }
+    public void setUnavailableSGVCount(int unavailableSGVCount) {
+        this.unavailableSGVCount = unavailableSGVCount;
+    }
+
+    public long getActivePumpMac() {
+        return activePumpMac;
+    }
+
+    public void setActivePumpMac(long activePumpMac) {
+        this.activePumpMac = activePumpMac;
+    }
+}
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..002ab6b2b69f925b9ba39a44288991bc6416fc10 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,14 @@ 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(" ?")
+                    .append(HEX_DIGITS[i]);
+        }
         result.append("\n0x");
+
         result.append(toHexString(offset));
 
         for (int i = offset; i < offset + length; i++) {
@@ -63,19 +70,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(".");
             }
         }
 
@@ -102,6 +107,7 @@ public class HexDump {
 
         return new String(buf);
     }
+
     public static String toHexString(int i) {
         return toHexString(toByteArray(i));
     }
@@ -137,8 +143,8 @@ public class HexDump {
     public static byte[] hexStringToByteArray(String hexString) {
         int length = hexString.length();
         byte[] buffer = new byte[length / 2];
-        if (length% 2 == 1)
-        	length--;
+        if (length % 2 == 1)
+            length--;
         for (int i = 0; i < length; i += 2) {
             buffer[i / 2] = (byte) ((toByte(hexString.charAt(i)) << 4) | toByte(hexString
                     .charAt(i + 1)));
@@ -146,36 +152,39 @@ public class HexDump {
 
         return buffer;
     }
-    
-    public static int unsignedByte(byte b){
-    	return  (b & 0xFF);
+
+    public static int unsignedByte(byte b) {
+        return (b & 0xFF);
     }
-    
-    public static byte bUnsignedByte(byte b){
-    	return  (byte)(b & 0xFF);
+
+    public static byte bUnsignedByte(byte b) {
+        return (byte) (b & 0xFF);
     }
+
+    @SuppressWarnings("ResultOfMethodCallIgnored")
     public static boolean isHexaNumber(String cadena) {
         try {
-            Long.parseLong(cadena,16);
+            Long.parseLong(cadena, 16);
             return true;
         } catch (NumberFormatException nfe) {
             return false;
         }
     }
-    
-    public static int byteArrayToInt (byte[] arr){
-    	int length = arr.length;
-    	int mult = 1;
-    	int res = 0;
-    	if (length > 0 && length <5){
-    		for (int i = length-1; i >= 0; i--){
-    			res += unsignedByte(arr[i])*mult;
-    			mult *=256;
-    		}
-    	}
-    	return res;
+
+    public static int byteArrayToInt(byte[] arr) {
+        int length = arr.length;
+        int mult = 1;
+        int res = 0;
+        if (length > 0 && length < 5) {
+            for (int i = length - 1; i >= 0; i--) {
+                res += unsignedByte(arr[i]) * mult;
+                mult *= 256;
+            }
+        }
+        return res;
     }
-    public static short byteArrayToShort (byte[] arr){
-    	return (short) (unsignedByte(arr[0])*256 + unsignedByte(arr[1]));
+
+    public static short byteArrayToShort(byte[] arr) {
+        return (short) (unsignedByte(arr[0]) * 256 + unsignedByte(arr[1]));
     }
 }
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..e06a88b8cf047dde4d5a812b58198e69bd508573 100644
--- a/app/src/main/java/info/nightscout/android/xdrip_plus/XDripPlusUploadIntentService.java
+++ b/app/src/main/java/info/nightscout/android/xdrip_plus/XDripPlusUploadIntentService.java
@@ -16,9 +16,9 @@ import java.text.SimpleDateFormat;
 import java.util.List;
 import java.util.Locale;
 
-import info.nightscout.android.medtronic.MainActivity;
 import info.nightscout.android.model.medtronicNg.PumpStatusEvent;
 import info.nightscout.android.upload.nightscout.serializer.EntriesSerializer;
+import info.nightscout.android.utils.DataStore;
 import io.realm.Realm;
 import io.realm.RealmResults;
 import io.realm.Sort;
@@ -33,7 +33,6 @@ public class XDripPlusUploadIntentService extends IntentService {
     private static final String TAG = XDripPlusUploadIntentService.class.getSimpleName();
     private static final SimpleDateFormat ISO8601_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault());
     Context mContext;
-    private Realm mRealm;
 
     public XDripPlusUploadIntentService() {
         super(XDripPlusUploadIntentService.class.getName());
@@ -58,7 +57,7 @@ public class XDripPlusUploadIntentService extends IntentService {
     @Override
     protected void onHandleIntent(Intent intent) {
         Log.d(TAG, "onHandleIntent called");
-        mRealm = Realm.getDefaultInstance();
+        Realm mRealm = Realm.getDefaultInstance();
 
         RealmResults<PumpStatusEvent> all_records = mRealm
                 .where(PumpStatusEvent.class)
@@ -70,6 +69,7 @@ public class XDripPlusUploadIntentService extends IntentService {
             List<PumpStatusEvent> records = all_records.subList(0, 1);
             doXDripUpload(records);
         }
+        mRealm.close();
         XDripPlusUploadReceiver.completeWakefulIntent(intent);
     }
 
@@ -85,10 +85,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,14 +106,16 @@ 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");
+        }
     }
 
 
     private void addDeviceStatus(JSONArray devicestatusArray, PumpStatusEvent record) throws Exception {
         JSONObject json = new JSONObject();
-        json.put("uploaderBattery", MainActivity.batLevel);
+        json.put("uploaderBattery", DataStore.getInstance().getUploaderBatteryLevel());
         json.put("device", record.getDeviceName());
         json.put("created_at", ISO8601_DATE_FORMAT.format(record.getPumpDate()));
 
@@ -141,8 +145,8 @@ public class XDripPlusUploadIntentService extends IntentService {
         json.put("direction", EntriesSerializer.getDirectionString(pumpRecord.getCgmTrend()));
         json.put("device", pumpRecord.getDeviceName());
         json.put("type", "sgv");
-        json.put("date", pumpRecord.getEventDate().getTime());
-        json.put("dateString", pumpRecord.getEventDate());
+        json.put("date", pumpRecord.getSgvDate().getTime());
+        json.put("dateString", pumpRecord.getSgvDate());
 
         entriesArray.put(json);
     }
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..f528c75f14698284d48e3cb0c5bc811ee6817ad6
--- /dev/null
+++ b/app/src/main/java/info/nightscout/api/DeviceEndpoints.java
@@ -0,0 +1,96 @@
+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.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..a2c4a1a322a6c897c2e49b40b52f1ab52ee536a5
--- /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/drawable/drawer_header.jpg b/app/src/main/res/drawable/drawer_header.jpg
index 251abd5c8a8ee193ef484393a966c891c8f5478a..2f33ff7e47ebc0f2ea9af68e5a0d58b183a38bed 100644
Binary files a/app/src/main/res/drawable/drawer_header.jpg and b/app/src/main/res/drawable/drawer_header.jpg 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..9810c1ad7339738df8fb7cde34bbd38dbee7c78a 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -52,8 +52,9 @@
                 android:layout_height="wrap_content"
                 android:layout_gravity="bottom"
                 android:layout_weight="1"
-                android:singleLine="true"
+                android:maxLines="1"
                 android:text="-"
+                android:textAlignment="center"
                 android:textAppearance="?android:attr/textAppearanceLarge"
                 android:textSize="70sp" />
 
@@ -70,7 +71,7 @@
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:layout_gravity="center|top"
-                    android:singleLine="true"
+                    android:maxLines="1"
                     android:text="-"
                     android:textAppearance="?android:attr/textAppearanceLarge"
                     android:textSize="40sp" />
@@ -80,9 +81,9 @@
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:layout_gravity="center_horizontal"
-                    android:singleLine="true"
                     android:text="mmol/L"
-                    android:textAppearance="?android:attr/textAppearanceSmall" />
+                    android:textAppearance="?android:attr/textAppearanceSmall"
+                    android:maxLines="1" />
 
             </LinearLayout>
 
@@ -119,11 +120,10 @@
 
     </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"
@@ -140,7 +140,8 @@
                 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..620622de5bc12f18f4abd312f6a0c5659ac7ba6f 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">
 
@@ -25,40 +26,91 @@
     </android.support.design.widget.AppBarLayout>
 
     <ScrollView
+        android:id="@+id/status_scroll_view"
         android:layout_width="fill_parent"
         android:layout_height="fill_parent"
-        android:id="@+id/status_scroll_view"
-        android:fillViewport="true" >
+        android:layout_marginLeft="@dimen/activity_horizontal_margin"
+        android:layout_marginRight="@dimen/activity_horizontal_margin"
+        android:fillViewport="true">
 
         <LinearLayout
-            android:orientation="vertical"
+            android:id="@+id/x"
             android:layout_width="match_parent"
-            android:layout_height="wrap_content">
+            android:layout_height="wrap_content"
+            android:layout_margin="5dp"
+            android:orientation="vertical">
 
             <TextView
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="Pump Status"
                 android:id="@+id/status_pump_text_view"
-                android:singleLine="true" />
+                style="?android:attr/listSeparatorTextViewStyle"
+                android:layout_height="wrap_content"
+                android:maxLines="1"
+                android:text="Pump Status" />
+
+            <GridLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+
+                <android.support.v7.widget.CardView
+                    android:id="@+id/card_view"
+                    android:layout_width="100dp"
+                    android:layout_height="100dp"
+                    android:layout_gravity="center"
+                    app:cardCornerRadius="4dp">
+
+                    <RelativeLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="match_parent"
+                        android:layout_margin="5dp">
+
+                        <ImageView
+                            android:id="@+id/imageView"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:scaleType="center"
+                            app:srcCompat="@drawable/battery_0" />
+
+                        <TextView
+                            android:id="@+id/textView2"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_below="@+id/textView"
+                            android:text="Units Remaining"
+                            android:textAppearance="@style/TextAppearance.AppCompat.Caption" />
+
+                        <TextView
+                            android:id="@+id/textView"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_below="@+id/imageView"
+                            android:layout_centerHorizontal="true"
+                            android:layout_centerInParent="true"
+                            android:text="150.250"
+                            android:textAlignment="center"
+                            android:textAppearance="@style/TextAppearance.AppCompat.Headline" />
+                    </RelativeLayout>
+
+                </android.support.v7.widget.CardView>
+
+            </GridLayout>
 
             <TextView
-                android:layout_width="wrap_content"
+                android:id="@+id/status_uploader_text_view"
+                style="?android:attr/listSeparatorTextViewStyle"
                 android:layout_height="wrap_content"
-                android:text="Uploader Status"
-                android:id="@+id/status_uploader_text_view" />
+                android:text="Uploader Status" />
 
             <TextView
-                android:layout_width="wrap_content"
+                android:id="@+id/status_cgm_text_view"
+                style="?android:attr/listSeparatorTextViewStyle"
                 android:layout_height="wrap_content"
-                android:text="CGM Status"
-                android:id="@+id/status_cgm_text_view" />
+                android:text="CGM Status" />
 
             <TextView
-                android:layout_width="wrap_content"
+                android:id="@+id/status_nightscout_text_view"
+                style="?android:attr/listSeparatorTextViewStyle"
                 android:layout_height="wrap_content"
-                android:text="Nightscout Status"
-                android:id="@+id/status_nightscout_text_view" />
+                android:text="Nightscout Status" />
 
         </LinearLayout>
     </ScrollView>
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..2fb909e0027846c2b164a33e14ba004a6785bf10
--- /dev/null
+++ b/app/src/main/res/layout/cnl_item.xml
@@ -0,0 +1,25 @@
+<?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:paddingRight="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..5eb735dd3b5aa22f786779f76b01059c5917e7a8 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,8 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
-
-    <string name="hello">---</string>
-    <string name="app_name">NS 640g Uploader</string>
+    <string name="app_name">600 Series Uploader</string>
     <string name="eula_title">Disclaimer</string>
     <string name="eula_accept">Accept</string>
     <string name="eula_refuse">Refuse</string>
@@ -16,29 +14,11 @@
         <item>Info</item>
         <item>Debug</item>
     </string-array>
-    <string name="title_activity_login">CareLink login</string>
 
     <!-- Strings related to login -->
-    <string name="prompt_username">CareLink Username</string>
-    <string name="prompt_password">Password</string>
-    <string name="action_sign_in">Retrieve keys for USB</string>
-    <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_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>
     <string name="preferences_enable_crashlytics">prefs_enable_crashlytics</string>
     <string name="preferences_enable_answers">prefs_enable_answers</string>
     <string name="preferences_enable_remote_logcat">prefs_enable_remote_logcat</string>
-    <string name="menu_name_preferences">Preferences</string>
-    <string name="button_text_stop_uploading_data">Stop Uploading CGM Data</string>
-    <string name="button_text_clear_log">Clear Log</string>
-    <string name="button_text_get_now">Get Now</string>
-    <string name="button_text_start_uploading_data">Start Uploading CGM Data</string>
     <string name="preference_eula_accepted">IUNDERSTAND</string>
     <string name="preference_enable_rest_upload">EnableRESTUpload</string>
     <string name="preference_enable_xdrip_plus">EnablexDripPlusUpload</string>
@@ -46,8 +26,16 @@
     <string name="text_unit_mmolxl">mmol/L</string>
     <string name="text_unit_mgxdl">mg/dL</string>
 
-    <string name="title_activity_status">Uploader Status</string>
-    <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..ff89a6f2bb0e9027c0f4fc857a79595b7a6fe4d5 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
@@ -39,6 +70,10 @@
             android:dialogTitle="Enter your Nightscout API secret"
             android:key="@string/preference_api_secret"
             android:title="API Secret"/>
+        <Preference android:title="scan NS-Autoconfig QR-Code"
+            android:key="scanButton"
+            android:dependency="@string/preference_enable_rest_upload"
+            android:summary="Click here to scan QR-Code from http://nightscout.github.io/pages/configure/ using ZXing barcode scanner."/>
         <CheckBoxPreference
             android:key="@string/preference_enable_xdrip_plus"
             android:summary="Enable local broadcast of data to xDrip+"
diff --git a/build.gradle b/build.gradle
index 9da42b0b994170c75de5ee547bfabc0007cf8dd7..958eeb218133aa2d20df86abbece04f6df2ed49b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,10 +5,7 @@ buildscript {
         jcenter()
     }
     dependencies {
-        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 'com.android.tools.build:gradle:2.3.3'
     }
 }
 
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 3d5435e07e9964062e47fc0426eaa27552a17041..8e8e1988d58c6650b6e10423666fdeef466bac95 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Sat Nov 12 11:44:13 AEDT 2016
+#Tue Mar 28 09:13:19 AEDT 2017
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip