+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/glass/proguard-rules.pro b/glass/proguard-rules.pro
new file mode 100644
index 0000000..8b3ee7d
--- /dev/null
+++ b/glass/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/klee24/Android/android-sdk-mac_x86/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/glass/src/androidTest/java/com/ktind/cgm/bgscout/ApplicationTest.java b/glass/src/androidTest/java/com/ktind/cgm/bgscout/ApplicationTest.java
new file mode 100644
index 0000000..c16e3ed
--- /dev/null
+++ b/glass/src/androidTest/java/com/ktind/cgm/bgscout/ApplicationTest.java
@@ -0,0 +1,13 @@
+package com.ktind.cgm.bgscout;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * Testing Fundamentals
+ */
+public class ApplicationTest extends ApplicationTestCase {
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
\ No newline at end of file
diff --git a/glass/src/main/AndroidManifest.xml b/glass/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..523378a
--- /dev/null
+++ b/glass/src/main/AndroidManifest.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
diff --git a/glass/src/main/res/drawable-hdpi/ic_launcher.png b/glass/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
Binary files /dev/null and b/glass/src/main/res/drawable-hdpi/ic_launcher.png differ
diff --git a/glass/src/main/res/drawable-mdpi/ic_launcher.png b/glass/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
Binary files /dev/null and b/glass/src/main/res/drawable-mdpi/ic_launcher.png differ
diff --git a/glass/src/main/res/drawable-xhdpi/ic_launcher.png b/glass/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
Binary files /dev/null and b/glass/src/main/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/glass/src/main/res/drawable-xxhdpi/ic_launcher.png b/glass/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4df1894
Binary files /dev/null and b/glass/src/main/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/glass/src/main/res/values/strings.xml b/glass/src/main/res/values/strings.xml
new file mode 100644
index 0000000..1f3c202
--- /dev/null
+++ b/glass/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ BG Scout
+
diff --git a/glass/src/main/res/values/styles.xml b/glass/src/main/res/values/styles.xml
new file mode 100644
index 0000000..65a325d
--- /dev/null
+++ b/glass/src/main/res/values/styles.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..5d08ba7
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,18 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Settings specified in this file will override any Gradle settings
+# configured through the IDE.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..1e61d1f
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Apr 10 15:27:10 PDT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/mobile/.gitignore b/mobile/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/mobile/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/mobile/build.gradle b/mobile/build.gradle
new file mode 100644
index 0000000..56d2bab
--- /dev/null
+++ b/mobile/build.gradle
@@ -0,0 +1,28 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 20
+ buildToolsVersion '20.0.0'
+
+ defaultConfig {
+ applicationId "com.ktind.cgm.bgscout"
+ minSdkVersion 16
+ targetSdkVersion 20
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ runProguard false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ wearApp project(':wear')
+ compile 'com.google.android.gms:play-services-wearable:+'
+ // You must install or update the Support Repository through the SDK manager to use this dependency.
+ compile 'com.android.support:support-v4:19.+'
+}
diff --git a/mobile/libs/mongo-java-driver-2.12.3.jar b/mobile/libs/mongo-java-driver-2.12.3.jar
new file mode 100644
index 0000000..98c555a
Binary files /dev/null and b/mobile/libs/mongo-java-driver-2.12.3.jar differ
diff --git a/mobile/libs/square-otto-1.3.2.jar b/mobile/libs/square-otto-1.3.2.jar
new file mode 100644
index 0000000..7a2ed89
Binary files /dev/null and b/mobile/libs/square-otto-1.3.2.jar differ
diff --git a/mobile/mobile.iml b/mobile/mobile.iml
new file mode 100644
index 0000000..f94d476
--- /dev/null
+++ b/mobile/mobile.iml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/proguard-rules.pro b/mobile/proguard-rules.pro
new file mode 100644
index 0000000..8b3ee7d
--- /dev/null
+++ b/mobile/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/klee24/Android/android-sdk-mac_x86/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/mobile/src/androidTest/java/com/ktind/cgm/bgscout/ApplicationTest.java b/mobile/src/androidTest/java/com/ktind/cgm/bgscout/ApplicationTest.java
new file mode 100644
index 0000000..c16e3ed
--- /dev/null
+++ b/mobile/src/androidTest/java/com/ktind/cgm/bgscout/ApplicationTest.java
@@ -0,0 +1,13 @@
+package com.ktind.cgm.bgscout;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * Testing Fundamentals
+ */
+public class ApplicationTest extends ApplicationTestCase {
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
\ No newline at end of file
diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..c368441
--- /dev/null
+++ b/mobile/src/main/AndroidManifest.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/AbstractCGMDevice.java b/mobile/src/main/java/com/ktind/cgm/bgscout/AbstractCGMDevice.java
new file mode 100644
index 0000000..98bff25
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/AbstractCGMDevice.java
@@ -0,0 +1,156 @@
+package com.ktind.cgm.bgscout;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+import android.os.Handler;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Date;
+
+/**
+ * Created by klee24 on 8/2/14.
+ */
+abstract public class AbstractCGMDevice implements CGMDeviceInterface {
+ private static final String TAG = DeviceDownloadService.class.getSimpleName();
+ protected String name;
+ protected int deviceID;
+// private Date date;
+ protected GlucoseUnit unit =GlucoseUnit.MGDL;
+ protected ArrayList monitors=new ArrayList();
+ protected DeviceDownloadObject lastDownloadObject;
+ protected Context appContext;
+ protected CGMTransportAbstract cgmTransport;
+ protected MonitorProxy monitorProxy=new MonitorProxy();
+ protected boolean virtual =false;
+ protected long nextFire=45000L;
+ protected Handler mHandler;
+ protected int pollInterval=302000;
+
+ public boolean isVirtual() {
+ return virtual;
+ }
+
+ public AbstractCGMDevice(String n,int deviceID,Context appContext,Handler mH){
+ Log.i(TAG, "Creating CGM " + n);
+ setName(n);
+ this.setDeviceID(deviceID);
+ this.setAppContext(appContext);
+ this.setHandler(mH);
+
+ AbstractMonitor anm=new AndroidNotificationMonitor(getName(),deviceID,appContext);
+ AbstractMonitor mongo=new MongoUploadMonitor(getName());
+ monitors.add(anm);
+ monitors.add(mongo);
+ monitorProxy.setMonitors(monitors);
+ }
+
+ public void setHandler(Handler mH){
+ this.mHandler=mH;
+ }
+
+ public float getUploaderBattery(){
+ IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
+ Intent batteryStatus = appContext.registerReceiver(null, ifilter);
+ int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
+ int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
+ return level / (float) scale;
+ }
+
+ abstract int getCGMBattery();
+
+ public int getDeviceID() {
+ return deviceID;
+ }
+
+ public void setDeviceID(int deviceID) {
+ this.deviceID = deviceID;
+ }
+
+ public Context getAppContext() {
+ return appContext;
+ }
+
+ final public DeviceDownloadObject download(){
+ lastDownloadObject=this.doDownload();
+// lastDownloadObject.setLastDownloadDate(new Date());
+ return lastDownloadObject;
+ }
+
+ abstract protected DeviceDownloadObject doDownload();
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public GlucoseUnit getUnit() {
+ return unit;
+ }
+
+ public void stopMonitors(){
+ monitorProxy.stopMonitors();
+ }
+
+ public void setUnit(GlucoseUnit unit) {
+ this.unit = unit;
+ }
+
+ @Override
+ public void fireMonitors() {
+ // FIXME - Not sure this is healthy....?
+ MonitorProxy myProxy=new MonitorProxy(monitorProxy);
+ myProxy.execute(lastDownloadObject);
+ }
+
+
+
+ public boolean isConnected(){
+ return cgmTransport.isOpen();
+ }
+
+ public void setAppContext(Context appContext) {
+ this.appContext = appContext;
+ }
+
+// public abstract float getUploaderBattery();
+
+ public abstract void connect() throws DeviceNotConnected;
+
+ public abstract void disconnect();
+
+ public int getPollInterval() {
+ return pollInterval;
+ }
+
+ public void setPollInterval(int pollInterval) {
+ Log.d(TAG,"Setting poll interval to: "+pollInterval);
+ this.pollInterval = pollInterval;
+ }
+
+ public long nextFire(){
+ return nextFire(getPollInterval());
+ }
+
+ public long nextFire(long millis){
+ if (lastDownloadObject!=null){
+ long diff=(millis-(new Date().getTime() - lastDownloadObject.getEgvRecords()[lastDownloadObject.getEgvRecords().length-1].getDate().getTime()));
+ Log.d(TAG,"nextFire calculated to be: "+diff+" for "+getName()+" using a poll interval of "+millis);
+ if (diff<0) {
+ Log.d(TAG,"nextFire returning 45 seconds because diff was negative");
+ return 45000;
+ }
+ return diff;
+ } else {
+ Log.d(TAG,"nextFire returning 45 seconds because there wasn't a lastdownloadobject set");
+ return 450000;
+ }
+ }
+
+// public abstract G4EGVRecord[] getReadings();
+}
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/AbstractMonitor.java b/mobile/src/main/java/com/ktind/cgm/bgscout/AbstractMonitor.java
new file mode 100644
index 0000000..081b524
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/AbstractMonitor.java
@@ -0,0 +1,62 @@
+package com.ktind.cgm.bgscout;
+
+import android.util.Log;
+
+/**
+ * Created by klee24 on 8/2/14.
+ */
+abstract public class AbstractMonitor implements MonitorInterface {
+ private static final String TAG = DeviceDownloadService.class.getSimpleName();
+ protected String name;
+ //FIXME find a better way to manage this?
+ protected boolean allowVirtual=false;
+ protected String monitorType="generic";
+ protected int highThreshold=180;
+ protected int lowThreshold=60;
+
+ public AbstractMonitor(String n){
+ this.name=n;
+ }
+
+ public String getMonitorType() {
+ return monitorType;
+ }
+
+ public void setMonitorType(String monitorType) {
+ this.monitorType = monitorType;
+ }
+
+ abstract protected void doProcess(DeviceDownloadObject d);
+
+ public boolean isAllowVirtual() {
+ return allowVirtual;
+ }
+
+ public void setAllowVirtual(boolean allowVirtual) {
+ this.allowVirtual = allowVirtual;
+ }
+
+ @Override
+ final public void process(DeviceDownloadObject d) {
+ Log.d(TAG,"Monitor "+name+" has fired for "+monitorType);
+ if (isAllowVirtual() || ! d.getDevice().isVirtual()){
+ Log.d(TAG, "Processing monitor "+name+" for "+monitorType);
+ this.doProcess(d);
+ } else {
+ Log.d(TAG, "Not processing monitor "+name+" for "+monitorType);
+ }
+ }
+
+ @Override
+ public void stop(){
+ Log.i(TAG,"Stopping monitor "+monitorType+" for "+name);
+ }
+
+ public void setHighThreshold(int highThreshold) {
+ this.highThreshold = highThreshold;
+ }
+
+ public void setLowThreshold(int lowThreshold) {
+ this.lowThreshold = lowThreshold;
+ }
+}
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/AndroidNotificationMonitor.java b/mobile/src/main/java/com/ktind/cgm/bgscout/AndroidNotificationMonitor.java
new file mode 100644
index 0000000..f5a8f58
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/AndroidNotificationMonitor.java
@@ -0,0 +1,184 @@
+package com.ktind.cgm.bgscout;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.util.Log;
+
+/**
+ * Created by klee24 on 8/2/14.
+ */
+public class AndroidNotificationMonitor extends AbstractMonitor {
+ private static final String TAG = AndroidNotificationMonitor.class.getSimpleName();
+ protected int notifID;
+ protected Context appContext;
+ protected Notification.Builder notifBuilder;
+ protected NotificationManager mNotifyMgr;
+ final protected String monitorType="android notification";
+
+ public NotificationManager getmNotifyMgr() {
+ return mNotifyMgr;
+ }
+
+ public void setmNotifyMgr(NotificationManager mNotifyMgr) {
+ this.mNotifyMgr = mNotifyMgr;
+ }
+
+ public int getNotifID() {
+ return notifID;
+ }
+
+ public void setNotifID(int notifID) {
+ this.notifID = notifID;
+ }
+
+ public Context getAppContext() {
+ return appContext;
+ }
+
+ public void setAppContext(Context appContext) {
+ this.appContext = appContext;
+ }
+
+ public Notification.Builder getNotifBuilder() {
+ return notifBuilder;
+ }
+
+ public void setNotifBuilder(Notification.Builder notifBuilder) {
+ this.notifBuilder = notifBuilder;
+ }
+
+ AndroidNotificationMonitor(String name,int notifID,Context appContext){
+ super(name);
+
+ this.setNotifID(notifID);
+// this.name=name;
+ this.setAppContext(appContext);
+ this.setMonitorType("mongo uploader");
+ mNotifyMgr = (NotificationManager) appContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ PendingIntent contentIntent = PendingIntent.getActivity(appContext, 0, new Intent(appContext, MainActivity.class), 0);
+ Bitmap bm = BitmapFactory.decodeResource(appContext.getResources(), R.drawable.icon);
+ this.setNotifBuilder(new Notification.Builder(appContext)
+ .setSmallIcon(R.drawable.icon)
+ .setContentTitle(name)
+ .setContentText("Monitor started. No data yet")
+ .setContentIntent(contentIntent)
+ .setOngoing(true)
+// .addAction(R.drawable.icon24x24, "Snooze", contentIntent)
+ .setLargeIcon(bm));
+ Notification notification = notifBuilder.build();
+ mNotifyMgr.notify(notifID, notification);
+ this.setAllowVirtual(true);
+ }
+
+ @Override
+ public void doProcess(DeviceDownloadObject dl) {
+ EGVRecord[] recs = dl.getEgvRecords();
+ EGVRecord lastRec = null;
+ if (recs != null && recs.length > 0)
+ lastRec = recs[recs.length - 1];
+ String msg="";
+
+ Notification notification;
+ int icon = R.drawable.questionmarkicon;
+ Log.v(TAG,"Status: "+dl.getStatus().toString());
+ if (dl.getStatus() != DownloadStatus.SUCCESS && dl.getStatus() != DownloadStatus.SPECIALVALUE) {
+ msg = dl.getStatus().toString()+"\n";
+ notifBuilder.setTicker(msg);
+ }
+ if (dl.getStatus() == DownloadStatus.SPECIALVALUE){
+ msg = dl.getSpecialValueMessage()+"\n";
+ notifBuilder.setTicker(msg);
+ }
+
+// if (dl.getStatus()== DownloadStatus.NORECORDS){
+// msg+=dl.getStatus().toString();
+// notifBuilder.setTicker(msg);
+// }
+
+ if (lastRec != null && dl.getStatus() != DownloadStatus.SPECIALVALUE) {
+ icon = getIcon(lastRec.getEgv(), lastRec.getTrend());
+ msg+= "BG: " + lastRec.getEgv() + " " + dl.getDevice().getUnit().toString() + " and " + lastRec.getTrend().toString();
+// msg+="\nLast reading: "+lastRec.getDate().toString();
+ notifBuilder.setContentText(msg);
+// notifBuilder.setDefaults(Notification.DEFAULT_ALL);
+ }
+ notifBuilder.setStyle(new Notification.BigTextStyle().bigText(msg));
+ notification = notifBuilder
+ .setSmallIcon(icon)
+ .build();
+ mNotifyMgr.notify(notifID, notification);
+ }
+
+
+ private int getIcon(int bgValue,Trend trend){
+ // Handle "NOT COMPUTABLE", "RATE OUT OF RANGE", and anything else that crops up.
+ int icon=R.drawable.questionmarkicon;
+ if (bgValue>=highThreshold){
+ if (trend==Trend.NONE) {
+ icon=R.drawable.nonered;
+ }else if(trend==Trend.DOUBLEUP) {
+ icon=R.drawable.arrowdoubleupred;
+ }else if(trend==Trend.SINGLEUP) {
+ icon=R.drawable.arrowupred;
+ }else if(trend==Trend.FORTYFIVEUP) {
+ icon=R.drawable.arrow45upred;
+ }else if(trend==Trend.FLAT) {
+ icon=R.drawable.arrowflatred;
+ }else if(trend==Trend.DOUBLEDOWN) {
+ icon=R.drawable.arrowdoubledownred;
+ }else if(trend==Trend.SINGLEDOWN) {
+ icon=R.drawable.arrowdownred;
+ }else if(trend==Trend.FORTYFIVEDOWN) {
+ icon=R.drawable.arrow45downred;
+ }
+ }else if (bgValue<=lowThreshold){
+ if (trend==Trend.NONE) {
+ icon=R.drawable.noneyellow;
+ }else if(trend==Trend.DOUBLEUP) {
+ icon=R.drawable.arrowdoubleupyellow;
+ }else if(trend==Trend.SINGLEUP) {
+ icon=R.drawable.arrowupyellow;
+ }else if(trend==Trend.FORTYFIVEUP) {
+ icon=R.drawable.arrow45upyellow;
+ }else if(trend==Trend.FLAT) {
+ icon=R.drawable.arrowflatyellow;
+ }else if(trend==Trend.DOUBLEDOWN) {
+ icon=R.drawable.arrowdoubledownyellow;
+ }else if(trend==Trend.SINGLEDOWN) {
+ icon=R.drawable.arrowdownyellow;
+ }else if(trend==Trend.FORTYFIVEDOWN) {
+ icon=R.drawable.arrow45downyellow;
+ }
+ }else{
+ if (trend==Trend.NONE) {
+ icon=R.drawable.noneblue;
+ }else if(trend==Trend.DOUBLEUP) {
+ icon=R.drawable.arrowdoubleupblue;
+ }else if(trend==Trend.SINGLEUP) {
+ icon=R.drawable.arrowupblue;
+ }else if(trend==Trend.FORTYFIVEUP) {
+ icon=R.drawable.arrow45upblue;
+ }else if(trend==Trend.FLAT) {
+ icon=R.drawable.arrowflatblue;
+ }else if(trend==Trend.DOUBLEDOWN) {
+ icon=R.drawable.arrowdoubledownblue;
+ }else if(trend==Trend.SINGLEDOWN) {
+ icon=R.drawable.arrowdownblue;
+ }else if(trend==Trend.FORTYFIVEDOWN) {
+ icon=R.drawable.arrow45downblue;
+ }
+ }
+ return icon;
+ }
+
+ @Override
+ public void stop() {
+ Log.i(TAG, "Stopping monitor " + monitorType + " for " + name);
+ mNotifyMgr.cancel(notifID);
+ }
+}
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/BitTools.java b/mobile/src/main/java/com/ktind/cgm/bgscout/BitTools.java
new file mode 100644
index 0000000..823d6b8
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/BitTools.java
@@ -0,0 +1,48 @@
+package com.ktind.cgm.bgscout;
+
+//import android.util.Log;
+
+/**
+ * Created by klee24 on 7/20/14.
+ */
+public class BitTools {
+ private static final String TAG = BitTools.class.getSimpleName();
+ final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
+
+ // TODO Need to do some more checking but in theory this could be larger than an int.
+ public static int byteArraytoInt(byte[] b){
+// if (b == null || b.length<4)
+ if (b == null )
+ return -1;
+ int val=0;
+ int counter=0;
+ for (byte v: b){
+ val+=(v & 0x000000FF) << counter*8;
+ counter+=1;
+ }
+ return val;
+ }
+
+
+
+ public static String bytesToHex(byte[] bytes) {
+ char[] hexChars = new char[bytes.length * 3];
+ for ( int j = 0; j < bytes.length; j++ ) {
+ int v = bytes[j] & 0xFF;
+ hexChars[j * 3] = hexArray[v >>> 4];
+ hexChars[j * 3 + 1] = hexArray[v & 0x0F];
+ hexChars[j * 3 + 2] = " ".toCharArray()[0];
+ }
+ return new String(hexChars);
+ }
+
+ public static byte[] intToByteArray(int i){
+ byte[] byteArray=new byte[4];
+ byteArray[0]=(byte) (i & 0xFF);
+ byteArray[1]=(byte) ((i >> 8) & 0xFF);
+ byteArray[2]=(byte) ((i >> 16) & 0xFF);
+ byteArray[3]=(byte) ((i >> 24) & 0xFF);
+ return byteArray;
+ }
+
+}
\ No newline at end of file
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/CGMBus.java b/mobile/src/main/java/com/ktind/cgm/bgscout/CGMBus.java
new file mode 100644
index 0000000..6aaddba
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/CGMBus.java
@@ -0,0 +1,14 @@
+package com.ktind.cgm.bgscout;
+
+import com.squareup.otto.Bus;
+
+/**
+ * Created by klee24 on 7/29/14.
+ */
+public class CGMBus {
+ private static final Bus bus=new Bus();
+
+ public static Bus getInstance(){
+ return bus;
+ }
+}
\ No newline at end of file
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/CGMDeviceInterface.java b/mobile/src/main/java/com/ktind/cgm/bgscout/CGMDeviceInterface.java
new file mode 100644
index 0000000..019ef81
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/CGMDeviceInterface.java
@@ -0,0 +1,10 @@
+package com.ktind.cgm.bgscout;
+
+/**
+ * Created by klee24 on 8/2/14.
+ */
+public interface CGMDeviceInterface {
+ public DeviceDownloadObject download();
+ void fireMonitors();
+ void stopMonitors();
+}
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/CGMTransportAbstract.java b/mobile/src/main/java/com/ktind/cgm/bgscout/CGMTransportAbstract.java
new file mode 100644
index 0000000..56fe02f
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/CGMTransportAbstract.java
@@ -0,0 +1,28 @@
+package com.ktind.cgm.bgscout;
+
+import android.util.Log;
+
+import java.io.IOException;
+
+
+/**
+ * Created by klee24 on 8/2/14.
+ */
+abstract public class CGMTransportAbstract {
+ protected boolean isopen=false;
+ protected boolean chargeDevice=false;
+
+ abstract public boolean open() throws DeviceNotConnected;
+ abstract public void close();
+ abstract public int read(byte[] responseBuffer,int timeoutMillis) throws IOException;
+ abstract public int write(byte[] packet,int timeoutMillis) throws IOException;
+
+ public void setChargeReceiver(boolean c){
+ Log.d("G4Device", "Abstract setting charge receiver to " + c);
+ chargeDevice=c;
+ }
+
+ public boolean isOpen(){
+ return isopen;
+ }
+}
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/Constants.java b/mobile/src/main/java/com/ktind/cgm/bgscout/Constants.java
new file mode 100644
index 0000000..a8b9224
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/Constants.java
@@ -0,0 +1,8 @@
+package com.ktind.cgm.bgscout;
+
+/**
+ * Created by klee24 on 8/2/14.
+ */
+public class Constants {
+ public final static int READINGINTERVALMS=45000;
+}
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceDownloadObject.java b/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceDownloadObject.java
new file mode 100644
index 0000000..9331408
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceDownloadObject.java
@@ -0,0 +1,69 @@
+package com.ktind.cgm.bgscout;
+
+import java.util.Date;
+
+/**
+ * Created by klee24 on 8/2/14.
+ */
+public class DeviceDownloadObject {
+ private AbstractCGMDevice device;
+ private EGVRecord[] egvRecords;
+ private DownloadStatus status;
+ private String specialValueMessage;
+ private Date lastDownloadDate;
+
+ public Date getLastDownloadDate() {
+ return lastDownloadDate;
+ }
+
+ public void setLastDownloadDate(Date lastDownloadDate) {
+ this.lastDownloadDate = lastDownloadDate;
+ }
+
+ public String getSpecialValueMessage() {
+ return specialValueMessage;
+ }
+
+ public void setSpecialValueMessage(String specialValueMessage) {
+ this.specialValueMessage = specialValueMessage;
+ }
+
+ DeviceDownloadObject(AbstractCGMDevice c,EGVRecord[] e, DownloadStatus s){
+ super();
+ setDevice(c);
+ setEgvRecords(e);
+ setStatus(s);
+ }
+
+ DeviceDownloadObject(){
+ egvRecords=new EGVRecord[0];
+ }
+
+ public AbstractCGMDevice getDevice() {
+ return device;
+ }
+
+ public void setDevice(AbstractCGMDevice device) {
+ this.device = device;
+ }
+
+ public EGVRecord[] getEgvRecords() {
+ return egvRecords;
+ }
+
+ public void setEgvRecords(EGVRecord[] egvRecords) {
+ this.egvRecords = egvRecords;
+ }
+
+ public DownloadStatus getStatus() {
+ return status;
+ }
+
+ public void setStatus(DownloadStatus status) {
+ this.status = status;
+ }
+
+// public boolean didFail(){
+// return this.getStatus()!=DownloadStatus.SUCCESS;
+// }
+}
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceDownloadService.java b/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceDownloadService.java
new file mode 100644
index 0000000..ba24da8
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceDownloadService.java
@@ -0,0 +1,121 @@
+package com.ktind.cgm.bgscout;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.util.Log;
+
+import com.squareup.otto.Subscribe;
+
+import java.util.ArrayList;
+
+public class DeviceDownloadService extends Service {
+ private static final String TAG = DeviceDownloadService.class.getSimpleName();
+ private ArrayList cgms=new ArrayList();
+ private DeviceDownloadObject lastDownload;
+ private Notification.Builder notificationBuilder;
+ private Handler mHandler=new Handler();
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ CGMBus.getInstance().register(this);
+// cgms.add(new G4CGMDevice("Melissa",1,getBaseContext(),mHandler));
+ cgms.add(new RemoteMongoDevice("Melissa",1,getBaseContext(),mHandler));
+// cgms.add(new FakeCGMDevice("Billy",2,this.getBaseContext()));
+// cgms.add(new FakeCGMDevice("Sue",3,this.getBaseContext()));
+ Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.icon);
+ PendingIntent contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, new Intent(getApplicationContext(), MainActivity.class), 0);
+ notificationBuilder = new Notification.Builder(getApplicationContext())
+ .setContentTitle(getText(R.string.cgm_service_title))
+ .setContentIntent(contentIntent)
+ .setSmallIcon(R.drawable.icon24x24)
+ .setNumber(cgms.size())
+ .setLargeIcon(bm);
+ }
+
+ //FIXME right now this treats all CGMs as firing at the same time. Need individual timers.
+ private Runnable pollDevices = new Runnable() {
+ @Override
+ public void run() {
+ for (AbstractCGMDevice cgm:cgms) {
+ innerDeviceProxy deviceProxy=new innerDeviceProxy(cgm);
+ deviceProxy.execute();
+ }
+// mHandler.postDelayed(pollDevices, nextFire);
+ }
+ };
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Notification notification=notificationBuilder.build();
+ for (AbstractCGMDevice cgm:cgms) {
+ innerDeviceProxy deviceProxy=new innerDeviceProxy(cgm);
+ deviceProxy.execute();
+ }
+// mHandler.post(pollDevices);
+ startForeground(1,notification);
+ return super.onStartCommand(intent, flags, startId);
+ }
+
+ @Override
+ public void onDestroy() {
+// mHandler.removeCallbacks(pollDevices);
+ CGMBus.getInstance().unregister(this);
+ stopForeground(false);
+ for (AbstractCGMDevice cgm:cgms){
+ cgm.stopMonitors();
+ }
+ super.onDestroy();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ // TODO: Return the communication channel to the service.
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+
+ @Subscribe
+ public void onDeviceDownloadObject(AbstractCGMDevice cgm) {
+ Log.d(TAG,"message received! Starting over again!");
+ innerDeviceProxy deviceProxy=new innerDeviceProxy(cgm);
+ deviceProxy.execute();
+ }
+
+ public class innerDeviceProxy extends AsyncTask {
+ AbstractCGMDevice cgmDevice;
+ Handler myInnerHandler=new Handler();
+
+ innerDeviceProxy(AbstractCGMDevice cD){
+ super();
+ this.cgmDevice=cD;
+ }
+
+ public Runnable nextPoll = new Runnable() {
+ @Override
+ public void run() {
+ CGMBus.getInstance().post(cgmDevice);
+// onDeviceDownloadObject(cgmDevice);
+ }
+ };
+
+ @Override protected DeviceDownloadObject doInBackground(Void... params) {
+ return cgmDevice.download();
+ }
+
+ @Override protected void onPostExecute(DeviceDownloadObject result) {
+ super.onPostExecute(result);
+ result.getDevice().fireMonitors();
+ myInnerHandler.postDelayed(nextPoll, result.getDevice().nextFire());
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceDownloadServiceInterface.java b/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceDownloadServiceInterface.java
new file mode 100644
index 0000000..43de0c4
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceDownloadServiceInterface.java
@@ -0,0 +1,9 @@
+package com.ktind.cgm.bgscout;
+
+/**
+ * Created by klee24 on 8/2/14.
+ */
+public interface DeviceDownloadServiceInterface {
+ public DeviceDownloadObject downloadDevice();
+ public void processDownload(DeviceDownloadObject d);
+}
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceNotConnected.java b/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceNotConnected.java
new file mode 100644
index 0000000..caa65fa
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceNotConnected.java
@@ -0,0 +1,17 @@
+package com.ktind.cgm.bgscout;
+
+/**
+ * Created by klee24 on 8/3/14.
+ */
+public class DeviceNotConnected extends Exception{
+ public DeviceNotConnected(){}
+
+ public DeviceNotConnected(String message){
+ super(message);
+ }
+
+ public DeviceNotConnected(String message, Throwable e){
+ super(message,e);
+ }
+
+}
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceProxy.java b/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceProxy.java
new file mode 100644
index 0000000..d76f63e
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceProxy.java
@@ -0,0 +1,45 @@
+package com.ktind.cgm.bgscout;
+
+import android.os.AsyncTask;
+import android.os.Handler;
+
+/**
+ * Created by klee24 on 8/3/14.
+ */
+public class DeviceProxy extends AsyncTask {
+ AbstractCGMDevice cgmDevice;
+ Handler mHandler;
+
+ DeviceProxy(AbstractCGMDevice cgm,Handler mH){
+ this.cgmDevice=cgm;
+ this.mHandler=mH;
+ }
+
+ @Override
+ protected Void doInBackground(AbstractCGMDevice... devices) {
+ cgmDevice.download();
+// cgmDevice.doDownload();
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ super.onPostExecute(aVoid);
+ cgmDevice.fireMonitors();
+ }
+// public Runnable pollCallback = new Runnable() {
+// @Override
+// public void run() {
+// long nextFire=45000;
+// for (AbstractCGMDevice cgm:cgms){
+// long tmpNextFire=cgm.nextFire();
+// if (nextFire egvHistory=new ArrayList(FakeCGMDeviceConstants.MAXEGV);
+ private boolean initialRun;
+ private Date lastReading=new Date(new Date().getTime()-10800000L);
+
+ public FakeCGMDevice(String n, int deviceID, Context appContext,Handler mH) {
+ // public AbstractCGMDevice(String n,int deviceID,Context appContext){
+ super(n,deviceID,appContext,mH);
+ generateEGVHistory();
+ initialRun=true;
+ virtual =false;
+ }
+
+ @Override
+ int getCGMBattery() {
+ return 0;
+ }
+
+ @Override
+ protected DeviceDownloadObject doDownload() {
+ return generateDownloadObject();
+ }
+
+ @Override
+ public void connect() {
+ }
+
+ @Override
+ public void disconnect() {
+ }
+
+// @Override
+// public G4EGVRecord[] getReadings() {
+// return new G4EGVRecord[0];
+// }
+
+ private DeviceDownloadObject generateDownloadObject(){
+ Log.d(TAG,"Generating download object");
+ if (!initialRun)
+ this.addEGV();
+ DownloadStatus downloadStatus=generateStatus();
+ EGVRecord[] egvArray=egvHistory.toArray(new EGVRecord[egvHistory.size()]);
+ DeviceDownloadObject ddo=new DeviceDownloadObject(this,egvArray,downloadStatus);
+ lastDownloadObject=ddo;
+ initialRun=false;
+// for (EGVRecord r:egvHistory){
+// r.setNew(false);
+// }
+ return ddo;
+ }
+
+ @Override
+ public void fireMonitors() {
+ super.fireMonitors();
+ lastReading=egvHistory.get(egvHistory.size()-1).getDate();
+// for (EGVRecord r:egvHistory){
+// r.setNew(false);
+// }
+ }
+
+ private void addEGV(){
+ egvHistory.remove(0);
+ for (EGVRecord r:egvHistory){
+ r.setNew(false);
+ }
+ int lastIndex=egvHistory.size()-1;
+ int lastBG=egvHistory.get(lastIndex).getEgv();
+ Trend lastTrend=egvHistory.get(lastIndex).getTrend();
+ Date lastDate=egvHistory.get(lastIndex).getDate();
+ Log.d(TAG,"Last reading. BG: "+lastBG+" Trend: "+lastTrend.toString()+" Date: "+lastDate);
+ EGVRecord record=generateNextEGV(lastBG, lastTrend, lastDate);
+ if (record.getDate().after(lastReading))
+ record.setNew(true);
+ egvHistory.add(record);
+ }
+
+ // This patient doesn't seem to mind extreme highs or lows so you won't
+ // see the normal patterns for corrections
+ private void generateEGVHistory(){
+ Log.d(TAG,"Generating new EGV History");
+ Random rand=new Random();
+ int initialBG=rand.nextInt(FakeCGMDeviceConstants.MAXEGV+1)+ FakeCGMDeviceConstants.MINEGV;
+ Trend initialTrend=generateInitialTrend();
+ long initialMillis=1000*(FakeCGMDeviceConstants.READINGINTERVALSECONDS* FakeCGMDeviceConstants.MAXRECORDS);
+ Date initialDate=new Date(new Date().getTime()-initialMillis);
+ egvHistory.add(generateEGV(initialBG, initialTrend, initialDate, true));
+// int missedCounter=0;
+ for (int i=1;i< FakeCGMDeviceConstants.MAXRECORDS;i++){
+ Log.d(TAG,"Counter: "+i+" egvHistory.size(): "+egvHistory.size());
+ if (rand.nextFloat()> FakeCGMDeviceConstants.MISSEDREADINGRATE) {
+ int lastIndex=egvHistory.size()-1;
+ int lastBG = egvHistory.get(lastIndex).getEgv();
+ Trend lastTrend = egvHistory.get(lastIndex).getTrend();
+ Date date = new Date(egvHistory.get(lastIndex).getDate().getTime() - (1000 * FakeCGMDeviceConstants.READINGINTERVALSECONDS * (FakeCGMDeviceConstants.MAXRECORDS - i)));
+ egvHistory.add(generateNextEGV(lastBG, lastTrend, date));
+// missedCounter+=1;
+ }
+ }
+ }
+
+ private DownloadStatus generateStatus(){
+ DownloadStatus status=generateStatus(FakeCGMDeviceConstants.FAILRATE);
+ return status;
+ }
+
+ private DownloadStatus generateStatus(float failRate){
+ Random rand=new Random();
+ DownloadStatus status=DownloadStatus.SUCCESS;
+ float check=rand.nextFloat();
+ Log.d(TAG,"Checking for failure: "+check+" Rate is: "+failRate);
+ if (check0.50f){
+ negTrend=true;
+ }
+ break;
+ }
+ if (changeRate==0)
+ changeRate=1;
+ int bgChange=rand.nextInt(changeRate*(FakeCGMDeviceConstants.READINGINTERVALSECONDS/60));
+ if (negTrend)
+ bgChange=bgChange*-1;
+ int newEGV=lastBG+bgChange;
+ if (newEGV< FakeCGMDeviceConstants.MINEGV)
+ newEGV= FakeCGMDeviceConstants.MINEGV;
+ if (newEGV> FakeCGMDeviceConstants.MAXEGV)
+ newEGV= FakeCGMDeviceConstants.MAXEGV;
+ record.setEgv(newEGV);
+ Log.d(TAG,"Generated EGV. BG: "+record.getEgv()+" Trend: "+record.getTrend().toString()+" Date: "+record.getDate().toString());
+ return record;
+ }
+}
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/FakeCGMDeviceConstants.java b/mobile/src/main/java/com/ktind/cgm/bgscout/FakeCGMDeviceConstants.java
new file mode 100644
index 0000000..0868be5
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/FakeCGMDeviceConstants.java
@@ -0,0 +1,13 @@
+package com.ktind.cgm.bgscout;
+
+/**
+ * Created by klee24 on 8/2/14.
+ */
+public class FakeCGMDeviceConstants {
+ public static final int MAXRECORDS=36;
+ public static final float FAILRATE=0.08f;
+ public static final int READINGINTERVALSECONDS=300;
+ public static final int MAXEGV=401;
+ public static final int MINEGV=39;
+ public static final float MISSEDREADINGRATE=0.10f;
+}
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/G4BatteryState.java b/mobile/src/main/java/com/ktind/cgm/bgscout/G4BatteryState.java
new file mode 100644
index 0000000..3778ade
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/G4BatteryState.java
@@ -0,0 +1,28 @@
+package com.ktind.cgm.bgscout;
+
+/**
+ * Created by klee24 on 8/2/14.
+ */
+public enum G4BatteryState {
+ Charging((byte) 1,"Charging"),
+ NotCharging((byte) 2, "Not charging"),
+ NTCFault((byte) 3,"NTCFault"),
+ BadBattery((byte) 4, "Bad battery");
+
+ private String stringValue=null;
+ private byte byteVal;
+
+ private G4BatteryState(byte b, String s){
+ stringValue=s;
+ byteVal=b;
+ }
+
+ public byte getValue(){
+ return byteVal;
+ }
+
+ @Override
+ public String toString(){
+ return stringValue;
+ }
+}
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/G4CGMDevice.java b/mobile/src/main/java/com/ktind/cgm/bgscout/G4CGMDevice.java
new file mode 100644
index 0000000..6812640
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/G4CGMDevice.java
@@ -0,0 +1,700 @@
+package com.ktind.cgm.bgscout;
+
+import android.content.Context;
+import android.os.Handler;
+import android.util.Log;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+
+/**
+ * Created by klee24 on 8/2/14.
+ */
+public class G4CGMDevice extends AbstractCGMDevice {
+ protected String serialNum;
+ protected String receiverID;
+ protected int cgmBattery=-1;
+ private static final String TAG = G4CGMDevice.class.getSimpleName();
+ // TODO add this as a shared preference...
+ protected Date myLastDownload=new Date(new Date().getTime()-10800000);
+
+ // public AbstractCGMDevice(String n,int deviceID,Context appContext){
+
+ public String getSerialNum() {
+ return serialNum;
+ }
+
+ public void setSerialNum(String serialNum) {
+ this.serialNum = serialNum;
+ }
+
+ public G4CGMDevice(String name,int devID,Context appContext,Handler mH){
+ super(name,devID,appContext,mH);
+// this.appContext=context;
+ virtual = false;
+ cgmTransport=new G4USBSerialTransport(appContext);
+ }
+
+ @Override
+ int getCGMBattery() {
+ if (cgmBattery==-1)
+ cgmBattery=getCGMBattery();
+ return cgmBattery;
+ }
+
+ @Override
+ public void connect() throws DeviceNotConnected {
+ cgmTransport.open();
+ }
+
+ public void setup() throws IOException {
+ if (isConnected()) {
+ unit = getGlucoseUnit();
+ serialNum = getRcvrSerial();
+ cgmBattery=getBatteryLevel();
+// myBattery=getUploaderBattery();
+
+ }else{
+ Log.e("G4Device","Unable to setup device that I am not connected to");
+ }
+ }
+
+ @Override
+ protected DeviceDownloadObject doDownload() {
+ DeviceDownloadObject ddo=new DeviceDownloadObject();
+ ddo.setDevice(this);
+ if (lastDownloadObject==null)
+ lastDownloadObject=ddo;
+ try {
+ cgmTransport.open();
+ this.setup();
+ EGVRecord[] egvArray = this.getLastEGVRecords();
+ getReadingsSince(myLastDownload, egvArray);
+ int batteryLevel = this.getBatteryLevel();
+ float myBattery= getUploaderBattery();
+ Log.d(TAG, "Device battery level: " + batteryLevel);
+ Log.d(TAG, "Phone battery level: " + myBattery);
+ if (batteryLevel < 40) {
+ if (myBattery > 0.40) {
+ Log.d(TAG, "Setting phone to charge device");
+ this.setChargeDevice(true);
+ } else {
+ Log.d(TAG, "G4 battery low but this device is too low to charge it");
+ this.setChargeDevice(false);
+ }
+ } else {
+ Log.d(TAG, "Stopping this device from charging G4");
+ this.setChargeDevice(false);
+ }
+ try {
+ Date dispDate=getDisplayTime();
+ Long jitter=dispDate.getTime()-new Date().getTime();
+ if (Math.abs(jitter) > 30000 ) {
+ Log.w(TAG,"Device time off by "+jitter+" ms");
+ this.syncTimeToDevice();
+ }
+ }catch (IOException e){
+ Log.e(TAG,"Unable to syncTime to device");
+ }
+ cgmTransport.close();
+ // FIXME reflect actual status
+ DownloadStatus downloadStatus = DownloadStatus.SUCCESS;
+ ddo = new DeviceDownloadObject(this, egvArray, downloadStatus);
+ lastDownloadObject = ddo;
+ myLastDownload=lastDownloadObject.getEgvRecords()[lastDownloadObject.getEgvRecords().length-1].getDate();
+ } catch (DeviceNotConnected e){
+ Log.d(TAG, "Not finding device here!");
+ EGVRecord[] records=lastDownloadObject.getEgvRecords();
+// records=lastDownloadObject.getEgvRecords();
+ ddo.setEgvRecords(records);
+ lastDownloadObject=ddo;
+ ddo.setStatus(DownloadStatus.DEVICENOTFOUND);
+ } catch (IOException e){
+ EGVRecord[] records=lastDownloadObject.getEgvRecords();
+ ddo.setEgvRecords(records);
+ lastDownloadObject=ddo;
+ ddo.setStatus(DownloadStatus.IOERROR);
+ }
+
+ for (G4EGVSpecialValue specialValue:G4EGVSpecialValue.values()) {
+ EGVRecord[] records=lastDownloadObject.getEgvRecords();
+ if (records!=null && records.length>0) {
+ EGVRecord rec = records[records.length - 1];
+ if (rec.getEgv() == specialValue.getValue()) {
+ ddo.setStatus(DownloadStatus.SPECIALVALUE);
+ ddo.setSpecialValueMessage(G4EGVSpecialValue.getEGVSpecialValue(rec.getEgv()).toString());
+ break;
+ }
+ }
+ }
+
+ return ddo;
+// return super.doDownload();
+ }
+
+ @Override
+ public void disconnect() {
+ cgmTransport.close();
+ }
+
+ public void setChargeDevice(boolean c){
+ cgmTransport.setChargeReceiver(c);
+ }
+
+// @Override
+ public G4EGVRecord[] getReadings() {
+ return this.getReadings(G4Constants.defaultReadings);
+ }
+
+ public G4EGVRecord[] getReadings(int numReadings) {
+ return new G4EGVRecord[0];
+ }
+
+ // Retrieves the last 4 pages
+ public G4EGVRecord[] getLastEGVRecords() throws IOException {
+ G4Partition partition=getDBPageRange(G4RecType.EGVDATA);
+ G4EGVRecord[] results=getEGVPages(partition.lastPage - 3, 4);
+ return results;
+ }
+
+ // TODO Possibly abstract this out and have it return a collection of parsed records?
+ public G4EGVRecord[] getEGVPages(int firstPage,int lastPage) throws IOException {
+
+ G4DBPage[] pages=getDBPages(G4RecType.EGVDATA,firstPage,lastPage);
+ int totalNumRecords=0;
+ for (G4DBPage page: pages){
+ totalNumRecords+=page.PageHeader.NumberOfRecords;
+ }
+
+ Log.d(TAG,"Record type: "+pages[0].PageHeader.RecordType.toString()+"PageCount: "+pages.length+"Total records: "+totalNumRecords);
+ G4EGVRecord[] egvrecords = new G4EGVRecord[totalNumRecords];
+ int i=0;
+ for (G4DBPage page: pages) {
+ G4EGVRecord[] tmprecs = new G4EGVRecord[page.PageHeader.NumberOfRecords];
+ tmprecs=parsePage(page);
+ System.arraycopy(tmprecs,0,egvrecords,i,page.PageHeader.NumberOfRecords);
+ Log.d(TAG,"Start index: "+i+" Record count: "+page.PageHeader.NumberOfRecords+" End: "+(page.PageHeader.NumberOfRecords*i+page.PageHeader.NumberOfRecords));
+ i+=page.PageHeader.NumberOfRecords;
+ }
+ return egvrecords;
+ }
+
+ public G4EGVRecord lastReading() throws IOException {
+ G4EGVRecord[] results=getLastEGVRecords();
+ if (results==null | results.length<1)
+ return null;
+ return results[results.length-1];
+ }
+
+ public EGVRecord[] getReadingsSince(Date d) throws IOException {
+ // TODO continue to go back in time until we find the earliest record.
+ G4EGVRecord[] recs=getLastEGVRecords();
+ return getReadingsSince(d,recs);
+ }
+
+ //FIXME: clean this up. There shouldn't be a separate method to set this value should there? Violates SRP
+ public EGVRecord[] getReadingsSince(Date d,EGVRecord[] recs){
+ ArrayList resultsArrayList=new ArrayList();
+ Log.d(TAG,"getReadingsSince date=> "+d);
+ for (EGVRecord record:recs){
+ if (record.getDate().after(d)) {
+ record.setNew(true);
+ } else {
+ record.setNew(false);
+ }
+ resultsArrayList.add(record);
+ }
+ return resultsArrayList.toArray(new EGVRecord[resultsArrayList.size()]);
+ }
+
+ public boolean ping() throws IOException {
+ if (!isConnected()){
+ Log.e(TAG,"Ping failed - not connected to device");
+ return false;
+ }
+ writeCmd(G4RcvrCmd.PING);
+ byte[] result=readResponse();
+ if (result==null || result.length!=6){
+ Log.e(TAG,"Ping unsuccessful");
+ return false;
+ }
+ Log.i(TAG,"Ping successful");
+ return true;
+ }
+
+ public G4Partition getDBPageRange(G4RecType recordType) throws IOException {
+ G4Partition response=new G4Partition();
+ writeCmd(G4RcvrCmd.READDATABASEPAGERANGE,recordType.getValue());
+ byte[] responseBuff = readResponse();
+ if (responseBuff==null || responseBuff.length!=8){
+ throw new IOException("Problem reading response");
+// return null;
+ }
+ byte[] firstPage = {responseBuff[0],responseBuff[1],responseBuff[2],responseBuff[3]};
+ byte[] lastPage = {responseBuff[4],responseBuff[5],responseBuff[6],responseBuff[7]};
+ response.Partition=recordType;
+ response.firstPage= BitTools.byteArraytoInt(firstPage);
+ response.lastPage= BitTools.byteArraytoInt(lastPage);
+ Log.d(TAG,"Partition: "+response.Partition.toString()+"First Page: "+response.firstPage+"Last Page: "+response.lastPage);
+ return response;
+ }
+
+ // There has to be a better way to do this?
+ private void writeCmd(G4RcvrCmd cmd, byte value) throws IOException {
+ byte[] b=new byte[1];
+ b[0]=value;
+ writeCmd(cmd,b);
+ }
+
+ public GlucoseUnit getGlucoseUnit() throws IOException {
+ writeCmd(G4RcvrCmd.READGLUCOSEUNIT);
+ byte[] res=readResponse();
+ int result=0;
+ if (res.length>0)
+ result=(int) res[0];
+ return GlucoseUnit.values()[result];
+ }
+
+ public String getTransmitterId() throws IOException {
+ writeCmd(G4RcvrCmd.READTRANSMITTERID);
+ String result=null;
+ try {
+ result = new String(readResponse(), "UTF-8");
+ }catch(UnsupportedEncodingException e){
+ Log.e(TAG,"Exception converting byte array to String",e);
+ }
+ return result;
+ }
+
+ public String getBatteryState() throws IOException {
+ writeCmd(G4RcvrCmd.READBATTERYSTATE);
+ String result="Unable to determine battery state";
+ byte[] response=readResponse();
+
+ if (response!=null && response.length>=0)
+ result= G4BatteryState.values()[response[0]-1].toString();
+ return result;
+ }
+
+ public int getBatteryLevel() throws IOException {
+ writeCmd(G4RcvrCmd.READBATTERYLEVEL);
+ int result=0;
+ result = BitTools.byteArraytoInt(readResponse());
+ return result;
+ }
+
+ public Date getDisplayTime() throws IOException {
+ return new Date(getDisplayTimeLong());
+ }
+
+ public void syncTimeToDevice() throws IOException {
+ Calendar mCalendar = new GregorianCalendar();
+ TimeZone mTimeZone = mCalendar.getTimeZone();
+ // TODO add protections against the device settings its time pre Jan 1 2009...
+ long dispTimeOffset=new Date().getTime()/1000-G4Constants.RECEIVERBASEDATE/1000-getSystemTimeLong();
+ // TODO Test daylight time change.
+ // TODO Also test daylight time change and time zones that don't observe daylight time
+ if (mTimeZone.inDaylightTime(new Date())){
+ dispTimeOffset+=3600L; // 1 hour for daylight time if it is observed
+ }
+ // TODO switch everything to ByteBuffers - all the things.
+ byte[] byteArray=ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt((int) dispTimeOffset).array();
+// Log.d(TAG,"Hex sync time(Little): "+ BitTools.bytesToHex(byteArray));
+ writeCmd(G4RcvrCmd.WRITEDISPLAYTIMEOFFSET, byteArray);
+ // TODO add verification that device did receive the command?
+ byte[] resp=readResponse();
+ Log.i(TAG,"Sync'd device time with cell. Set display time offset to: "+dispTimeOffset);
+ }
+
+ public long getDisplayTimeLong() throws IOException {
+ Calendar mCalendar = new GregorianCalendar();
+ TimeZone mTimeZone = mCalendar.getTimeZone();
+ long dispTime=G4Constants.RECEIVERBASEDATE+getDisplayTimeOffsetLong()*1000L+getSystemTimeLong()*1000L;
+ if (mTimeZone.inDaylightTime(new Date()))
+ dispTime-=3600000L;
+ Log.d(TAG,"getDisplayTimeLong: "+dispTime);
+ return dispTime;
+ }
+
+ public long getSystemTimeLong() throws IOException {
+ writeCmd(G4RcvrCmd.READSYSTEMTIME);
+ long result=0;
+ result = BitTools.byteArraytoInt(readResponse());
+ Log.d(TAG,"getSystemTimeLong=>"+result);
+ return result;
+ }
+
+ protected long getDisplayTimeOffsetLong() throws IOException {
+ writeCmd(G4RcvrCmd.READDISPLAYTIMEOFFSET);
+ long result=0;
+ result = BitTools.byteArraytoInt(readResponse());
+ Log.d(TAG,"getDisplayTimeOffsetLong=>"+result);
+ return result;
+ }
+
+ protected long getSystemTimeOffsetLong() throws IOException {
+ writeCmd(G4RcvrCmd.READSYSTEMTIMEOFFSET);
+ long result=0;
+ result = BitTools.byteArraytoInt(readResponse());
+ Log.d(TAG,"getSystemTimeOffsetLong=>"+result);
+ return result;
+ }
+
+ //@Override
+ protected String getDatabasePartitionInfo() throws IOException {
+ writeCmd(G4RcvrCmd.READDATABASEPARTITIONINFO);
+ String response = new String(readResponse());
+ return response;
+ }
+
+ //CRC methods
+ public static int calcCrc16 (byte [] buff) {
+ int crc = 0;
+ for (int i = 0; i < buff.length; i++)
+ {
+ crc = ((crc >>> 8) | (crc << 8) )& 0xffff;
+ crc ^= (buff[i] & 0xff);
+ crc ^= ((crc & 0xff) >> 4);
+ crc ^= (crc << 12) & 0xffff;
+ crc ^= ((crc & 0xFF) << 5) & 0xffff;
+
+ }
+ crc &= 0xffff;
+ return crc;
+ }
+
+ public int writeCmd(G4RcvrCmd rcvrCmd, byte [] payload) throws IOException {
+ Log.d(TAG,"Attempting to write to receiver");
+ if(!isConnected()){
+ Log.e(TAG,"Write failed - not connected to device");
+ return 0;
+ }
+ int bytesWritten=0;
+ if (! cgmTransport.isOpen())
+ return bytesWritten;
+
+ // Retrieve how many bytes should be sent by this command
+ int bytesToWrite=rcvrCmd.getCmdSize();
+ Log.d(TAG,"Bytes to write: "+bytesToWrite);
+ int calcBytesToWrite=6;
+ if (payload!=null)
+ calcBytesToWrite = 4 + payload.length + 2;
+// Log.d(TAG,"Calculated bytes to write: "+calcBytesToWrite);
+ if (bytesToWrite != calcBytesToWrite){
+ Log.e(TAG,"Insufficient data for command");
+ return 0;
+ }
+ //TODO throw an exception?
+ if (bytesToWrite==-1){
+ Log.e(TAG,"Command "+rcvrCmd.toString()+" has not been implemented");
+ return bytesToWrite;
+ }
+ byte[] packet = new byte[bytesToWrite];
+
+ packet[0]=0x01; // Always 1
+
+ // next two bytes are the size of the command => SOF+packet size+command+payload+crc
+ packet[1]=(byte) (bytesToWrite & 0xFF);
+ packet[2]=(byte) (bytesToWrite >> 8 & 0xFF);
+ packet[3]=rcvrCmd.getValue();
+
+ // Copy the payload if it exists
+ if (payload!=null && bytesToWrite>6 && payload.length>0)
+ System.arraycopy(payload,0,packet,4,bytesToWrite-6);
+ byte [] crcPacket=new byte[bytesToWrite-2];
+ System.arraycopy(packet,0,crcPacket,0,bytesToWrite-2);
+ int crc=calcCrc16(crcPacket);
+ packet[bytesToWrite-2]=(byte)(crc & 255);
+ packet[bytesToWrite-1]=(byte)(crc >> 8 & 255);
+
+ try {
+ Log.v(TAG,"Writing("+rcvrCmd.toString()+"): "+ BitTools.bytesToHex(packet));
+ bytesWritten=cgmTransport.write(packet,G4Constants.defaultWriteTimeout);
+ Log.d(TAG,"Bytes written - "+bytesWritten);
+ } catch (IOException e) {
+ throw new IOException("Unable to write to Dexcom G4");
+// Log.e(TAG, "Unable to write to Dexcom G4", e);
+
+ }
+ return bytesWritten;
+ }
+
+
+ public int writeCmd(G4RcvrCmd rcvCmd) throws IOException {
+ return writeCmd(rcvCmd,null);
+ }
+
+ public byte[] readResponse(){
+ return this.readResponse(G4Constants.defaultReadTimeout);
+ }
+
+ public byte[] readResponse(int millis) {
+ Log.d(TAG,"Attempting to read to receiver");
+ if(!isConnected()){
+ Log.e(TAG,"Read failed - not connected to device");
+ return null;
+ }
+
+ int bytesRead=0;
+ if (! isConnected()) {
+ Log.e(TAG,"Device is not connected");
+ return null;
+ }
+ // Seems we can't read fewer bytes than they send..
+ // but we can request more bytes than they'll send.
+ // Setting max response buffer to 3072 to prevent
+ // a read failure.
+ // Max size required should be somewhere around 2122
+ // while reading database pages. Let's just round
+ // that up a bit
+ byte [] responseBuffer = new byte[3072];
+
+ try {
+ bytesRead=cgmTransport.read(responseBuffer,millis);
+ Log.d(TAG,"Bytes read - "+bytesRead);
+ } catch (IOException e) {
+ Log.e(TAG,"Unable to read headers from Dexcom G4");
+ return null;
+ }
+ if (responseBuffer[0]!=0x01) {
+ Log.e(TAG, "Unexpected response back while parsing header: "+ BitTools.bytesToHex(responseBuffer));
+ return null;
+ }
+ int bytesToRead=(int)responseBuffer[2]<<8 ^ (int)responseBuffer[1];
+ Log.d(TAG,"Calculated bytes to read at "+bytesRead);
+ if (bytesToRead!=bytesRead) {
+ Log.e(TAG, "Calculated bytes to read does not equal the bytes actually read");
+ return null;
+ }
+
+ byte [] header = new byte[4];
+ byte [] body = new byte[bytesRead-6];
+ byte [] crc=new byte[2];
+
+ System.arraycopy(responseBuffer,0,header,0,4);
+ System.arraycopy(responseBuffer,4+(bytesToRead-6),crc,0,2);
+ System.arraycopy(responseBuffer,4,body,0,bytesRead-6);
+
+// Log.d(TAG,"Response hex: " + BitTools.bytesToHex(header) + BitTools.bytesToHex(body) + BitTools.bytesToHex(crc));
+ Log.v(TAG,"Header hex:"+ BitTools.bytesToHex(header));
+ Log.v(TAG,"Body hex:"+ BitTools.bytesToHex(body));
+ Log.v(TAG,"CRC hex: "+ BitTools.bytesToHex(crc));
+ byte[] crcCheckArray=new byte[bytesToRead-2];
+ System.arraycopy(responseBuffer,0,crcCheckArray,0,bytesToRead-2);
+ int calcCRC=calcCrc16(crcCheckArray);
+ int crcInt=(crc[0] & 0xFF);
+ crcInt+=(crc[1] << 8) & 0xFF00;
+ if (calcCRC!=crcInt) {
+ Log.d(TAG, "Calculated CRC: " + calcCRC+"Response CRC: " + crcInt);
+ Log.e(TAG, "CRC check failed!");
+ return null;
+ }else{
+ Log.d(TAG,"Successful CRC check");
+ }
+ return body;
+ }
+
+ private G4DBPageHeader getPageHeader(G4RecType recType,int pageNumber) throws IOException {
+ Log.d(TAG,"getPageHeader called");
+ G4DBPageHeader result=new G4DBPageHeader();
+ byte[] requestPayload=new byte[5];
+ requestPayload[0]=recType.getValue();
+ System.arraycopy(BitTools.intToByteArray(pageNumber),0,requestPayload,1,4);
+ writeCmd(G4RcvrCmd.READDATABASEPAGEHEADER, requestPayload);
+ byte[] resultBuffer=readResponse();
+ byte[] FirstRecordIndexArray=new byte[4];
+ byte[] NumberOfRecordsArray=new byte[4];
+// byte[] RecordTypeArray=new byte[1];
+// byte[] RevisionArray=new byte[1];
+ byte[] PageNumberArray=new byte[4];
+ byte[] Reserved2Array=new byte[4];
+ byte[] Reserved3Array=new byte[4];
+ byte[] Reserved4Array=new byte[4];
+ byte[] CRCArray=new byte[2];
+
+ System.arraycopy(resultBuffer,0,FirstRecordIndexArray,0,4);
+ System.arraycopy(resultBuffer,4,NumberOfRecordsArray,0,4);
+// System.arraycopy(resultBuffer,8,RecordTypeArray,0,1);
+// System.arraycopy(resultBuffer,9,RevisionArray,0,1);
+ System.arraycopy(resultBuffer,10,PageNumberArray,0,4);
+ System.arraycopy(resultBuffer,14,Reserved2Array,0,4);
+ System.arraycopy(resultBuffer,18,Reserved3Array,0,4);
+ System.arraycopy(resultBuffer,22,Reserved4Array,0,4);
+ System.arraycopy(resultBuffer,26,CRCArray,0,2);
+ result.FirstRecordIndex= BitTools.byteArraytoInt(FirstRecordIndexArray);
+ result.NumberOfRecords= BitTools.byteArraytoInt(NumberOfRecordsArray);
+ result.RecordType=G4RecType.values()[(int) resultBuffer[8]];
+ result.Revision=resultBuffer[9];
+ result.PageNumber= BitTools.byteArraytoInt(PageNumberArray);
+ result.Reserved2= BitTools.byteArraytoInt(Reserved2Array);
+ result.Reserved3= BitTools.byteArraytoInt(Reserved3Array);
+ result.Reserved4= BitTools.byteArraytoInt(Reserved4Array);
+ int crcInt=(CRCArray[0] & 0xFF);
+ crcInt+=(CRCArray[1] << 8) & 0xFF00;
+ result.Crc=crcInt;
+ Log.v(TAG,"FirstRecordIndex: "+result.FirstRecordIndex+" NumberOfRecords: "+result.NumberOfRecords+" RecordType: "+result.RecordType.toString()+" Revision: "+result.Revision+" PageNumber: "+result.PageNumber+"CRC: "+result.Crc);
+ return result;
+ }
+
+ public G4DBPage[] getDBPages(G4RecType recType,int startPage, int numPages) throws IOException {
+ Log.d(TAG,"Requesting "+numPages+" starting with "+startPage);
+ byte [] requestPayload=new byte[6];
+ requestPayload[0]=recType.getValue();
+ requestPayload[1]=(byte) (startPage & 0xFF);
+ requestPayload[2]=(byte) ((startPage >> 8) & 0xFF);
+ requestPayload[3]=(byte) ((startPage >> 16) & 0xFF);
+ requestPayload[4]=(byte) ((startPage >> 24) & 0xFF);
+ requestPayload[5]=(byte) numPages;
+ writeCmd(G4RcvrCmd.READDATABASEPAGES, requestPayload);
+ byte[] response=readResponse();
+ Log.d(TAG,"Response Length: "+response.length);
+ G4DBPage[] pages=new G4DBPage[numPages];
+ for (int i=0;ii*13){
+ results[i]=new G4EGVRecord();
+ byte[] recordBuffer=new byte[13];
+ System.arraycopy(page.PageData, i * 13, recordBuffer, 0, 13);
+ byte[] sysTimeArray=new byte[4];
+ byte[] dispTimeArray=new byte[4];
+ byte[] egvwflagArray=new byte[2];
+ byte[] dirnoiseArray=new byte[1];
+ byte[] crcArray=new byte[2];
+ System.arraycopy(recordBuffer, 0, sysTimeArray, 0, 4);
+ System.arraycopy(recordBuffer, 4, dispTimeArray, 0, 4);
+ System.arraycopy(recordBuffer, 8, egvwflagArray, 0, 2);
+ System.arraycopy(recordBuffer, 10, dirnoiseArray, 0, 1);
+ System.arraycopy(recordBuffer, 11, crcArray, 0, 2);
+ results[i].setSystemTime(BitTools.byteArraytoInt(sysTimeArray));
+ long dtime=(long) BitTools.byteArraytoInt(dispTimeArray)*1000;
+ Calendar mCalendar = new GregorianCalendar();
+ TimeZone mTimeZone = mCalendar.getTimeZone();
+// long mGMTOffset = mTimeZone.getRawOffset();
+// long displayTimeLong=rcvrBaseDatems+dtime+mGMTOffset;
+ long displayTimeLong=G4Constants.RECEIVERBASEDATE+dtime;
+ if (mTimeZone.inDaylightTime(new Date())){
+ displayTimeLong-=3600000L;
+ }
+ Date displayTimeDate=new Date(displayTimeLong);
+
+ results[i].setDate(displayTimeDate);
+ int bgValue= BitTools.byteArraytoInt(egvwflagArray) & 0x3FF;
+ // This means we've found the end
+ if (bgValue==1023) {
+ Log.d(TAG,"Last reading found in this page");
+ break;
+ }
+ results[i].setEgv(bgValue);
+ results[i].setSpecialValue(null);
+// results[i].s(deviceUnits);
+ for (G4EGVSpecialValue e: G4EGVSpecialValue.values()) {
+ if (e.getValue()==bgValue) {
+ results[i].setSpecialValue(G4EGVSpecialValue.getEGVSpecialValue(bgValue));
+ Log.w(TAG,"Special value set: "+results[i].getSpecialValue().toString());
+ break;
+ }
+ }
+ int trendNoise= BitTools.byteArraytoInt(dirnoiseArray);
+ results[i].setTrend(Trend.values()[trendNoise & 0xF]);
+ results[i].setNoiseMode(G4NoiseMode.getNoiseMode((byte)(trendNoise & 0xF)>>4));
+ Log.v(TAG,"Reading time("+i+"): "+results[i].getDate().toString()+" EGV: "+results[i].getEgv()+" Trend: "+results[i].getTrend().toString()+" Noise: "+results[i].getNoiseMode().toString());
+ } else {
+ Log.w(TAG,"Record ("+i+") appears to be truncated in page number "+page.PageHeader.PageNumber);
+ }
+ }
+ Log.d(TAG,"Number of Records: "+results.length);
+ return results;
+ }
+
+ public String getRcvrSerial() throws IOException {
+ serialNum = getParam(G4RecType.MANUFACTURINGDATA, "SerialNumber");
+ return serialNum;
+ }
+
+ private String getParam(G4RecType recType,String param) throws IOException {
+ G4Partition part=getDBPageRange(recType);
+ String result="";
+// Charset charset=
+ G4DBPage[] pages=getDBPages(recType,part.firstPage,1);
+ String data=new String();
+ for (G4DBPage page:pages){
+ int i;
+ // Ugly code to capture the null terminated string
+ for (i = 0; i < page.PageData.length && page.PageData[i] != 0x00; i++) { }
+ // Strip the header and reduce the size of the string by the header length
+ data+=new String(page.PageData,8,i-8);
+ }
+ try {
+ InputStream is = new ByteArrayInputStream(data.getBytes("UTF-8"));
+ DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();
+ DocumentBuilder builder=factory.newDocumentBuilder();
+ Document dom=builder.parse(is);
+ String elemName;
+ if (recType==G4RecType.PCSOFTWAREPARAMETER) {
+ elemName = "PCParameterRecord";
+ }else if (recType==G4RecType.MANUFACTURINGDATA){
+ elemName="ManufacturingParameters";
+ } else {
+ return "";
+ }
+ // TODO Need to add checking for null pointers...
+ Element elem = (Element) dom.getElementsByTagName(elemName).item(0);
+ result=elem.getAttribute(param);
+ }catch (Exception e) {
+ // TODO specific error handling
+ Log.e(TAG,"Problem parsing XML from "+recType.toString()+" partition in getParam",e);
+ }
+ Log.d(TAG,recType.toString()+" data:"+data+"Result: "+result);
+ return result;
+ }
+
+ public String getRcvrID() throws IOException {
+ // Only get this value once per instance of a G4Device
+ if (receiverID=="") {
+ receiverID = getParam(G4RecType.PCSOFTWAREPARAMETER, "ReceiverId");
+ } else {
+ Log.d(TAG, "Returning cached results for ReceiverId");
+ }
+ String result=receiverID;
+ return result;
+ }
+
+}
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/G4Constants.java b/mobile/src/main/java/com/ktind/cgm/bgscout/G4Constants.java
new file mode 100644
index 0000000..c1fd607
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/G4Constants.java
@@ -0,0 +1,14 @@
+package com.ktind.cgm.bgscout;
+
+import com.ktind.cgm.bgscout.Constants;
+
+/**
+ * Created by klee24 on 8/2/14.
+ */
+public class G4Constants {
+ final static int READING_INTERVAL= 60*5; // 5 minutes in seconds (60 seconds * 5 minutes)
+ final static int defaultReadings=10;
+ final static int defaultReadTimeout=200;
+ final static int defaultWriteTimeout=200;
+ final static long RECEIVERBASEDATE=1230789600000L;
+}
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/G4DBPage.java b/mobile/src/main/java/com/ktind/cgm/bgscout/G4DBPage.java
new file mode 100644
index 0000000..7c7ab0e
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/G4DBPage.java
@@ -0,0 +1,9 @@
+package com.ktind.cgm.bgscout;
+
+/**
+ * Created by klee24 on 8/2/14.
+ */
+public class G4DBPage {
+ public G4DBPageHeader PageHeader=new G4DBPageHeader();
+ public byte[] PageData=new byte[500];
+}
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/G4DBPageHeader.java b/mobile/src/main/java/com/ktind/cgm/bgscout/G4DBPageHeader.java
new file mode 100644
index 0000000..dd3443d
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/G4DBPageHeader.java
@@ -0,0 +1,16 @@
+package com.ktind.cgm.bgscout;
+
+/**
+ * Created by klee24 on 8/2/14.
+ */
+public class G4DBPageHeader {
+ public int FirstRecordIndex;
+ public int NumberOfRecords;
+ public G4RecType RecordType;
+ public byte Revision;
+ public int PageNumber;
+ public int Reserved2;
+ public int Reserved3;
+ public int Reserved4;
+ public int Crc;
+}
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/G4EGVRecord.java b/mobile/src/main/java/com/ktind/cgm/bgscout/G4EGVRecord.java
new file mode 100644
index 0000000..3e23f9c
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/G4EGVRecord.java
@@ -0,0 +1,38 @@
+package com.ktind.cgm.bgscout;
+
+import com.ktind.cgm.bgscout.EGVRecord;
+import com.ktind.cgm.bgscout.G4EGVSpecialValue;
+import com.ktind.cgm.bgscout.G4NoiseMode;
+
+/**
+ * Created by klee24 on 7/13/14.
+ */
+public class G4EGVRecord extends EGVRecord {;
+ private long systemTime=0L;
+ private G4EGVSpecialValue specialValue;
+ private G4NoiseMode noiseMode;
+
+ public void setSystemTime(long systemTime) {
+ this.systemTime = systemTime;
+ }
+
+ public long getSystemTime() {
+ return systemTime;
+ }
+
+ public void setSpecialValue(G4EGVSpecialValue specialValue) {
+ this.specialValue = specialValue;
+ }
+
+ public G4EGVSpecialValue getSpecialValue() {
+ return specialValue;
+ }
+
+ public void setNoiseMode(G4NoiseMode noiseMode) {
+ this.noiseMode = noiseMode;
+ }
+
+ public G4NoiseMode getNoiseMode() {
+ return noiseMode;
+ }
+}
\ No newline at end of file
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/G4EGVSpecialValue.java b/mobile/src/main/java/com/ktind/cgm/bgscout/G4EGVSpecialValue.java
new file mode 100644
index 0000000..51f8233
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/G4EGVSpecialValue.java
@@ -0,0 +1,41 @@
+package com.ktind.cgm.bgscout;
+
+/**
+ * Created by klee24 on 7/20/14.
+ */
+public enum G4EGVSpecialValue {
+
+ NONE("None",0),
+ SENSORNOTACTIVE("Sensor not active",1),
+ MINIMALLYEGVAB("Minimally EGV Aberration",2),
+ NOANTENNA("No Antenna",3),
+ SENSOROUTOFCAL("Sensor needs Calibration",5),
+ COUNTSAB("Counts Aberration",6),
+ ABSOLUTEAB("Absolute Aberration",9),
+ POWERAB("Power Aberration",10),
+ RFBADSTATUS("RF bad status",12);
+
+
+ private String name;
+ private int val;
+ private G4EGVSpecialValue(String s, int i){
+ name=s;
+ val=i;
+ }
+
+ public int getValue(){
+ return val;
+ }
+
+ public String toString(){
+ return name;
+ }
+
+ public static G4EGVSpecialValue getEGVSpecialValue(int val){
+ for (G4EGVSpecialValue e: values()){
+ if (e.getValue()==val)
+ return e;
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/G4NoiseMode.java b/mobile/src/main/java/com/ktind/cgm/bgscout/G4NoiseMode.java
new file mode 100644
index 0000000..6ac734e
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/G4NoiseMode.java
@@ -0,0 +1,32 @@
+package com.ktind.cgm.bgscout;
+
+/**
+ * Created by klee24 on 7/20/14.
+ */
+public enum G4NoiseMode {
+ None(0),
+ Clean(1),
+ Light(2),
+ Medium(3),
+ Heavy(4),
+ NotComputed(5),
+ Max(6);
+
+ private int index;
+ private G4NoiseMode(int i){
+ index=i;
+ }
+
+ public int getValue(){
+ return index;
+ }
+
+ public static G4NoiseMode getNoiseMode(int val){
+ for (G4NoiseMode e: values()){
+ if (e.getValue()==val)
+ return e;
+ }
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/G4Partition.java b/mobile/src/main/java/com/ktind/cgm/bgscout/G4Partition.java
new file mode 100644
index 0000000..c2d0aa0
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/G4Partition.java
@@ -0,0 +1,10 @@
+package com.ktind.cgm.bgscout;
+
+/**
+ * Created by klee24 on 7/20/14.
+ */
+public class G4Partition {
+ public G4RecType Partition;
+ public int firstPage;
+ public int lastPage;
+}
\ No newline at end of file
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/G4RcvrCmd.java b/mobile/src/main/java/com/ktind/cgm/bgscout/G4RcvrCmd.java
new file mode 100644
index 0000000..51a2315
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/G4RcvrCmd.java
@@ -0,0 +1,88 @@
+package com.ktind.cgm.bgscout;
+
+/**
+ * Created by klee24 on 8/2/14.
+ */
+public enum G4RcvrCmd {
+ NUL("Nul",(byte)0x00),
+ ACK("Ack",(byte)0x01),
+ NAK("Nak",(byte)0x02),
+ INVALIDCOMMAND("InvalidCommand",(byte)0x03),
+ INVALIDPARAM("InvalidParam",(byte)0x04),
+ INCOMPLETEPACKETRECEIVED("IncompletePacketReceived",(byte)0x05),
+ RECEIVERERROR("ReceiverError",(byte)0x06),
+ INVALIDMODE("InvalidMode",(byte)0x07),
+ PING("Ping",(byte)0x0A,6),
+ READFIRMWAREHEADER("ReadFirmwareHeader",(byte)0x0B,6),
+ READDATABASEPARTITIONINFO("ReadDatabasePartitionInfo",(byte)0x0F,6),
+ READDATABASEPAGERANGE("ReadDatabasePageRange",(byte)0x10,7),
+ READDATABASEPAGES("ReadDatabasePages",(byte)0x11,12),
+ READDATABASEPAGEHEADER("ReadDatabasePageHeader",(byte)0x12,11),
+ READTRANSMITTERID("ReadTransmitterID",(byte)0x19,6),
+ WRITETRANSMITTERID("WriteTransmitterID",(byte)0x20),
+ READLANGUAGE("ReadLanguage",(byte)0x1B,6),
+ WRITELANGUAGE("WriteLanguage",(byte)0x1C,8),
+ READDISPLAYTIMEOFFSET("ReadDisplayTimeOffset",(byte)0x1D,6),
+ WRITEDISPLAYTIMEOFFSET("WriteDisplayTimeOffset",(byte)0x1E,10),
+ READRTC("ReadRTC",(byte)0x1F,6),
+ RESETRECEIVER("ResetReceiver",(byte)0x20,6),
+ READBATTERYLEVEL("ReadBatteryLevel",(byte)0x21,6),
+ READSYSTEMTIME("ReadSystemTime",(byte)0x22,6),
+ READSYSTEMTIMEOFFSET("ReadSystemTimeOffset",(byte)0x23),
+ WRITESYSTEMTIME("WriteSystemTime",(byte)0x24,6),
+ READGLUCOSEUNIT("ReadGlucoseUnit",(byte)0x25,6),
+ WRITEGLUCOSEUNIT("WriteGlucoseUnit",(byte)0x26,7),
+ READBLINDEDMODE("ReadBlindedMode",(byte)0x27,6),
+ WRITEBLINDEDMODE("WriteBlindedMode",(byte)0x28,7),
+ READCLOCKMODE("ReadClockMode",(byte)0x29,6),
+ WRITECLOCKMODE("WriteClockMode",(byte)0x2A,7),
+ READDEVICEMODE("ReadDeviceMode",(byte)0x2B,6),
+ ERASEDATABASE("EraseDatabase",(byte)0x2D),
+ SHUTDOWNRECEIVER("ShutdownReceiver",(byte)0x2E,6),
+ WRITEPCPARAMETERS("WritePCParameters",(byte)0x2F),
+ READBATTERYSTATE("ReadBatteryState",(byte)0x30,6),
+ READHARDWAREBOARDID("ReadHardwareBoardId",(byte)0x31,6),
+ ENTERFIRMWAREUPGRADEMODE("EnterFirmwareUpgradeMode",(byte)0x32),
+ READFLASHPAGE("ReadFlashPage",(byte)0x33,10),
+ WRITEFLASHPAGE("WriteFlashPage",(byte)0x34),
+ ENTERSAMBAACCESSMODE("EnterSambaAccessMode",(byte)0x35),
+ READFIRMWARESETTINGS("ReadFirmwareSettings",(byte)0x36),
+ READENABLESETUPWIZARDFLAG("ReadEnableSetupWizardFlag",(byte)0x37),
+ WRITEENABLESETUPWIZARDFLAG("WriteEnableSetUpWizardFlag",(byte)0x38),
+ READSETUPWIZARDSTATE("ReadSetUpWizardState",(byte)0x39),
+ WRITESETUPWIZARDSTATE("WriteSetupWizardState",(byte)0x3A),
+ MAXCOMMAND("MaxCommand",(byte)0x3B),
+ MAXPOSSIBLECOMMAND("MaxPossibleCommand",(byte)0xFF);
+
+
+ private String stringValue;
+ private byte byteVal;
+ // This is the expected size of the command to be written.
+ // Todo: determine how to variable sized commands. Perhaps if the value is 0?
+ private int cmdSize;
+
+ public byte getValue(){
+ return byteVal;
+ }
+
+ private G4RcvrCmd(String toString,byte value, int size){
+ stringValue=toString;
+ byteVal=value;
+ cmdSize=size;
+ }
+
+ private G4RcvrCmd(String toString, byte value){
+ stringValue=toString;
+ byteVal=value;
+ cmdSize=-1;
+ }
+
+ public int getCmdSize() {
+ return cmdSize;
+ }
+
+ @Override
+ public String toString(){
+ return stringValue;
+ }
+}
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/G4RecType.java b/mobile/src/main/java/com/ktind/cgm/bgscout/G4RecType.java
new file mode 100644
index 0000000..678f0c7
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/G4RecType.java
@@ -0,0 +1,37 @@
+package com.ktind.cgm.bgscout;
+
+/**
+ * Created by klee24 on 7/13/14.
+ */
+public enum G4RecType {
+ MANUFACTURINGDATA("ManufacturingData",(byte)0x00),
+ FIRMWAREPARAMETERDATA("FirmwareParameterData",(byte)0x01),
+ PCSOFTWAREPARAMETER("PCSoftwareParameter",(byte)0x02),
+ SENSORDATA("SensorData",(byte)0x03),
+ EGVDATA("EGVData",(byte)0x04),
+ CALSET("CalSet",(byte)0x05),
+ ABERRATION("Aberration",(byte)0x06),
+ INSERTIONTIME("InsertionTime",(byte)0x07),
+ RECEIVERLOGDATA("ReceiverLogData",(byte)0x08),
+ RECEIVERERRORDATA("ReceiverErrorData",(byte)0x09),
+ METERDATA("MeterData",(byte)0x0a),
+ USEREVENTDATA("UserEventData",(byte)0x0b),
+ USERSETTINGDATA("UserSettingData",(byte)0x0c),
+ MAXVALUE("MaxValue",(byte)0x0d);
+
+ private String stringValue;
+ private byte byteVal;
+ private G4RecType(String toString,byte value){
+ stringValue=toString;
+ byteVal=value;
+ }
+
+ public byte getValue(){
+ return byteVal;
+ }
+
+ @Override
+ public String toString(){
+ return stringValue;
+ }
+}
\ No newline at end of file
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/G4USBSerialTransport.java b/mobile/src/main/java/com/ktind/cgm/bgscout/G4USBSerialTransport.java
new file mode 100644
index 0000000..0267d1e
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/G4USBSerialTransport.java
@@ -0,0 +1,101 @@
+package com.ktind.cgm.bgscout;
+
+import android.content.Context;
+import android.hardware.usb.UsbManager;
+import android.util.Log;
+
+//import com.ktind.cgm.bgscout.CGMTransportAbstract;
+
+import com.ktind.cgm.bgscout.USB.USBPower;
+import com.ktind.cgm.bgscout.USB.UsbSerialDriver;
+import com.ktind.cgm.bgscout.USB.UsbSerialProber;
+
+import java.io.IOException;
+
+/**
+ * Created by klee24 on 7/21/14.
+ */
+public class G4USBSerialTransport extends CGMTransportAbstract {
+ private static final String TAG = G4USBSerialTransport.class.getSimpleName();
+ // private UsbManager mUsbManager;
+ private UsbSerialDriver mSerialDevice;
+ Context appContext;
+ int totalBytesRead=0;
+ int totalBytesWritten=0;
+// boolean chargeReceiver=false;
+
+
+ public G4USBSerialTransport(Context c){
+ appContext=c;
+ }
+
+ @Override
+ public boolean open() throws DeviceNotConnected {
+ USBPower.PowerOn();
+ if (! isOpen()){
+ Log.d(TAG, "Attempting to connect");
+ UsbManager mUsbManager;
+ mUsbManager=(UsbManager) appContext.getSystemService(Context.USB_SERVICE);
+ mSerialDevice = UsbSerialProber.acquire(mUsbManager);
+ if (mSerialDevice != null) {
+ try {
+ mSerialDevice.open();
+ Log.d(TAG, "Successfully connected");
+ isopen = true;
+ } catch (IOException e) {
+// Log.e(TAG, "Unable to establish a serial connection to Dexcom G4");
+ isopen = false;
+ throw new DeviceNotConnected("Unable to establish a serial connection to Dexcom G4");
+ }
+ }else{
+// Log.e(TAG,"Unable to acquire USB Manager");
+ throw new DeviceNotConnected("Unable to acquire USB manager");
+ }
+ }else{
+ Log.d(TAG, "Already connected");
+ }
+
+ return false;
+ }
+
+// public void setChargeReceiver(boolean charge){
+// chargeReceiver=charge;
+// }
+
+ @Override
+ public void close() {
+ if (isOpen() && mSerialDevice!=null) {
+ if (!chargeDevice) {
+ Log.d(TAG,"chargeReceiver:"+chargeDevice);
+ Log.d(TAG,"Disabling USB power");
+ USBPower.PowerOff();
+ }
+ Log.d(TAG, "Attempting to disconnect");
+ try {
+ mSerialDevice.close();
+ Log.d(TAG, "Successfully disconnected");
+ isopen = false;
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to close serial connection to Dexcom G4");
+ }
+ } else {
+ Log.d(TAG,"Already disconnected");
+ }
+ }
+
+ @Override
+ public int read(byte[] responseBuffer,int timeoutMillis) throws IOException {
+ int bytesRead;
+ bytesRead=mSerialDevice.read(responseBuffer,timeoutMillis);
+ totalBytesRead+=bytesRead;
+ return bytesRead;
+ }
+
+ @Override
+ public int write(byte [] packet, int writeTimeout) throws IOException {
+ int bytesWritten;
+ bytesWritten=mSerialDevice.write(packet, writeTimeout);
+ totalBytesWritten+=bytesWritten;
+ return bytesWritten;
+ }
+}
\ No newline at end of file
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/GlucoseUnit.java b/mobile/src/main/java/com/ktind/cgm/bgscout/GlucoseUnit.java
new file mode 100644
index 0000000..1e084ee
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/GlucoseUnit.java
@@ -0,0 +1,27 @@
+package com.ktind.cgm.bgscout;
+
+/**
+ * Created by klee24 on 8/2/14.
+ */
+public enum GlucoseUnit {
+ NONE("None",(byte) 0),
+ MGDL("mg/dL",(byte) 1),
+ MMOL("mmol/L",(byte) 2);
+
+
+ private String unit;
+ private byte value;
+ private GlucoseUnit(String mUnit, byte mVal){
+ unit=mUnit;
+ value=mVal;
+ }
+
+ public byte getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return unit;
+ }
+}
\ No newline at end of file
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/MainActivity.java b/mobile/src/main/java/com/ktind/cgm/bgscout/MainActivity.java
new file mode 100644
index 0000000..f9f8dc9
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/MainActivity.java
@@ -0,0 +1,73 @@
+package com.ktind.cgm.bgscout;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+
+import com.ktind.cgm.bgscout.R;
+
+public class MainActivity extends Activity {
+ private static final String TAG = DeviceDownloadService.class.getSimpleName();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ final Button button= (Button) findViewById(R.id.button);
+ if (isServiceRunning()) {
+ button.setText("Start");
+ } else {
+ button.setText("Stop");
+ }
+
+ }
+
+ public void toggleService(View view){
+ Intent mIntent=new Intent(MainActivity.this,DeviceDownloadService.class);
+ final Button button= (Button) findViewById(R.id.button);
+ if (isServiceRunning()) {
+ Log.d(TAG, "Stopping service");
+ stopService(mIntent);
+ button.setText("Start");
+ } else {
+ Log.d(TAG, "Starting service");
+ startService(mIntent);
+ button.setText("Stop");
+ }
+ }
+
+ private boolean isServiceRunning() {
+ ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
+ for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
+ if ("com.ktind.cgm.bgscout.DeviceDownloadService".equals(service.service.getClassName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.main, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ int id = item.getItemId();
+ if (id == R.id.action_settings) {
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/MongoUploadMonitor.java b/mobile/src/main/java/com/ktind/cgm/bgscout/MongoUploadMonitor.java
new file mode 100644
index 0000000..6728e17
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/MongoUploadMonitor.java
@@ -0,0 +1,75 @@
+package com.ktind.cgm.bgscout;
+
+import android.util.Log;
+
+import com.mongodb.*;
+
+import java.net.UnknownHostException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * Created by klee24 on 7/27/14.
+ */
+public class MongoUploadMonitor extends AbstractMonitor {
+ private static final String TAG = MongoUploadMonitor.class.getSimpleName();
+
+ MongoUploadMonitor(String name) {
+ super(name);
+ this.setAllowVirtual(false);
+ this.setMonitorType("mongo uploader");
+ }
+
+ @Override
+ protected void doProcess(DeviceDownloadObject d) {
+ //FIXME add these as user configurable options
+ String mongoURI = "";
+ String collectionName="";
+ DBCollection deviceData;
+
+ MongoClientURI uri = new MongoClientURI(mongoURI);
+ DB db;
+
+ try{
+ MongoClient mongoClient = new MongoClient(uri);
+ db = mongoClient.getDB(uri.getDatabase());
+ deviceData = db.getCollection(collectionName);
+ EGVRecord[] r=d.getEgvRecords();
+ int uploadCount=0;
+ for (EGVRecord sr:r) {
+ BasicDBObject data = new BasicDBObject();
+ if (sr.isNew()) {
+ //Fixme: need to be a separate document
+ data.put("name", d.getDevice().getName());
+ data.put("cgmbattery", d.getDevice().getCGMBattery());
+ data.put("uploaderBattery", d.getDevice().getUploaderBattery());
+ data.put("units", d.getDevice().getUnit().getValue());
+
+ data.put("trend", sr.getTrend().getVal());
+ // NightScout comptability
+ data.put("device", "dexcom");
+ data.put("date", sr.getDate().getTime());
+ data.put("dateString", new SimpleDateFormat("MM/dd/yyy hh:mm:ss aa").format(sr.getDate()));
+ data.put("sgv", sr.getEgv());
+ data.put("direction", sr.getTrend().getNsString());
+
+ deviceData.update(data, data, true, false, WriteConcern.UNACKNOWLEDGED);
+ uploadCount+=1;
+ Log.v(TAG, "Added Record - EGV: " + sr.getEgv() + " Trend: " + sr.getTrend().getNsString() + " Date: " + new SimpleDateFormat("MM/dd/yyy hh:mm:ss aa").format(sr.getDate()));
+ }
+ }
+ Log.i(TAG,"Records processed: "+r.length+" Records Uploaded: "+uploadCount);
+// BasicDBObject data= new BasicDBObject();
+// data.put("deviceCheckinDate",new Date().getTime());
+ if (mongoClient != null)
+ mongoClient.close();
+ } catch (UnknownHostException e) {
+ Log.e(TAG,"Unable to upload to mongoDB",e);
+ }
+ }
+
+ @Override
+ public void stop() {
+ Log.i(TAG, "Stopping monitor " + monitorType + " for " + name);
+ }
+}
\ No newline at end of file
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/MonitorInterface.java b/mobile/src/main/java/com/ktind/cgm/bgscout/MonitorInterface.java
new file mode 100644
index 0000000..067dfd0
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/MonitorInterface.java
@@ -0,0 +1,11 @@
+package com.ktind.cgm.bgscout;
+
+/**
+ * Created by klee24 on 8/2/14.
+ */
+public interface MonitorInterface {
+ public void process(DeviceDownloadObject d);
+ public void stop();
+ public void setHighThreshold(int highThreshold);
+ public void setLowThreshold(int lowThreshold);
+}
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/MonitorProxy.java b/mobile/src/main/java/com/ktind/cgm/bgscout/MonitorProxy.java
new file mode 100644
index 0000000..d590aa4
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/MonitorProxy.java
@@ -0,0 +1,44 @@
+package com.ktind.cgm.bgscout;
+
+import android.os.AsyncTask;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Created by klee24 on 8/3/14.
+ */
+public class MonitorProxy extends AsyncTask {
+
+ private ArrayList monitors;
+
+ MonitorProxy(){
+
+ }
+
+ MonitorProxy(MonitorProxy mProxy){
+ this.monitors=mProxy.monitors;
+ }
+
+ public void setMonitors(AbstractMonitor[] mons){
+ ArrayList listMons=new ArrayList(Arrays.asList(mons));
+ this.monitors=listMons;
+ }
+ public void setMonitors(ArrayList mons) {
+ this.monitors = mons;
+ }
+
+ public void stopMonitors() {
+ for (AbstractMonitor mon:monitors){
+ mon.stop();
+ }
+ }
+
+ @Override
+ protected Void doInBackground(DeviceDownloadObject... dl) {
+ for (AbstractMonitor mon: monitors){
+ mon.process(dl[0]);
+ }
+ return null;
+ }
+}
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/RemoteMongoDevice.java b/mobile/src/main/java/com/ktind/cgm/bgscout/RemoteMongoDevice.java
new file mode 100644
index 0000000..727a833
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/RemoteMongoDevice.java
@@ -0,0 +1,106 @@
+package com.ktind.cgm.bgscout;
+
+import android.content.Context;
+import android.os.Handler;
+import android.util.Log;
+
+import com.mongodb.*;
+
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Created by klee24 on 8/3/14.
+ */
+public class RemoteMongoDevice extends AbstractCGMDevice {
+ private static final String TAG = RemoteMongoDevice.class.getSimpleName();
+ private String mongoURI = "";
+ private String collectionName="";
+ private DBCollection deviceData;
+ MongoClientURI uri = new MongoClientURI(mongoURI);
+ DB db;
+ MongoClient mongoClient = null;
+ long lastQueryDate;
+ EGVRecord[] lastRecord=new EGVRecord[1];
+
+ public RemoteMongoDevice(String n,int deviceID,Context appContext,Handler mH){
+ super(n,deviceID,appContext,mH);
+ // Quasi race condition - CGM takes a second or 2 to read and upload while the "virtual CGM" takes less time.
+ // Give it some time to settle. If not it'll try again in 45 seconds.
+ this.setPollInterval(304000);
+ // stop infinite loops!
+ virtual = true;
+ }
+
+ @Override
+ public int getCGMBattery() {
+ return 100;
+ }
+
+ @Override
+ public void connect() throws DeviceNotConnected {
+ }
+
+ @Override
+ protected DeviceDownloadObject doDownload() {
+ DeviceDownloadObject ddo=new DeviceDownloadObject();
+ ddo.setDevice(this);
+ ddo.setStatus(DownloadStatus.APPLICATIONERROR);
+ ArrayList egvRecords=new ArrayList();
+ ddo.setEgvRecords(new EGVRecord[0]);
+ try {
+ mongoClient = new MongoClient(uri);
+ db = mongoClient.getDB(uri.getDatabase());
+ deviceData = db.getCollection(collectionName);
+ //FIXME Limit this unless we want to kill the heap over time...
+ BasicDBObject query=new BasicDBObject("date", new BasicDBObject("$gt",lastQueryDate));
+ DBCursor cursor=deviceData.find(query);
+ try {
+ List dbObjects=cursor.toArray();
+ Log.d(TAG,"Size of response from mongo query: "+dbObjects.size());
+ for (DBObject dbObject:dbObjects){
+ long recQueryDate=Long.valueOf(dbObject.get("date").toString());
+ Date recDate=new Date(recQueryDate);
+ int bgValue=Integer.valueOf(dbObject.get("sgv").toString());
+ Trend trend=Trend.values()[Integer.valueOf(dbObject.get("trend").toString())];
+ EGVRecord record;
+ if (recQueryDate>lastQueryDate){
+ lastQueryDate=recQueryDate;
+ record=new EGVRecord(bgValue,recDate,trend,true);
+ lastRecord[0]=record;
+ }else{
+ record=new EGVRecord(bgValue,recDate,trend,false);
+ }
+ egvRecords.add(record);
+ ddo.setEgvRecords(egvRecords.toArray(new EGVRecord[egvRecords.size()]));
+ }
+ ddo.setStatus(DownloadStatus.SUCCESS);
+ //FIXME there has to be a more efficient way
+ if (lastRecord!=null && ddo.getEgvRecords().length==0){
+ ddo.setStatus(DownloadStatus.NORECORDS);
+ ddo.setEgvRecords(lastRecord);
+ }
+
+ } finally {
+ cursor.close();
+ }
+ Log.d(TAG, "Performing download of data from mongo for " + getName());
+ mongoClient.close();
+ }catch(UnknownHostException e){
+ Log.e(TAG,"Unable to connect to MongoDB URI",e);
+ ddo.setStatus(DownloadStatus.DEVICENOTFOUND);
+ ddo.setEgvRecords(new EGVRecord[0]);
+ }
+ // FIXME potential cause for a race condition
+ // Should be resolved by device proxy
+ lastDownloadObject=ddo;
+ return ddo;
+ }
+
+ @Override
+ public void disconnect() {
+
+ }
+}
\ No newline at end of file
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/SettingsActivity.java b/mobile/src/main/java/com/ktind/cgm/bgscout/SettingsActivity.java
new file mode 100644
index 0000000..2fc01c2
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/SettingsActivity.java
@@ -0,0 +1,292 @@
+package com.ktind.cgm.bgscout;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceCategory;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceManager;
+import android.preference.RingtonePreference;
+import android.text.TextUtils;
+import android.view.MenuItem;
+import android.support.v4.app.NavUtils;
+import com.ktind.cgm.bgscout.R;
+
+import java.util.List;
+
+/**
+ * A {@link PreferenceActivity} that presents a set of application settings. On
+ * handset devices, settings are presented as a single list. On tablets,
+ * settings are split by category, with category headers shown to the left of
+ * the list of settings.
+ *
+ * See
+ * Android Design: Settings for design guidelines and the Settings
+ * API Guide for more information on developing a Settings UI.
+ */
+public class SettingsActivity extends PreferenceActivity {
+ /**
+ * Determines whether to always show the simplified settings UI, where
+ * settings are presented in a single list. When false, settings are shown
+ * as a master/detail two-pane view on tablets. When true, a single pane is
+ * shown on tablets.
+ */
+ private static final boolean ALWAYS_SIMPLE_PREFS = false;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setupActionBar();
+ }
+
+ /**
+ * Set up the {@link android.app.ActionBar}, if the API is available.
+ */
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ private void setupActionBar() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ // Show the Up button in the action bar.
+ getActionBar().setDisplayHomeAsUpEnabled(true);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int id = item.getItemId();
+ if (id == android.R.id.home) {
+ // This ID represents the Home or Up button. In the case of this
+ // activity, the Up button is shown. Use NavUtils to allow users
+ // to navigate up one level in the application structure. For
+ // more details, see the Navigation pattern on Android Design:
+ //
+ // http://developer.android.com/design/patterns/navigation.html#up-vs-back
+ //
+ // TODO: If Settings has multiple levels, Up should navigate up
+ // that hierarchy.
+ NavUtils.navigateUpFromSameTask(this);
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+
+ setupSimplePreferencesScreen();
+ }
+
+ /**
+ * Shows the simplified settings UI if the device configuration if the
+ * device configuration dictates that a simplified, single-pane UI should be
+ * shown.
+ */
+ private void setupSimplePreferencesScreen() {
+ if (!isSimplePreferences(this)) {
+ return;
+ }
+
+ // In the simplified UI, fragments are not used at all and we instead
+ // use the older PreferenceActivity APIs.
+
+ // Add 'general' preferences.
+ addPreferencesFromResource(R.xml.pref_general);
+
+ // Add 'notifications' preferences, and a corresponding header.
+ PreferenceCategory fakeHeader = new PreferenceCategory(this);
+ fakeHeader.setTitle(R.string.pref_header_notifications);
+ getPreferenceScreen().addPreference(fakeHeader);
+ addPreferencesFromResource(R.xml.pref_notification);
+
+ // Add 'data and sync' preferences, and a corresponding header.
+ fakeHeader = new PreferenceCategory(this);
+ fakeHeader.setTitle(R.string.pref_header_data_sync);
+ getPreferenceScreen().addPreference(fakeHeader);
+ addPreferencesFromResource(R.xml.pref_data_sync);
+
+ // Bind the summaries of EditText/List/Dialog/Ringtone preferences to
+ // their values. When their values change, their summaries are updated
+ // to reflect the new value, per the Android Design guidelines.
+ bindPreferenceSummaryToValue(findPreference("example_text"));
+ bindPreferenceSummaryToValue(findPreference("example_list"));
+ bindPreferenceSummaryToValue(findPreference("notifications_new_message_ringtone"));
+ bindPreferenceSummaryToValue(findPreference("sync_frequency"));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean onIsMultiPane() {
+ return isXLargeTablet(this) && !isSimplePreferences(this);
+ }
+
+ /**
+ * Helper method to determine if the device has an extra-large screen. For
+ * example, 10" tablets are extra-large.
+ */
+ private static boolean isXLargeTablet(Context context) {
+ return (context.getResources().getConfiguration().screenLayout
+ & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
+ }
+
+ /**
+ * Determines whether the simplified settings UI should be shown. This is
+ * true if this is forced via {@link #ALWAYS_SIMPLE_PREFS}, or the device
+ * doesn't have newer APIs like {@link PreferenceFragment}, or the device
+ * doesn't have an extra-large screen. In these cases, a single-pane
+ * "simplified" settings UI should be shown.
+ */
+ private static boolean isSimplePreferences(Context context) {
+ return ALWAYS_SIMPLE_PREFS
+ || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB
+ || !isXLargeTablet(context);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public void onBuildHeaders(List target) {
+ if (!isSimplePreferences(this)) {
+ loadHeadersFromResource(R.xml.pref_headers, target);
+ }
+ }
+
+ /**
+ * A preference value change listener that updates the preference's summary
+ * to reflect its new value.
+ */
+ private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object value) {
+ String stringValue = value.toString();
+
+ if (preference instanceof ListPreference) {
+ // For list preferences, look up the correct display value in
+ // the preference's 'entries' list.
+ ListPreference listPreference = (ListPreference) preference;
+ int index = listPreference.findIndexOfValue(stringValue);
+
+ // Set the summary to reflect the new value.
+ preference.setSummary(
+ index >= 0
+ ? listPreference.getEntries()[index]
+ : null);
+
+ } else if (preference instanceof RingtonePreference) {
+ // For ringtone preferences, look up the correct display value
+ // using RingtoneManager.
+ if (TextUtils.isEmpty(stringValue)) {
+ // Empty values correspond to 'silent' (no ringtone).
+ preference.setSummary(R.string.pref_ringtone_silent);
+
+ } else {
+ Ringtone ringtone = RingtoneManager.getRingtone(
+ preference.getContext(), Uri.parse(stringValue));
+
+ if (ringtone == null) {
+ // Clear the summary if there was a lookup error.
+ preference.setSummary(null);
+ } else {
+ // Set the summary to reflect the new ringtone display
+ // name.
+ String name = ringtone.getTitle(preference.getContext());
+ preference.setSummary(name);
+ }
+ }
+
+ } else {
+ // For all other preferences, set the summary to the value's
+ // simple string representation.
+ preference.setSummary(stringValue);
+ }
+ return true;
+ }
+ };
+
+ /**
+ * Binds a preference's summary to its value. More specifically, when the
+ * preference's value is changed, its summary (line of text below the
+ * preference title) is updated to reflect the value. The summary is also
+ * immediately updated upon calling this method. The exact display format is
+ * dependent on the type of preference.
+ *
+ * @see #sBindPreferenceSummaryToValueListener
+ */
+ private static void bindPreferenceSummaryToValue(Preference preference) {
+ // Set the listener to watch for value changes.
+ preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);
+
+ // Trigger the listener immediately with the preference's
+ // current value.
+ sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
+ PreferenceManager
+ .getDefaultSharedPreferences(preference.getContext())
+ .getString(preference.getKey(), ""));
+ }
+
+ /**
+ * This fragment shows general preferences only. It is used when the
+ * activity is showing a two-pane settings UI.
+ */
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public static class GeneralPreferenceFragment extends PreferenceFragment {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.pref_general);
+
+ // Bind the summaries of EditText/List/Dialog/Ringtone preferences
+ // to their values. When their values change, their summaries are
+ // updated to reflect the new value, per the Android Design
+ // guidelines.
+ bindPreferenceSummaryToValue(findPreference("example_text"));
+ bindPreferenceSummaryToValue(findPreference("example_list"));
+ }
+ }
+
+ /**
+ * This fragment shows notification preferences only. It is used when the
+ * activity is showing a two-pane settings UI.
+ */
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public static class NotificationPreferenceFragment extends PreferenceFragment {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.pref_notification);
+
+ // Bind the summaries of EditText/List/Dialog/Ringtone preferences
+ // to their values. When their values change, their summaries are
+ // updated to reflect the new value, per the Android Design
+ // guidelines.
+ bindPreferenceSummaryToValue(findPreference("notifications_new_message_ringtone"));
+ }
+ }
+
+ /**
+ * This fragment shows data and sync preferences only. It is used when the
+ * activity is showing a two-pane settings UI.
+ */
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public static class DataSyncPreferenceFragment extends PreferenceFragment {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.pref_data_sync);
+
+ // Bind the summaries of EditText/List/Dialog/Ringtone preferences
+ // to their values. When their values change, their summaries are
+ // updated to reflect the new value, per the Android Design
+ // guidelines.
+ bindPreferenceSummaryToValue(findPreference("sync_frequency"));
+ }
+ }
+}
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/Trend.java b/mobile/src/main/java/com/ktind/cgm/bgscout/Trend.java
new file mode 100644
index 0000000..c5c8e81
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/Trend.java
@@ -0,0 +1,39 @@
+package com.ktind.cgm.bgscout;
+
+/**
+ * Created by klee24 on 8/2/14.
+ */
+public enum Trend {
+ NONE("None",0,"NONE"),
+ DOUBLEUP("Double up",1,"DoubleUp"),
+ SINGLEUP("Single up",2,"SingleUp"),
+ FORTYFIVEUP("Forty-five up",3,"FortyFiveUp"),
+ FLAT("Flat",4,"Flat"),
+ FORTYFIVEDOWN("Forty-five down",5,"FortyFiveDown"),
+ SINGLEDOWN("Single down",6,"SingleDown"),
+ DOUBLEDOWN("Double down",7,"DoubleDown"),
+ NOTCOMPUTE("Not computable",8,"NOT COMPUTABLE"),
+ RATEOUTRANGE("Rate out of Range",9,"RATE OUT OF RANGE");
+
+ private String stringVal;
+ private int intVal;
+ private String nsString="NONE";
+ private Trend(String strVal, int integerVal,String nsStr){
+ this.stringVal=strVal;
+ this.intVal=integerVal;
+ this.nsString=nsStr;
+ }
+
+ public int getVal() {
+ return intVal;
+ }
+
+ @Override
+ public String toString(){
+ return stringVal;
+ }
+
+ public String getNsString(){
+ return nsString;
+ }
+}
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/USB/CdcAcmSerialDriver.java b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/CdcAcmSerialDriver.java
new file mode 100644
index 0000000..08d5e2d
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/CdcAcmSerialDriver.java
@@ -0,0 +1,251 @@
+package com.ktind.cgm.bgscout.USB;
+
+import android.hardware.usb.*;
+import android.util.Log;
+
+import java.io.IOException;
+
+//import com.hoho.android.usbserial.driver.UsbId;
+
+/**
+ * USB CDC/ACM serial driver implementation.
+ *
+ * @author mike wakerly (opensource@hoho.com)
+ * @see Universal
+ * Serial Bus Class Definitions for Communication Devices, v1.1
+ */
+public class CdcAcmSerialDriver extends CommonUsbSerialDriver {
+
+ private final String TAG = CdcAcmSerialDriver.class.getSimpleName();
+
+ private UsbInterface mControlInterface;
+ private UsbInterface mDataInterface;
+
+ private UsbEndpoint mControlEndpoint;
+ private UsbEndpoint mReadEndpoint;
+ private UsbEndpoint mWriteEndpoint;
+
+ private boolean mRts = false;
+ private boolean mDtr = false;
+
+ private static final int USB_RECIP_INTERFACE = 0x01;
+ private static final int USB_RT_ACM = UsbConstants.USB_TYPE_CLASS | USB_RECIP_INTERFACE;
+
+ private static final int SET_LINE_CODING = 0x20; // USB CDC 1.1 section 6.2
+ private static final int GET_LINE_CODING = 0x21;
+ private static final int SET_CONTROL_LINE_STATE = 0x22;
+ private static final int SEND_BREAK = 0x23;
+ private static final String SET_POWER_ON_COMMAND = "echo 'on' > \"/sys/bus/usb/devices/1-1/power/level\"";
+
+ public CdcAcmSerialDriver(UsbDevice device, UsbDeviceConnection connection) {
+ super(device, connection);
+ }
+
+ @Override
+ public void open() throws IOException {
+ Log.d(TAG, "claiming interfaces, count=" + mDevice.getInterfaceCount());
+
+ Log.d(TAG, "Claiming control interface.");
+ mControlInterface = mDevice.getInterface(0);
+ Log.d(TAG, "Control iface=" + mControlInterface);
+ // class should be USB_CLASS_COMM
+
+ if (!mConnection.claimInterface(mControlInterface, true)) {
+ throw new IOException("Could not claim control interface.");
+ }
+ mControlEndpoint = mControlInterface.getEndpoint(0);
+ Log.d(TAG, "Control endpoint direction: " + mControlEndpoint.getDirection());
+
+ Log.d(TAG, "Claiming data interface.");
+ mDataInterface = mDevice.getInterface(1);
+ Log.d(TAG, "data iface=" + mDataInterface);
+ // class should be USB_CLASS_CDC_DATA
+
+ if (!mConnection.claimInterface(mDataInterface, true)) {
+ throw new IOException("Could not claim data interface.");
+ }
+ mReadEndpoint = mDataInterface.getEndpoint(1);
+ Log.d(TAG, "Read endpoint direction: " + mReadEndpoint.getDirection());
+ mWriteEndpoint = mDataInterface.getEndpoint(0);
+ Log.d(TAG, "Write endpoint direction: " + mWriteEndpoint.getDirection());
+ }
+
+ private int sendAcmControlMessage(int request, int value, byte[] buf) {
+ return mConnection.controlTransfer(
+ USB_RT_ACM, request, value, 0, buf, buf != null ? buf.length : 0, 5000);
+ }
+
+ @Override
+ public void close() throws IOException {
+ mConnection.close();
+ }
+
+ @Override
+ public int read(byte[] dest, int timeoutMillis) throws IOException {
+ final int numBytesRead;
+ synchronized (mReadBufferLock) {
+ 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
+ Log.e("G4Device","possible timeout (numBytesRead): "+numBytesRead);
+ Log.d("G4Device","timeoutMillis "+timeoutMillis);
+ Log.d("G4Device","readAmt "+readAmt);
+ return 0;
+ }
+
+ System.arraycopy(mReadBuffer, 0, dest, 0, numBytesRead);
+ }
+ return numBytesRead;
+ }
+
+ @Override
+ public int write(byte[] src, int timeoutMillis) throws IOException {
+ // TODO(mikey): Nearly identical to FtdiSerial write. Refactor.
+ int offset = 0;
+
+ while (offset < src.length) {
+ final int writeLength;
+ final int amtWritten;
+
+ synchronized (mWriteBufferLock) {
+ final byte[] writeBuffer;
+
+ writeLength = Math.min(src.length - offset, mWriteBuffer.length);
+ if (offset == 0) {
+ writeBuffer = src;
+ } else {
+ // bulkTransfer does not support offsets, make a copy.
+ System.arraycopy(src, offset, mWriteBuffer, 0, writeLength);
+ writeBuffer = mWriteBuffer;
+ }
+
+ amtWritten = mConnection.bulkTransfer(mWriteEndpoint, writeBuffer, writeLength,
+ timeoutMillis);
+ }
+ if (amtWritten <= 0) {
+ throw new IOException("Error writing " + writeLength
+ + " bytes at offset " + offset + " length=" + src.length);
+ }
+
+ Log.d(TAG, "Wrote amt=" + amtWritten + " attempted=" + writeLength);
+ offset += amtWritten;
+ }
+ return offset;
+ }
+
+ @Override
+ public void setParameters(int baudRate, int dataBits, int stopBits, int parity) {
+ byte stopBitsByte;
+ switch (stopBits) {
+ case STOPBITS_1: stopBitsByte = 0; break;
+ case STOPBITS_1_5: stopBitsByte = 1; break;
+ case STOPBITS_2: stopBitsByte = 2; break;
+ default: throw new IllegalArgumentException("Bad value for stopBits: " + stopBits);
+ }
+
+ byte parityBitesByte;
+ switch (parity) {
+ case PARITY_NONE: parityBitesByte = 0; break;
+ case PARITY_ODD: parityBitesByte = 1; break;
+ case PARITY_EVEN: parityBitesByte = 2; break;
+ case PARITY_MARK: parityBitesByte = 3; break;
+ case PARITY_SPACE: parityBitesByte = 4; break;
+ default: throw new IllegalArgumentException("Bad value for parity: " + parity);
+ }
+
+ byte[] msg = {
+ (byte) ( baudRate & 0xff),
+ (byte) ((baudRate >> 8 ) & 0xff),
+ (byte) ((baudRate >> 16) & 0xff),
+ (byte) ((baudRate >> 24) & 0xff),
+ stopBitsByte,
+ parityBitesByte,
+ (byte) dataBits};
+ sendAcmControlMessage(SET_LINE_CODING, 0, msg);
+ }
+
+ @Override
+ public boolean getCD() throws IOException {
+ return false; // TODO
+ }
+
+ @Override
+ public boolean getCTS() throws IOException {
+ return false; // TODO
+ }
+
+ @Override
+ public boolean getDSR() throws IOException {
+ return false; // TODO
+ }
+
+ @Override
+ public boolean getDTR() throws IOException {
+ return mDtr;
+ }
+
+ @Override
+ public void setDTR(boolean value) throws IOException {
+ mDtr = value;
+ setDtrRts();
+ }
+
+ @Override
+ public boolean getRI() throws IOException {
+ return false; // TODO
+ }
+
+ @Override
+ public boolean getRTS() throws IOException {
+ return mRts;
+ }
+
+ @Override
+ public void setRTS(boolean value) throws IOException {
+ mRts = value;
+ setDtrRts();
+ }
+
+ private void setDtrRts() {
+ int value = (mRts ? 0x2 : 0) | (mDtr ? 0x1 : 0);
+ sendAcmControlMessage(SET_CONTROL_LINE_STATE, value, null);
+ }
+
+
+
+/* public static Map getSupportedDevices() {
+ final Map supportedDevices = new LinkedHashMap();
+ supportedDevices.put(Integer.valueOf(UsbId.VENDOR_ARDUINO),
+ new int[] {
+ UsbId.ARDUINO_UNO,
+ UsbId.ARDUINO_UNO_R3,
+ UsbId.ARDUINO_MEGA_2560,
+ UsbId.ARDUINO_MEGA_2560_R3,
+ UsbId.ARDUINO_SERIAL_ADAPTER,
+ UsbId.ARDUINO_SERIAL_ADAPTER_R3,
+ UsbId.ARDUINO_MEGA_ADK,
+ UsbId.ARDUINO_MEGA_ADK_R3,
+ UsbId.ARDUINO_LEONARDO,
+ });
+ supportedDevices.put(Integer.valueOf(UsbId.VENDOR_VAN_OOIJEN_TECH),
+ new int[] {
+ UsbId.VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL,
+ });
+ supportedDevices.put(Integer.valueOf(UsbId.VENDOR_ATMEL),
+ new int[] {
+ UsbId.ATMEL_LUFA_CDC_DEMO_APP,
+ });
+ supportedDevices.put(Integer.valueOf(UsbId.VENDOR_LEAFLABS),
+ new int[] {
+ UsbId.LEAFLABS_MAPLE,
+ });
+ return supportedDevices;
+ }*/
+
+}
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/USB/CommonUsbSerialDriver.java b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/CommonUsbSerialDriver.java
new file mode 100644
index 0000000..7dc5c9c
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/CommonUsbSerialDriver.java
@@ -0,0 +1,138 @@
+/* Copyright 2013 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ *
+ * Project home page: http://code.google.com/p/usb-serial-for-android/
+ */
+
+package com.ktind.cgm.bgscout.USB;
+
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbDeviceConnection;
+
+import java.io.IOException;
+
+
+/**
+ * A base class shared by several driver implementations.
+ *
+ * @author mike wakerly (opensource@hoho.com)
+ */
+abstract class CommonUsbSerialDriver implements UsbSerialDriver {
+
+ public static final int DEFAULT_READ_BUFFER_SIZE = 16 * 1024;
+ public static final int DEFAULT_WRITE_BUFFER_SIZE = 16 * 1024;
+
+ protected final UsbDevice mDevice;
+ protected final UsbDeviceConnection mConnection;
+
+ protected final Object mReadBufferLock = new Object();
+ protected final Object mWriteBufferLock = new Object();
+
+ /** Internal read buffer. Guarded by {@link #mReadBufferLock}. */
+ protected byte[] mReadBuffer;
+
+ /** Internal write buffer. Guarded by {@link #mWriteBufferLock}. */
+ protected byte[] mWriteBuffer;
+
+ public CommonUsbSerialDriver(UsbDevice device, UsbDeviceConnection connection) {
+ mDevice = device;
+ mConnection = connection;
+
+ mReadBuffer = new byte[DEFAULT_READ_BUFFER_SIZE];
+ mWriteBuffer = new byte[DEFAULT_WRITE_BUFFER_SIZE];
+ }
+
+ /**
+ * Returns the currently-bound USB device.
+ *
+ * @return the device
+ */
+ public final UsbDevice getDevice() {
+ return mDevice;
+ }
+
+ /**
+ * Sets the size of the internal buffer used to exchange data with the USB
+ * stack for read operations. Most users should not need to change this.
+ *
+ * @param bufferSize the size in bytes
+ */
+ public final void setReadBufferSize(int bufferSize) {
+ synchronized (mReadBufferLock) {
+ if (bufferSize == mReadBuffer.length) {
+ return;
+ }
+ mReadBuffer = new byte[bufferSize];
+ }
+ }
+
+ /**
+ * Sets the size of the internal buffer used to exchange data with the USB
+ * stack for write operations. Most users should not need to change this.
+ *
+ * @param bufferSize the size in bytes
+ */
+ public final void setWriteBufferSize(int bufferSize) {
+ synchronized (mWriteBufferLock) {
+ if (bufferSize == mWriteBuffer.length) {
+ return;
+ }
+ mWriteBuffer = new byte[bufferSize];
+ }
+ }
+
+ @Override
+ public abstract void open() throws IOException;
+
+ @Override
+ public abstract void close() throws IOException;
+
+ @Override
+ public abstract int read(final byte[] dest, final int timeoutMillis) throws IOException;
+
+ @Override
+ public abstract int write(final byte[] src, final int timeoutMillis) throws IOException;
+
+ @Override
+ public abstract void setParameters(
+ int baudRate, int dataBits, int stopBits, int parity) throws IOException;
+
+ @Override
+ public abstract boolean getCD() throws IOException;
+
+ @Override
+ public abstract boolean getCTS() throws IOException;
+
+ @Override
+ public abstract boolean getDSR() throws IOException;
+
+ @Override
+ public abstract boolean getDTR() throws IOException;
+
+ @Override
+ public abstract void setDTR(boolean value) throws IOException;
+
+ @Override
+ public abstract boolean getRI() throws IOException;
+
+ @Override
+ public abstract boolean getRTS() throws IOException;
+
+ @Override
+ public abstract void setRTS(boolean value) throws IOException;
+
+}
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/USB/HexDump.java b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/HexDump.java
new file mode 100644
index 0000000..73c2da6
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/HexDump.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ktind.cgm.bgscout.USB;
+
+/**
+ * Clone of Android's HexDump class, for use in debugging. Cosmetic changes
+ * only.
+ */
+public class HexDump {
+ private final static char[] HEX_DIGITS = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+ };
+
+ public static String dumpHexString(byte[] array) {
+ return dumpHexString(array, 0, array.length);
+ }
+
+ public static String dumpHexString(byte[] array, int offset, int length) {
+ StringBuilder result = new StringBuilder();
+
+ byte[] line = new byte[16];
+ int lineIndex = 0;
+
+ result.append("\n0x");
+ result.append(toHexString(offset));
+
+ for (int i = offset; i < offset + length; i++) {
+ if (lineIndex == 16) {
+ result.append(" ");
+
+ for (int j = 0; j < 16; j++) {
+ if (line[j] > ' ' && line[j] < '~') {
+ result.append(new String(line, j, 1));
+ } else {
+ result.append(".");
+ }
+ }
+
+ result.append("\n0x");
+ result.append(toHexString(i));
+ lineIndex = 0;
+ }
+
+ byte b = array[i];
+ result.append(" ");
+ result.append(HEX_DIGITS[(b >>> 4) & 0x0F]);
+ result.append(HEX_DIGITS[b & 0x0F]);
+
+ line[lineIndex++] = b;
+ }
+
+ if (lineIndex != 16) {
+ int count = (16 - lineIndex) * 3;
+ count++;
+ for (int i = 0; i < count; i++) {
+ result.append(" ");
+ }
+
+ for (int i = 0; i < lineIndex; i++) {
+ if (line[i] > ' ' && line[i] < '~') {
+ result.append(new String(line, i, 1));
+ } else {
+ result.append(".");
+ }
+ }
+ }
+
+ return result.toString();
+ }
+
+ public static String toHexString(byte b) {
+ return toHexString(toByteArray(b));
+ }
+
+ public static String toHexString(byte[] array) {
+ return toHexString(array, 0, array.length);
+ }
+
+ public static String toHexString(byte[] array, int offset, int length) {
+ char[] buf = new char[length * 2];
+
+ int bufIndex = 0;
+ for (int i = offset; i < offset + length; i++) {
+ byte b = array[i];
+ buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F];
+ buf[bufIndex++] = HEX_DIGITS[b & 0x0F];
+ }
+
+ return new String(buf);
+ }
+
+ public static String toHexString(int i) {
+ return toHexString(toByteArray(i));
+ }
+
+ public static byte[] toByteArray(byte b) {
+ byte[] array = new byte[1];
+ array[0] = b;
+ return array;
+ }
+
+ public static byte[] toByteArray(int i) {
+ byte[] array = new byte[4];
+
+ array[3] = (byte) (i & 0xFF);
+ array[2] = (byte) ((i >> 8) & 0xFF);
+ array[1] = (byte) ((i >> 16) & 0xFF);
+ array[0] = (byte) ((i >> 24) & 0xFF);
+
+ return array;
+ }
+
+ private static int toByte(char c) {
+ if (c >= '0' && c <= '9')
+ return (c - '0');
+ if (c >= 'A' && c <= 'F')
+ return (c - 'A' + 10);
+ if (c >= 'a' && c <= 'f')
+ return (c - 'a' + 10);
+
+ throw new RuntimeException("Invalid hex char '" + c + "'");
+ }
+
+ public static byte[] hexStringToByteArray(String hexString) {
+ int length = hexString.length();
+ byte[] buffer = new byte[length / 2];
+
+ for (int i = 0; i < length; i += 2) {
+ buffer[i / 2] = (byte) ((toByte(hexString.charAt(i)) << 4) | toByte(hexString
+ .charAt(i + 1)));
+ }
+
+ return buffer;
+ }
+}
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/USB/SerialInputOutputManager.java b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/SerialInputOutputManager.java
new file mode 100644
index 0000000..79f86de
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/SerialInputOutputManager.java
@@ -0,0 +1,187 @@
+/* Copyright 2011 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ *
+ * Project home page: http://code.google.com/p/usb-serial-for-android/
+ */
+
+package com.ktind.cgm.bgscout.USB;
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+
+/**
+ * Utility class which services a {@link UsbSerialDriver} in its {@link #run()}
+ * method.
+ *
+ * @author mike wakerly (opensource@hoho.com)
+ */
+public class SerialInputOutputManager implements Runnable {
+
+ private static final String TAG = SerialInputOutputManager.class.getSimpleName();
+ private static final boolean DEBUG = true;
+
+ private static final int READ_WAIT_MILLIS = 200;
+ private static final int BUFSIZ = 4096;
+
+ private final UsbSerialDriver mDriver;
+
+ private final ByteBuffer mReadBuffer = ByteBuffer.allocate(BUFSIZ);
+
+ // Synchronized by 'mWriteBuffer'
+ private final ByteBuffer mWriteBuffer = ByteBuffer.allocate(BUFSIZ);
+
+ private enum State {
+ STOPPED,
+ RUNNING,
+ STOPPING
+ }
+
+ // Synchronized by 'this'
+ private State mState = State.STOPPED;
+
+ // Synchronized by 'this'
+ private Listener mListener;
+
+ public interface Listener {
+ /**
+ * Called when new incoming data is available.
+ */
+ public void onNewData(byte[] data);
+
+ /**
+ * Called when {@link com.ktind.cgm.bgscout.USB.SerialInputOutputManager#run()} aborts due to an
+ * error.
+ */
+ public void onRunError(Exception e);
+ }
+
+ /**
+ * Creates a new instance with no listener.
+ */
+ public SerialInputOutputManager(UsbSerialDriver driver) {
+ this(driver, null);
+ }
+
+ /**
+ * Creates a new instance with the provided listener.
+ */
+ public SerialInputOutputManager(UsbSerialDriver driver, Listener listener) {
+ mDriver = driver;
+ mListener = listener;
+ }
+
+ public synchronized void setListener(Listener listener) {
+ mListener = listener;
+ }
+
+ public synchronized Listener getListener() {
+ return mListener;
+ }
+
+ public void writeAsync(byte[] data) {
+ synchronized (mWriteBuffer) {
+ mWriteBuffer.put(data);
+ }
+ }
+
+ public synchronized void stop() {
+ if (getState() == State.RUNNING) {
+ Log.i(TAG, "Stop requested");
+ mState = State.STOPPING;
+ }
+ }
+
+ private synchronized State getState() {
+ return mState;
+ }
+
+ /**
+ * Continuously services the read and write buffers until {@link #stop()} is
+ * called, or until a driver exception is raised.
+ *
+ * NOTE(mikey): Uses inefficient read/write-with-timeout.
+ * TODO(mikey): Read asynchronously with {@link android.hardware.usb.UsbRequest#queue(java.nio.ByteBuffer, int)}
+ */
+ @Override
+ public void run() {
+ synchronized (this) {
+ if (getState() != State.STOPPED) {
+ throw new IllegalStateException("Already running.");
+ }
+ mState = State.RUNNING;
+ }
+
+ Log.i(TAG, "Running ..");
+ try {
+ while (true) {
+ if (getState() != State.RUNNING) {
+ Log.i(TAG, "Stopping mState=" + getState());
+ break;
+ }
+ step();
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Run ending due to exception: " + e.getMessage(), e);
+ final Listener listener = getListener();
+ if (listener != null) {
+ listener.onRunError(e);
+ }
+ } finally {
+ synchronized (this) {
+ mState = State.STOPPED;
+ Log.i(TAG, "Stopped.");
+ }
+ }
+ }
+
+ private void step() throws IOException {
+ // Handle incoming data.
+ int len = mDriver.read(mReadBuffer.array(), READ_WAIT_MILLIS);
+ if (len > 0) {
+ if (DEBUG) Log.d(TAG, "Read data len=" + len);
+ final Listener listener = getListener();
+ if (listener != null) {
+ final byte[] data = new byte[len];
+ mReadBuffer.get(data, 0, len);
+ listener.onNewData(data);
+ }
+ mReadBuffer.clear();
+ }
+
+ // Handle outgoing data.
+ byte[] outBuff = null;
+ synchronized (mWriteBuffer) {
+ if (mWriteBuffer.position() > 0) {
+ len = mWriteBuffer.position();
+ outBuff = new byte[len];
+ mWriteBuffer.rewind();
+ mWriteBuffer.get(outBuff, 0, len);
+ mWriteBuffer.clear();
+ }
+ }
+ if (outBuff != null) {
+ if (DEBUG) {
+ Log.d(TAG, "Writing data len=" + len);
+ }
+ mDriver.write(outBuff, READ_WAIT_MILLIS);
+ }
+ }
+
+}
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/USB/USBPower.java b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/USBPower.java
new file mode 100644
index 0000000..18a246c
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/USBPower.java
@@ -0,0 +1,44 @@
+package com.ktind.cgm.bgscout.USB;
+
+import android.util.Log;
+
+import java.io.DataOutputStream;
+
+public class USBPower {
+
+ private static final String TAG = USBPower.class.getSimpleName();
+
+ private static final String SET_POWER_ON_COMMAND = "echo 'on' > \"/sys/bus/usb/devices/1-1/power/level\"";
+ private static final String SET_POWER_SUSPEND_COMMAND_A = "echo \"0\" > \"/sys/bus/usb/devices/1-1/power/autosuspend\"";
+ private static final String SET_POWER_SUSPEND_COMMAND_B = "echo \"auto\" > \"/sys/bus/usb/devices/1-1/power/level\"";
+
+ public static void PowerOff() {
+ try {
+ runCommand(SET_POWER_SUSPEND_COMMAND_A);
+ runCommand(SET_POWER_SUSPEND_COMMAND_B);
+ Log.i(TAG, "PowerOff USB complete");
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to PowerOff USB");
+ }
+ }
+
+ public static void PowerOn(){
+ try {
+ runCommand(SET_POWER_ON_COMMAND);
+ Log.i(TAG, "PowerOn USB complete");
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to PowerOn USB");
+ }
+ }
+
+ private static void runCommand(String command) throws Exception {
+ Process process = Runtime.getRuntime().exec("su");
+ DataOutputStream os = new DataOutputStream(process.getOutputStream());
+ os.writeBytes(command + "\n");
+ os.flush();
+ os.writeBytes("exit \n");
+ os.flush();
+ os.close();
+ process.waitFor();
+ }
+}
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/USB/USBPowerState.java b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/USBPowerState.java
new file mode 100644
index 0000000..966ae7d
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/USBPowerState.java
@@ -0,0 +1,9 @@
+package com.ktind.cgm.bgscout.USB;
+
+/**
+ * Created by klee24 on 7/27/14.
+ */
+public enum USBPowerState {
+ ON,
+ OFF
+}
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/USB/UsbSerialDriver.java b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/UsbSerialDriver.java
new file mode 100644
index 0000000..e25c030
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/UsbSerialDriver.java
@@ -0,0 +1,200 @@
+/* Copyright 2011 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ *
+ * Project home page: http://code.google.com/p/usb-serial-for-android/
+ */
+
+package com.ktind.cgm.bgscout.USB;
+
+import java.io.IOException;
+
+/**
+ * Driver interface for a USB serial device.
+ *
+ * @author mike wakerly (opensource@hoho.com)
+ */
+public interface UsbSerialDriver {
+
+ /** 5 data bits. */
+ public static final int DATABITS_5 = 5;
+
+ /** 6 data bits. */
+ public static final int DATABITS_6 = 6;
+
+ /** 7 data bits. */
+ public static final int DATABITS_7 = 7;
+
+ /** 8 data bits. */
+ public static final int DATABITS_8 = 8;
+
+ /** No flow control. */
+ public static final int FLOWCONTROL_NONE = 0;
+
+ /** RTS/CTS input flow control. */
+ public static final int FLOWCONTROL_RTSCTS_IN = 1;
+
+ /** RTS/CTS output flow control. */
+ public static final int FLOWCONTROL_RTSCTS_OUT = 2;
+
+ /** XON/XOFF input flow control. */
+ public static final int FLOWCONTROL_XONXOFF_IN = 4;
+
+ /** XON/XOFF output flow control. */
+ public static final int FLOWCONTROL_XONXOFF_OUT = 8;
+
+ /** No parity. */
+ public static final int PARITY_NONE = 0;
+
+ /** Odd parity. */
+ public static final int PARITY_ODD = 1;
+
+ /** Even parity. */
+ public static final int PARITY_EVEN = 2;
+
+ /** Mark parity. */
+ public static final int PARITY_MARK = 3;
+
+ /** Space parity. */
+ public static final int PARITY_SPACE = 4;
+
+ /** 1 stop bit. */
+ public static final int STOPBITS_1 = 1;
+
+ /** 1.5 stop bits. */
+ public static final int STOPBITS_1_5 = 3;
+
+ /** 2 stop bits. */
+ public static final int STOPBITS_2 = 2;
+
+ /**
+ * Opens and initializes the device as a USB serial device. Upon success,
+ * caller must ensure that {@link #close()} is eventually called.
+ *
+ * @throws java.io.IOException on error opening or initializing the device.
+ */
+ public void open() throws IOException;
+
+ /**
+ * Closes the serial device.
+ *
+ * @throws java.io.IOException on error closing the device.
+ */
+ public void close() throws IOException;
+
+ /**
+ * Reads as many bytes as possible into the destination buffer.
+ *
+ * @param dest the destination byte buffer
+ * @param timeoutMillis the timeout for reading
+ * @return the actual number of bytes read
+ * @throws java.io.IOException if an error occurred during reading
+ */
+ public int read(final byte[] dest, final int timeoutMillis) throws IOException;
+
+ /**
+ * Writes as many bytes as possible from the source buffer.
+ *
+ * @param src the source byte buffer
+ * @param timeoutMillis the timeout for writing
+ * @return the actual number of bytes written
+ * @throws java.io.IOException if an error occurred during writing
+ */
+ public int write(final byte[] src, final int timeoutMillis) throws IOException;
+
+ /**
+ * Sets various serial port parameters.
+ *
+ * @param baudRate baud rate as an integer, for example {@code 115200}.
+ * @param dataBits one of {@link #DATABITS_5}, {@link #DATABITS_6},
+ * {@link #DATABITS_7}, or {@link #DATABITS_8}.
+ * @param stopBits one of {@link #STOPBITS_1}, {@link #STOPBITS_1_5}, or
+ * {@link #STOPBITS_2}.
+ * @param parity one of {@link #PARITY_NONE}, {@link #PARITY_ODD},
+ * {@link #PARITY_EVEN}, {@link #PARITY_MARK}, or
+ * {@link #PARITY_SPACE}.
+ * @throws java.io.IOException on error setting the port parameters
+ */
+ public void setParameters(
+ int baudRate, int dataBits, int stopBits, int parity) throws IOException;
+
+ /**
+ * Gets the CD (Carrier Detect) bit from the underlying UART.
+ *
+ * @return the current state, or {@code false} if not supported.
+ * @throws java.io.IOException if an error occurred during reading
+ */
+ public boolean getCD() throws IOException;
+
+ /**
+ * Gets the CTS (Clear To Send) bit from the underlying UART.
+ *
+ * @return the current state, or {@code false} if not supported.
+ * @throws java.io.IOException if an error occurred during reading
+ */
+ public boolean getCTS() throws IOException;
+
+ /**
+ * Gets the DSR (Data Set Ready) bit from the underlying UART.
+ *
+ * @return the current state, or {@code false} if not supported.
+ * @throws java.io.IOException if an error occurred during reading
+ */
+ public boolean getDSR() throws IOException;
+
+ /**
+ * Gets the DTR (Data Terminal Ready) bit from the underlying UART.
+ *
+ * @return the current state, or {@code false} if not supported.
+ * @throws java.io.IOException if an error occurred during reading
+ */
+ public boolean getDTR() throws IOException;
+
+ /**
+ * Sets the DTR (Data Terminal Ready) bit on the underlying UART, if
+ * supported.
+ *
+ * @param value the value to set
+ * @throws java.io.IOException if an error occurred during writing
+ */
+ public void setDTR(boolean value) throws IOException;
+
+ /**
+ * Gets the RI (Ring Indicator) bit from the underlying UART.
+ *
+ * @return the current state, or {@code false} if not supported.
+ * @throws java.io.IOException if an error occurred during reading
+ */
+ public boolean getRI() throws IOException;
+
+ /**
+ * Gets the RTS (Request To Send) bit from the underlying UART.
+ *
+ * @return the current state, or {@code false} if not supported.
+ * @throws java.io.IOException if an error occurred during reading
+ */
+ public boolean getRTS() throws IOException;
+
+ /**
+ * Sets the RTS (Request To Send) bit on the underlying UART, if
+ * supported.
+ *
+ * @param value the value to set
+ * @throws java.io.IOException if an error occurred during writing
+ */
+ public void setRTS(boolean value) throws IOException;
+
+}
diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/USB/UsbSerialProber.java b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/UsbSerialProber.java
new file mode 100644
index 0000000..9e857f5
--- /dev/null
+++ b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/UsbSerialProber.java
@@ -0,0 +1,164 @@
+/* Copyright 2011 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ *
+ * Project home page: http://code.google.com/p/usb-serial-for-android/
+ */
+
+package com.ktind.cgm.bgscout.USB;
+
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbDeviceConnection;
+import android.hardware.usb.UsbManager;
+
+import java.util.Map;
+
+//import com.hoho.android.usbserial.driver.Cp2102SerialDriver;
+//import com.hoho.android.usbserial.driver.FtdiSerialDriver;
+
+/**
+ * Helper class to assist in detecting and building {@link UsbSerialDriver}
+ * instances from available hardware.
+ *
+ * @author mike wakerly (opensource@hoho.com)
+ */
+public enum UsbSerialProber {
+
+ // TODO(mikey): Too much boilerplate.
+
+ /**
+ * Prober for {@link FtdiSerialDriver}.
+ *
+ * @see FtdiSerialDriver
+ */
+/* FTDI_SERIAL {
+ @Override
+ public UsbSerialDriver getDevice(final UsbManager manager, final UsbDevice usbDevice) {
+ if (!testIfSupported(usbDevice, FtdiSerialDriver.getSupportedDevices())) {
+ return null;
+ }
+ final UsbDeviceConnection connection = manager.openDevice(usbDevice);
+ if (connection == null) {
+ return null;
+ }
+ return new FtdiSerialDriver(usbDevice, connection);
+ }
+ },*/
+
+ CDC_ACM_SERIAL {
+ @Override
+ public UsbSerialDriver getDevice(UsbManager manager, UsbDevice usbDevice) {
+// if (!testIfSupported(usbDevice, CdcAcmSerialDriver.getSupportedDevices())) {
+// return null;
+// }
+ final UsbDeviceConnection connection = manager.openDevice(usbDevice);
+ if (connection == null) {
+ return null;
+ }
+ return new CdcAcmSerialDriver(usbDevice, connection);
+ }
+ }; //,
+
+/* SILAB_SERIAL {
+ @Override
+ public UsbSerialDriver getDevice(final UsbManager manager, final UsbDevice usbDevice) {
+ if (!testIfSupported(usbDevice, Cp2102SerialDriver.getSupportedDevices())) {
+ return null;
+ }
+ final UsbDeviceConnection connection = manager.openDevice(usbDevice);
+ if (connection == null) {
+ return null;
+ }
+ return new Cp2102SerialDriver(usbDevice, connection);
+ }
+ };*/
+
+ /**
+ * Builds a new {@link UsbSerialDriver} instance from the raw device, or
+ * returns null if it could not be built (for example, if the
+ * probe failed).
+ *
+ * @param manager the {@link android.hardware.usb.UsbManager} to use
+ * @param usbDevice the raw {@link android.hardware.usb.UsbDevice} to use
+ * @return the first available {@link UsbSerialDriver}, or {@code null} if
+ * no devices could be acquired
+ */
+ public abstract UsbSerialDriver getDevice(final UsbManager manager, final UsbDevice usbDevice);
+
+ /**
+ * Acquires and returns the first available serial device among all
+ * available {@link android.hardware.usb.UsbDevice}s, or returns {@code null} if no device could
+ * be acquired.
+ *
+ * @param usbManager the {@link android.hardware.usb.UsbManager} to use
+ * @return the first available {@link UsbSerialDriver}, or {@code null} if
+ * no devices could be acquired
+ */
+ public static UsbSerialDriver acquire(final UsbManager usbManager) {
+ for (final UsbDevice usbDevice : usbManager.getDeviceList().values()) {
+ final UsbSerialDriver probedDevice = acquire(usbManager, usbDevice);
+ if (probedDevice != null) {
+ return probedDevice;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Builds and returns a new {@link UsbSerialDriver} from the given
+ * {@link android.hardware.usb.UsbDevice}, or returns {@code null} if no drivers supported this
+ * device.
+ *
+ * @param usbManager the {@link android.hardware.usb.UsbManager} to use
+ * @param usbDevice the {@link android.hardware.usb.UsbDevice} to use
+ * @return a new {@link UsbSerialDriver}, or {@code null} if no devices
+ * could be acquired
+ */
+ public static UsbSerialDriver acquire(final UsbManager usbManager, final UsbDevice usbDevice) {
+ for (final UsbSerialProber prober : values()) {
+ final UsbSerialDriver probedDevice = prober.getDevice(usbManager, usbDevice);
+ if (probedDevice != null) {
+ return probedDevice;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns {@code true} if the given device is found in the vendor/product map.
+ *
+ * @param usbDevice the device to test
+ * @param supportedDevices map of vendor ids to product id(s)
+ * @return {@code true} if supported
+ */
+ private static boolean testIfSupported(final UsbDevice usbDevice,
+ final Map supportedDevices) {
+ final int[] supportedProducts = supportedDevices.get(
+ Integer.valueOf(usbDevice.getVendorId()));
+ if (supportedProducts == null) {
+ return false;
+ }
+
+ final int productId = usbDevice.getProductId();
+ for (int supportedProductId : supportedProducts) {
+ if (productId == supportedProductId) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/mobile/src/main/res/drawable-hdpi/arrow45downblue.png b/mobile/src/main/res/drawable-hdpi/arrow45downblue.png
new file mode 100644
index 0000000..cf01061
Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrow45downblue.png differ
diff --git a/mobile/src/main/res/drawable-hdpi/arrow45downred.png b/mobile/src/main/res/drawable-hdpi/arrow45downred.png
new file mode 100644
index 0000000..6fd665e
Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrow45downred.png differ
diff --git a/mobile/src/main/res/drawable-hdpi/arrow45downyellow.png b/mobile/src/main/res/drawable-hdpi/arrow45downyellow.png
new file mode 100644
index 0000000..0d5dc49
Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrow45downyellow.png differ
diff --git a/mobile/src/main/res/drawable-hdpi/arrow45upblue.png b/mobile/src/main/res/drawable-hdpi/arrow45upblue.png
new file mode 100644
index 0000000..7a8a2bc
Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrow45upblue.png differ
diff --git a/mobile/src/main/res/drawable-hdpi/arrow45upred.png b/mobile/src/main/res/drawable-hdpi/arrow45upred.png
new file mode 100644
index 0000000..53858c0
Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrow45upred.png differ
diff --git a/mobile/src/main/res/drawable-hdpi/arrow45upyellow.png b/mobile/src/main/res/drawable-hdpi/arrow45upyellow.png
new file mode 100644
index 0000000..d3835b5
Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrow45upyellow.png differ
diff --git a/mobile/src/main/res/drawable-hdpi/arrowdoubledownblue.png b/mobile/src/main/res/drawable-hdpi/arrowdoubledownblue.png
new file mode 100644
index 0000000..82ae153
Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowdoubledownblue.png differ
diff --git a/mobile/src/main/res/drawable-hdpi/arrowdoubledownred.png b/mobile/src/main/res/drawable-hdpi/arrowdoubledownred.png
new file mode 100644
index 0000000..067123c
Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowdoubledownred.png differ
diff --git a/mobile/src/main/res/drawable-hdpi/arrowdoubledownyellow.png b/mobile/src/main/res/drawable-hdpi/arrowdoubledownyellow.png
new file mode 100644
index 0000000..45be6fc
Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowdoubledownyellow.png differ
diff --git a/mobile/src/main/res/drawable-hdpi/arrowdoubleupblue.png b/mobile/src/main/res/drawable-hdpi/arrowdoubleupblue.png
new file mode 100644
index 0000000..0056e4d
Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowdoubleupblue.png differ
diff --git a/mobile/src/main/res/drawable-hdpi/arrowdoubleupred.png b/mobile/src/main/res/drawable-hdpi/arrowdoubleupred.png
new file mode 100644
index 0000000..6900bb0
Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowdoubleupred.png differ
diff --git a/mobile/src/main/res/drawable-hdpi/arrowdoubleupyellow.png b/mobile/src/main/res/drawable-hdpi/arrowdoubleupyellow.png
new file mode 100644
index 0000000..d53674d
Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowdoubleupyellow.png differ
diff --git a/mobile/src/main/res/drawable-hdpi/arrowdownblue.png b/mobile/src/main/res/drawable-hdpi/arrowdownblue.png
new file mode 100644
index 0000000..d4cf027
Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowdownblue.png differ
diff --git a/mobile/src/main/res/drawable-hdpi/arrowdownred.png b/mobile/src/main/res/drawable-hdpi/arrowdownred.png
new file mode 100644
index 0000000..c6c8332
Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowdownred.png differ
diff --git a/mobile/src/main/res/drawable-hdpi/arrowdownyellow.png b/mobile/src/main/res/drawable-hdpi/arrowdownyellow.png
new file mode 100644
index 0000000..9c67bb8
Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowdownyellow.png differ
diff --git a/mobile/src/main/res/drawable-hdpi/arrowflatblue.png b/mobile/src/main/res/drawable-hdpi/arrowflatblue.png
new file mode 100644
index 0000000..7fe4d5e
Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowflatblue.png differ
diff --git a/mobile/src/main/res/drawable-hdpi/arrowflatred.png b/mobile/src/main/res/drawable-hdpi/arrowflatred.png
new file mode 100644
index 0000000..76dd031
Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowflatred.png differ
diff --git a/mobile/src/main/res/drawable-hdpi/arrowflatyellow.png b/mobile/src/main/res/drawable-hdpi/arrowflatyellow.png
new file mode 100644
index 0000000..18808ab
Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowflatyellow.png differ
diff --git a/mobile/src/main/res/drawable-hdpi/arrowupblue.png b/mobile/src/main/res/drawable-hdpi/arrowupblue.png
new file mode 100644
index 0000000..8b1360f
Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowupblue.png differ
diff --git a/mobile/src/main/res/drawable-hdpi/arrowupred.png b/mobile/src/main/res/drawable-hdpi/arrowupred.png
new file mode 100644
index 0000000..5aa24b0
Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowupred.png differ
diff --git a/mobile/src/main/res/drawable-hdpi/arrowupyellow.png b/mobile/src/main/res/drawable-hdpi/arrowupyellow.png
new file mode 100644
index 0000000..e33b40b
Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowupyellow.png differ
diff --git a/mobile/src/main/res/drawable-hdpi/ic_launcher.png b/mobile/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/ic_launcher.png differ
diff --git a/mobile/src/main/res/drawable-hdpi/icon.png b/mobile/src/main/res/drawable-hdpi/icon.png
new file mode 100644
index 0000000..6ca9c46
Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/icon.png differ
diff --git a/mobile/src/main/res/drawable-hdpi/icon24x24.png b/mobile/src/main/res/drawable-hdpi/icon24x24.png
new file mode 100644
index 0000000..dd8d454
Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/icon24x24.png differ
diff --git a/mobile/src/main/res/drawable-hdpi/noneblue.png b/mobile/src/main/res/drawable-hdpi/noneblue.png
new file mode 100644
index 0000000..f053146
Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/noneblue.png differ
diff --git a/mobile/src/main/res/drawable-hdpi/nonered.png b/mobile/src/main/res/drawable-hdpi/nonered.png
new file mode 100644
index 0000000..e36cb64
Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/nonered.png differ
diff --git a/mobile/src/main/res/drawable-hdpi/noneyellow.png b/mobile/src/main/res/drawable-hdpi/noneyellow.png
new file mode 100644
index 0000000..b899710
Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/noneyellow.png differ
diff --git a/mobile/src/main/res/drawable-hdpi/questionmarkicon.png b/mobile/src/main/res/drawable-hdpi/questionmarkicon.png
new file mode 100644
index 0000000..c7687b3
Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/questionmarkicon.png differ
diff --git a/mobile/src/main/res/drawable-mdpi/ic_launcher.png b/mobile/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
Binary files /dev/null and b/mobile/src/main/res/drawable-mdpi/ic_launcher.png differ
diff --git a/mobile/src/main/res/drawable-xhdpi/ic_launcher.png b/mobile/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
Binary files /dev/null and b/mobile/src/main/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/mobile/src/main/res/drawable-xxhdpi/ic_launcher.png b/mobile/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4df1894
Binary files /dev/null and b/mobile/src/main/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/mobile/src/main/res/layout/activity_main.xml b/mobile/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..790dfb7
--- /dev/null
+++ b/mobile/src/main/res/layout/activity_main.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
diff --git a/mobile/src/main/res/menu/main.xml b/mobile/src/main/res/menu/main.xml
new file mode 100644
index 0000000..29c73a1
--- /dev/null
+++ b/mobile/src/main/res/menu/main.xml
@@ -0,0 +1,8 @@
+
diff --git a/mobile/src/main/res/values-w820dp/dimens.xml b/mobile/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/mobile/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/mobile/src/main/res/values/dimens.xml b/mobile/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/mobile/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml
new file mode 100644
index 0000000..bfcefb8
--- /dev/null
+++ b/mobile/src/main/res/values/strings.xml
@@ -0,0 +1,11 @@
+
+
+
+ BG Scout
+ MainActivity
+ BG Scout!
+ Settings
+ Start service
+ CGM Monitoring service
+
+
diff --git a/mobile/src/main/res/values/strings_activity_settings.xml b/mobile/src/main/res/values/strings_activity_settings.xml
new file mode 100644
index 0000000..a5600f4
--- /dev/null
+++ b/mobile/src/main/res/values/strings_activity_settings.xml
@@ -0,0 +1,59 @@
+
+ Settings
+
+
+
+
+ General
+
+ Enable social recommendations
+ Recommendations for people to contact based on your message history
+
+ Display name
+ John Smith
+
+ Add friends to messages
+
+ Always
+ When possible
+ Never
+
+
+ 1
+ 0
+ -1
+
+
+
+ Data & sync
+
+ Sync frequency
+
+ 15 minutes
+ 30 minutes
+ 1 hour
+ 3 hours
+ 6 hours
+ Never
+
+
+ 15
+ 30
+ 60
+ 180
+ 360
+ -1
+
+
+ System sync settings
+
+
+ Notifications
+
+ New message notifications
+
+ Ringtone
+ Silent
+
+ Vibrate
+
diff --git a/mobile/src/main/res/values/styles.xml b/mobile/src/main/res/values/styles.xml
new file mode 100644
index 0000000..ff6c9d2
--- /dev/null
+++ b/mobile/src/main/res/values/styles.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/mobile/src/main/res/xml/device_filter.xml b/mobile/src/main/res/xml/device_filter.xml
new file mode 100644
index 0000000..ea07578
--- /dev/null
+++ b/mobile/src/main/res/xml/device_filter.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/src/main/res/xml/pref_data_sync.xml b/mobile/src/main/res/xml/pref_data_sync.xml
new file mode 100644
index 0000000..ffda831
--- /dev/null
+++ b/mobile/src/main/res/xml/pref_data_sync.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/src/main/res/xml/pref_general.xml b/mobile/src/main/res/xml/pref_general.xml
new file mode 100644
index 0000000..c49cbed
--- /dev/null
+++ b/mobile/src/main/res/xml/pref_general.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/src/main/res/xml/pref_headers.xml b/mobile/src/main/res/xml/pref_headers.xml
new file mode 100644
index 0000000..1888c3b
--- /dev/null
+++ b/mobile/src/main/res/xml/pref_headers.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/src/main/res/xml/pref_notification.xml b/mobile/src/main/res/xml/pref_notification.xml
new file mode 100644
index 0000000..b4b8cae
--- /dev/null
+++ b/mobile/src/main/res/xml/pref_notification.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..6a925fc
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':mobile', ':wear', ':glass'
diff --git a/wear/.gitignore b/wear/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/wear/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/wear/build.gradle b/wear/build.gradle
new file mode 100644
index 0000000..3c00c5c
--- /dev/null
+++ b/wear/build.gradle
@@ -0,0 +1,27 @@
+apply plugin: 'com.android.application'
+
+
+android {
+ compileSdkVersion 20
+ buildToolsVersion "20.0.0"
+
+ defaultConfig {
+ applicationId "com.ktind.cgm.bgscout"
+ minSdkVersion 20
+ targetSdkVersion 20
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ runProguard false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile 'com.google.android.support:wearable:+'
+ compile 'com.google.android.gms:play-services-wearable:+'
+}
diff --git a/wear/proguard-rules.pro b/wear/proguard-rules.pro
new file mode 100644
index 0000000..8b3ee7d
--- /dev/null
+++ b/wear/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/klee24/Android/android-sdk-mac_x86/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/wear/src/main/AndroidManifest.xml b/wear/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..69d5ae3
--- /dev/null
+++ b/wear/src/main/AndroidManifest.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
diff --git a/wear/src/main/res/drawable-hdpi/ic_launcher.png b/wear/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..55621cc
Binary files /dev/null and b/wear/src/main/res/drawable-hdpi/ic_launcher.png differ
diff --git a/wear/src/main/res/drawable-mdpi/ic_launcher.png b/wear/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..11ec206
Binary files /dev/null and b/wear/src/main/res/drawable-mdpi/ic_launcher.png differ
diff --git a/wear/src/main/res/drawable-xhdpi/ic_launcher.png b/wear/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..7c02b78
Binary files /dev/null and b/wear/src/main/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/wear/src/main/res/drawable-xxhdpi/ic_launcher.png b/wear/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..915d914
Binary files /dev/null and b/wear/src/main/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/wear/src/main/res/values/strings.xml b/wear/src/main/res/values/strings.xml
new file mode 100644
index 0000000..1f3c202
--- /dev/null
+++ b/wear/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ BG Scout
+
diff --git a/wear/wear.iml b/wear/wear.iml
new file mode 100644
index 0000000..2910fcf
--- /dev/null
+++ b/wear/wear.iml
@@ -0,0 +1,71 @@
+
+
+
+
+
+