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/CONTRIBUTING.md b/CONTRIBUTING.md
index 24349769999d1fd5bc19e3f775a14f62e682829d..f9eb138370f9fba6133b0f8575defb57d38f71b2 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,10 +1,10 @@
-## How to contribute to 640gAndroidUploader
+## How to contribute to 600SeriesAndroidUploader
 
 #### **Did you find a bug?**
 
-* **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/pazaan/640gAndroidUploader/issues).
+* **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/pazaan/600SeriesAndroidUploader/issues).
 
-* If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/pazaan/640gAndroidUploader/issues/new). Be sure to include a **title and clear description**, and as much relevant information as possible, to help us reproduce the problem on our own gear.
+* If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/pazaan/600SeriesAndroidUploader/issues/new). Be sure to include a **title and clear description**, and as much relevant information as possible, to help us reproduce the problem on our own gear.
 
 #### **Did you write a patch that fixes a bug?**
 
@@ -13,6 +13,6 @@
 * Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable.
 
 </br>
-640gAndroidUploader is a volunteer effort. We encourage you to pitch in and [join the team](https://gitter.im/pazaan/decoding-contour-next-link)
+600SeriesAndroidUploader is a volunteer effort. We encourage you to pitch in and [join the team](https://gitter.im/pazaan/decoding-contour-next-link)
 
 Thanks! :heart: :squirrel: :heart:
diff --git a/README.md b/README.md
index 599ec520263dbf85f894c94c8bc2c130f1d7a9e9..82c9500a436425a1dd728010090afa76c3b7e769 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,17 @@
-[![Stories in Progress](https://badge.waffle.io/pazaan/640gAndroidUploader.svg?label=in%20progress&title=In%20Progress)](http://waffle.io/pazaan/640gAndroidUploader)
+[![Stories in Progress](https://badge.waffle.io/pazaan/600SeriesAndroidUploader.svg?label=in%20progress&title=In%20Progress)](http://waffle.io/pazaan/600SeriesAndroidUploader)
 
-## 640gAndroidUploader
+## 600SeriesAndroidUploader
 
-This is an Android app to upload data from a MiniMed 640G insulin pump to a Nightscout website via a Contour Next Link 2.4 blood glucose meter
+This is an Android app to upload data from a MiniMed 600 Series insulin pump to a Nightscout website via a Contour Next Link 2.4 blood glucose meter
 
-###### [Click here for more info](https://github.com/pazaan/640gAndroidUploader/wiki)
-###### [Click here for Releases](https://github.com/pazaan/640gAndroidUploader/releases)
-###### [Click here for the Main Project Page](http://pazaan.github.io/640gAndroidUploader/)
+###### [Click here for more info](https://github.com/pazaan/600SeriesAndroidUploader/wiki)
+###### [Click here for Releases](https://github.com/pazaan/600SeriesAndroidUploader/releases)
+###### [Click here for the Main Project Page](http://pazaan.github.io/600SeriesAndroidUploader/)
 
 <br/>
-<a target="blank" href="https://raw.githubusercontent.com/wiki/pazaan/640gAndroidUploader/images/kit-showing-app.jpg"><img src="https://raw.githubusercontent.com/wiki/pazaan/640gAndroidUploader/images/kit-showing-app.jpg" width="200"></a>
-<a target="blank" href="https://raw.githubusercontent.com/wiki/pazaan/640gAndroidUploader/images/kit-in-case-1.jpg"><img src="https://raw.githubusercontent.com/wiki/pazaan/640gAndroidUploader/images/kit-in-case-1.jpg" width="200"></a>
-<a target="blank" href="https://raw.githubusercontent.com/wiki/pazaan/640gAndroidUploader/images/kit-in-case-2.jpg"><img src="https://raw.githubusercontent.com/wiki/pazaan/640gAndroidUploader/images/kit-in-case-2.jpg" width="200"></a>
+<a target="blank" href="https://raw.githubusercontent.com/wiki/pazaan/600SeriesAndroidUploader/images/kit-showing-app.jpg"><img src="https://raw.githubusercontent.com/wiki/pazaan/600SeriesAndroidUploader/images/kit-showing-app.jpg" width="200"></a>
+<a target="blank" href="https://raw.githubusercontent.com/wiki/pazaan/600SeriesAndroidUploader/images/kit-in-case-1.jpg"><img src="https://raw.githubusercontent.com/wiki/pazaan/600SeriesAndroidUploader/images/kit-in-case-1.jpg" width="200"></a>
+<a target="blank" href="https://raw.githubusercontent.com/wiki/pazaan/600SeriesAndroidUploader/images/kit-in-case-2.jpg"><img src="https://raw.githubusercontent.com/wiki/pazaan/600SeriesAndroidUploader/images/kit-in-case-2.jpg" width="200"></a>
 <br/><br/>
 
 #### Development - getting started
@@ -25,7 +25,13 @@ This is an Android app to upload data from a MiniMed 640G insulin pump to a Nigh
        android:value="YOUR-FABRIC-KEY" />
    ```
 
-   (**take care not to commit this change**)
+   (**Please take care not to commit this change.
+      If you're considering sharing your changes, or are using a non-private
+      Github repository, you should remove this change in
+      `app/src/AndroidManifest.xml` and copy the value as `apiKey` property
+      to file `app/fabric.properties` instead. See
+      https://docs.fabric.io/android/fabric/settings/working-in-teams.html#android-projects
+      for more information.**)
  - Create a [BugFender](https://app.bugfender.com) account, create `app/bugfender.properties` and populate with
 
    ```
@@ -36,16 +42,17 @@ This is an Android app to upload data from a MiniMed 640G insulin pump to a Nigh
  - Use one of the run configurations, eg `installDebug`
  
 #### App Credits
-* Based on https://github.com/arbox0/MedtronicUploader *(though the internals are completely changed for the 640G)*
+* Based on https://github.com/arbox0/MedtronicUploader *(though the internals are completely changed for the 600 Series pumps)*
 * Uses the [android-service-example](https://code.launchpad.net/~binwiederhier/+junk/android-service-example) by Philipp C. Heckel
 * Project initiated by [@pazaan](https://github.com/pazaan)
 
 <br/>
+
 #### Disclaimer And Warning
 
 + All information, thought, and code described here is intended for informational and educational purposes only. Nightscout currently makes no attempt at HIPAA privacy compliance. Use Nightscout at your own risk, and do not use the information or code to make medical decisions.
 
-+ Use of code from github.com is without warranty or formal support of any kind. Please review this repository's [LICENSE](https://github.com/pazaan/640gAndroidUploader/blob/master/LICENSE) for details. 
++ Use of code from github.com is without warranty or formal support of any kind. Please review this repository's [LICENSE](https://github.com/pazaan/600SeriesAndroidUploader/blob/master/LICENSE) for details. 
 
 + All product and company names, trademarks, servicemarks, registered trademarks, and registered servicemarks are the property of their respective holders. Their use is for information purposes and does not imply any affiliation with or endorsement by them. 
 
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..68e1d9d74a77133b4e43f5bbafc086de825f1067 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.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()
     }
@@ -115,11 +117,11 @@ task signRelease << {
 
 task zipalignRelease << {
     def command = [
-            '/Users/lennart/Library/Android/sdk/build-tools/23.0.3/zipalign',
+            '/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/640g-android-uploader.apk'
+            'app/build/outputs/apk/600-series-uploader.apk'
     ]
 
     def proc = new ProcessBuilder(command)
@@ -144,21 +146,30 @@ 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'
+
+    // 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..82e3026de27326a6e6bf9a294eb1ee731697992c 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,18 @@ 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.Viewport;
+import com.jjoe64.graphview.series.DataPoint;
+import com.jjoe64.graphview.series.DataPointInterface;
+import com.jjoe64.graphview.series.OnDataPointTapListener;
+import com.jjoe64.graphview.series.PointsGraphSeries;
+import com.jjoe64.graphview.series.Series;
 import com.mikepenz.google_material_typeface_library.GoogleMaterial;
 import com.mikepenz.materialdrawer.AccountHeaderBuilder;
 import com.mikepenz.materialdrawer.Drawer;
@@ -51,7 +58,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 +69,72 @@ import info.nightscout.android.R;
 import info.nightscout.android.USB.UsbHidDriver;
 import info.nightscout.android.eula.Eula;
 import info.nightscout.android.eula.Eula.OnEulaAgreedTo;
+import info.nightscout.android.medtronic.service.MedtronicCnlAlarmManager;
 import info.nightscout.android.medtronic.service.MedtronicCnlAlarmReceiver;
 import info.nightscout.android.medtronic.service.MedtronicCnlIntentService;
-import info.nightscout.android.model.medtronicNg.ContourNextLinkInfo;
 import info.nightscout.android.model.medtronicNg.PumpInfo;
 import info.nightscout.android.model.medtronicNg.PumpStatusEvent;
 import info.nightscout.android.settings.SettingsActivity;
-import info.nightscout.android.upload.nightscout.NightscoutUploadIntentService;
+import 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;
+    private NumberFormat sgvFormatter;
 
-    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 +143,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 +158,31 @@ 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));
+
+        if (configurationStore.isMmolxl()) {
+            if (configurationStore.isMmolxlDecimals())
+                sgvFormatter = new DecimalFormat("0.00");
+            else
+                sgvFormatter = new DecimalFormat("0.0");
+        } else {
+            sgvFormatter = new DecimalFormat("0");
+        }
+
         // Disable battery optimization to avoid missing values on 6.0+
         // taken from https://github.com/NightscoutFoundation/xDrip/blob/master/app/src/main/java/com/eveningoutpost/dexdrip/Home.java#L277L298
 
         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 +202,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,21 +240,29 @@ 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);
 
         new DrawerBuilder()
                 .withActivity(this)
@@ -204,10 +277,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 +296,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 +310,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 +384,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 +399,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 +438,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 +465,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 +487,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 +522,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 +556,29 @@ 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));
+            if (configurationStore.isMmolxl()) {
+                if (configurationStore.isMmolxlDecimals())
+                    sgvFormatter = new DecimalFormat("0.00");
+                else
+                    sgvFormatter = new DecimalFormat("0.0");
+            } else {
+                sgvFormatter = new DecimalFormat("0");
+            }
+            refreshDisplay();
+        } else if (key.equals("pollInterval")) {
+            configurationStore.setPollInterval(Long.parseLong(sharedPreferences.getString("pollInterval",
+                    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 +599,117 @@ 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();
+
+        if (configurationStore.isMmolxl()) {
+            NumberFormat sgvFormatter;
+            if (configurationStore.isMmolxlDecimals()) {
+                sgvFormatter = new DecimalFormat("0.00");
+            } else {
+                sgvFormatter = new DecimalFormat("0.0");
+            }
+            return sgvFormatter.format(sgvValue / MMOLXLFACTOR);
+        } else {
+            return String.valueOf(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 +718,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 +726,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 +754,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,124 +768,176 @@ 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();
-            }
-
-            // 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;
+            if (dataStore.getLastPumpStatus().getEventDate().getTime() > 0) {
+                pumpStatusData = dataStore.getLastPumpStatus();
             }
 
-            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);
-        }
-    }
+                mChart.getViewport().setYAxisBoundsManual(true);
+                if (configurationStore.isMmolxl()) {
+                    mChart.getViewport().setMinY(80 / MMOLXLFACTOR);
+                    mChart.getViewport().setMaxY(120 / MMOLXLFACTOR);
+                } else {
+                    mChart.getViewport().setMinY(80);
+                    mChart.getViewport().setMaxY(120);
+                }
+                mChart.postInvalidate();
+                return;
+            }
 
-    private class RefreshDataReceiver extends BroadcastReceiver {
+            DataPoint[] entries = new DataPoint[size];
 
-        @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;
+            int pos = 0;
+            for (PumpStatusEvent pumpStatus : results) {
+                // turn your data into Entry objects
+                entries[pos++] = new DataPoint(pumpStatus.getSgvDate(), (double) pumpStatus.getSgv());
             }
 
-            PumpStatusEvent pumpStatusData = null;
+            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);
 
-            PumpInfo pump = getActivePump();
+                PointsGraphSeries sgvSerie = new PointsGraphSeries(entries);
+//                sgvSerie.setSize(3.6f);
+//                sgvSerie.setColor(Color.LTGRAY);
 
-            if (pump != null && pump.isValid()) {
-                pumpStatusData = pump.getPumpHistory().last();
-            } else {
-                return;
-            }
 
-            long nextPoll = pumpStatusData.getEventDate().getTime() + pumpStatusData.getPumpTimeOffset()
-                    + MedtronicCnlIntentService.POLL_GRACE_PERIOD_MS + MedtronicCnlIntentService.POLL_PERIOD_MS;
-            startCgmService(nextPoll);
+                sgvSerie.setOnDataPointTapListener(new OnDataPointTapListener() {
+                    DateFormat mFormat = DateFormat.getTimeInstance(DateFormat.MEDIUM);
 
-            // 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();
+                    @Override
+                    public void onTap(Series series, DataPointInterface dataPoint) {
+                        double sgv = dataPoint.getY();
 
-            if (results.size() > 0) {
-                mRealm.executeTransaction(new Realm.Transaction() {
+                        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();
+                        boolean mmolxl = configurationStore.isMmolxl();
+                        if (sgv < (mmolxl ? 4.5 : 80))
+                            paint.setColor(Color.RED);
+                        else if (sgv <= (mmolxl ? 10 : 180))
+                            paint.setColor(Color.GREEN);
+                        else if (sgv <= (mmolxl ? 14 : 260))
+                            paint.setColor(Color.YELLOW);
+                        else
+                            paint.setColor(Color.RED);
+                        canvas.drawCircle(x, y, 3.6f, paint);
                     }
                 });
+
+                mChart.getViewport().setYAxisBoundsManual(false);
+                mChart.addSeries(sgvSerie);
+            } else {
+                if (entries.length > 0) {
+                    ((PointsGraphSeries) mChart.getSeries().get(0)).resetData(entries);
+                }
             }
 
-            // TODO - handle isOffline in NightscoutUploadIntentService?
-            uploadCgmData();
+            // set 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();
         }
     }
 
@@ -700,10 +956,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 +987,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..69106aa982bcf1bb08895ca3db121cc70597893d 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();
-
-        this.getPumpSession().setPackedLinkKey(packedLinkKey);
+        RequestLinkKeyResponseMessage response = new RequestLinkKeyRequestMessage(mPumpSession).send(mDevice);
+        this.getPumpSession().setKey(response.getKey());
 
         Log.d(TAG, String.format("Finished requestLinkKey. linkKey = '%s'", this.getPumpSession().getKey()));
     }
 
-    public byte negotiateChannel(byte lastRadioChannel) throws IOException, ChecksumException, TimeoutException {
+    public byte negotiateChannel(byte lastRadioChannel) throws IOException, ChecksumException, TimeoutException, EncryptionException {
         ArrayList<Byte> radioChannels = new ArrayList<>(Arrays.asList(ArrayUtils.toObject(RADIO_CHANNELS)));
 
         if (lastRadioChannel != 0x00) {
@@ -300,26 +148,14 @@ public class MedtronicCnlReader implements ContourNextLinkMessageHandler {
         for (byte channel : radioChannels) {
             Log.d(TAG, String.format("negotiateChannel: trying channel '%d'...", channel));
             mPumpSession.setRadioChannel(channel);
-            new ChannelNegotiateMessage(mPumpSession).send(this);
-
-            // Don't care what the 0x81 response message is at this stage
-            Log.d(TAG, "negotiateChannel: Reading 0x81 message");
-            readMessage();
-            // The 0x80 message
-            Log.d(TAG, "negotiateChannel: Reading 0x80 message");
-            ContourNextLinkMessage response = ContourNextLinkBinaryMessage.fromBytes(readMessage());
-            byte[] responseBytes = response.encode();
-
-            Log.d(TAG, "negotiateChannel: Check response length");
-            if (responseBytes.length > 46) {
-                // Looks promising, let's check the last byte of the payload to make sure
-                if (responseBytes[76] == mPumpSession.getRadioChannel()) {
-                    break;
-                } else {
-                    throw new IOException(String.format(Locale.getDefault(), "Expected to get a message for channel %d. Got %d", mPumpSession.getRadioChannel(), responseBytes[76]));
-                }
+            ChannelNegotiateResponseMessage response = new ChannelNegotiateRequestMessage(mPumpSession).send(mDevice);
+
+            if (response.getRadioChannel() == mPumpSession.getRadioChannel()) {
+                mPumpSession.setRadioRSSI(response.getRadioRSSI());
+                break;
             } else {
-                mPumpSession.setRadioChannel((byte) 0);
+                mPumpSession.setRadioChannel((byte)0);
+                mPumpSession.setRadioRSSI((byte)0);
             }
         }
 
@@ -327,216 +163,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..18de1f899ec1354ce7605ccd486c4aaa0bf845d8
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/CloseConnectionRequestMessage.java
@@ -0,0 +1,43 @@
+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) {
+            }
+        }
+
+        CloseConnectionResponseMessage response = this.getResponse(readMessage(mDevice));
+        return response;
+    }
+
+    @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..13858f38e76f4e761bd2bd13e6a343683ab34daf 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,57 @@
 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);
+
+        PumpStatusResponseMessage response = this.getResponse(payload);
+        return response;
+    }
+
+    @Override
+    protected PumpStatusResponseMessage getResponse(byte[] payload) throws ChecksumException, EncryptionException, IOException, UnexpectedMessageException {
+        return new PumpStatusResponseMessage(mPumpSession, payload);
+    }
 }
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/PumpStatusResponseMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/PumpStatusResponseMessage.java
index 06e9a0f3357d2456f64a5eb7c84ac3218d1e35ee..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..c4063b53e0681c472885b90ebf4ee6d7aa9ce231 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,52 @@
 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);
+
+        PumpTimeResponseMessage response = this.getResponse(payload);
+        return response;
+    }
+
+    @Override
+    protected PumpTimeResponseMessage getResponse(byte[] payload) throws ChecksumException, EncryptionException, IOException, UnexpectedMessageException {
+        return new PumpTimeResponseMessage(mPumpSession, payload);
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/PumpTimeResponseMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/PumpTimeResponseMessage.java
index 3437dd69c65e9adfe188966ea14cbc1a6f8afb18..2005880b7929d3dbd808d1f7be6e6592f07b1948 100644
--- a/app/src/main/java/info/nightscout/android/medtronic/message/PumpTimeResponseMessage.java
+++ b/app/src/main/java/info/nightscout/android/medtronic/message/PumpTimeResponseMessage.java
@@ -1,21 +1,51 @@
 package info.nightscout.android.medtronic.message;
 
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Date;
+
+import info.nightscout.android.BuildConfig;
 import info.nightscout.android.medtronic.MedtronicCnlSession;
+import info.nightscout.android.medtronic.exception.ChecksumException;
+import info.nightscout.android.medtronic.exception.EncryptionException;
+import info.nightscout.android.medtronic.exception.UnexpectedMessageException;
+import info.nightscout.android.utils.HexDump;
 
 /**
  * Created by lgoedhart on 27/03/2016.
  */
-public class PumpTimeResponseMessage extends MedtronicReceiveMessage {
-    protected PumpTimeResponseMessage(CommandType commandType, CommandAction commandAction, MedtronicCnlSession pumpSession, byte[] payload) {
-        super(commandType, commandAction, pumpSession, payload);
-    }
+public class PumpTimeResponseMessage extends MedtronicSendMessageResponseMessage {
+    private static final String TAG = PumpTimeResponseMessage.class.getSimpleName();
+
+    private Date pumpTime;
 
-    public static ContourNextLinkMessage fromBytes(MedtronicCnlSession pumpSession, byte[] bytes) throws ChecksumException, EncryptionException {
-        // TODO - turn this into a factory
-        ContourNextLinkMessage message = MedtronicReceiveMessage.fromBytes(pumpSession, bytes);
+    protected PumpTimeResponseMessage(MedtronicCnlSession pumpSession, byte[] payload) throws EncryptionException, ChecksumException, UnexpectedMessageException {
+        super(pumpSession, payload);
 
-        // TODO - Validate the MessageType
+        if (this.encode().length < (61 + 8)) {
+            // Invalid message. Return an invalid date.
+            // TODO - deal with this more elegantly
+            Log.e(TAG, "Invalid message received for getPumpTime");
+            throw new UnexpectedMessageException("Invalid message received for getPumpTime");
+        } else {
+            ByteBuffer dateBuffer = ByteBuffer.allocate(8);
+            dateBuffer.order(ByteOrder.BIG_ENDIAN);
+            dateBuffer.put(this.encode(), 0x3d, 8);
+
+            if (BuildConfig.DEBUG) {
+                String outputString = HexDump.dumpHexString(dateBuffer.array());
+                Log.d(TAG, "PAYLOAD: " + outputString);
+            }
+
+            long rtc = dateBuffer.getInt(0) & 0x00000000ffffffffL;
+            long offset = dateBuffer.getInt(4);
+            pumpTime = MessageUtils.decodeDateTime(rtc, offset);
+        }
+    }
 
-        return message;
+    public Date getPumpTime() {
+        return pumpTime;
     }
 }
diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/ReadHistoryInfoRequestMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/ReadHistoryInfoRequestMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..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..358cc203123e64d456f05c97cd116eb33e3af9a2
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/medtronic/service/MedtronicCnlAlarmManager.java
@@ -0,0 +1,94 @@
+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; // Alarm id
+
+    private static PendingIntent pendingIntent = null;
+    private static AlarmManager alarmManager = null;
+    private static long nextAlarm = Long.MAX_VALUE;
+
+    public static void setContext(Context context) {
+        cancelAlarm();
+
+        alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+        Intent intent = new Intent(context, MedtronicCnlAlarmReceiver.class);
+        pendingIntent = PendingIntent.getBroadcast(context, ALARM_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+    }
+
+    // Setting the alarm in 15 seconds from now
+    public static void setAlarm() {
+        setAlarm(System.currentTimeMillis());
+    }
+
+    /**
+     * set the alarm in the future
+     *
+     * @param inFuture number of millin in the future
+     */
+    public static void setAlarmAfterMillis(long inFuture) {
+        setAlarm(System.currentTimeMillis() + inFuture);
+    }
+
+    // Setting the alarm to call onRecieve
+    public static void setAlarm(long millis) {
+        if (alarmManager == null || pendingIntent == null)
+            return;
+
+        Log.d(TAG, "request to set Alarm at " + new Date(millis));
+
+        long now = System.currentTimeMillis();
+        // don't trigger the past
+        if (millis < now)
+            millis = now;
+
+        // only accept alarm nearer than the last one
+        //if (nextAlarm < millis && nextAlarm > now) {
+        //    return;
+        //}
+
+        cancelAlarm();
+
+        nextAlarm = millis;
+
+        Log.d(TAG, "Alarm set to fire at " + new Date(millis));
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            alarmManager.setAlarmClock(new AlarmManager.AlarmClockInfo(millis, null), pendingIntent);
+        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+            // Android 5.0.0 + 5.0.1 (e.g. Galaxy S4) has a bug.
+            // Alarms are not exact. Fixed in 5.0.2 oder CM12
+            alarmManager.setExact(AlarmManager.RTC_WAKEUP, millis, pendingIntent);
+        } else {
+            alarmManager.set(AlarmManager.RTC_WAKEUP, millis, pendingIntent);
+        }
+    }
+
+    // restarting the alarm after MedtronicCnlIntentService.POLL_PERIOD_MS from now
+    public static void restartAlarm() {
+        //setAlarmAfterMillis(MainActivity.pollInterval + MedtronicCnlIntentService.POLL_GRACE_PERIOD_MS);
+        setAlarmAfterMillis(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..9b46b76a7c1261f278ce403d497c7495e08c24da 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,17 @@ 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();
 
     public MedtronicCnlIntentService() {
         super(MedtronicCnlIntentService.class.getName());
@@ -97,6 +105,29 @@ public class MedtronicCnlIntentService extends IntentService {
     protected void onHandleIntent(Intent intent) {
         Log.d(TAG, "onHandleIntent called");
 
+        long timePollStarted = System.currentTimeMillis(),
+                timePollExpected = timePollStarted,
+                timeLastGoodSGV = dataStore.getLastPumpStatus().getEventDate().getTime();
+
+        short pumpBatteryLevel = dataStore.getLastPumpStatus().getBatteryPercentage();
+
+        if (timeLastGoodSGV != 0) {
+            timePollExpected = timeLastGoodSGV + POLL_PERIOD_MS + POLL_GRACE_PERIOD_MS + (POLL_PERIOD_MS * ((timePollStarted - 1000L - (timeLastGoodSGV + POLL_GRACE_PERIOD_MS)) / POLL_PERIOD_MS));
+        }
+
+        // avoid polling when too close to sensor-pump comms
+        if (((timePollExpected - timePollStarted) > 5000L) && ((timePollExpected - timePollStarted) < (POLL_GRACE_PERIOD_MS + 45000L))) {
+            sendStatus("Please wait: Poll due in " + ((timePollExpected - timePollStarted) / 1000L) + " seconds");
+            MedtronicCnlAlarmManager.setAlarm(timePollExpected);
+            MedtronicCnlAlarmReceiver.completeWakefulIntent(intent);
+            return;
+        }
+
+        long pollInterval = configurationStore.getPollInterval();
+        if ((pumpBatteryLevel > 0) && (pumpBatteryLevel <= 25)) {
+            pollInterval = configurationStore.getLowBatteryPollInterval();
+        }
+
         if (!hasUsbHostFeature()) {
             sendStatus("It appears that this device doesn't support USB OTG.");
             Log.e(TAG, "Device does not support USB OTG");
@@ -107,7 +138,7 @@ public class MedtronicCnlIntentService extends IntentService {
 
         UsbDevice cnlStick = UsbHidDriver.getUsbDevice(mUsbManager, USB_VID, USB_PID);
         if (cnlStick == null) {
-            sendStatus("USB connection error. Is the Bayer Contour Next Link plugged in?");
+            sendStatus("USB connection error. Is the Contour Next Link plugged in?");
             Log.w(TAG, "USB connection error. Is the CNL plugged in?");
 
             // TODO - set status if offline or Nightscout not reachable
@@ -134,14 +165,16 @@ public class MedtronicCnlIntentService extends IntentService {
             return;
         }
 
+        DateFormat df = new SimpleDateFormat("HH:mm:ss", Locale.US);
+
         MedtronicCnlReader cnlReader = new MedtronicCnlReader(mHidDevice);
 
         Realm realm = Realm.getDefaultInstance();
         realm.beginTransaction();
 
         try {
-            sendStatus("Connecting to the Contour Next Link...");
-            Log.d(TAG, "Connecting to the Contour Next Link.");
+            sendStatus("Connecting to Contour Next Link");
+            Log.d(TAG, "Connecting to Contour Next Link");
             cnlReader.requestDeviceInfo();
 
             // Is the device already configured?
@@ -151,11 +184,7 @@ public class MedtronicCnlIntentService extends IntentService {
                     .findFirst();
 
             if (info == null) {
-                // TODO - use realm.createObject()?
-                info = new ContourNextLinkInfo();
-                info.setSerialNumber(cnlReader.getStickSerial());
-
-                info = realm.copyToRealm(info);
+                info = realm.createObject(ContourNextLinkInfo.class, cnlReader.getStickSerial());
             }
 
             cnlReader.getPumpSession().setStickSerial(info.getSerialNumber());
@@ -165,6 +194,7 @@ public class MedtronicCnlIntentService extends IntentService {
             try {
                 cnlReader.enterPassthroughMode();
                 cnlReader.openConnection();
+
                 cnlReader.requestReadInfo();
 
                 String key = info.getKey();
@@ -180,30 +210,33 @@ public class MedtronicCnlIntentService extends IntentService {
 
                 long pumpMAC = cnlReader.getPumpSession().getPumpMAC();
                 Log.i(TAG, "PumpInfo MAC: " + (pumpMAC & 0xffffff));
-                MainActivity.setActivePumpMac(pumpMAC);
                 PumpInfo activePump = realm
                         .where(PumpInfo.class)
                         .equalTo("pumpMac", pumpMAC)
                         .findFirst();
 
                 if (activePump == null) {
-                    activePump = realm.createObject(PumpInfo.class);
-                    activePump.setPumpMac(pumpMAC);
+                    activePump = realm.createObject(PumpInfo.class, pumpMAC);
                 }
 
+                activePump.updateLastQueryTS();
+
                 byte radioChannel = cnlReader.negotiateChannel(activePump.getLastRadioChannel());
                 if (radioChannel == 0) {
-                    sendStatus("Could not communicate with the 640g. Are you near the pump?");
-                    Log.i(TAG, "Could not communicate with the 640g. Are you near the pump?");
+                    sendStatus("Could not communicate with the pump. Is it nearby?");
+                    Log.i(TAG, "Could not communicate with the pump. Is it nearby?");
+                    pollInterval = configurationStore.getPollInterval() / (configurationStore.isReducePollOnPumpAway() ? 2L : 1L); // reduce polling interval to half until pump is available
                 } else {
+                    dataStore.setActivePumpMac(pumpMAC);
+
                     activePump.setLastRadioChannel(radioChannel);
-                    sendStatus(String.format(Locale.getDefault(), "Connected to Contour Next Link on channel %d.", (int) radioChannel));
+                    sendStatus(String.format(Locale.getDefault(), "Connected on channel %d  RSSI: %d%%", (int) radioChannel, cnlReader.getPumpSession().getRadioRSSIpercentage()));
                     Log.d(TAG, String.format("Connected to Contour Next Link on channel %d.", (int) radioChannel));
-                    cnlReader.beginEHSMSession();
 
+                    // read pump status
                     PumpStatusEvent pumpRecord = realm.createObject(PumpStatusEvent.class);
 
-                    String deviceName = String.format("medtronic-640g://%s", cnlReader.getStickSerial());
+                    String deviceName = String.format("medtronic-600://%s", cnlReader.getStickSerial());
                     activePump.setDeviceName(deviceName);
 
                     // TODO - this should not be necessary. We should reverse lookup the device name from PumpInfo
@@ -216,45 +249,71 @@ public class MedtronicCnlIntentService extends IntentService {
                     // TODO - send ACTION to MainActivity to show offset between pump and uploader.
                     pumpRecord.setPumpTimeOffset(pumpOffset);
                     pumpRecord.setPumpDate(new Date(pumpTime - pumpOffset));
-                    cnlReader.getPumpStatus(pumpRecord, pumpOffset);
-                    activePump.getPumpHistory().add(pumpRecord);
+                    cnlReader.updatePumpStatus(pumpRecord);
 
-                    cnlReader.endEHSMSession();
-
-                    boolean cancelTransaction = true;
                     if (pumpRecord.getSgv() != 0) {
+                        String offsetSign = "";
+                        if (pumpOffset > 0) {
+                            offsetSign = "+";
+                        }
+                        sendStatus("SGV: " + MainActivity.strFormatSGV(pumpRecord.getSgv()) + "  At: " + df.format(pumpRecord.getEventDate().getTime()) + "  Pump: " + offsetSign + (pumpOffset / 1000L) + "sec");  //note: event time is currently stored with offset
+
+                        // Check if pump sent old event when new expected
+                        if (pumpRecord != null &&
+                                dataStore.getLastPumpStatus() != null &&
+                                dataStore.getLastPumpStatus().getPumpDate() != null &&
+                                ((pumpRecord.getPumpDate().getTime() - dataStore.getLastPumpStatus().getPumpDate().getTime()) < 5000L) &&
+                                ((timePollExpected - timePollStarted) < 5000L)) {
+                            sendStatus("Pump sent old SGV event, re-polling...");
+                        }
+
+                        //MainActivity.timeLastGoodSGV =  pumpRecord.getEventDate().getTime(); // track last good sgv event time
+                        //MainActivity.pumpBattery =  pumpRecord.getBatteryPercentage(); // track pump battery
+                        timeLastGoodSGV = pumpRecord.getEventDate().getTime();
+                        dataStore.clearUnavailableSGVCount(); // reset unavailable sgv count
+
                         // Check that the record doesn't already exist before committing
                         RealmResults<PumpStatusEvent> checkExistingRecords = activePump.getPumpHistory()
                                 .where()
-                                .equalTo("eventDate", pumpRecord.getEventDate())
+                                .equalTo("eventDate", pumpRecord.getEventDate())    // >>>>>>> check as event date may not = exact pump event date due to it being stored with offset added this could lead to dup events due to slight variability in time offset
                                 .equalTo("sgv", pumpRecord.getSgv())
                                 .findAll();
 
                         // There should be the 1 record we've already added in this transaction.
-                        if (checkExistingRecords.size() <= 1) {
-                            realm.commitTransaction();
-                            cancelTransaction = false;
+                        if (checkExistingRecords.size() == 0) {
+                            activePump.getPumpHistory().add(pumpRecord);
+                            dataStore.setLastPumpStatus(pumpRecord);
                         }
 
-                        // Tell the Main Activity we have new data
-                        sendMessage(Constants.ACTION_REFRESH_DATA);
+                    } else {
+                        sendStatus("SGV: unavailable from pump");
+                        dataStore.incUnavailableSGVCount(); // poll clash detection
                     }
 
-                    if (cancelTransaction) {
-                        realm.cancelTransaction();
-                    }
+                    realm.commitTransaction();
+                    // Tell the Main Activity we have new data
+                    sendMessage(Constants.ACTION_UPDATE_PUMP);
                 }
+
             } catch (UnexpectedMessageException e) {
                 Log.e(TAG, "Unexpected Message", e);
                 sendStatus("Communication Error: " + e.getMessage());
+                pollInterval = configurationStore.getPollInterval() / (configurationStore.isReducePollOnPumpAway() ? 2L : 1L);
+            } catch (TimeoutException e) {
+                Log.e(TAG, "Timeout communicating with the Contour Next Link.", e);
+                sendStatus("Timeout communicating with the Contour Next Link.");
+                pollInterval = configurationStore.getPollInterval() / (configurationStore.isReducePollOnPumpAway() ? 2L : 1L);
             } catch (NoSuchAlgorithmException e) {
                 Log.e(TAG, "Could not determine CNL HMAC", e);
                 sendStatus("Error connecting to Contour Next Link: Hashing error.");
             } finally {
-                //TODO : 05.11.2016 has the close to be here?
-                cnlReader.closeConnection();
-                cnlReader.endPassthroughMode();
-                cnlReader.endControlMode();
+                try {
+                    cnlReader.closeConnection();
+                    cnlReader.endPassthroughMode();
+                    cnlReader.endControlMode();
+                } catch (NoSuchAlgorithmException e) {
+                }
+
             }
         } catch (IOException e) {
             Log.e(TAG, "Error connecting to Contour Next Link.", e);
@@ -279,10 +338,33 @@ public class MedtronicCnlIntentService extends IntentService {
                 }
                 realm.close();
             }
-
             // TODO - set status if offline or Nightscout not reachable
             sendToXDrip();
             uploadToNightscout();
+
+            // smart polling and pump-sensor poll clash detection
+            long lastActualPollTime = timePollStarted;
+            if (timeLastGoodSGV > 0) {
+                lastActualPollTime = timeLastGoodSGV + POLL_GRACE_PERIOD_MS + (POLL_PERIOD_MS * ((System.currentTimeMillis() - (timeLastGoodSGV + POLL_GRACE_PERIOD_MS)) / POLL_PERIOD_MS));
+            }
+            long nextActualPollTime = lastActualPollTime + POLL_PERIOD_MS;
+            long nextRequestedPollTime = lastActualPollTime + pollInterval;
+            if ((nextRequestedPollTime - System.currentTimeMillis()) < 10000L) {
+                nextRequestedPollTime = nextActualPollTime;
+            }
+            // extended unavailable SGV may be due to clash with the current polling time
+            // while we wait for a good SGV event, polling is auto adjusted by offsetting the next poll based on miss count
+            if (dataStore.getUnavailableSGVCount() > 0) {
+                if (timeLastGoodSGV == 0) {
+                    nextRequestedPollTime += POLL_PERIOD_MS / 5L; // if there is a uploader/sensor poll clash on startup then this will push the next attempt out by 60 seconds
+                } else if (dataStore.getUnavailableSGVCount() > 2) {
+                    sendStatus("Warning: No SGV available from pump for " + dataStore.getUnavailableSGVCount() + " attempts");
+                    nextRequestedPollTime += ((long) ((dataStore.getUnavailableSGVCount() - 2) % 5)) * (POLL_PERIOD_MS / 10L); // adjust poll time in 1/10 steps to avoid potential poll clash (max adjustment at 5/10)
+                }
+            }
+            MedtronicCnlAlarmManager.setAlarm(nextRequestedPollTime);
+            sendStatus("Next poll due at: " + df.format(nextRequestedPollTime));
+
             MedtronicCnlAlarmReceiver.completeWakefulIntent(intent);
         }
     }
@@ -324,8 +406,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..fdc460fc15c0c491269d82f633bb1daa86a11862 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
@@ -1,7 +1,10 @@
 package info.nightscout.android.model.medtronicNg;
 
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
 import java.util.Date;
 
+import info.nightscout.android.utils.ConfigurationStore;
 import io.realm.RealmObject;
 import io.realm.annotations.Ignore;
 import io.realm.annotations.Index;
@@ -45,13 +48,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 +84,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 +278,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 +321,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..f29c2b894784635831a530b7f8d8bc7b030e95c6
--- /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 = 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..ce79ab7900ef741ecb3c8f35550eebd019f34d69 100644
--- a/app/src/main/java/info/nightscout/android/upload/nightscout/serializer/EntriesSerializer.java
+++ b/app/src/main/java/info/nightscout/android/upload/nightscout/serializer/EntriesSerializer.java
@@ -42,6 +42,35 @@ public class EntriesSerializer implements JsonSerializer<PumpStatusEvent> {
         }
     }
 
+    public static String getDirectionStringStatus(PumpStatusEvent.CGM_TREND trend) {
+        switch( trend ) {
+            case NONE:
+                return "NONE";
+            case DOUBLE_UP:
+                return "DoubleUp";
+            case SINGLE_UP:
+                return "SingleUp";
+            case FOURTY_FIVE_UP:
+                return "FortyFiveUp";
+            case FLAT:
+                return "Flat";
+            case FOURTY_FIVE_DOWN:
+                return "FortyFiveDown";
+            case SINGLE_DOWN:
+                return "SingleDown";
+            case DOUBLE_DOWN:
+                return "DoubleDown";
+            case NOT_COMPUTABLE:
+                return "NOT COMPUTABLE";
+            case RATE_OUT_OF_RANGE:
+                return "RATE OUT OF RANGE";
+            case NOT_SET:
+                return "NONE";
+            default:
+                return "NOT COMPUTABLE"; // TODO - should this be something else?
+        }
+    }
+
     @Override
     public JsonElement serialize(PumpStatusEvent src, Type typeOfSrc, JsonSerializationContext context) {
         final JsonObject jsonObject = new JsonObject();
diff --git a/app/src/main/java/info/nightscout/android/utils/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..fbd2a02011f40323c64f1b6cf977c59869555a3b
--- /dev/null
+++ b/app/src/main/java/info/nightscout/android/utils/DataStore.java
@@ -0,0 +1,78 @@
+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();
+
+            // 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..20a1183a83ecbae9432f0c048d5088a29f9ba8d5 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()));
 
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..344f44c17f69993a2e7ac637afc014f314b3ba3d 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..f67e0965164757e5c4131440ac5a5a3478909546 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,172 @@
     </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>
+
+                <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>
+
+                <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..00a38b6e1b8665f0e37a2f0d53f23ae380bd840f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2,7 +2,7 @@
 <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,7 +16,7 @@
         <item>Info</item>
         <item>Debug</item>
     </string-array>
-    <string name="title_activity_login">CareLink login</string>
+    <string name="title_activity_manage_cnl">Manage your Contour Next Link devices</string>
 
     <!-- Strings related to login -->
     <string name="prompt_username">CareLink Username</string>
@@ -25,9 +25,11 @@
     <string name="action_sign_in_short">Retrieve keys</string>
     <string name="error_invalid_password">Password is required</string>
     <string name="error_incorrect_password">The Username or password is incorrect</string>
+    <string name="error_client_protocol_exception">Could not communicate with server.</string>
+    <string name="error_io_exception">Could not connect to server.</string>
+    <string name="error_class_not_found_exception">Application code error.</string>
+    <string name="error_http_response">Server responded with error. Could be username or password problem.</string>
     <string name="error_field_required">This field is required</string>
-    <string name="preference_nightscout_url">Nightscout URL</string>
-    <string name="preference_api_secret">API SECRET</string>
     <string name="prompt_carelink_username_password">Please enter your CareLink details.\nThey will not be stored.</string>
     <string name="close">Close</string>
     <string name="register_contour_next_link">Registered Devices</string>
@@ -50,4 +52,15 @@
     <string name="dummy_button">Dummy Button</string>
     <string name="dummy_content">DUMMY\nCONTENT</string>
     <string name="menu_name_status">Status</string>
+    <string name="menu_name_battery_status">unknown</string>
+    <string name="preference_api_secret">YOUR.API.SECRET</string>
+    <string name="preference_nightscout_url">YOUR.NIGHTSCOUT.URL</string>
+
+    <string name="preferences_poll_interval">Poll interval</string>
+    <string name="preferences_low_battery_poll_interval">Poll interval on low pump battery</string>
+    <string name="no_registered_contour_next_link_devices">No registered Contour Next Link devices</string>
+    <string name="to_register_a_contour_next_link_you_must_first_plug_it_in_and_get_a_reading_from_the_pump">To register a Contour Next Link you must first plug it in, and get a reading from the pump.</string>
+    <string name="serial_number">Serial number</string>
+    <string name="preferences_chart_interval">Chart Zoom</string>
+
 </resources>
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 9e6793c85ec60101ef37b053d3af5d9caefd74b1..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/app/update.json b/app/update.json
new file mode 100644
index 0000000000000000000000000000000000000000..4c58d9459cd6887fd236f5890d94bb16a77243f8
--- /dev/null
+++ b/app/update.json
@@ -0,0 +1,10 @@
+{
+  "latestVersion": "0.4.0",
+  "latestVersionCode": "7",
+  "url": "https://github.com/pazaan/600SeriesAndroidUploader/releases",
+  "releaseNotes": [
+    "- Remove dependency on CareLink login. No more CareLink login issues!",
+    "- Local broadcasting to xDrip+",
+    "- Stability fixes"
+  ]
+}
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