diff --git a/640gAndroidUploader.iml b/640gAndroidUploader.iml index 21ad486e1dacdc31dfcf484957078ef9fbc29307..dd11c422309587fbfb2c40dcfbc1c5c64bb0d573 100644 --- a/640gAndroidUploader.iml +++ b/640gAndroidUploader.iml @@ -13,7 +13,7 @@ <content url="file://$MODULE_DIR$"> <excludeFolder url="file://$MODULE_DIR$/.gradle" /> </content> - <orderEntry type="inheritedJdk" /> + <orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" /> <orderEntry type="sourceFolder" forTests="false" /> </component> </module> \ No newline at end of file diff --git a/README.md b/README.md index 1b749f09ecacdb0af5e8e4f2ced8707c9a370492..599ec520263dbf85f894c94c8bc2c130f1d7a9e9 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,27 @@ This is an Android app to upload data from a MiniMed 640G insulin pump to a Nigh <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> <br/><br/> +#### Development - getting started + + - Install [Android Studio](https://developer.android.com/studio/index.html) + - Install [Fabric Plugin](https://fabric.io), enable Crashlytics, this should create `app/fabric.properties` with your fabric apiSecret and also add a fabric key to `app/src/AndroidManifest.xml` + + ``` + <meta-data + android:name="io.fabric.ApiKey" + android:value="YOUR-FABRIC-KEY" /> + ``` + + (**take care not to commit this change**) + - Create a [BugFender](https://app.bugfender.com) account, create `app/bugfender.properties` and populate with + + ``` + apiKey=YOUR-BUGFENDER-KEY + ``` + - Set up a virtual device in the AVD manager or connect an Android phone with USB debugging enabled. + + - 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)* * Uses the [android-service-example](https://code.launchpad.net/~binwiederhier/+junk/android-service-example) by Philipp C. Heckel diff --git a/app/app.iml b/app/app.iml deleted file mode 100644 index a296ac55fc53b536763a4cfd375a0802b8956039..0000000000000000000000000000000000000000 --- a/app/app.iml +++ /dev/null @@ -1,157 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<module external.linked.project.id=":app" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="640gAndroidUploader" external.system.module.version="0.3.0-SNAPSHOT" type="JAVA_MODULE" version="4"> - <component name="FacetManager"> - <facet type="android-gradle" name="Android-Gradle"> - <configuration> - <option name="GRADLE_PROJECT_PATH" value=":app" /> - </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/apt/debug" isTestSource="false" generated="true" /> - <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/fabric/res/debug" type="java-resource" /> - <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/apt/androidTest/debug" isTestSource="true" generated="true" /> - <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/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" /> - <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" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" /> - <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/animated-vector-drawable/23.4.0/jars" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/23.4.0/jars" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/design/23.4.0/jars" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/recyclerview-v7/23.4.0/jars" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/23.4.0/jars" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-vector-drawable/23.4.0/jars" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.bugfender.sdk/android/0.4/jars" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.crashlytics.sdk.android/answers/1.3.6/jars" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.crashlytics.sdk.android/beta/1.1.4/jars" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.crashlytics.sdk.android/crashlytics-core/2.3.8/jars" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.crashlytics.sdk.android/crashlytics/2.5.5/jars" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.getkeepsafe.relinker/relinker/1.2.1/jars" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.mikepenz/fastadapter/1.5.2/jars" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.mikepenz/google-material-typeface/2.2.0.1.original/jars" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.mikepenz/iconics-core/2.6.0/jars" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.mikepenz/materialdrawer/5.2.9/jars" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.mikepenz/materialize/0.8.8/jars" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/io.fabric.sdk.android/fabric/1.3.10/jars" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/io.realm/realm-android-library/1.0.0/jars" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/uk.co.chrisjenx/calligraphy/2.2.0/jars" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-safeguard" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" /> - <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/outputs" /> - <excludeFolder url="file://$MODULE_DIR$/build/tmp" /> - </content> - <orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" /> - <orderEntry type="sourceFolder" forTests="false" /> - <orderEntry type="library" exported="" name="okio-1.8.0" level="project" /> - <orderEntry type="library" exported="" name="support-annotations-23.4.0" level="project" /> - <orderEntry type="library" exported="" name="google-material-typeface-2.2.0.1.original" level="project" /> - <orderEntry type="library" exported="" name="MPAndroidChart-v2.2.5" level="project" /> - <orderEntry type="library" exported="" name="okhttp-2.4.0" level="project" /> - <orderEntry type="library" exported="" name="relinker-1.2.1" level="project" /> - <orderEntry type="library" exported="" name="retrofit-1.9.0" level="project" /> - <orderEntry type="library" exported="" name="realm-annotations-1.0.0" level="project" /> - <orderEntry type="library" exported="" name="animated-vector-drawable-23.4.0" level="project" /> - <orderEntry type="library" exported="" name="commons-lang3-3.4" level="project" /> - <orderEntry type="library" exported="" name="support-v4-23.4.0" level="project" /> - <orderEntry type="library" exported="" name="android-0.4" level="project" /> - <orderEntry type="library" exported="" name="recyclerview-v7-23.4.0" level="project" /> - <orderEntry type="library" exported="" name="slf4j-api-1.7.2" level="project" /> - <orderEntry type="library" exported="" name="support-vector-drawable-23.4.0" level="project" /> - <orderEntry type="library" exported="" name="materialize-0.8.8" level="project" /> - <orderEntry type="library" exported="" name="realm-android-library-1.0.0" level="project" /> - <orderEntry type="library" exported="" name="appcompat-v7-23.4.0" level="project" /> - <orderEntry type="library" exported="" name="fabric-1.3.10" level="project" /> - <orderEntry type="library" exported="" name="design-23.4.0" level="project" /> - <orderEntry type="library" exported="" name="crashlytics-2.5.5" level="project" /> - <orderEntry type="library" exported="" name="crashlytics-core-2.3.8" level="project" /> - <orderEntry type="library" exported="" name="beta-1.1.4" level="project" /> - <orderEntry type="library" exported="" name="fastadapter-1.5.2" level="project" /> - <orderEntry type="library" exported="" name="retrofit-2.1.0" level="project" /> - <orderEntry type="library" exported="" name="gson-2.7" level="project" /> - <orderEntry type="library" exported="" name="converter-gson-2.1.0" level="project" /> - <orderEntry type="library" exported="" name="materialdrawer-5.2.9" level="project" /> - <orderEntry type="library" exported="" name="calligraphy-2.2.0" level="project" /> - <orderEntry type="library" exported="" name="okhttp-3.3.0" level="project" /> - <orderEntry type="library" exported="" name="answers-1.3.6" level="project" /> - <orderEntry type="library" exported="" name="iconics-core-2.6.0" level="project" /> - <orderEntry type="library" exported="" name="org.apache.http.legacy-android-23" level="project" /> - </component> -</module> \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 0fa95d999428d38e08ad5f488c7cb844285ca4c7..321863d615cb36775b630d58e4edd1721a5f7a06 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,3 +1,5 @@ +import org.ajoberstar.grgit.Grgit + buildscript { repositories { maven { url 'https://maven.fabric.io/public' } @@ -5,6 +7,8 @@ buildscript { dependencies { classpath 'io.fabric.tools:gradle:1.21.6' + classpath 'io.realm:realm-gradle-plugin:1.1.1' + classpath 'org.ajoberstar:grgit:1.5.0' } } plugins { @@ -12,24 +16,41 @@ plugins { } apply plugin: 'com.android.application' -apply plugin: 'io.fabric' -apply plugin: 'realm-android' repositories { maven { url 'https://maven.fabric.io/public' } maven { url "https://jitpack.io" } } +apply plugin: 'io.fabric' +apply plugin: 'realm-android' def gitVersion() { - def process = ['sh', '-c', 'git tag -l | grep -c ".*" -'].execute().text.trim() - return process.toInteger() + 1 + // 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('..')) + + // should result in the same value as running + // git tag -l | wc -l or git tag -l | grep -c ".*" - + def numOfTags = ext.repo.tag.list().size() + return numOfTags +} + +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('..')) + + return ext.repo.log().first().id.substring(0, 7) } -def getBugfenderApiKey(){ +def getBugfenderApiKey() { Properties properties = new Properties() properties.load(new FileInputStream("app/bugfender.properties")) - return "\"" + properties.getProperty("apiKey", "") +"\"" + return "\"" + properties.getProperty("apiKey", "") + "\"" } android { @@ -46,7 +67,7 @@ android { applicationId "info.nightscout.android" minSdkVersion 14 targetSdkVersion 23 - versionName project.properties['version'] + versionName project.properties['version'] + "/" + gitCommitId() versionCode gitVersion() buildConfigField "String", "BUGFENDER_API_KEY", getBugfenderApiKey() } @@ -74,7 +95,7 @@ task signRelease << { '-digestalg', 'SHA1', '-keystore', - '/Users/lgoedhart/keystores/nightscout_android.jks', + '/Users/lennart/keystores/nightscout_android.jks', 'app/build/outputs/apk/app-release-unsigned.apk', 'nightscoutandroidkey' ] @@ -94,7 +115,7 @@ task signRelease << { task zipalignRelease << { def command = [ - '/Users/lgoedhart/Library/Android/sdk/build-tools/23.0.3/zipalign', + '/Users/lennart/Library/Android/sdk/build-tools/23.0.3/zipalign', '-v', '4', 'app/build/outputs/apk/app-release-unsigned.apk', @@ -116,14 +137,14 @@ task zipalignRelease << { release { tagTemplate = 'v${version}' - buildTasks = ['assembleRelease'] + buildTasks = ['build'] beforeReleaseBuild.dependsOn 'clean' afterReleaseBuild.dependsOn 'signRelease', 'zipalignRelease' } dependencies { compile files('libs/slf4j-api-1.7.2.jar') - compile('com.crashlytics.sdk.android:crashlytics:2.5.5@aar') { + compile('com.crashlytics.sdk.android:crashlytics:2.6.5@aar') { transitive = true; } compile('com.mikepenz:materialdrawer:5.2.9@aar') { @@ -133,8 +154,9 @@ dependencies { compile 'org.apache.commons:commons-lang3:3.4' compile 'com.mikepenz:google-material-typeface:2.2.0.1.original@aar' compile 'uk.co.chrisjenx:calligraphy:2.2.0' - compile 'com.bugfender.sdk:android:0.4' - compile 'com.github.PhilJay:MPAndroidChart:v2.2.5' + 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.google.code.gson:gson:2.7' compile 'com.squareup.retrofit2:retrofit:2.1.0' diff --git a/app/gradle.properties b/app/gradle.properties index 0db303f3a5e5d10435a82acd862bec04bc146452..1dd0e26d2b59d84aa9b32ab72e3c5b99065efabe 100644 --- a/app/gradle.properties +++ b/app/gradle.properties @@ -1 +1 @@ -version=0.4.0-SNAPSHOT +version=0.5.0-SNAPSHOT diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b875e849aaf3675d64cbee090851630aa761a929..f7d7185b04799440fc232b3ffd10ef632e76220c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" package="info.nightscout.android"> - <uses-sdk android:maxSdkVersion="23" /> + <uses-sdk android:minSdkVersion="14" android:maxSdkVersion="23" + tools:overrideLibrary="com.github.mikephil.charting.data.realm"/> <uses-feature android:name="android.hardware.usb.host" /> @@ -16,6 +18,9 @@ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.USB_PERMISSION" /> + <!-- allow to disable battery optimization --> + <uses-permission-sdk-23 android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" /> + <application android:name=".UploaderApplication" android:allowBackup="true" @@ -24,6 +29,7 @@ android:supportsRtl="true" android:theme="@style/AppTheme"> + <!-- I have set screenOrientation to "portrait" to avoid the restart of AsyncTasks when you rotate the phone --> <activity android:name=".medtronic.MainActivity" @@ -31,6 +37,7 @@ android:label="@string/app_name" android:launchMode="singleTask" android:screenOrientation="portrait"> + <intent-filter android:icon="@drawable/ic_launcher"> <category android:name="android.intent.category.DEFAULT" /> @@ -61,15 +68,22 @@ android:label="@string/title_activity_login" android:theme="@style/SettingsTheme" /> + <activity android:name=".medtronic.StatusActivity" /> + <service android:name=".upload.nightscout.NightscoutUploadIntentService" android:icon="@drawable/ic_launcher" /> - + <service + android:name=".xdrip_plus.XDripPlusUploadIntentService" + android:icon="@drawable/ic_launcher" /> <service android:name=".medtronic.service.MedtronicCnlIntentService" android:icon="@drawable/ic_launcher" /> - <activity android:name=".medtronic.StatusActivity"></activity> + <receiver android:name=".medtronic.service.MedtronicCnlAlarmReceiver" /> + <receiver android:name=".upload.nightscout.NightscoutUploadReceiver" /> + <receiver android:name=".xdrip_plus.XDripPlusUploadReceiver" /> + </application> </manifest> \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/android/USB/UsbHidDriver.java b/app/src/main/java/info/nightscout/android/USB/UsbHidDriver.java index ea63fa44d6f6f2019bfae1345156a09cd1a114ab..f7f1007adf8cf3c7bdc22e95a94a30cf1f0b481f 100644 --- a/app/src/main/java/info/nightscout/android/USB/UsbHidDriver.java +++ b/app/src/main/java/info/nightscout/android/USB/UsbHidDriver.java @@ -9,8 +9,6 @@ import android.util.Log; import java.io.IOException; -//import com.hoho.android.usbserial.driver.UsbId; - /** * USB HID Driver implementation. * @@ -89,14 +87,10 @@ public class UsbHidDriver extends CommonUsbDriver { int readAmt = Math.min(dest.length, mReadBuffer.length); numBytesRead = mConnection.bulkTransfer(mReadEndpoint, mReadBuffer, readAmt, timeoutMillis); - if (numBytesRead < 0) { - // This sucks: we get -1 on timeout, not 0 as preferred. - // We *should* use UsbRequest, except it has a bug/api oversight - // where there is no way to determine the number of bytes read - // in response :\ -- http://b.android.com/28023 - return 0; + if (numBytesRead > 0) { + System.arraycopy(mReadBuffer, 0, dest, 0, numBytesRead); } - System.arraycopy(mReadBuffer, 0, dest, 0, numBytesRead); + mConnection.releaseInterface(mInterface); } return numBytesRead; diff --git a/app/src/main/java/info/nightscout/android/UploaderApplication.java b/app/src/main/java/info/nightscout/android/UploaderApplication.java index 13fe3d456d7a3afbd0368ca2e10a47d1dbd5ec53..aa1944885e68507b24370f9bf2f2b75fe774bda1 100644 --- a/app/src/main/java/info/nightscout/android/UploaderApplication.java +++ b/app/src/main/java/info/nightscout/android/UploaderApplication.java @@ -32,7 +32,7 @@ public class UploaderApplication extends Application { Fabric.with(this, new Crashlytics()); } if (prefs.getBoolean(getString(R.string.preferences_enable_answers), true)) { - Fabric.with(this, new Answers()); + Fabric.with(this, new Answers(), new Crashlytics()); } if (prefs.getBoolean(getString(R.string.preferences_enable_remote_logcat), false)) { @@ -41,7 +41,10 @@ public class UploaderApplication extends Application { Bugfender.setDeviceString("NightscoutURL", prefs.getString(getString(R.string.preference_nightscout_url), "Not set")); } - RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(this).build(); + RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(this) + .deleteRealmIfMigrationNeeded() + .build(); + Realm.setDefaultConfiguration(realmConfiguration); } } diff --git a/app/src/main/java/info/nightscout/android/medtronic/GetHmacAndKeyActivity.java b/app/src/main/java/info/nightscout/android/medtronic/GetHmacAndKeyActivity.java index 64c30b3aea050802499e70eac5b49b00e373fbea..dfd43740a7f4ce86fafde82fc746d661476fd109 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/GetHmacAndKeyActivity.java +++ b/app/src/main/java/info/nightscout/android/medtronic/GetHmacAndKeyActivity.java @@ -62,19 +62,8 @@ import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper; */ public class GetHmacAndKeyActivity extends AppCompatActivity implements LoaderCallbacks<Cursor> { - /** - * Keep track of the login task to ensure we can cancel it if requested. - */ - // TODO - Replace with Rx.Java - private GetHmacAndKey mHmacAndKeyTask = null; - // UI references. - private EditText mUsernameView; - private EditText mPasswordView; - private View mProgressView; - private View mLoginFormView; private TextView mRegisteredStickView; - private MenuItem mLoginMenuItem; @Override protected void onCreate(Bundle savedInstanceState) { @@ -82,25 +71,8 @@ public class GetHmacAndKeyActivity extends AppCompatActivity implements LoaderCa setContentView(R.layout.activity_login); getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setTitle("Register USB"); - - // Set up the login form. - mUsernameView = (EditText) findViewById(R.id.username); + getSupportActionBar().setTitle("Registered Devices"); - mPasswordView = (EditText) findViewById(R.id.password); - mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() { - @Override - public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) { - if (id == EditorInfo.IME_ACTION_DONE) { - attemptLogin(); - return true; - } - return false; - } - }); - - mLoginFormView = findViewById(R.id.login_form); - mProgressView = findViewById(R.id.login_progress); mRegisteredStickView = (TextView) findViewById(R.id.registered_usb_devices); showRegisteredSticks(); @@ -108,21 +80,12 @@ public class GetHmacAndKeyActivity extends AppCompatActivity implements LoaderCa @Override public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.menu_register_usb, menu); - - mLoginMenuItem = menu.findItem(R.id.action_menu_login); - mLoginMenuItem.setIcon(new IconicsDrawable(this, GoogleMaterial.Icon.gmd_cloud_download).color(Color.WHITE).actionBar()); - return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case R.id.action_menu_login: - attemptLogin(); - break; case android.R.id.home: finish(); break; @@ -135,96 +98,12 @@ public class GetHmacAndKeyActivity extends AppCompatActivity implements LoaderCa super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase)); } - /** - * Attempts to sign in or register the account specified by the login form. - * If there are form errors (invalid username, missing fields, etc.), the - * errors are presented and no actual login attempt is made. - */ - private void attemptLogin() { - if (mHmacAndKeyTask != null || !checkOnline("Please connect to the Internet", "You must be online to register your USB stick.")) { - return; - } - - // Reset errors. - mUsernameView.setError(null); - mPasswordView.setError(null); - - // Store values at the time of the login attempt. - String username = mUsernameView.getText().toString(); - String password = mPasswordView.getText().toString(); - - boolean cancel = false; - View focusView = null; - - // Check for a valid password, if the user entered one. - if (TextUtils.isEmpty(password)) { - mPasswordView.setError(getString(R.string.error_invalid_password)); - focusView = mPasswordView; - cancel = true; - } - - // Check for a valid username address. - if (TextUtils.isEmpty(username)) { - mUsernameView.setError(getString(R.string.error_field_required)); - focusView = mUsernameView; - cancel = true; - } - - if (cancel) { - // There was an error; don't attempt login and focus the first - // form field with an error. - focusView.requestFocus(); - } else { - // Show a progress spinner, and kick off a background task to - // perform the user login attempt. - showProgress(true); - mHmacAndKeyTask = new GetHmacAndKey(username, password); - mHmacAndKeyTask.execute((Void) null); - } - } - - /** - * Shows the progress UI and hides the login form. - */ - @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2) - private void showProgress(final boolean show) { - // On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow - // for very easy animations. If available, use these APIs to fade-in - // the progress spinner. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) { - int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime); - - mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE); - mLoginFormView.animate().setDuration(shortAnimTime).alpha( - show ? 0 : 1).setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE); - } - }); - - mProgressView.setVisibility(show ? View.VISIBLE : View.GONE); - mProgressView.animate().setDuration(shortAnimTime).alpha( - show ? 1 : 0).setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mProgressView.setVisibility(show ? View.VISIBLE : View.GONE); - } - }); - } else { - // The ViewPropertyAnimator APIs are not available, so simply show - // and hide the relevant UI components. - mProgressView.setVisibility(show ? View.VISIBLE : View.GONE); - mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE); - } - } - private void showRegisteredSticks() { Realm realm = Realm.getDefaultInstance(); RealmResults<ContourNextLinkInfo> results = realm.where(ContourNextLinkInfo.class).findAll(); - String deviceTableHtml = "<big><b>Registered Devices</b></big><br/>"; + String deviceTableHtml = ""; for (ContourNextLinkInfo info : results) { String longSerial = info.getSerialNumber(); @@ -236,29 +115,6 @@ public class GetHmacAndKeyActivity extends AppCompatActivity implements LoaderCa mRegisteredStickView.setText(Html.fromHtml(deviceTableHtml)); } - private boolean checkOnline(String title, String message) { - ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo netInfo = cm.getActiveNetworkInfo(); - - boolean isOnline = (netInfo != null && netInfo.isConnectedOrConnecting()); - - if (!isOnline) { - new AlertDialog.Builder(this, R.style.AppTheme) - .setTitle(title) - .setMessage(message) - .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 isOnline; - } - @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { return null; @@ -271,116 +127,4 @@ public class GetHmacAndKeyActivity extends AppCompatActivity implements LoaderCa @Override public void onLoaderReset(Loader<Cursor> loader) { } - - /** - * Represents an asynchronous login/registration task used to authenticate - * the user. - */ - public class GetHmacAndKey extends AsyncTask<Void, Void, Boolean> { - - private final String mUsername; - private final String mPassword; - - GetHmacAndKey(String username, String password) { - mUsername = username; - mPassword = password; - } - - @Override - protected Boolean doInBackground(final Void... params) { - try { - DefaultHttpClient client = new DefaultHttpClient(); - HttpPost loginPost = new HttpPost("https://carelink.minimed.eu/patient/j_security_check"); - List<NameValuePair> nameValuePairs = new ArrayList<>(); - nameValuePairs.add(new BasicNameValuePair("j_username", mUsername)); - nameValuePairs.add(new BasicNameValuePair("j_password", mPassword)); - nameValuePairs.add(new BasicNameValuePair("j_character_encoding", "UTF-8")); - loginPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, "UTF-8")); - HttpResponse response = client.execute(loginPost); - - if (response.getStatusLine().getStatusCode() == 200) { - // Get the HMAC/keys for every serial we have seen - Realm realm = Realm.getDefaultInstance(); - - RealmResults<ContourNextLinkInfo> results = realm.where(ContourNextLinkInfo.class).findAll(); - for (ContourNextLinkInfo info : results) { - String longSerial = info.getSerialNumber(); - - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - ObjectOutputStream hmacRequest = new ObjectOutputStream(buffer); - hmacRequest.writeInt(0x1c); - hmacRequest.writeObject(longSerial.replaceAll("\\d+-", "")); - - HttpPost hmacPost = new HttpPost("https://carelink.minimed.eu/patient/secure/SnapshotServer/"); - hmacPost.setEntity(new ByteArrayEntity(buffer.toByteArray())); - hmacPost.setHeader("Content-type", "application/octet-stream"); - response = client.execute(hmacPost); - - ByteArrayInputStream inputBuffer = new ByteArrayInputStream(EntityUtils.toByteArray(response.getEntity())); - ObjectInputStream hmacResponse = new ObjectInputStream(inputBuffer); - byte[] hmacBytes = (byte[]) hmacResponse.readObject(); - ArrayUtils.reverse(hmacBytes); - String hmac = MessageUtils.byteArrayToHexString(hmacBytes); - - buffer.reset(); - inputBuffer.reset(); - - ObjectOutputStream keyRequest = new ObjectOutputStream(buffer); - keyRequest.writeInt(0x1f); - keyRequest.writeObject(longSerial); - - HttpPost keyPost = new HttpPost("https://carelink.minimed.eu/patient/secure/SnapshotServer/"); - keyPost.setEntity(new ByteArrayEntity(buffer.toByteArray())); - keyPost.setHeader("Content-type", "application/octet-stream"); - response = client.execute(keyPost); - - inputBuffer = new ByteArrayInputStream(EntityUtils.toByteArray(response.getEntity())); - ObjectInputStream keyResponse = new ObjectInputStream(inputBuffer); - keyResponse.readInt(); // Throw away the first int. Not sure what it does - String key = MessageUtils.byteArrayToHexString((byte[]) keyResponse.readObject()); - - realm.beginTransaction(); - info.setHmac(hmac); - info.setKey(key); - realm.commitTransaction(); - } - - return true; - } - - } catch (ClientProtocolException e) { - return false; - } catch (IOException e) { - return false; - } catch (ClassNotFoundException e) { - return false; - } - - return false; - } - - @Override - protected void onPostExecute(final Boolean success) { - mHmacAndKeyTask = null; - - if (success) { - showRegisteredSticks(); - mLoginMenuItem.setVisible(false); - mLoginFormView.setVisibility(View.GONE); - mProgressView.setVisibility(View.GONE); - InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(mLoginFormView.getWindowToken(), 0); - } else { - showProgress(false); - mPasswordView.setError(getString(R.string.error_incorrect_password)); - mPasswordView.requestFocus(); - } - } - - @Override - protected void onCancelled() { - mHmacAndKeyTask = null; - showProgress(false); - } - } } \ 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 9c632ee91d22a1c65128f9215a2e42ae3eb1c11c..d1249934460aebef1968d8ab832c1dcdbb04f5f3 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/MainActivity.java +++ b/app/src/main/java/info/nightscout/android/medtronic/MainActivity.java @@ -3,6 +3,7 @@ package info.nightscout.android.medtronic; import android.app.AlarmManager; import android.app.NotificationManager; import android.app.PendingIntent; +import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; @@ -12,10 +13,14 @@ import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; +import android.net.Uri; import android.os.BatteryManager; +import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.PowerManager; 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; @@ -32,7 +37,8 @@ import android.view.View; import android.widget.TextView; import android.widget.TextView.BufferType; -import com.github.mikephil.charting.data.realm.implementation.RealmLineData; +import com.github.mikephil.charting.charts.LineChart; +import com.github.mikephil.charting.data.LineData; import com.github.mikephil.charting.data.realm.implementation.RealmLineDataSet; import com.github.mikephil.charting.interfaces.datasets.ILineDataSet; import com.github.mikephil.charting.utils.ColorTemplate; @@ -43,40 +49,50 @@ import com.mikepenz.materialdrawer.DrawerBuilder; import com.mikepenz.materialdrawer.model.PrimaryDrawerItem; import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; +import java.text.DateFormat; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Date; import java.util.Locale; +import java.util.Queue; +import java.util.concurrent.ArrayBlockingQueue; import info.nightscout.android.R; import info.nightscout.android.USB.UsbHidDriver; import info.nightscout.android.eula.Eula; import info.nightscout.android.eula.Eula.OnEulaAgreedTo; +import info.nightscout.android.medtronic.service.MedtronicCnlAlarmReceiver; import info.nightscout.android.medtronic.service.MedtronicCnlIntentService; -import info.nightscout.android.model.CgmStatusEvent; import info.nightscout.android.model.medtronicNg.ContourNextLinkInfo; +import info.nightscout.android.model.medtronicNg.PumpInfo; +import info.nightscout.android.model.medtronicNg.PumpStatusEvent; import info.nightscout.android.settings.SettingsActivity; -import info.nightscout.android.upload.MedtronicNG.PumpStatusRecord; import info.nightscout.android.upload.nightscout.NightscoutUploadIntentService; import io.realm.Realm; import io.realm.RealmResults; import io.realm.Sort; import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper; -/* Main activity for the MainActivity program */ public class MainActivity extends AppCompatActivity implements OnSharedPreferenceChangeListener, OnEulaAgreedTo { private static final String TAG = MainActivity.class.getSimpleName(); public static int batLevel = 0; - public static PumpStatusRecord pumpStatusRecord = new PumpStatusRecord(); + private static long activePumpMac; boolean mEnableCgmService = true; SharedPreferences prefs = null; + private PumpInfo mActivePump; private TextView mTextViewLog; // This will eventually move to a status page. - private Intent mCnlIntentService; + private LineChart mChart; private Intent mNightscoutUploadService; private Handler mUiRefreshHandler = new Handler(); private Runnable mUiRefreshRunnable = new RefreshDisplayRunnable(); private Realm mRealm; + private StatusMessageReceiver statusMessageReceiver = new StatusMessageReceiver(); + private MedtronicCnlAlarmReceiver medtronicCnlAlarmReceiver = new MedtronicCnlAlarmReceiver(); + + public static void setActivePumpMac(long pumpMac) { + activePumpMac = pumpMac; + } @Override public void onCreate(Bundle savedInstanceState) { @@ -84,7 +100,6 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc super.onCreate(savedInstanceState); mRealm = Realm.getDefaultInstance(); - mCnlIntentService = new Intent(this, MedtronicCnlIntentService.class); mNightscoutUploadService = new Intent(this, NightscoutUploadIntentService.class); setContentView(R.layout.activity_main); @@ -96,8 +111,30 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc stopCgmService(); } + // 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 { + // ignoring battery optimizations required for constant connection + // to peripheral device - eg CGM transmitter. + final Intent intent = new Intent(); + intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); + intent.setData(Uri.parse("package:" + packageName)); + startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.d(TAG, "Device does not appear to support battery optimization whitelisting!"); + } + } + } + LocalBroadcastManager.getInstance(this).registerReceiver( - new StatusMessageReceiver(), + statusMessageReceiver, new IntentFilter(MedtronicCnlIntentService.Constants.ACTION_STATUS_MESSAGE)); LocalBroadcastManager.getInstance(this).registerReceiver( new RefreshDataReceiver(), @@ -138,7 +175,7 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc .withIcon(GoogleMaterial.Icon.gmd_settings) .withSelectable(false); final PrimaryDrawerItem itemRegisterUsb = new PrimaryDrawerItem() - .withName("Register Contour Next Link") + .withName("Registered Devices") .withIcon(GoogleMaterial.Icon.gmd_usb) .withSelectable(false); final PrimaryDrawerItem itemStopCollecting = new PrimaryDrawerItem() @@ -186,7 +223,7 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc } else if (drawerItem.equals(itemGetNow)) { startCgmService(); } else if (drawerItem.equals(itemClearLog)) { - mTextViewLog.setText("", BufferType.EDITABLE); + clearLogText(); } return false; @@ -195,6 +232,7 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc .build(); mTextViewLog = (TextView) findViewById(R.id.textview_log); + mChart = (LineChart) findViewById(R.id.chart); } @Override @@ -207,6 +245,9 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc @Override protected void attachBaseContext(Context newBase) { super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase)); + + // setup self handling alarm receiver + medtronicCnlAlarmReceiver.setContext(getBaseContext()); } @Override @@ -231,8 +272,8 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc private boolean hasDetectedCnl() { if (mRealm.where(ContourNextLinkInfo.class).count() == 0) { new AlertDialog.Builder(this, R.style.AppTheme) - .setTitle("Contour Next Link not detected") - .setMessage("To register a Contour Next Link you must first plug it in.") + .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) { @@ -251,7 +292,6 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc UsbDevice cnlDevice = UsbHidDriver.getUsbDevice(usbManager, MedtronicCnlIntentService.USB_VID, MedtronicCnlIntentService.USB_PID); return !(usbManager != null && cnlDevice != null && !usbManager.hasPermission(cnlDevice)); - } private void waitForUsbPermission() { @@ -276,6 +316,11 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc startDisplayRefreshLoop(); } + private void clearLogText() { + statusMessageReceiver.clearMessages(); + //mTextViewLog.setText("", BufferType.EDITABLE); + } + private void startDisplayRefreshLoop() { mUiRefreshHandler.post(mUiRefreshRunnable); } @@ -285,41 +330,21 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc } private void startCgmService() { - startCgmService(0L); + startCgmService(System.currentTimeMillis() + 1000); } - private void startCgmService(long runAtTime) { + private void startCgmService(long initialPoll) { Log.i(TAG, "startCgmService called"); if (!mEnableCgmService) { return; } - if (runAtTime == 0L) { - startService(mCnlIntentService); - } else { - AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - PendingIntent pending = PendingIntent.getService(this, 0, mCnlIntentService, 0); - - alarmManager.set(AlarmManager.RTC_WAKEUP, runAtTime, pending); - } - } - - private void startCgmServicePolling(long initialPoll) { - Log.i(TAG, "startCgmServicePolling called"); - - if (!mEnableCgmService) { - return; - } + //clearLogText(); // Cancel any existing polling. stopCgmService(); - - AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - PendingIntent pending = PendingIntent.getService(this, 0, mCnlIntentService, 0); - - alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, - initialPoll, MedtronicCnlIntentService.POLL_PERIOD_MS, pending); + medtronicCnlAlarmReceiver.setAlarm(initialPoll); } private void uploadCgmData() { @@ -328,11 +353,7 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc private void stopCgmService() { Log.i(TAG, "stopCgmService called"); - - AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - PendingIntent pending = PendingIntent.getService(this, 0, mCnlIntentService, 0); - - alarmManager.cancel(pending); + medtronicCnlAlarmReceiver.cancelAlarm(); } private void showDisconnectionNotification(String title, String message) { @@ -369,15 +390,16 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc @Override protected void onDestroy() { Log.i(TAG, "onDestroy called"); + super.onDestroy(); + PreferenceManager.getDefaultSharedPreferences(getBaseContext()).unregisterOnSharedPreferenceChangeListener(this); cancelDisplayRefreshLoop(); + mRealm.close(); + if (!mEnableCgmService) { stopCgmService(); } - - mRealm.close(); - super.onDestroy(); } @Override @@ -418,7 +440,7 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc } } - private String renderTrendHtml(CgmStatusEvent.TREND trend) { + private String renderTrendHtml(PumpStatusEvent.CGM_TREND trend) { switch (trend) { case DOUBLE_UP: return "⇈"; @@ -439,13 +461,88 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc } } + private PumpInfo getActivePump() { + if (activePumpMac != 0L && (mActivePump == null || !mActivePump.isValid() || mActivePump.getPumpMac() != activePumpMac)) { + mActivePump = null; + + PumpInfo pump = mRealm + .where(PumpInfo.class) + .equalTo("pumpMac", MainActivity.activePumpMac) + .findFirst(); + + if (pump != null && pump.isValid()) { + mActivePump = pump; + } + } + + return mActivePump; + } + private class StatusMessageReceiver extends BroadcastReceiver { + private class StatusMessage { + private long timestamp; + private String message; + + public StatusMessage(String message) { + this(System.currentTimeMillis(), message); + } + + public StatusMessage(long timestamp, String message) { + this.timestamp = timestamp; + this.message = message; + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String toString() { + return DateFormat.getTimeInstance(DateFormat.MEDIUM).format(timestamp) + ": " + message; + } + } + + private Queue<StatusMessage> messages = new ArrayBlockingQueue<>(10); + @Override public void onReceive(Context context, Intent intent) { String message = intent.getStringExtra(MedtronicCnlIntentService.Constants.EXTENDED_DATA); - Log.i(TAG, "Message Receiver: " + message); - mTextViewLog.setText(mTextViewLog.getText() + "\n" + message, BufferType.EDITABLE); + + synchronized (messages) { + while (messages.size() > 8) { + messages.poll(); + } + messages.add(new StatusMessage(message)); + } + + StringBuilder sb = new StringBuilder(); + for (StatusMessage msg : messages) { + if (sb.length() > 0) + sb.append("\n"); + sb.append(msg); + } + + mTextViewLog.setText(sb.toString(), BufferType.EDITABLE); + } + + public void clearMessages() { + synchronized (messages) { + messages.clear(); + } + + mTextViewLog.setText("", BufferType.EDITABLE); } } @@ -463,20 +560,22 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc } TextView textViewTrend = (TextView) findViewById(R.id.textview_trend); TextView textViewIOB = (TextView) findViewById(R.id.textview_iob); - //LineChart chart = (LineChart) findViewById(R.id.chart); - // Get the most recently written CGM record. - RealmResults<CgmStatusEvent> results = - mRealm.where(CgmStatusEvent.class) - .findAllSorted("eventDate", Sort.ASCENDING); + // Get the most recently written CGM record for the active pump. + PumpStatusEvent pumpStatusData = null; - CgmStatusEvent cgmRecord = null; + PumpInfo pump = getActivePump(); - if (results.size() > 0) { - cgmRecord = results.last(); + if (pump != null && pump.isValid()) { + pumpStatusData = pump.getPumpHistory().last(); } - if (cgmRecord == null) { + // 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; } @@ -488,34 +587,34 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc String sgvString, units; if (prefs.getBoolean("mmolxl", false)) { - float fBgValue = (float) cgmRecord.getSgv(); + float fBgValue = (float) pumpStatusData.getSgv(); sgvString = df.format(fBgValue / 18.016f); units = "mmol/L"; Log.d(TAG, "mmolxl true --> " + sgvString); } else { - sgvString = String.valueOf(cgmRecord.getSgv()); + sgvString = String.valueOf(pumpStatusData.getSgv()); units = "mg/dL"; Log.d(TAG, "mmolxl false --> " + sgvString); } textViewBg.setText(sgvString); textViewUnits.setText(units); - textViewBgTime.setText(DateUtils.getRelativeTimeSpanString(cgmRecord.getEventDate().getTime())); - textViewTrend.setText(Html.fromHtml(renderTrendHtml(cgmRecord.getTrend()))); - textViewIOB.setText(String.format(Locale.getDefault(), "%.2f", pumpStatusRecord.activeInsulin)); + 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); + //updateChart(results); // Run myself again in 60 seconds; mUiRefreshHandler.postDelayed(this, 60000L); } - private void updateChart(RealmResults<CgmStatusEvent> results) { - RealmLineDataSet<CgmStatusEvent> lineDataSet = new RealmLineDataSet<>(results, "sgv", "eventDate"); + private void updateChart(RealmResults<PumpStatusEvent> results) { + RealmLineDataSet<PumpStatusEvent> lineDataSet = new RealmLineDataSet<>(results, "eventDate", "sgv"); lineDataSet.setDrawCircleHole(false); lineDataSet.setColor(ColorTemplate.rgb("#FF5722")); @@ -526,11 +625,11 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc ArrayList<ILineDataSet> dataSets = new ArrayList<ILineDataSet>(); dataSets.add(lineDataSet); - RealmLineData lineData = new RealmLineData(results, "eventDate", dataSets); + LineData lineData = new LineData(dataSets); // set data - //chart.setMinimumHeight(200); - //chart.setData(lineData); + mChart.setMinimumHeight(200); + mChart.setData(lineData); } } @@ -538,18 +637,30 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc @Override public void onReceive(Context context, Intent intent) { - CgmStatusEvent record = mRealm.where(CgmStatusEvent.class) - .findAll() - .last(); + // 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; + } + + PumpStatusEvent pumpStatusData = null; - long nextPoll = record.getEventDate().getTime() + MedtronicCnlIntentService.POLL_GRACE_PERIOD_MS + MedtronicCnlIntentService.POLL_PERIOD_MS; - startCgmServicePolling(nextPoll); - Log.d(TAG, "Next Poll at " + new Date(nextPoll).toString()); + PumpInfo pump = getActivePump(); + + 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); // Delete invalid or old records from Realm // TODO - show an error message if the valid records haven't been uploaded - final RealmResults<CgmStatusEvent> results = - mRealm.where(CgmStatusEvent.class) + final RealmResults<PumpStatusEvent> results = + mRealm.where(PumpStatusEvent.class) .equalTo("sgv", 0) .or() .lessThan("eventDate", new Date(System.currentTimeMillis() - (24 * 60 * 60 * 1000))) @@ -569,6 +680,7 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc // TODO - handle isOffline in NightscoutUploadIntentService? uploadCgmData(); + refreshDisplay(); } } diff --git a/app/src/main/java/info/nightscout/android/medtronic/MedtronicCNLSession.java b/app/src/main/java/info/nightscout/android/medtronic/MedtronicCNLSession.java deleted file mode 100644 index 6c1ac2f55a6295cb70d7ada74b9825bd6755926f..0000000000000000000000000000000000000000 --- a/app/src/main/java/info/nightscout/android/medtronic/MedtronicCNLSession.java +++ /dev/null @@ -1,78 +0,0 @@ -package info.nightscout.android.medtronic; - -/** - * Created by lgoedhart on 26/03/2016. - */ -public class MedtronicCNLSession { - private byte[] HMAC; - private byte[] key; - - private long linkMAC; - private long pumpMAC; - - private byte radioChannel; - private int bayerSequenceNumber = 1; - private int medtronicSequenceNumber = 1; - - public byte[] getHMAC() { - return HMAC; - } - - public byte[] getKey() { - return key; - } - public byte[] getIV() { - byte[] iv = new byte[key.length]; - System.arraycopy(key,0,iv,0,key.length); - iv[0] = radioChannel; - return iv; - } - - public long getLinkMAC() { - return linkMAC; - } - - public void setLinkMAC( long linkMAC ) { - this.linkMAC = linkMAC; - } - - public long getPumpMAC() { - return pumpMAC; - } - - public void setPumpMAC( long pumpMAC ) { - this.pumpMAC = pumpMAC; - } - - public int getBayerSequenceNumber() { - return bayerSequenceNumber; - } - - public int getMedtronicSequenceNumber() { - return medtronicSequenceNumber; - } - - public byte getRadioChannel() { - return radioChannel; - } - - public void incrBayerSequenceNumber() { - bayerSequenceNumber++; - } - - public void incrMedtronicSequenceNumber() { - medtronicSequenceNumber++; - } - - public void setRadioChannel(byte radioChannel) { - this.radioChannel = radioChannel; - } - - public void setHMAC( byte[] hmac ) { - this.HMAC = hmac; - } - - public void setKey( byte[] key ) { - this.key = key; - } -} diff --git a/app/src/main/java/info/nightscout/android/medtronic/MedtronicCNLReader.java b/app/src/main/java/info/nightscout/android/medtronic/MedtronicCnlReader.java similarity index 57% rename from app/src/main/java/info/nightscout/android/medtronic/MedtronicCNLReader.java rename to app/src/main/java/info/nightscout/android/medtronic/MedtronicCnlReader.java index 8b56ea37e359278c03f25173894a2bd12f9b5ea0..6dd908f0dd3f57b8d8c585d51690eaee4b82df5b 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/MedtronicCNLReader.java +++ b/app/src/main/java/info/nightscout/android/medtronic/MedtronicCnlReader.java @@ -2,11 +2,16 @@ package info.nightscout.android.medtronic; import android.util.Log; +import org.apache.commons.lang3.ArrayUtils; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.math.BigDecimal; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.Locale; import java.util.concurrent.TimeoutException; @@ -25,22 +30,24 @@ import info.nightscout.android.medtronic.message.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.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.ReadInfoResponseMessage; +import info.nightscout.android.medtronic.message.RequestLinkKeyResponseMessage; import info.nightscout.android.medtronic.message.UnexpectedMessageException; -import info.nightscout.android.medtronic.service.MedtronicCnlIntentService; -import info.nightscout.android.model.CgmStatusEvent; +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 implements ContourNextLinkMessageHandler { - private static final String TAG = MedtronicCnlIntentService.class.getSimpleName(); + private static final String TAG = MedtronicCnlReader.class.getSimpleName(); private static final int USB_BLOCKSIZE = 64; private static final int READ_TIMEOUT_MS = 5000; @@ -49,19 +56,40 @@ public class MedtronicCNLReader implements ContourNextLinkMessageHandler { private static final byte[] RADIO_CHANNELS = {0x14, 0x11, 0x0e, 0x17, 0x1a}; private UsbHidDriver mDevice; - private MedtronicCNLSession mPumpSession = new MedtronicCNLSession(); + private MedtronicCnlSession mPumpSession = new MedtronicCnlSession(); private String mStickSerial = null; - public MedtronicCNLReader(UsbHidDriver device) { + 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; } - public MedtronicCNLSession getPumpSession() { + public MedtronicCnlSession getPumpSession() { return mPumpSession; } @@ -70,28 +98,28 @@ public class MedtronicCNLReader implements ContourNextLinkMessageHandler { byte[] responseBuffer = new byte[USB_BLOCKSIZE]; int bytesRead; - int messageSize; + int messageSize = 0; do { bytesRead = mDevice.read(responseBuffer, READ_TIMEOUT_MS); - if (bytesRead == 0) { + if (bytesRead == -1) { throw new TimeoutException("Timeout waiting for response from pump"); + } else if (bytesRead > 0) { + // Validate the header + ByteBuffer header = ByteBuffer.allocate(3); + header.put(responseBuffer, 0, 3); + String headerString = new String(header.array()); + if (!headerString.equals(BAYER_USB_HEADER)) { + throw new IOException("Unexpected header received"); + } + messageSize = responseBuffer[3]; + responseMessage.write(responseBuffer, 4, messageSize); + } else { + Log.w(TAG, "readMessage: got a zero-sized response."); } + } while (bytesRead > 0 && messageSize == 60); - // Validate the header - ByteBuffer header = ByteBuffer.allocate(3); - header.put(responseBuffer, 0, 3); - String headerString = new String(header.array()); - if (!headerString.equals(BAYER_USB_HEADER)) { - throw new IOException("Unexpected header received"); - } - messageSize = responseBuffer[3]; - responseMessage.write(responseBuffer, 4, messageSize); - } while (bytesRead > 0 && (messageSize + 4) == bytesRead); - // TODO - how to deal with messages that finish on the boundary? - - // FIXME - remove debugging String responseString = HexDump.dumpHexString(responseMessage.toByteArray()); Log.d(TAG, "READ: " + responseString); @@ -182,27 +210,6 @@ public class MedtronicCNLReader implements ContourNextLinkMessageHandler { } } - private static CgmStatusEvent.TREND fromMessageByte(byte messageByte) { - switch( messageByte ) { - case (byte) 0x60: - return CgmStatusEvent.TREND.FLAT; - case (byte) 0xc0: - return CgmStatusEvent.TREND.DOUBLE_UP; - case (byte) 0xa0: - return CgmStatusEvent.TREND.SINGLE_UP; - case (byte) 0x80: - return CgmStatusEvent.TREND.FOURTY_FIVE_UP; - case (byte) 0x40: - return CgmStatusEvent.TREND.FOURTY_FIVE_DOWN; - case (byte) 0x20: - return CgmStatusEvent.TREND.SINGLE_DOWN; - case (byte) 0x00: - return CgmStatusEvent.TREND.DOUBLE_DOWN; - default: - return CgmStatusEvent.TREND.NOT_COMPUTABLE; - } - } - public void enterControlMode() throws IOException, TimeoutException, UnexpectedMessageException { boolean doRetry = false; @@ -223,21 +230,26 @@ public class MedtronicCNLReader implements ContourNextLinkMessageHandler { } public void enterPassthroughMode() throws IOException, TimeoutException, UnexpectedMessageException { + Log.d(TAG, "Begin enterPasshtroughMode"); new ContourNextLinkCommandMessage("W|").send(this); checkControlMessage(readMessage(), ASCII.ACK.value); new ContourNextLinkCommandMessage("Q|").send(this); checkControlMessage(readMessage(), ASCII.ACK.value); new ContourNextLinkCommandMessage("1|").send(this); checkControlMessage(readMessage(), ASCII.ACK.value); + Log.d(TAG, "Finished enterPasshtroughMode"); } - public void openConnection() throws IOException, TimeoutException { + public void openConnection() throws IOException, TimeoutException, NoSuchAlgorithmException { + Log.d(TAG, "Begin openConnection"); new ContourNextLinkBinaryMessage(ContourNextLinkBinaryMessage.CommandType.OPEN_CONNECTION, mPumpSession, mPumpSession.getHMAC()).send(this); // FIXME - We need to care what the response message is - wrong MAC and all that readMessage(); + Log.d(TAG, "Finished openConnection"); } public void requestReadInfo() throws IOException, TimeoutException, EncryptionException, ChecksumException { + Log.d(TAG, "Begin requestReadInfo"); new ContourNextLinkBinaryMessage(ContourNextLinkBinaryMessage.CommandType.READ_INFO, mPumpSession, null).send(this); ContourNextLinkMessage response = ReadInfoResponseMessage.fromBytes(mPumpSession, readMessage()); @@ -251,19 +263,54 @@ public class MedtronicCNLReader implements ContourNextLinkMessageHandler { this.getPumpSession().setLinkMAC(linkMAC); this.getPumpSession().setPumpMAC(pumpMAC); + Log.d(TAG, String.format("Finished requestReadInfo. linkMAC = '%d', pumpMAC = '%d", linkMAC, pumpMAC)); + } + + public void requestLinkKey() throws IOException, TimeoutException, EncryptionException, ChecksumException { + 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); + + Log.d(TAG, String.format("Finished requestLinkKey. linkKey = '%s'", this.getPumpSession().getKey())); } - public byte negotiateChannel() throws IOException, ChecksumException, TimeoutException { - for (byte channel : RADIO_CHANNELS) { + public byte negotiateChannel(byte lastRadioChannel) throws IOException, ChecksumException, TimeoutException { + ArrayList<Byte> radioChannels = new ArrayList<>(Arrays.asList(ArrayUtils.toObject(RADIO_CHANNELS))); + + if (lastRadioChannel != 0x00) { + // If we know the last channel that was used, shuffle the negotiation order + Byte lastChannel = radioChannels.remove(radioChannels.indexOf(lastRadioChannel)); + + if (lastChannel != null) { + radioChannels.add(0, lastChannel); + } + } + + Log.d(TAG, "Begin negotiateChannel"); + for (byte channel : radioChannels) { + Log.d(TAG, String.format("negotiateChannel: trying channel '%d'...", channel)); mPumpSession.setRadioChannel(channel); new ChannelNegotiateMessage(mPumpSession).send(this); // Don't care what the 0x81 response message is at this stage + Log.d(TAG, "negotiateChannel: Reading 0x81 message"); readMessage(); // The 0x80 message + Log.d(TAG, "negotiateChannel: Reading 0x80 message"); ContourNextLinkMessage response = ContourNextLinkBinaryMessage.fromBytes(readMessage()); byte[] responseBytes = response.encode(); + Log.d(TAG, "negotiateChannel: Check response length"); if (responseBytes.length > 46) { // Looks promising, let's check the last byte of the payload to make sure if (responseBytes[76] == mPumpSession.getRadioChannel()) { @@ -276,18 +323,21 @@ public class MedtronicCNLReader implements ContourNextLinkMessageHandler { } } + Log.d(TAG, String.format("Finished negotiateChannel with channel '%d'", mPumpSession.getRadioChannel())); return mPumpSession.getRadioChannel(); } public void beginEHSMSession() throws EncryptionException, IOException, TimeoutException { + Log.d(TAG, "Begin beginEHSMSession"); new BeginEHSMMessage(mPumpSession).send(this); // The Begin EHSM Session only has an 0x81 response readMessage(); + Log.d(TAG, "Finished beginEHSMSession"); } public Date getPumpTime() throws EncryptionException, IOException, ChecksumException, TimeoutException { + Log.d(TAG, "Begin getPumpTime"); // FIXME - throw if not in EHSM mode (add a state machine) - Date timeAtCapture = new Date(); new PumpTimeRequestMessage(mPumpSession).send(this); // Read the 0x81 @@ -296,8 +346,10 @@ public class MedtronicCNLReader implements ContourNextLinkMessageHandler { // Read the 0x80 ContourNextLinkMessage response = PumpTimeResponseMessage.fromBytes(mPumpSession, readMessage()); - if (response.encode().length < 57) { + if (response.encode().length < (61 + 8)) { // Invalid message. Return an invalid date. + // TODO - deal with this more elegantly + Log.e(TAG, "Invalid message received for getPumpTime"); return new Date(); } @@ -308,10 +360,12 @@ public class MedtronicCNLReader implements ContourNextLinkMessageHandler { long rtc = dateBuffer.getInt(0) & 0x00000000ffffffffL; long offset = dateBuffer.getInt(4); + Log.d(TAG, "Finished getPumpTime with date " + MessageUtils.decodeDateTime(rtc, offset)); return MessageUtils.decodeDateTime(rtc, offset); } - public void getPumpStatus(CgmStatusEvent cgmRecord, long pumpTimeOffset) throws IOException, EncryptionException, ChecksumException, TimeoutException { + public void getPumpStatus(PumpStatusEvent pumpRecord, long pumpTimeOffset) throws IOException, EncryptionException, ChecksumException, TimeoutException { + Log.d(TAG, "Begin getPumpStatus"); // FIXME - throw if not in EHSM mode (add a state machine) new PumpStatusRequestMessage(mPumpSession).send(this); @@ -321,8 +375,10 @@ public class MedtronicCNLReader implements ContourNextLinkMessageHandler { // Read the 0x80 ContourNextLinkMessage response = PumpStatusResponseMessage.fromBytes(mPumpSession, readMessage()); - if (response.encode().length < 57) { + if (response.encode().length < (57 + 96)) { // Invalid message. Don't try and parse it + // TODO - deal with this more elegantly + Log.e(TAG, "Invalid message received for getPumpStatus"); return; } @@ -331,55 +387,143 @@ public class MedtronicCNLReader implements ContourNextLinkMessageHandler { statusBuffer.order(ByteOrder.BIG_ENDIAN); statusBuffer.put(response.encode(), 0x39, 96); - // Read the data into the record + // 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; - MainActivity.pumpStatusRecord.activeInsulin = new BigDecimal(rawActiveInsulin / 10000f).setScale(3, BigDecimal.ROUND_HALF_UP); - cgmRecord.setSgv(statusBuffer.getShort(0x35) & 0x0000ffff); // In mg/DL. 0 means no CGM reading + 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 ((cgmRecord.getSgv() & 0x200) == 0x200) { + if ((pumpRecord.getSgv() & 0x200) == 0x200) { // Sensor error. Let's reset. FIXME - solve this more elegantly later - cgmRecord.setSgv(0); + pumpRecord.setSgv(0); rtc = 0; offset = 0; - cgmRecord.setTrend(CgmStatusEvent.TREND.NOT_SET); + pumpRecord.setCgmTrend(PumpStatusEvent.CGM_TREND.NOT_SET); } else { rtc = statusBuffer.getInt(0x37) & 0x00000000ffffffffL; offset = statusBuffer.getInt(0x3b); - cgmRecord.setTrend(fromMessageByte(statusBuffer.get(0x40))); + pumpRecord.setCgmTrend(fromMessageByte(statusBuffer.get(0x40))); } - cgmRecord.setEventDate(new Date(MessageUtils.decodeDateTime(rtc, offset).getTime() - pumpTimeOffset)); - MainActivity.pumpStatusRecord.recentBolusWizard = statusBuffer.get(0x48) != 0; - MainActivity.pumpStatusRecord.bolusWizardBGL = statusBuffer.getShort(0x49); // In mg/DL - long rawReservoirAmount = statusBuffer.getInt(0x2b); - MainActivity.pumpStatusRecord.reservoirAmount = new BigDecimal(rawReservoirAmount / 10000f).setScale(3, BigDecimal.ROUND_HALF_UP); - MainActivity.pumpStatusRecord.batteryPercentage = (statusBuffer.get(0x2a)); + // 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"); + } + + public void getBasalPatterns() throws EncryptionException, IOException, ChecksumException, TimeoutException { + 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(); + + // 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; + } + */ + + // FIXME - this needs to go into PumpBasalPatternResponseMessage + ByteBuffer basalRatesBuffer = ByteBuffer.allocate(96); + basalRatesBuffer.order(ByteOrder.BIG_ENDIAN); + basalRatesBuffer.put(response.encode(), 0x39, 96); + + Log.d(TAG, "Finished getBasalPatterns"); } public void endEHSMSession() throws EncryptionException, IOException, TimeoutException { + Log.d(TAG, "Begin endEHSMSession"); new EndEHSMMessage(mPumpSession).send(this); // The End EHSM Session only has an 0x81 response readMessage(); + Log.d(TAG, "Finished endEHSMSession"); } public void closeConnection() throws IOException, TimeoutException { + Log.d(TAG, "Begin closeConnection"); new ContourNextLinkBinaryMessage(ContourNextLinkBinaryMessage.CommandType.CLOSE_CONNECTION, mPumpSession, null).send(this); // FIXME - We need to care what the response message is - wrong MAC and all that readMessage(); + Log.d(TAG, "Finished closeConnection"); } public void endPassthroughMode() throws IOException, TimeoutException, UnexpectedMessageException { + Log.d(TAG, "Begin endPassthroughMode"); new ContourNextLinkCommandMessage("W|").send(this); checkControlMessage(readMessage(), ASCII.ACK.value); new ContourNextLinkCommandMessage("Q|").send(this); checkControlMessage(readMessage(), ASCII.ACK.value); new ContourNextLinkCommandMessage("0|").send(this); checkControlMessage(readMessage(), ASCII.ACK.value); + Log.d(TAG, "Finished endPassthroughMode"); } public void endControlMode() throws IOException, TimeoutException, UnexpectedMessageException { + Log.d(TAG, "Begin endControlMode"); new ContourNextLinkCommandMessage(ASCII.EOT.value).send(this); checkControlMessage(readMessage(), ASCII.ENQ.value); + Log.d(TAG, "Finished endControlMode"); } public enum ASCII { diff --git a/app/src/main/java/info/nightscout/android/medtronic/MedtronicCnlSession.java b/app/src/main/java/info/nightscout/android/medtronic/MedtronicCnlSession.java new file mode 100644 index 0000000000000000000000000000000000000000..215e7f3e6174163da35db8824c928d87f5d454bb --- /dev/null +++ b/app/src/main/java/info/nightscout/android/medtronic/MedtronicCnlSession.java @@ -0,0 +1,130 @@ +package info.nightscout.android.medtronic; + +import org.apache.commons.lang3.ArrayUtils; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * Created by lgoedhart on 26/03/2016. + */ +public class MedtronicCnlSession { + private static final String HMAC_PADDING = "A4BD6CED9A42602564F413123"; + + private byte[] HMAC; + private byte[] key; + + private String stickSerial; + + private long linkMAC; + private long pumpMAC; + + private byte radioChannel; + private int bayerSequenceNumber = 1; + private int medtronicSequenceNumber = 1; + + /*public byte[] getHMAC() { + return HMAC; + }*/ + + public byte[] getHMAC() throws NoSuchAlgorithmException { + String shortSerial = this.stickSerial.replaceAll("\\d+-", ""); + byte[] message = (shortSerial + HMAC_PADDING).getBytes(); + byte[] numArray; + + MessageDigest instance = MessageDigest.getInstance("SHA-256"); + instance.update(message); + + numArray = instance.digest(); + ArrayUtils.reverse(numArray); + + return numArray; + } + + public byte[] getKey() { + return key; + } + + public byte[] getIV() { + byte[] iv = new byte[key.length]; + System.arraycopy(key, 0, iv, 0, key.length); + iv[0] = radioChannel; + return iv; + } + + public long getLinkMAC() { + return linkMAC; + } + + public void setLinkMAC(long linkMAC) { + this.linkMAC = linkMAC; + } + + public long getPumpMAC() { + return pumpMAC; + } + + public void setPumpMAC(long pumpMAC) { + this.pumpMAC = pumpMAC; + } + + public int getBayerSequenceNumber() { + return bayerSequenceNumber; + } + + public int getMedtronicSequenceNumber() { + return medtronicSequenceNumber; + } + + public byte getRadioChannel() { + return radioChannel; + } + + public void incrBayerSequenceNumber() { + bayerSequenceNumber++; + } + + public void incrMedtronicSequenceNumber() { + medtronicSequenceNumber++; + } + + public void setRadioChannel(byte radioChannel) { + this.radioChannel = radioChannel; + } + + public void setHMAC(byte[] hmac) { + this.HMAC = hmac; + } + + 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; + } + + public void setStickSerial(String stickSerial) { + this.stickSerial = stickSerial; + } +} 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 ed1c7d8f18c7cc61920428de4ec47ed5f6a1dc91..e2ee643a1a0d9f7de0a0c566c6d2d147ad6e3e19 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,12 @@ package info.nightscout.android.medtronic.message; -import info.nightscout.android.medtronic.MedtronicCNLSession; +import info.nightscout.android.medtronic.MedtronicCnlSession; /** * Created by lgoedhart on 26/03/2016. */ public class BeginEHSMMessage extends MedtronicSendMessage { - public BeginEHSMMessage(MedtronicCNLSession pumpSession) throws EncryptionException { + public BeginEHSMMessage(MedtronicCnlSession pumpSession) throws EncryptionException { 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 index cce33d28ea977c9f978269c0d1938bb189e0cff2..eda3ce6f6f514f3045396216dc658b65e825e5fc 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/message/ChannelNegotiateMessage.java +++ b/app/src/main/java/info/nightscout/android/medtronic/message/ChannelNegotiateMessage.java @@ -1,6 +1,6 @@ package info.nightscout.android.medtronic.message; -import info.nightscout.android.medtronic.MedtronicCNLSession; +import info.nightscout.android.medtronic.MedtronicCnlSession; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -9,11 +9,11 @@ import java.nio.ByteOrder; * Created by lgoedhart on 26/03/2016. */ public class ChannelNegotiateMessage extends MedtronicMessage { - public ChannelNegotiateMessage(MedtronicCNLSession pumpSession) { + public ChannelNegotiateMessage(MedtronicCnlSession pumpSession) { super(CommandType.SEND_MESSAGE, CommandAction.CHANNEL_NEGOTIATE, pumpSession, buildPayload(pumpSession)); } - protected static byte[] buildPayload( MedtronicCNLSession 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, diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkBinaryMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkBinaryMessage.java index 1f773efbdfa5e3430cce9e277c412ba3c9487765..95e94baa56ef73718e3db0d2d13e272f8b48a058 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkBinaryMessage.java +++ b/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkBinaryMessage.java @@ -1,6 +1,6 @@ package info.nightscout.android.medtronic.message; -import info.nightscout.android.medtronic.MedtronicCNLSession; +import info.nightscout.android.medtronic.MedtronicCnlSession; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -34,13 +34,17 @@ public class ContourNextLinkBinaryMessage extends ContourNextLinkMessage{ CommandType(int commandType) { value = (byte) commandType; } + + public int getValue() { + return value; + } } - public ContourNextLinkBinaryMessage(CommandType commandType, MedtronicCNLSession pumpSession, byte[] payload) { + public ContourNextLinkBinaryMessage(CommandType commandType, MedtronicCnlSession pumpSession, byte[] payload) { super(buildPayload(commandType, pumpSession, payload)); } - protected static byte[] buildPayload(CommandType commandType, MedtronicCNLSession pumpSession, byte[] payload) { + protected static byte[] buildPayload(CommandType commandType, MedtronicCnlSession pumpSession, byte[] payload) { int payloadLength = payload == null ? 0 : payload.length; ByteBuffer payloadBuffer = ByteBuffer.allocate( ENVELOPE_SIZE + payloadLength ); @@ -48,7 +52,7 @@ public class ContourNextLinkBinaryMessage extends ContourNextLinkMessage{ payloadBuffer.put((byte) 0x51); payloadBuffer.put((byte) 0x3); - payloadBuffer.put("000000".getBytes()); // Text of Pump serial, but 000000 for 640g + payloadBuffer.put("000000".getBytes()); // Text of PumpInfo serial, but 000000 for 640g byte[] unknownBytes = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; payloadBuffer.put(unknownBytes); payloadBuffer.put(commandType.value); diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/EndEHSMMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/EndEHSMMessage.java index fe1adae580d34b37306f8a7ed30d536bff49448b..0364110375aec3d9c5f6b9c940f500d363758e4f 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,12 @@ package info.nightscout.android.medtronic.message; -import info.nightscout.android.medtronic.MedtronicCNLSession; +import info.nightscout.android.medtronic.MedtronicCnlSession; /** * Created by lgoedhart on 26/03/2016. */ public class EndEHSMMessage extends MedtronicSendMessage { - public EndEHSMMessage(MedtronicCNLSession pumpSession) throws EncryptionException { + public EndEHSMMessage(MedtronicCnlSession pumpSession) throws EncryptionException { super(SendMessageType.END_EHSM_SESSION, pumpSession, buildPayload()); } diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicMessage.java index aed8d129f378cd16c779c13f83b3d0c857ccce81..25588505ab2e817307d0428108a0e2bb98ef4eb7 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicMessage.java +++ b/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicMessage.java @@ -1,6 +1,6 @@ package info.nightscout.android.medtronic.message; -import info.nightscout.android.medtronic.MedtronicCNLSession; +import info.nightscout.android.medtronic.MedtronicCnlSession; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -29,7 +29,7 @@ public class MedtronicMessage extends ContourNextLinkBinaryMessage { } } - protected MedtronicMessage(CommandType commandType, CommandAction commandAction, MedtronicCNLSession pumpSession, byte[] payload) { + protected MedtronicMessage(CommandType commandType, CommandAction commandAction, MedtronicCnlSession pumpSession, byte[] payload) { super(commandType, pumpSession, buildPayload(commandAction, payload)); } 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 index 63d854a6e12871c1a6fae098011f5ecfb120dbb3..c2f4a7d58cf10d70925e3d2ab15e3358a4ff3a3f 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicReceiveMessage.java +++ b/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicReceiveMessage.java @@ -1,6 +1,6 @@ package info.nightscout.android.medtronic.message; -import info.nightscout.android.medtronic.MedtronicCNLSession; +import info.nightscout.android.medtronic.MedtronicCnlSession; import java.nio.ByteBuffer; @@ -12,7 +12,7 @@ public class MedtronicReceiveMessage extends MedtronicMessage { static int ENCRYPTED_ENVELOPE_SIZE = 3; static int CRC_SIZE = 2; - protected MedtronicReceiveMessage(CommandType commandType, CommandAction commandAction, MedtronicCNLSession pumpSession, byte[] payload) { + protected MedtronicReceiveMessage(CommandType commandType, CommandAction commandAction, MedtronicCnlSession pumpSession, byte[] payload) { super(commandType, commandAction, pumpSession, payload); } @@ -38,14 +38,17 @@ public class MedtronicReceiveMessage extends MedtronicMessage { * | byte receiveSequenceNumber | BE short receiveMessageType | byte[] Payload bytes | BE short CCITT CRC | * +----------------------------+-----------------------------+----------------------+--------------------+ */ - public static ContourNextLinkMessage fromBytes(MedtronicCNLSession pumpSession, byte[] bytes) throws ChecksumException, EncryptionException { + 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. - if( bytes.length >= 57 ) { + // 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]; diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicSendMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicSendMessage.java index 61d416c81b8dc87f7185d32f9a37c0690c2cf4c4..7bba4ded16da96428fd056f5b985ce821ee9e63e 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicSendMessage.java +++ b/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicSendMessage.java @@ -1,6 +1,6 @@ package info.nightscout.android.medtronic.message; -import info.nightscout.android.medtronic.MedtronicCNLSession; +import info.nightscout.android.medtronic.MedtronicCnlSession; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -18,6 +18,7 @@ public class MedtronicSendMessage extends MedtronicMessage { BEGIN_EHSM_SESSION(0x412), TIME_REQUEST(0x0403), READ_PUMP_STATUS_REQUEST(0x0112), + READ_BASAL_PATTERN_REQUEST(0x0112), END_EHSM_SESSION(0x412); private short value; @@ -27,7 +28,7 @@ public class MedtronicSendMessage extends MedtronicMessage { } } - protected MedtronicSendMessage(SendMessageType sendMessageType, MedtronicCNLSession pumpSession, byte[] payload) throws EncryptionException { + protected MedtronicSendMessage(SendMessageType sendMessageType, MedtronicCnlSession pumpSession, byte[] payload) throws EncryptionException { super(CommandType.SEND_MESSAGE, CommandAction.PUMP_REQUEST, pumpSession, buildPayload(sendMessageType, pumpSession, payload)); } @@ -42,7 +43,7 @@ public class MedtronicSendMessage extends MedtronicMessage { * | byte sendSequenceNumber | BE short sendMessageType | byte[] Payload bytes | BE short CCITT CRC | * +-------------------------+----------------------+----------------------+--------------------+ */ - protected static byte[] buildPayload(SendMessageType sendMessageType, MedtronicCNLSession pumpSession, byte[] payload) throws EncryptionException { + protected static byte[] buildPayload(SendMessageType sendMessageType, MedtronicCnlSession pumpSession, byte[] payload) throws EncryptionException { byte payloadLength = (byte) (payload == null ? 0 : payload.length); ByteBuffer sendPayloadBuffer = ByteBuffer.allocate(ENCRYPTED_ENVELOPE_SIZE + payloadLength + CRC_SIZE); 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 new file mode 100644 index 0000000000000000000000000000000000000000..d31eb36a229654efedd6b9849fac749c1b748254 --- /dev/null +++ b/app/src/main/java/info/nightscout/android/medtronic/message/PumpBasalPatternRequestMessage.java @@ -0,0 +1,12 @@ +package info.nightscout.android.medtronic.message; + +import info.nightscout.android.medtronic.MedtronicCnlSession; + +/** + * Created by lgoedhart on 26/03/2016. + */ +public class PumpBasalPatternRequestMessage extends MedtronicSendMessage { + public PumpBasalPatternRequestMessage(MedtronicCnlSession pumpSession) throws EncryptionException { + super(SendMessageType.READ_BASAL_PATTERN_REQUEST, pumpSession, null); + } +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..d4b4ef97d55422a5d5be2a5179879569a59cfcad --- /dev/null +++ b/app/src/main/java/info/nightscout/android/medtronic/message/PumpBasalPatternResponseMessage.java @@ -0,0 +1,21 @@ +package info.nightscout.android.medtronic.message; + +import info.nightscout.android.medtronic.MedtronicCnlSession; + +/** + * 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 static ContourNextLinkMessage fromBytes(MedtronicCnlSession pumpSession, byte[] bytes) throws ChecksumException, EncryptionException { + // TODO - turn this into a factory + ContourNextLinkMessage message = MedtronicReceiveMessage.fromBytes(pumpSession, bytes); + + // TODO - Validate the MessageType + + return message; + } +} 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 aba4dc62d0db5aea298fa4fcc8160b3e7162185a..1162bfe139ca65e06fe1eb95571f1ff67520f78c 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,12 @@ package info.nightscout.android.medtronic.message; -import info.nightscout.android.medtronic.MedtronicCNLSession; +import info.nightscout.android.medtronic.MedtronicCnlSession; /** * Created by lgoedhart on 26/03/2016. */ public class PumpStatusRequestMessage extends MedtronicSendMessage { - public PumpStatusRequestMessage(MedtronicCNLSession pumpSession) throws EncryptionException { + public PumpStatusRequestMessage(MedtronicCnlSession pumpSession) throws EncryptionException { super(SendMessageType.READ_PUMP_STATUS_REQUEST, pumpSession, null); } } 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 bbd3c3b63c916c6ee20439a83dbc70223d54bf8a..06e9a0f3357d2456f64a5eb7c84ac3218d1e35ee 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,16 +1,16 @@ package info.nightscout.android.medtronic.message; -import info.nightscout.android.medtronic.MedtronicCNLSession; +import info.nightscout.android.medtronic.MedtronicCnlSession; /** * Created by lgoedhart on 27/03/2016. */ public class PumpStatusResponseMessage extends MedtronicReceiveMessage { - protected PumpStatusResponseMessage(CommandType commandType, CommandAction commandAction, MedtronicCNLSession pumpSession, byte[] payload) { + protected PumpStatusResponseMessage(CommandType commandType, CommandAction commandAction, MedtronicCnlSession pumpSession, byte[] payload) { super(commandType, commandAction, pumpSession, payload); } - public static ContourNextLinkMessage fromBytes(MedtronicCNLSession pumpSession, byte[] bytes) throws ChecksumException, EncryptionException { + public static ContourNextLinkMessage fromBytes(MedtronicCnlSession pumpSession, byte[] bytes) throws ChecksumException, EncryptionException { // TODO - turn this into a factory ContourNextLinkMessage message = MedtronicReceiveMessage.fromBytes(pumpSession, bytes); 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 97fb82065755fe04806bb1862ea38940c8f1c40d..89305b0a9b3873d3ec4079eeacebf8d740443ffe 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,12 @@ package info.nightscout.android.medtronic.message; -import info.nightscout.android.medtronic.MedtronicCNLSession; +import info.nightscout.android.medtronic.MedtronicCnlSession; /** * Created by lgoedhart on 26/03/2016. */ public class PumpTimeRequestMessage extends MedtronicSendMessage { - public PumpTimeRequestMessage(MedtronicCNLSession pumpSession) throws EncryptionException { + public PumpTimeRequestMessage(MedtronicCnlSession pumpSession) throws EncryptionException { super(SendMessageType.TIME_REQUEST, pumpSession, null); } } 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 940f9afa38015f6bb9dc185fdd84a28baf4d1a8e..3437dd69c65e9adfe188966ea14cbc1a6f8afb18 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,16 +1,16 @@ package info.nightscout.android.medtronic.message; -import info.nightscout.android.medtronic.MedtronicCNLSession; +import info.nightscout.android.medtronic.MedtronicCnlSession; /** * Created by lgoedhart on 27/03/2016. */ public class PumpTimeResponseMessage extends MedtronicReceiveMessage { - protected PumpTimeResponseMessage(CommandType commandType, CommandAction commandAction, MedtronicCNLSession pumpSession, byte[] payload) { + protected PumpTimeResponseMessage(CommandType commandType, CommandAction commandAction, MedtronicCnlSession pumpSession, byte[] payload) { super(commandType, commandAction, pumpSession, payload); } - public static ContourNextLinkMessage fromBytes(MedtronicCNLSession pumpSession, byte[] bytes) throws ChecksumException, EncryptionException { + public static ContourNextLinkMessage fromBytes(MedtronicCnlSession pumpSession, byte[] bytes) throws ChecksumException, EncryptionException { // TODO - turn this into a factory ContourNextLinkMessage message = MedtronicReceiveMessage.fromBytes(pumpSession, bytes); 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 980392929a454a5cd841d74de46f5e46b7669aef..68aab2df7977d4235e1c445b4b8ac0692d16e88b 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,16 +1,16 @@ package info.nightscout.android.medtronic.message; -import info.nightscout.android.medtronic.MedtronicCNLSession; +import info.nightscout.android.medtronic.MedtronicCnlSession; /** * Created by lgoedhart on 10/05/2016. */ public class ReadInfoResponseMessage extends MedtronicReceiveMessage { - protected ReadInfoResponseMessage(CommandType commandType, CommandAction commandAction, MedtronicCNLSession pumpSession, byte[] payload) { + protected ReadInfoResponseMessage(CommandType commandType, CommandAction commandAction, MedtronicCnlSession pumpSession, byte[] payload) { super(commandType, commandAction, pumpSession, payload); } - public static ContourNextLinkMessage fromBytes(MedtronicCNLSession pumpSession, byte[] bytes) throws ChecksumException, EncryptionException { + public static ContourNextLinkMessage fromBytes(MedtronicCnlSession pumpSession, byte[] bytes) throws ChecksumException, EncryptionException { // TODO - turn this into a factory ContourNextLinkMessage message = MedtronicReceiveMessage.fromBytes(pumpSession, bytes); 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 new file mode 100644 index 0000000000000000000000000000000000000000..423e40fb8ab61296fc9c26995837a56932715a7e --- /dev/null +++ b/app/src/main/java/info/nightscout/android/medtronic/message/RequestLinkKeyResponseMessage.java @@ -0,0 +1,21 @@ +package info.nightscout.android.medtronic.message; + +import info.nightscout.android.medtronic.MedtronicCnlSession; + +/** + * 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 static ContourNextLinkMessage fromBytes(MedtronicCnlSession pumpSession, byte[] bytes) throws ChecksumException, EncryptionException { + // TODO - turn this into a factory + ContourNextLinkMessage message = MedtronicReceiveMessage.fromBytes(pumpSession, bytes); + + // TODO - Validate the MessageType + + return message; + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/android/medtronic/service/MedtronicCnlAlarmReceiver.java b/app/src/main/java/info/nightscout/android/medtronic/service/MedtronicCnlAlarmReceiver.java new file mode 100644 index 0000000000000000000000000000000000000000..49e648c68cc37df7ca643e632c9c76b1457b32f0 --- /dev/null +++ b/app/src/main/java/info/nightscout/android/medtronic/service/MedtronicCnlAlarmReceiver.java @@ -0,0 +1,78 @@ +package info.nightscout.android.medtronic.service; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.support.v4.content.WakefulBroadcastReceiver; +import android.util.Log; + +import java.util.Date; + +/** + * Created by lgoedhart on 14/07/2016. + */ +public class MedtronicCnlAlarmReceiver extends WakefulBroadcastReceiver { + private static final String TAG = MedtronicCnlAlarmReceiver.class.getSimpleName(); + private static final int ALARM_ID = 102; // Alarm id + + private static PendingIntent pendingIntent = null; + private static AlarmManager alarmManager = null; + + @Override + public void onReceive(final Context context, Intent intent) { + // Start the IntentService + Log.d(TAG, "Received broadcast message at " + new Date(System.currentTimeMillis())); + Intent service = new Intent(context, MedtronicCnlIntentService.class); + startWakefulService(context, service); + restartAlarm(); + } + + public void setContext(Context context) { + cancelAlarm(); + + alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + Intent intent = new Intent(context, MedtronicCnlAlarmReceiver.class); + pendingIntent = PendingIntent.getBroadcast(context, ALARM_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT); + } + + // Setting the alarm in 15 seconds from now + public void setAlarm() { + setAlarm(System.currentTimeMillis()); + } + + // Setting the alarm to call onRecieve + public void setAlarm(long millis) { + if (alarmManager == null || pendingIntent == null) + return; + + cancelAlarm(); + + // don't trigger the past and at least 30 sec away + if (millis < System.currentTimeMillis()) + millis = System.currentTimeMillis(); + + Log.d(TAG, "AlarmManager set to fire at " + new Date(millis)); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + alarmManager.setAlarmClock(new AlarmManager.AlarmClockInfo(millis, null), pendingIntent); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + alarmManager.setExact(AlarmManager.RTC_WAKEUP, millis, pendingIntent); + } else + alarmManager.set(AlarmManager.RTC_WAKEUP, millis, pendingIntent); + } + + // restarting the alarm after MedtronicCnlIntentService.POLL_PERIOD_MS from now + public void restartAlarm() { + setAlarm(System.currentTimeMillis() + MedtronicCnlIntentService.POLL_PERIOD_MS + MedtronicCnlIntentService.POLL_GRACE_PERIOD_MS); + } + + // 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 d39f3246dadb395b61d450476ec7e65335f8eca8..b958a2c3ac004079815a46121313dc9e74cf097a 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 @@ -1,29 +1,39 @@ package info.nightscout.android.medtronic.service; +import android.app.AlarmManager; import android.app.IntentService; +import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; +import android.os.Build; +import android.preference.PreferenceManager; import android.support.v4.app.NotificationManagerCompat; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; import java.io.IOException; +import java.security.NoSuchAlgorithmException; import java.util.Date; import java.util.Locale; import java.util.concurrent.TimeoutException; +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.MedtronicCnlReader; import info.nightscout.android.medtronic.message.ChecksumException; import info.nightscout.android.medtronic.message.EncryptionException; import info.nightscout.android.medtronic.message.MessageUtils; import info.nightscout.android.medtronic.message.UnexpectedMessageException; -import info.nightscout.android.model.CgmStatusEvent; import info.nightscout.android.model.medtronicNg.ContourNextLinkInfo; +import info.nightscout.android.model.medtronicNg.PumpInfo; +import info.nightscout.android.model.medtronicNg.PumpStatusEvent; +import info.nightscout.android.upload.nightscout.NightscoutUploadReceiver; +import info.nightscout.android.xdrip_plus.XDripPlusUploadReceiver; import io.realm.Realm; import io.realm.RealmResults; @@ -89,7 +99,9 @@ public class MedtronicCnlIntentService extends IntentService { if (!hasUsbHostFeature()) { sendStatus("It appears that this device doesn't support USB OTG."); - Log.w(TAG, "Device does not support USB OTG"); + Log.e(TAG, "Device does not support USB OTG"); + MedtronicCnlAlarmReceiver.completeWakefulIntent(intent); + // TODO - throw, don't return return; } @@ -97,26 +109,34 @@ public class MedtronicCnlIntentService extends IntentService { if (cnlStick == null) { sendStatus("USB connection error. Is the Bayer Contour Next Link plugged in?"); Log.w(TAG, "USB connection error. Is the CNL plugged in?"); + + // TODO - set status if offline or Nightscout not reachable + uploadToNightscout(); + MedtronicCnlAlarmReceiver.completeWakefulIntent(intent); + // TODO - throw, don't return return; } if (!mUsbManager.hasPermission(UsbHidDriver.getUsbDevice(mUsbManager, USB_VID, USB_PID))) { sendMessage(Constants.ACTION_NO_USB_PERMISSION); + MedtronicCnlAlarmReceiver.completeWakefulIntent(intent); + // TODO - throw, don't return return; } mHidDevice = UsbHidDriver.acquire(mUsbManager, cnlStick); - Realm realm = Realm.getDefaultInstance(); - try { mHidDevice.open(); } catch (Exception e) { Log.e(TAG, "Unable to open serial device", e); + MedtronicCnlAlarmReceiver.completeWakefulIntent(intent); + // TODO - throw, don't return return; } - MedtronicCNLReader cnlReader = new MedtronicCNLReader(mHidDevice); + MedtronicCnlReader cnlReader = new MedtronicCnlReader(mHidDevice); + Realm realm = Realm.getDefaultInstance(); realm.beginTransaction(); try { @@ -131,25 +151,14 @@ public class MedtronicCnlIntentService extends IntentService { .findFirst(); if (info == null) { + // TODO - use realm.createObject()? info = new ContourNextLinkInfo(); info.setSerialNumber(cnlReader.getStickSerial()); info = realm.copyToRealm(info); } - String hmac = info.getHmac(); - String key = info.getKey(); - - if (hmac == null || key == null) { - // Must commit the transaction before we send the Registration activation message - realm.commitTransaction(); - - sendMessage(Constants.ACTION_USB_REGISTER); - return; - } - - cnlReader.getPumpSession().setHMAC(MessageUtils.hexStringToByteArray(hmac)); - cnlReader.getPumpSession().setKey(MessageUtils.hexStringToByteArray(key)); + cnlReader.getPumpSession().setStickSerial(info.getSerialNumber()); cnlReader.enterControlMode(); @@ -157,41 +166,68 @@ public class MedtronicCnlIntentService extends IntentService { cnlReader.enterPassthroughMode(); cnlReader.openConnection(); cnlReader.requestReadInfo(); - byte radioChannel = cnlReader.negotiateChannel(); + + String key = info.getKey(); + + if (key == null) { + cnlReader.requestLinkKey(); + + info.setKey(MessageUtils.byteArrayToHexString(cnlReader.getPumpSession().getKey())); + key = info.getKey(); + } + + cnlReader.getPumpSession().setKey(MessageUtils.hexStringToByteArray(key)); + + long pumpMAC = cnlReader.getPumpSession().getPumpMAC(); + Log.i(TAG, "PumpInfo MAC: " + (pumpMAC & 0xffffff)); + MainActivity.setActivePumpMac(pumpMAC); + PumpInfo activePump = realm + .where(PumpInfo.class) + .equalTo("pumpMac", pumpMAC) + .findFirst(); + + if (activePump == null) { + activePump = realm.createObject(PumpInfo.class); + activePump.setPumpMac(pumpMAC); + } + + byte radioChannel = cnlReader.negotiateChannel(activePump.getLastRadioChannel()); if (radioChannel == 0) { sendStatus("Could not communicate with the 640g. Are you near the pump?"); Log.i(TAG, "Could not communicate with the 640g. Are you near the pump?"); } else { + activePump.setLastRadioChannel(radioChannel); sendStatus(String.format(Locale.getDefault(), "Connected to Contour Next Link on channel %d.", (int) radioChannel)); Log.d(TAG, String.format("Connected to Contour Next Link on channel %d.", (int) radioChannel)); cnlReader.beginEHSMSession(); - //PumpStatusEvent pumpRecord = realm.createObject(PumpStatusEvent.class); - CgmStatusEvent cgmRecord = realm.createObject(CgmStatusEvent.class); + PumpStatusEvent pumpRecord = realm.createObject(PumpStatusEvent.class); String deviceName = String.format("medtronic-640g://%s", cnlReader.getStickSerial()); - cgmRecord.setDeviceName(deviceName); - //pumpRecord.setDeviceName(deviceName); - // TODO - legacy. Remove once we've plumbed in pumpRecord. - MainActivity.pumpStatusRecord.setDeviceName(deviceName); + activePump.setDeviceName(deviceName); + + // TODO - this should not be necessary. We should reverse lookup the device name from PumpInfo + pumpRecord.setDeviceName(deviceName); - //pumpRecord.setPumpDate(cnlReader.getPumpTime()); long pumpTime = cnlReader.getPumpTime().getTime(); long pumpOffset = pumpTime - System.currentTimeMillis(); + Log.d(TAG, "Time offset between pump and device: " + pumpOffset + " millis."); + // TODO - send ACTION to MainActivity to show offset between pump and uploader. - MainActivity.pumpStatusRecord.pumpDate = new Date(pumpTime - pumpOffset); - cnlReader.getPumpStatus(cgmRecord, pumpOffset); + pumpRecord.setPumpTimeOffset(pumpOffset); + pumpRecord.setPumpDate(new Date(pumpTime - pumpOffset)); + cnlReader.getPumpStatus(pumpRecord, pumpOffset); + activePump.getPumpHistory().add(pumpRecord); cnlReader.endEHSMSession(); boolean cancelTransaction = true; - if (cgmRecord.getSgv() != 0) { + if (pumpRecord.getSgv() != 0) { // Check that the record doesn't already exist before committing - RealmResults<CgmStatusEvent> checkExistingRecords = realm - .where(CgmStatusEvent.class) - .equalTo("eventDate", cgmRecord.getEventDate()) - .equalTo("deviceName", cgmRecord.getDeviceName()) - .equalTo("sgv", cgmRecord.getSgv()) + RealmResults<PumpStatusEvent> checkExistingRecords = activePump.getPumpHistory() + .where() + .equalTo("eventDate", pumpRecord.getEventDate()) + .equalTo("sgv", pumpRecord.getSgv()) .findAll(); // There should be the 1 record we've already added in this transaction. @@ -208,12 +244,15 @@ public class MedtronicCnlIntentService extends IntentService { realm.cancelTransaction(); } } - - cnlReader.closeConnection(); } catch (UnexpectedMessageException e) { Log.e(TAG, "Unexpected Message", e); sendStatus("Communication Error: " + e.getMessage()); + } catch (NoSuchAlgorithmException e) { + Log.e(TAG, "Could not determine CNL HMAC", e); + sendStatus("Error connecting to Contour Next Link: Hashing error."); } finally { + //TODO : 05.11.2016 has the close to be here? + cnlReader.closeConnection(); cnlReader.endPassthroughMode(); cnlReader.endControlMode(); } @@ -233,13 +272,48 @@ public class MedtronicCnlIntentService extends IntentService { Log.e(TAG, "Could not close connection.", e); sendStatus("Could not close connection: " + e.getMessage()); } finally { - if (realm.isInTransaction()) { - // If we didn't commit the transaction, we've run into an error. Let's roll it back - realm.cancelTransaction(); + if (!realm.isClosed()) { + if (realm.isInTransaction()) { + // If we didn't commit the transaction, we've run into an error. Let's roll it back + realm.cancelTransaction(); + } + realm.close(); } + + // TODO - set status if offline or Nightscout not reachable + sendToXDrip(); + uploadToNightscout(); + MedtronicCnlAlarmReceiver.completeWakefulIntent(intent); } + } + + // reliable wake alarm manager wake up for all android versions + public static void wakeUpIntent(Context context, long wakeTime, PendingIntent pendingIntent) { + final AlarmManager alarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + alarm.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, wakeTime, pendingIntent); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + alarm.setExact(AlarmManager.RTC_WAKEUP, wakeTime, pendingIntent); + } else + alarm.set(AlarmManager.RTC_WAKEUP, wakeTime, pendingIntent); + } + + private void sendToXDrip() { + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + if (prefs.getBoolean(getString(R.string.preference_enable_xdrip_plus), false)) { + final Intent receiverIntent = new Intent(this, XDripPlusUploadReceiver.class); + final long timestamp = System.currentTimeMillis() + 500L; + final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, (int) timestamp, receiverIntent, PendingIntent.FLAG_ONE_SHOT); + Log.d(TAG, "Scheduling xDrip+ send"); + wakeUpIntent(getApplicationContext(), timestamp, pendingIntent); + } + } - realm.close(); + private void uploadToNightscout() { + Intent receiverIntent = new Intent(this, NightscoutUploadReceiver.class); + final long timestamp = System.currentTimeMillis() + 1000L; + final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, (int) timestamp, receiverIntent, PendingIntent.FLAG_ONE_SHOT); + wakeUpIntent(getApplicationContext(), timestamp, pendingIntent); } private boolean hasUsbHostFeature() { diff --git a/app/src/main/java/info/nightscout/android/model/CgmStatusEvent.java b/app/src/main/java/info/nightscout/android/model/CgmStatusEvent.java deleted file mode 100644 index 1100d3270879dfb38dd38311cde16f46235899b9..0000000000000000000000000000000000000000 --- a/app/src/main/java/info/nightscout/android/model/CgmStatusEvent.java +++ /dev/null @@ -1,82 +0,0 @@ -package info.nightscout.android.model; - -import java.util.Date; - -import io.realm.RealmObject; -import io.realm.annotations.Index; - -/** - * Created by lgoedhart on 4/06/2016. - */ -public class CgmStatusEvent extends RealmObject { - @Index - private Date eventDate; // The actual time of the event (assume the capture device eventDate/time is accurate) - private Date pumpDate; // The eventDate/time on the pump at the time of the event - private String deviceName; - private int sgv; - private String trend; - @Index - private boolean uploaded = false; - - public Date getEventDate() { - return eventDate; - } - - public void setEventDate(Date eventDate) { - this.eventDate = eventDate; - } - - public Date getPumpDate() { - return pumpDate; - } - - public void setPumpDate(Date pumpDate) { - this.pumpDate = pumpDate; - } - - public String getDeviceName() { - return deviceName; - } - - public void setDeviceName(String deviceName) { - this.deviceName = deviceName; - } - - public int getSgv() { - return sgv; - } - - public void setSgv(int sgv) { - this.sgv = sgv; - } - - public TREND getTrend() { - return TREND.valueOf(trend); - } - - public void setTrend(TREND trend) { - this.trend = trend.name(); - } - - public boolean isUploaded() { - return uploaded; - } - - public void setUploaded(boolean uploaded) { - this.uploaded = uploaded; - } - - public enum TREND { - NONE, - DOUBLE_UP, - SINGLE_UP, - FOURTY_FIVE_UP, - FLAT, - FOURTY_FIVE_DOWN, - SINGLE_DOWN, - DOUBLE_DOWN, - NOT_COMPUTABLE, - RATE_OUT_OF_RANGE, - NOT_SET - } -} diff --git a/app/src/main/java/info/nightscout/android/model/medtronicNg/ContourNextLinkInfo.java b/app/src/main/java/info/nightscout/android/model/medtronicNg/ContourNextLinkInfo.java index 404d801c8250aaa33c13ca53e58b0d9f0e852a05..9b054671a3580a5c5e142405e89d8ea52147e100 100644 --- a/app/src/main/java/info/nightscout/android/model/medtronicNg/ContourNextLinkInfo.java +++ b/app/src/main/java/info/nightscout/android/model/medtronicNg/ContourNextLinkInfo.java @@ -9,7 +9,6 @@ import io.realm.annotations.PrimaryKey; public class ContourNextLinkInfo extends RealmObject { @PrimaryKey private String serialNumber; - private String hmac; private String key; public String getSerialNumber() { @@ -20,14 +19,6 @@ public class ContourNextLinkInfo extends RealmObject { this.serialNumber = serialNumber; } - public String getHmac() { - return hmac; - } - - public void setHmac(String hmac) { - this.hmac = hmac; - } - public String getKey() { return key; } diff --git a/app/src/main/java/info/nightscout/android/model/medtronicNg/Pump.java b/app/src/main/java/info/nightscout/android/model/medtronicNg/PumpInfo.java similarity index 57% rename from app/src/main/java/info/nightscout/android/model/medtronicNg/Pump.java rename to app/src/main/java/info/nightscout/android/model/medtronicNg/PumpInfo.java index 3d34bef3dceece251d193f1f9b60b6fb4c022d3b..3b25a51612206e6ffc265a1b6e1e18190b056d71 100644 --- a/app/src/main/java/info/nightscout/android/model/medtronicNg/Pump.java +++ b/app/src/main/java/info/nightscout/android/model/medtronicNg/PumpInfo.java @@ -1,6 +1,5 @@ package info.nightscout.android.model.medtronicNg; -import info.nightscout.android.model.CgmStatusEvent; import io.realm.RealmList; import io.realm.RealmObject; import io.realm.annotations.PrimaryKey; @@ -8,27 +7,35 @@ import io.realm.annotations.PrimaryKey; /** * Created by lgoedhart on 4/06/2016. */ -public class Pump extends RealmObject { +public class PumpInfo extends RealmObject { @PrimaryKey - private String serialNumber; - private int lastRadioChannel; + private long pumpMac; + private String deviceName; + private byte lastRadioChannel; private RealmList<ContourNextLinkInfo> associatedCnls; - private RealmList<CgmStatusEvent> cgmHistory; private RealmList<PumpStatusEvent> pumpHistory; - public String getSerialNumber() { - return serialNumber; + public long getPumpMac() { + return pumpMac; } - public void setSerialNumber(String serialNumber) { - this.serialNumber = serialNumber; + public void setPumpMac(long pumpMac) { + this.pumpMac = pumpMac; } - public int getLastRadioChannel() { + public String getDeviceName() { + return deviceName; + } + + public void setDeviceName(String deviceName) { + this.deviceName = deviceName; + } + + public byte getLastRadioChannel() { return lastRadioChannel; } - public void setLastRadioChannel(int lastRadioChannel) { + public void setLastRadioChannel(byte lastRadioChannel) { this.lastRadioChannel = lastRadioChannel; } @@ -40,14 +47,6 @@ public class Pump extends RealmObject { this.associatedCnls = associatedCnls; } - public RealmList<CgmStatusEvent> getCgmHistory() { - return cgmHistory; - } - - public void setCgmHistory(RealmList<CgmStatusEvent> cgmHistory) { - this.cgmHistory = cgmHistory; - } - public RealmList<PumpStatusEvent> getPumpHistory() { return pumpHistory; } @@ -55,4 +54,8 @@ public class Pump extends RealmObject { public void setPumpHistory(RealmList<PumpStatusEvent> pumpHistory) { this.pumpHistory = pumpHistory; } + + public long getPumpSerial() { + return pumpMac & 0xffffff; + } } 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 1a11543456646e2b9040966c3b545085c1f6e704..ab95d50f6d346d506d3a6f03a992e53cfaa00804 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 @@ -3,6 +3,7 @@ package info.nightscout.android.model.medtronicNg; import java.util.Date; import io.realm.RealmObject; +import io.realm.annotations.Ignore; import io.realm.annotations.Index; /** @@ -13,11 +14,37 @@ public class PumpStatusEvent extends RealmObject { private Date eventDate; // The actual time of the event (assume the capture device eventDate/time is accurate) private Date pumpDate; // The eventDate/time on the pump at the time of the event private String deviceName; - private float activeInsulin; + + // 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 String 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. + @Ignore + private long pumpTimeOffset; // millis the pump is ahead + + @Index + private boolean uploaded = false; + public Date getEventDate() { return eventDate; } @@ -42,6 +69,26 @@ public class PumpStatusEvent extends RealmObject { this.deviceName = deviceName; } + public int getSgv() { + return sgv; + } + + public void setSgv(int sgv) { + this.sgv = sgv; + } + + public CGM_TREND getCgmTrend() { + return CGM_TREND.valueOf(cgmTrend); + } + + public void setCgmTrend(CGM_TREND cgmTrend) { + this.cgmTrend = cgmTrend.name(); + } + + public void setCgmTrend(String cgmTrend) { + this.cgmTrend = cgmTrend; + } + public float getActiveInsulin() { return activeInsulin; } @@ -50,6 +97,14 @@ public class PumpStatusEvent extends RealmObject { this.activeInsulin = activeInsulin; } + public short getBatteryPercentage() { + return batteryPercentage; + } + + public void setBatteryPercentage(short batteryPercentage) { + this.batteryPercentage = batteryPercentage; + } + public float getReservoirAmount() { return reservoirAmount; } @@ -58,6 +113,138 @@ public class PumpStatusEvent extends RealmObject { this.reservoirAmount = reservoirAmount; } + public boolean hasRecentBolusWizard() { + return recentBolusWizard; + } + + public int getBolusWizardBGL() { + return bolusWizardBGL; + } + + public void setBolusWizardBGL(int bolusWizardBGL) { + this.bolusWizardBGL = bolusWizardBGL; + } + + public boolean isUploaded() { + return uploaded; + } + + public void setUploaded(boolean uploaded) { + this.uploaded = uploaded; + } + + public boolean isSuspended() { + return suspended; + } + + public void setSuspended(boolean suspended) { + this.suspended = suspended; + } + + public boolean isBolusing() { + return bolusing; + } + + public void setBolusing(boolean bolusing) { + this.bolusing = bolusing; + } + + public boolean isDeliveringInsulin() { + return deliveringInsulin; + } + + public void setDeliveringInsulin(boolean deliveringInsulin) { + this.deliveringInsulin = deliveringInsulin; + } + + public boolean isTempBasalActive() { + return tempBasalActive; + } + + public void setTempBasalActive(boolean tempBasalActive) { + this.tempBasalActive = tempBasalActive; + } + + public boolean isCgmActive() { + return cgmActive; + } + + public void setCgmActive(boolean cgmActive) { + this.cgmActive = cgmActive; + } + + public byte getActiveBasalPattern() { + return activeBasalPattern; + } + + public void setActiveBasalPattern(byte activeBasalPattern) { + this.activeBasalPattern = activeBasalPattern; + } + + public float getBasalRate() { + return basalRate; + } + + public void setBasalRate(float basalRate) { + this.basalRate = basalRate; + } + + public float getTempBasalRate() { + return tempBasalRate; + } + + public void setTempBasalRate(float tempBasalRate) { + this.tempBasalRate = tempBasalRate; + } + + public byte getTempBasalPercentage() { + return tempBasalPercentage; + } + + public void setTempBasalPercentage(byte tempBasalPercentage) { + this.tempBasalPercentage = tempBasalPercentage; + } + + public short getTempBasalMinutesRemaining() { + return tempBasalMinutesRemaining; + } + + public void setTempBasalMinutesRemaining(short tempBasalMinutesRemaining) { + this.tempBasalMinutesRemaining = tempBasalMinutesRemaining; + } + + public float getBasalUnitsDeliveredToday() { + return basalUnitsDeliveredToday; + } + + public void setBasalUnitsDeliveredToday(float basalUnitsDeliveredToday) { + this.basalUnitsDeliveredToday = basalUnitsDeliveredToday; + } + + public short getMinutesOfInsulinRemaining() { + return minutesOfInsulinRemaining; + } + + public void setMinutesOfInsulinRemaining(short minutesOfInsulinRemaining) { + this.minutesOfInsulinRemaining = minutesOfInsulinRemaining; + } + + public Date getSgvDate() { + return sgvDate; + } + + public void setSgvDate(Date sgvDate) { + this.sgvDate = sgvDate; + } + + public boolean isLowSuspendActive() { + return lowSuspendActive; + } + + public void setLowSuspendActive(boolean lowSuspendActive) { + this.lowSuspendActive = lowSuspendActive; + } + public boolean isRecentBolusWizard() { return recentBolusWizard; } @@ -66,11 +253,25 @@ public class PumpStatusEvent extends RealmObject { this.recentBolusWizard = recentBolusWizard; } - public int getBolusWizardBGL() { - return bolusWizardBGL; + public long getPumpTimeOffset() { + return pumpTimeOffset; } - public void setBolusWizardBGL(int bolusWizardBGL) { - this.bolusWizardBGL = bolusWizardBGL; + public void setPumpTimeOffset(long pumpTimeOffset) { + this.pumpTimeOffset = pumpTimeOffset; + } + + public enum CGM_TREND { + NONE, + DOUBLE_UP, + SINGLE_UP, + FOURTY_FIVE_UP, + FLAT, + FOURTY_FIVE_DOWN, + SINGLE_DOWN, + DOUBLE_DOWN, + NOT_COMPUTABLE, + RATE_OUT_OF_RANGE, + NOT_SET } } 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 e070ac229cd308a3e7f82ae50528aa789bfcbb21..7519a5c7f75cc4e177012ca37b2ec08d272d0b8a 100644 --- a/app/src/main/java/info/nightscout/android/settings/SettingsFragment.java +++ b/app/src/main/java/info/nightscout/android/settings/SettingsFragment.java @@ -10,13 +10,7 @@ import android.preference.Preference; import android.preference.PreferenceCategory; import android.preference.PreferenceFragment; -import java.io.IOException; - import info.nightscout.android.R; -import info.nightscout.android.upload.nightscout.NightscoutApi; -import retrofit2.Call; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; public class SettingsFragment extends PreferenceFragment implements OnSharedPreferenceChangeListener { diff --git a/app/src/main/java/info/nightscout/android/upload/DeviceRecord.java b/app/src/main/java/info/nightscout/android/upload/DeviceRecord.java deleted file mode 100644 index 0d4b5b2739774506e4ae41584a7bc8850905c498..0000000000000000000000000000000000000000 --- a/app/src/main/java/info/nightscout/android/upload/DeviceRecord.java +++ /dev/null @@ -1,22 +0,0 @@ -package info.nightscout.android.upload; - -import java.io.Serializable; - -public class DeviceRecord extends Record implements Serializable{ - - /** - * - */ - private static final long serialVersionUID = 6321618305992689901L; - - public String deviceId = ""; - - protected String deviceName = ""; - - public String getDeviceName(){ - return deviceName; - } - public void setDeviceName( String deviceName ){ - this.deviceName = deviceName; - } -} diff --git a/app/src/main/java/info/nightscout/android/upload/GlucometerRecord.java b/app/src/main/java/info/nightscout/android/upload/GlucometerRecord.java deleted file mode 100644 index 10dbb2c22df4d20f73d65a354a7c89f940eae596..0000000000000000000000000000000000000000 --- a/app/src/main/java/info/nightscout/android/upload/GlucometerRecord.java +++ /dev/null @@ -1,19 +0,0 @@ -package info.nightscout.android.upload; - -import java.io.Serializable; - -public class GlucometerRecord extends Record implements Serializable{ - public float numGlucometerValue = 0; - public long lastDate = 0; - - private static final long serialVersionUID = 4654897648L; - - public void setNumGlucometerValue(int numGlucometerValue) { - this.numGlucometerValue = numGlucometerValue; - } - - public void setLastDate(long lastDate) { - this.lastDate = lastDate; - } - -} diff --git a/app/src/main/java/info/nightscout/android/upload/MedtronicNG/CGMRecord.java b/app/src/main/java/info/nightscout/android/upload/MedtronicNG/CGMRecord.java deleted file mode 100644 index 514c7704cdb501cc2be078510d8fdc64f74ac0d0..0000000000000000000000000000000000000000 --- a/app/src/main/java/info/nightscout/android/upload/MedtronicNG/CGMRecord.java +++ /dev/null @@ -1,101 +0,0 @@ -package info.nightscout.android.upload.MedtronicNG; - -import info.nightscout.android.upload.DeviceRecord; - -import java.io.Serializable; -import java.util.Date; - -/** - * Created by lgoedhart on 27/03/2016. - */ -public class CGMRecord extends DeviceRecord implements Serializable { - public enum TREND { - NONE(0), - DOUBLE_UP(1), - SINGLE_UP(2), - FOURTY_FIVE_UP(3), - FLAT(4), - FOURTY_FIVE_DOWN(5), - SINGLE_DOWN(6), - DOUBLE_DOWN(7), - NOT_COMPUTABLE(8), - RATE_OUT_OF_RANGE(9), - NOT_SET(10); - - private byte value; - TREND(int trend) { - this.value = (byte)trend; - } - } - - private TREND trend = TREND.NOT_SET; - - public int sgv = 0; // in mg/dL. 0 means no sensor reading - public Date sgvDate = new Date(); - public String direction; - - public void setTrend( TREND trend ) { - this.trend = trend; - - switch( trend ) { - case NONE: - this.direction = "NONE"; - break; - case DOUBLE_UP: - this.direction = "DoubleUp"; - break; - case SINGLE_UP: - this.direction = "SingleUp"; - break; - case FOURTY_FIVE_UP: - this.direction = "FortyFiveUp"; - break; - case FLAT: - this.direction = "Flat"; - break; - case FOURTY_FIVE_DOWN: - this.direction = "FortyFiveDown"; - break; - case SINGLE_DOWN: - this.direction = "SingleDown"; - break; - case DOUBLE_DOWN: - this.direction = "DoubleDown"; - break; - case NOT_COMPUTABLE: - this.direction = "NOT COMPUTABLE"; - break; - case RATE_OUT_OF_RANGE: - this.direction = "RATE OUT OF RANGE"; - break; - case NOT_SET: - this.direction = "NONE"; - break; - } - } - - public TREND getTrend() { - return trend; - } - - public static TREND fromMessageByte(byte messageByte) { - switch( messageByte ) { - case (byte) 0x60: - return TREND.FLAT; - case (byte) 0xc0: - return TREND.DOUBLE_UP; - case (byte) 0xa0: - return TREND.SINGLE_UP; - case (byte) 0x80: - return TREND.FOURTY_FIVE_UP; - case (byte) 0x40: - return TREND.FOURTY_FIVE_DOWN; - case (byte) 0x20: - return TREND.SINGLE_DOWN; - case (byte) 0x00: - return TREND.DOUBLE_DOWN; - default: - return TREND.NOT_COMPUTABLE; - } - } -} diff --git a/app/src/main/java/info/nightscout/android/upload/MedtronicNG/PumpStatusRecord.java b/app/src/main/java/info/nightscout/android/upload/MedtronicNG/PumpStatusRecord.java deleted file mode 100644 index 8f0c5c239ea3c17f131b2e2088f2bf567dad9f1f..0000000000000000000000000000000000000000 --- a/app/src/main/java/info/nightscout/android/upload/MedtronicNG/PumpStatusRecord.java +++ /dev/null @@ -1,19 +0,0 @@ -package info.nightscout.android.upload.MedtronicNG; - -import info.nightscout.android.upload.DeviceRecord; - -import java.io.Serializable; -import java.math.BigDecimal; -import java.util.Date; - -/** - * Created by lgoedhart on 27/03/2016. - */ -public class PumpStatusRecord extends DeviceRecord implements Serializable { - public int batteryPercentage; - public Date pumpDate = new Date(); - public BigDecimal activeInsulin = new BigDecimal(0); - public BigDecimal reservoirAmount = new BigDecimal(0); - public boolean recentBolusWizard = false; // Whether a bolus wizard has been run recently - public int bolusWizardBGL = 0; // in mg/dL. 0 means no recent bolus wizard reading. -} diff --git a/app/src/main/java/info/nightscout/android/upload/Record.java b/app/src/main/java/info/nightscout/android/upload/Record.java deleted file mode 100644 index 61a75faa79a09054a50215cc01f52a65972abb9c..0000000000000000000000000000000000000000 --- a/app/src/main/java/info/nightscout/android/upload/Record.java +++ /dev/null @@ -1,17 +0,0 @@ -package info.nightscout.android.upload; - -import java.io.Serializable; - -public class Record implements Serializable { - public String displayTime = "---"; - - /** - * - */ - private static final long serialVersionUID = -1381174446348390503L; - - public void setDisplayTime (String input) { - this.displayTime = input; - } - -} 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 similarity index 100% rename from app/src/main/java/info/nightscout/android/upload/nightscout/NightScoutApi.java rename to app/src/main/java/info/nightscout/android/upload/nightscout/NightscoutApi.java 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 6ba4b5c8d65b8f71dfa44fcef38cadad02e4c87b..6ac7129878dfdcf45118b3f1f4cf83538766de24 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,6 +10,8 @@ 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; @@ -18,8 +20,11 @@ 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; @@ -28,7 +33,7 @@ import java.util.regex.Pattern; import info.nightscout.android.R; import info.nightscout.android.medtronic.MainActivity; -import info.nightscout.android.model.CgmStatusEvent; +import info.nightscout.android.model.medtronicNg.PumpStatusEvent; import info.nightscout.android.upload.nightscout.serializer.EntriesSerializer; import io.realm.Realm; import io.realm.RealmResults; @@ -66,28 +71,34 @@ public class NightscoutUploadIntentService extends IntentService { Log.d(TAG, "onHandleIntent called"); mRealm = Realm.getDefaultInstance(); - RealmResults<CgmStatusEvent> records = mRealm - .where(CgmStatusEvent.class) + RealmResults<PumpStatusEvent> records = mRealm + .where(PumpStatusEvent.class) .equalTo("uploaded", false) .notEqualTo("sgv", 0) .findAll(); - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext); + if (records.size() > 0) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext); - Boolean enableRESTUpload = prefs.getBoolean("EnableRESTUpload", false); - try { - if (enableRESTUpload) { - long start = System.currentTimeMillis(); - Log.i(TAG, String.format("Starting upload of %s record using a REST API", records.size())); - doRESTUpload(prefs, records); - Log.i(TAG, String.format("Finished upload of %s record using a REST API in %s ms", records.size(), System.currentTimeMillis() - start)); + Boolean enableRESTUpload = prefs.getBoolean("EnableRESTUpload", false); + try { + if (enableRESTUpload) { + long start = System.currentTimeMillis(); + Log.i(TAG, String.format("Starting upload of %s record using a REST API", records.size())); + doRESTUpload(prefs, records); + Log.i(TAG, String.format("Finished upload of %s record using a REST API in %s ms", records.size(), System.currentTimeMillis() - start)); + } + } catch (Exception e) { + Log.e(TAG, "ERROR uploading data!!!!!", e); } - } catch (Exception e) { - Log.e(TAG, "ERROR uploading data!!!!!", e); + } else { + Log.i(TAG, "No records has to be uploaded"); } + + NightscoutUploadReceiver.completeWakefulIntent(intent); } - private void doRESTUpload(SharedPreferences prefs, RealmResults<CgmStatusEvent> records) { + 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"); @@ -125,7 +136,7 @@ public class NightscoutUploadIntentService extends IntentService { } } - private void doRESTUploadTo(String baseURI, RealmResults<CgmStatusEvent> records) { + private void doRESTUploadTo(String baseURI, RealmResults<PumpStatusEvent> records) { try { String baseURL; String secret = null; @@ -151,128 +162,151 @@ public class NightscoutUploadIntentService extends IntentService { throw new Exception(String.format("Unexpected baseURI: %s, uriParts.length: %s", baseURI, uriParts.length)); } - HttpParams params = new BasicHttpParams(); - HttpConnectionParams.setSoTimeout(params, SOCKET_TIMEOUT); - HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT); + JSONArray devicestatusBody = new JSONArray(); + JSONArray entriesBody = new JSONArray(); - DefaultHttpClient httpclient = new DefaultHttpClient(params); + for (PumpStatusEvent record : records) { + addDeviceStatus(devicestatusBody, record); + addSgvEntry(entriesBody, record); + addMbgEntry(entriesBody, record); + } - postDeviceStatus(baseURL, httpclient); + boolean isUploaded = uploadToNightscout(new URL(baseURL + "/entries"), secret, entriesBody); - for (CgmStatusEvent record : records) { - String postURL = baseURL + "entries"; + for(int i = 0; isUploaded && i < devicestatusBody.length(); i++) { + isUploaded &= uploadToNightscout(new URL(baseURL + "/devicestatus"), secret, devicestatusBody.getJSONObject(i)); + } - Log.i(TAG, "postURL: " + postURL); + if (isUploaded) { + // Yay! We uploaded. Tell Realm + // FIXME - check the upload succeeded! + mRealm.beginTransaction(); + for (PumpStatusEvent updateRecord : records) { + updateRecord.setUploaded(true); + } + mRealm.commitTransaction(); + } - HttpPost post = new HttpPost(postURL); + } catch (Exception e) { + Log.e(TAG, "Unable to post data", e); + } + } - 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); - } + private boolean uploadToNightscout(URL endpoint, String secret, JSONObject httpBody) throws Exception { + return uploadToNightscout(endpoint, secret, httpBody.toString()); + } - JSONObject json = new JSONObject(); + private boolean uploadToNightscout(URL endpoint, String secret, JSONArray httpBody) throws Exception { + return uploadToNightscout(endpoint, secret, httpBody.toString()); + } - try { - // FIXME - Change this to bulk uploads - populateV1APIEntry(json, record); - } catch (Exception e) { - Log.w(TAG, "Unable to populate entry", e); - continue; - } + 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); + } - String jsonString = json.toString(); + HttpParams params = new BasicHttpParams(); + HttpConnectionParams.setSoTimeout(params, SOCKET_TIMEOUT); + HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT); - Log.i(TAG, "Upload JSON: " + jsonString); + DefaultHttpClient httpclient = new DefaultHttpClient(params); - try { - StringEntity se = new StringEntity(jsonString); - post.setEntity(se); - post.setHeader("Accept", "application/json"); - post.setHeader("Content-type", "application/json"); + Log.i(TAG, "Upload JSON: " + httpBody); - ResponseHandler responseHandler = new BasicResponseHandler(); - httpclient.execute(post, responseHandler); - } catch (Exception e) { - Log.w(TAG, "Unable to post data to: '" + post.getURI().toString() + "'", e); - } + try { + StringEntity se = new StringEntity(httpBody); + post.setEntity(se); + post.setHeader("Accept", "application/json"); + post.setHeader("Content-type", "application/json"); - // Yay! We uploaded. Tell Realm - mRealm.beginTransaction(); - // TODO - does realm have auto incrementing keys? - // Turns out not yet (https://github.com/realm/realm-java/issues/469), - // but in the meantime, use this: https://gist.github.com/carloseduardosx/a7bd88d7337660cd10a2c5dcc580ebd0 - RealmResults<CgmStatusEvent> updateRecordResults = mRealm - .where(CgmStatusEvent.class) - .equalTo("eventDate", record.getEventDate()) - .equalTo("deviceName", record.getDeviceName()) - .equalTo("sgv", record.getSgv()) - .findAll(); - // FIXME - We shouldn't need this after we remove insertion of duplicates - for (CgmStatusEvent updateRecord : updateRecordResults) { - updateRecord.setUploaded(true); - } - mRealm.commitTransaction(); - } + ResponseHandler responseHandler = new BasicResponseHandler(); + httpclient.execute(post, responseHandler); } catch (Exception e) { - Log.e(TAG, "Unable to post data", e); + Log.w(TAG, "Unable to post data to: '" + post.getURI().toString() + "'", e); + return false; } - } - private void postDeviceStatus(String baseURL, DefaultHttpClient httpclient) throws Exception { - String devicestatusURL = baseURL + "devicestatus"; - Log.i(TAG, "devicestatusURL: " + devicestatusURL); + return true; + } + private void addDeviceStatus(JSONArray devicestatusArray, PumpStatusEvent record) throws Exception { JSONObject json = new JSONObject(); json.put("uploaderBattery", MainActivity.batLevel); - json.put("device", MainActivity.pumpStatusRecord.getDeviceName()); + 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(MainActivity.pumpStatusRecord.pumpDate)); - pumpInfo.put("reservoir", MainActivity.pumpStatusRecord.reservoirAmount); + 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", MainActivity.pumpStatusRecord.pumpDate); - iob.put("bolusiob", MainActivity.pumpStatusRecord.activeInsulin); + 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", MainActivity.pumpStatusRecord.batteryPercentage); + 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); - HttpPost post = new HttpPost(devicestatusURL); - StringEntity se = new StringEntity(jsonString); - post.setEntity(se); - post.setHeader("Accept", "application/json"); - post.setHeader("Content-type", "application/json"); - - ResponseHandler responseHandler = new BasicResponseHandler(); - httpclient.execute(post, responseHandler); + devicestatusArray.put(json); } - private void populateV1APIEntry(JSONObject json, CgmStatusEvent pumpRecord) throws Exception { + 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.getTrend())); + 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() { diff --git a/app/src/main/java/info/nightscout/android/upload/nightscout/NightscoutUploadReceiver.java b/app/src/main/java/info/nightscout/android/upload/nightscout/NightscoutUploadReceiver.java new file mode 100644 index 0000000000000000000000000000000000000000..2eb58caadcb41fbde9bb36e7b9d40778967d1465 --- /dev/null +++ b/app/src/main/java/info/nightscout/android/upload/nightscout/NightscoutUploadReceiver.java @@ -0,0 +1,21 @@ +package info.nightscout.android.upload.nightscout; + +import android.content.Context; +import android.content.Intent; +import android.support.v4.content.WakefulBroadcastReceiver; +import android.util.Log; + +/** + * Created by lgoedhart on 14/07/2016. + */ +public class NightscoutUploadReceiver extends WakefulBroadcastReceiver { + private static final String TAG = NightscoutUploadReceiver.class.getSimpleName(); + + @Override + public void onReceive(final Context context, Intent intent) { + // Start the IntentService + Log.d(TAG, "Received broadcast message"); + Intent service = new Intent(context, NightscoutUploadIntentService.class); + startWakefulService(context, service); + } +} 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 02da1b07766c5e7f2a3ce413f791c380ca977d7e..260b8c3b2acab0a24e81dd6c35dee9f9872f4e21 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 @@ -7,13 +7,13 @@ import com.google.gson.JsonSerializer; import java.lang.reflect.Type; -import info.nightscout.android.model.CgmStatusEvent; +import info.nightscout.android.model.medtronicNg.PumpStatusEvent; /** * Created by lgoedhart on 26/06/2016. */ -public class EntriesSerializer implements JsonSerializer<CgmStatusEvent> { - public static String getDirectionString(CgmStatusEvent.TREND trend) { +public class EntriesSerializer implements JsonSerializer<PumpStatusEvent> { + public static String getDirectionString(PumpStatusEvent.CGM_TREND trend) { switch( trend ) { case NONE: return "NONE"; @@ -43,10 +43,10 @@ public class EntriesSerializer implements JsonSerializer<CgmStatusEvent> { } @Override - public JsonElement serialize(CgmStatusEvent src, Type typeOfSrc, JsonSerializationContext context) { + public JsonElement serialize(PumpStatusEvent src, Type typeOfSrc, JsonSerializationContext context) { final JsonObject jsonObject = new JsonObject(); jsonObject.addProperty("sgv", src.getSgv()); - jsonObject.addProperty("direction", getDirectionString(src.getTrend())); + jsonObject.addProperty("direction", getDirectionString(src.getCgmTrend())); jsonObject.addProperty("device", src.getDeviceName()); jsonObject.addProperty("type", "sgv"); jsonObject.addProperty("date", src.getEventDate().getTime()); 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 new file mode 100644 index 0000000000000000000000000000000000000000..6cc1a6f91b54362685926b06cb6f9072b78704bd --- /dev/null +++ b/app/src/main/java/info/nightscout/android/xdrip_plus/XDripPlusUploadIntentService.java @@ -0,0 +1,171 @@ +package info.nightscout.android.xdrip_plus; + +import android.app.IntentService; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.os.Bundle; +import android.support.v4.content.LocalBroadcastManager; +import android.util.Log; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.math.BigDecimal; +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 io.realm.Realm; +import io.realm.RealmResults; +import io.realm.Sort; + +/** + * Created by jamorham on 17/11/2016. + */ + + +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()); + } + + // status unused + protected void sendStatus(String message) { + Intent localIntent = + new Intent(info.nightscout.android.xdrip_plus.XDripPlusUploadIntentService.Constants.ACTION_STATUS_MESSAGE) + .putExtra(info.nightscout.android.xdrip_plus.XDripPlusUploadIntentService.Constants.EXTENDED_DATA, message); + LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent); + } + + @Override + public void onCreate() { + super.onCreate(); + + Log.i(TAG, "onCreate called"); + mContext = this.getBaseContext(); + } + + @Override + protected void onHandleIntent(Intent intent) { + Log.d(TAG, "onHandleIntent called"); + mRealm = Realm.getDefaultInstance(); + + RealmResults<PumpStatusEvent> all_records = mRealm + .where(PumpStatusEvent.class) + .notEqualTo("sgv", 0) + .findAllSorted("eventDate", Sort.DESCENDING); + + // get the most recent record and send that + if (all_records.size() > 0) { + List<PumpStatusEvent> records = all_records.subList(0, 1); + doXDripUpload(records); + } + XDripPlusUploadReceiver.completeWakefulIntent(intent); + } + + private void doXDripUpload(List<PumpStatusEvent> records) { + try { + + final JSONArray devicestatusBody = new JSONArray(); + final JSONArray entriesBody = new JSONArray(); + + for (PumpStatusEvent record : records) { + addDeviceStatus(devicestatusBody, record); + addSgvEntry(entriesBody, record); + addMbgEntry(entriesBody, record); + } + + 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); + } + } + + private void sendBundle(Context context, String action, String collection, JSONArray json) { + final Bundle bundle = new Bundle(); + bundle.putString("action", action); + bundle.putString("collection", collection); + bundle.putString("data", json.toString()); + final Intent intent = new Intent(Constants.XDRIP_PLUS_NS_EMULATOR); + intent.putExtras(bundle).addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); + 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"); + } + + + 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 battery = new JSONObject(); + battery.put("percent", record.getBatteryPercentage()); + + pumpInfo.put("iob", iob); + pumpInfo.put("battery", battery); + json.put("pump", pumpInfo); + //String jsonString = json.toString(); + + 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); + } + } + + + public final class Constants { + public static final String ACTION_STATUS_MESSAGE = "info.nightscout.android.xdrip_plus.STATUS_MESSAGE"; + public static final String EXTENDED_DATA = "info.nightscout.android.xdrip_plus.DATA"; + private static final String XDRIP_PLUS_NS_EMULATOR = "com.eveningoutpost.dexdrip.NS_EMULATOR"; + } +} diff --git a/app/src/main/java/info/nightscout/android/xdrip_plus/XDripPlusUploadReceiver.java b/app/src/main/java/info/nightscout/android/xdrip_plus/XDripPlusUploadReceiver.java new file mode 100644 index 0000000000000000000000000000000000000000..734cd34d65542d827f26d5b81290ccb42023db47 --- /dev/null +++ b/app/src/main/java/info/nightscout/android/xdrip_plus/XDripPlusUploadReceiver.java @@ -0,0 +1,22 @@ +package info.nightscout.android.xdrip_plus; + +import android.content.Context; +import android.content.Intent; +import android.support.v4.content.WakefulBroadcastReceiver; +import android.util.Log; + + +/** + * Created by jamorham on 17/11/2016. + */ +public class XDripPlusUploadReceiver extends WakefulBroadcastReceiver { + private static final String TAG = XDripPlusUploadReceiver.class.getSimpleName(); + + @Override + public void onReceive(final Context context, Intent intent) { + // Start the IntentService + Log.d(TAG, "Received broadcast message"); + Intent service = new Intent(context, XDripPlusUploadIntentService.class); + startWakefulService(context, service); + } +} diff --git a/app/src/main/res/drawable/drawer_header.jpg b/app/src/main/res/drawable/drawer_header.jpg index e3f8ab881ebc761afbfdad7ae6b554adb658d83a..251abd5c8a8ee193ef484393a966c891c8f5478a 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 index 56ac4a9c6e3a449922fac8c1bcedbd368be5f4a1..84cc07b2b8603da6a558869fe53fa029ae140bcd 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -11,13 +11,6 @@ tools:context=".medtronic.GetHmacAndKeyActivity"> <!-- Login progress --> - <ProgressBar - android:id="@+id/login_progress" - style="?android:attr/progressBarStyleLarge" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginBottom="8dp" - android:visibility="gone"/> <ScrollView android:layout_width="match_parent" @@ -28,52 +21,10 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - <LinearLayout - android:id="@+id/login_form" + <TextView android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="vertical"> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceMedium" - android:text="@string/prompt_carelink_username_password" /> - - <AutoCompleteTextView - android:id="@+id/username" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:hint="@string/prompt_username" - android:inputType="text" - android:imeOptions="actionNext" - android:maxLines="1" - android:singleLine="true"/> - - <EditText - android:id="@+id/password" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:hint="@string/prompt_password" - android:imeActionId="@+id/login" - android:imeActionLabel="@string/action_sign_in_short" - android:imeOptions="actionDone|actionUnspecified" - android:inputType="textPassword" - android:maxLines="1" - android:singleLine="true"/> - - </LinearLayout> - - <LinearLayout - android:orientation="horizontal" - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:id="@+id/registered_usb_devices" /> - </LinearLayout> + android:id="@+id/registered_usb_devices" /> </LinearLayout> </ScrollView> diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index b8e7b4b93be8c470d57fcb88bf2d70fa92a9fed9..af14d6804fd460697687fa207ac50f610a4bf518 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -119,13 +119,11 @@ </LinearLayout> - <!-- <com.github.mikephil.charting.charts.LineChart android:id="@+id/chart" android:layout_width="match_parent" android:layout_height="wrap_content" android:visibility="invisible" /> - --> <ScrollView android:id="@+id/scrollView" diff --git a/app/src/main/res/menu/menu_register_usb.xml b/app/src/main/res/menu/menu_register_usb.xml deleted file mode 100644 index 81a6c5646903e8d5f192edbd9d651e4b025c70dc..0000000000000000000000000000000000000000 --- a/app/src/main/res/menu/menu_register_usb.xml +++ /dev/null @@ -1,9 +0,0 @@ -<?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"> - <item android:id="@+id/action_menu_login" - android:title="Login" - android:orderInCategory="100" - app:showAsAction="always" - /> -</menu> \ 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 bbb585428d3ae24f746f6ff16c7ccdb68440663e..443d000163d74d1afe620bd3cb389b7d005b04b1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -30,7 +30,7 @@ <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">Register Contour Next Link</string> + <string name="register_contour_next_link">Registered Devices</string> <string name="preferences_enable_crashlytics">prefs_enable_crashlytics</string> <string name="preferences_enable_answers">prefs_enable_answers</string> <string name="preferences_enable_remote_logcat">prefs_enable_remote_logcat</string> @@ -41,6 +41,7 @@ <string name="button_text_start_uploading_data">Start Uploading CGM Data</string> <string name="preference_eula_accepted">IUNDERSTAND</string> <string name="preference_enable_rest_upload">EnableRESTUpload</string> + <string name="preference_enable_xdrip_plus">EnablexDripPlusUpload</string> <string name="error_msg_api_secret_length">API Secret must be 12 characters or longer.</string> <string name="text_unit_mmolxl">mmol/L</string> <string name="text_unit_mgxdl">mg/dL</string> diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 8a8fb08d3852284649d8428d84e72fac42c4d557..9e6793c85ec60101ef37b053d3af5d9caefd74b1 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -39,6 +39,10 @@ android:dialogTitle="Enter your Nightscout API secret" android:key="@string/preference_api_secret" android:title="API Secret"/> + <CheckBoxPreference + android:key="@string/preference_enable_xdrip_plus" + android:summary="Enable local broadcast of data to xDrip+" + android:title="Send to xDrip+"/> </PreferenceCategory> <PreferenceCategory android:title="Disclaimer"> <SwitchPreference diff --git a/build.gradle b/build.gradle index c08cd482b77a2b4b7463d7023f54a0d5f4adb69f..9da42b0b994170c75de5ee547bfabc0007cf8dd7 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,11 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. + // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.0' + 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" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 52e3cb73d4fabf8cfd57d817bcf31c28a211bdf1..3d5435e07e9964062e47fc0426eaa27552a17041 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sun May 01 23:04:04 AEST 2016 +#Sat Nov 12 11:44:13 AEDT 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip