├── core ├── .gitignore ├── src │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── nightscout │ │ │ └── core │ │ │ ├── dexcom │ │ │ ├── BatteryState.java │ │ │ ├── CRCFailError.java │ │ │ ├── InsertionState.java │ │ │ ├── InvalidRecordLengthException.java │ │ │ ├── RecordType.java │ │ │ ├── Constants.java │ │ │ ├── CRC16.java │ │ │ ├── TrendArrow.java │ │ │ ├── ReadPacket.java │ │ │ ├── SpecialValue.java │ │ │ ├── Command.java │ │ │ ├── PacketBuilder.java │ │ │ ├── records │ │ │ │ ├── GlucoseDataSet.java │ │ │ │ ├── GenericTimestampRecord.java │ │ │ │ ├── CalSubrecord.java │ │ │ │ ├── SensorRecord.java │ │ │ │ ├── PageHeader.java │ │ │ │ ├── MeterRecord.java │ │ │ │ └── EGVRecord.java │ │ │ └── Utils.java │ │ │ ├── drivers │ │ │ ├── AbstractUploaderDevice.java │ │ │ ├── AbstractDevice.java │ │ │ └── DeviceTransport.java │ │ │ ├── barcode │ │ │ ├── NSBarcodeConfigKeys.java │ │ │ └── NSBarcodeConfig.java │ │ │ ├── records │ │ │ └── DeviceStatus.java │ │ │ ├── model │ │ │ ├── GlucoseUnit.java │ │ │ ├── G4Noise.java │ │ │ ├── ReceiverStatus.java │ │ │ ├── DownloadStatus.java │ │ │ ├── G4Trend.java │ │ │ ├── DownloadResults.java │ │ │ ├── ReceiverState.java │ │ │ ├── Download.proto │ │ │ ├── MeterEntry.java │ │ │ ├── SensorEntry.java │ │ │ ├── CalibrationEntry.java │ │ │ └── SensorGlucoseValueEntry.java │ │ │ ├── preferences │ │ │ ├── NightscoutPreferences.java │ │ │ └── TestPreferences.java │ │ │ ├── utils │ │ │ ├── GlucoseReading.java │ │ │ └── RestUriUtils.java │ │ │ └── upload │ │ │ ├── AbstractRestUploader.java │ │ │ ├── RestLegacyUploader.java │ │ │ ├── BaseUploader.java │ │ │ └── RestV1Uploader.java │ └── test │ │ └── java │ │ └── com │ │ └── nightscout │ │ └── core │ │ ├── dexcom │ │ ├── CRC16Test.java │ │ ├── SpecialValueTest.java │ │ ├── ReadPacketTest.java │ │ ├── SensorRecordTest.java │ │ ├── UtilsTest.java │ │ ├── EgvRecordTest.java │ │ ├── MeterRecordTest.java │ │ └── PacketBuilderTest.java │ │ ├── utils │ │ └── RestUriUtilsTest.java │ │ └── upload │ │ └── RestLegacyUploaderTest.java └── build.gradle ├── settings.gradle ├── app ├── libs │ └── acra-4.5.0.jar ├── src │ ├── main │ │ ├── res │ │ │ ├── drawable-hdpi │ │ │ │ ├── battery15.png │ │ │ │ ├── battery25.png │ │ │ │ ├── battery50.png │ │ │ │ ├── battery75.png │ │ │ │ ├── batteryempty.png │ │ │ │ ├── batteryfull.png │ │ │ │ ├── ic_clock_bad.png │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_clock_good.png │ │ │ │ ├── ic_upload_fail.png │ │ │ │ ├── ic_upload_success.png │ │ │ │ ├── ic_usb_connected.png │ │ │ │ ├── ic_usb_disconnected.png │ │ │ │ └── battery.xml │ │ │ ├── drawable-mdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── drawable-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── values │ │ │ │ ├── integers.xml │ │ │ │ ├── dimens.xml │ │ │ │ └── strings.xml │ │ │ ├── xml │ │ │ │ ├── analytics_global_config.xml │ │ │ │ ├── app_tracker.xml │ │ │ │ └── device_filter.xml │ │ │ ├── values-w820dp │ │ │ │ └── dimens.xml │ │ │ ├── menu │ │ │ │ └── menu.xml │ │ │ └── values-it │ │ │ │ └── strings.xml │ │ ├── resources │ │ │ └── android-logger.properties │ │ ├── java │ │ │ └── com │ │ │ │ └── nightscout │ │ │ │ └── android │ │ │ │ ├── settings │ │ │ │ ├── SimpleDialogPreference.java │ │ │ │ ├── SummaryBoundEditTextPreference.java │ │ │ │ └── CustomSwitchPreference.java │ │ │ │ ├── barcode │ │ │ │ └── AndroidBarcode.java │ │ │ │ ├── OnUpgradeReceiver.java │ │ │ │ ├── ToastReceiver.java │ │ │ │ ├── preferences │ │ │ │ ├── PreferenceKeys.java │ │ │ │ └── PreferencesValidator.java │ │ │ │ ├── drivers │ │ │ │ ├── AndroidUploaderDevice.java │ │ │ │ └── USB │ │ │ │ │ ├── USBPower.java │ │ │ │ │ └── UsbSerialProber.java │ │ │ │ └── Nightscout.java │ │ ├── assets │ │ │ ├── css │ │ │ │ └── main.css │ │ │ └── index.html │ │ └── AndroidManifest.xml │ └── test │ │ └── java │ │ └── com │ │ └── nightscout │ │ ├── robolectric │ │ └── RobolectricGradleRunner.java │ │ └── android │ │ ├── wearables │ │ └── WearableTest.java │ │ ├── OnUpgradeReceiverTest.java │ │ ├── test │ │ └── RobolectricTestBase.java │ │ ├── preferences │ │ └── PreferencesValidatorTest.java │ │ ├── upload │ │ └── UploaderTest.java │ │ └── settings │ │ └── SettingsActivityTest.java └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── COPYRIGHT ├── .gitignore ├── ci └── wait_for_emulator ├── .travis.yml ├── README.md ├── gradlew.bat └── gradlew /core/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':core' 2 | -------------------------------------------------------------------------------- /app/libs/acra-4.5.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nightscout/android-uploader/HEAD/app/libs/acra-4.5.0.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nightscout/android-uploader/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/battery15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nightscout/android-uploader/HEAD/app/src/main/res/drawable-hdpi/battery15.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/battery25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nightscout/android-uploader/HEAD/app/src/main/res/drawable-hdpi/battery25.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/battery50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nightscout/android-uploader/HEAD/app/src/main/res/drawable-hdpi/battery50.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/battery75.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nightscout/android-uploader/HEAD/app/src/main/res/drawable-hdpi/battery75.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/batteryempty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nightscout/android-uploader/HEAD/app/src/main/res/drawable-hdpi/batteryempty.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/batteryfull.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nightscout/android-uploader/HEAD/app/src/main/res/drawable-hdpi/batteryfull.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_clock_bad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nightscout/android-uploader/HEAD/app/src/main/res/drawable-hdpi/ic_clock_bad.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nightscout/android-uploader/HEAD/app/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nightscout/android-uploader/HEAD/app/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nightscout/android-uploader/HEAD/app/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_clock_good.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nightscout/android-uploader/HEAD/app/src/main/res/drawable-hdpi/ic_clock_good.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_upload_fail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nightscout/android-uploader/HEAD/app/src/main/res/drawable-hdpi/ic_upload_fail.png -------------------------------------------------------------------------------- /app/src/main/resources/android-logger.properties: -------------------------------------------------------------------------------- 1 | root=ERROR:com.nightscout.android:%logger{-2}: 2 | logger.com.nightscout=WARN:com.nightscout.android:%logger{-2}: -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_upload_success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nightscout/android-uploader/HEAD/app/src/main/res/drawable-hdpi/ic_upload_success.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_usb_connected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nightscout/android-uploader/HEAD/app/src/main/res/drawable-hdpi/ic_usb_connected.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_usb_disconnected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nightscout/android-uploader/HEAD/app/src/main/res/drawable-hdpi/ic_usb_disconnected.png -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | 2 | We track contributions on a per-patch basis using git. 3 | Please see our published git log: 4 | * https://github.com/nightscout/android-uploader/commits/master 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/integers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 60000 4 | 30000 5 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/dexcom/BatteryState.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.dexcom; 2 | 3 | public enum BatteryState { 4 | NONE, 5 | CHARGING, 6 | NOT_CHARGING, 7 | NTC_FAULT, 8 | BAD_BATTERY 9 | } 10 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/dexcom/CRCFailError.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.dexcom; 2 | 3 | public class CRCFailError extends Error { 4 | public CRCFailError(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/xml/analytics_global_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | warning 4 | 7200 5 | false 6 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/drivers/AbstractUploaderDevice.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.drivers; 2 | 3 | /** 4 | * Represents the device used to perform the uploads 5 | */ 6 | abstract public class AbstractUploaderDevice { 7 | abstract public int getBatteryLevel(); 8 | } 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Nov 18 09:12:09 PST 2014 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip 7 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/dexcom/InsertionState.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.dexcom; 2 | 3 | public enum InsertionState { 4 | NONE, 5 | REMOVED, 6 | EXPIRED, 7 | RESIDUAL_DEVIATION, 8 | COUNTS_DEVIATION, 9 | SECOND_SESSION, 10 | OFF_TIME_LOSS, 11 | STARTED, 12 | BAD_TRANSMITTER, 13 | MANUFACTURING_MODE, 14 | MAX_VALUE 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/dexcom/InvalidRecordLengthException.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.dexcom; 2 | 3 | public class InvalidRecordLengthException extends Error { 4 | public InvalidRecordLengthException(String message){ 5 | super(message); 6 | } 7 | 8 | public InvalidRecordLengthException(String message,Throwable throwable){ 9 | super(message,throwable); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/nightscout/android/settings/SimpleDialogPreference.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.android.settings; 2 | 3 | import android.content.Context; 4 | import android.preference.DialogPreference; 5 | import android.util.AttributeSet; 6 | 7 | public class SimpleDialogPreference extends DialogPreference { 8 | 9 | public SimpleDialogPreference(Context context, AttributeSet attrs) { 10 | super(context, attrs); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/dexcom/RecordType.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.dexcom; 2 | 3 | public enum RecordType { 4 | MANUFACTURING_DATA, 5 | FIRMWARE_PARAMETER_DATA, 6 | PC_SOFTWARE_PARAMETER, 7 | SENSOR_DATA, 8 | EGV_DATA, 9 | CAL_SET, 10 | DEVIATION, 11 | INSERTION_TIME, 12 | RECEIVER_LOG_DATA, 13 | RECEIVER_ERROR_DATA, 14 | METER_DATA, 15 | USER_EVENT_DATA, 16 | USER_SETTING_DATA, 17 | MAX_VALUE 18 | } 19 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/barcode/NSBarcodeConfigKeys.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.barcode; 2 | 3 | 4 | public class NSBarcodeConfigKeys { 5 | public static final String MONGO_CONFIG="mongo"; 6 | public static final String MONGO_URI="uri"; 7 | public static final String API_CONFIG="rest"; 8 | public static final String API_URI="endpoint"; 9 | public static final String MONGO_COLLECTION="collection"; 10 | public static final String MONGO_DEVICE_STATUS_COLLECTION="status"; 11 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | gen 4 | # built application files 5 | *.apk 6 | *.ap_ 7 | 8 | # files for the dex VM 9 | *.dex 10 | 11 | # Java class files 12 | *.class 13 | 14 | # generated files 15 | bin/ 16 | gen/ 17 | out 18 | logs 19 | .DS_Store 20 | 21 | # Ignore gradle files 22 | .gradle/ 23 | build/ 24 | 25 | # Local configuration file (sdk path, etc) 26 | local.properties 27 | 28 | # Proguard folder generated by Eclipse 29 | proguard/ 30 | 31 | # Intellij project files 32 | *.iml 33 | *.ipr 34 | *.iws 35 | .idea/ 36 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/records/DeviceStatus.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.records; 2 | 3 | public class DeviceStatus { 4 | private int batteryLevel; 5 | 6 | public DeviceStatus() { 7 | batteryLevel = -1; 8 | } 9 | 10 | public int getBatteryLevel() { 11 | return batteryLevel; 12 | } 13 | 14 | public void setBatteryLevel(int batteryLevel) { 15 | this.batteryLevel = batteryLevel; 16 | } 17 | 18 | public static DeviceStatus getCurrentStatus() { 19 | return null; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/nightscout/android/barcode/AndroidBarcode.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.android.barcode; 2 | 3 | import android.app.Activity; 4 | 5 | import com.google.zxing.client.android.Intents; 6 | import com.google.zxing.integration.android.IntentIntegrator; 7 | 8 | public class AndroidBarcode { 9 | public static final String SCAN_INTENT = Intents.Scan.ACTION; 10 | Activity activity; 11 | 12 | public AndroidBarcode(Activity activity){ 13 | this.activity = activity; 14 | } 15 | 16 | public void scan() { 17 | new IntentIntegrator(activity).initiateScan(); 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/assets/css/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Open Sans', Helvetica, Arial, sans-serif; 3 | fill: #fff; 4 | background: #000; 5 | color: #808080; 6 | overflow: hidden; 7 | } 8 | 9 | .container { 10 | overflow: hidden; 11 | display: block; 12 | height: 95%; 13 | width: 100%; 14 | left: 0; 15 | margin: 0; 16 | padding: 0; 17 | top: 0px; 18 | z-index: 2; 19 | } 20 | 21 | .axis path, 22 | .axis line { 23 | fill: none; 24 | stroke: #ffffff; 25 | shape-rendering: crispEdges; 26 | } 27 | 28 | .grid path, .axis line { 29 | stroke: #808080; 30 | stroke-opacity: 1; 31 | } -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/dexcom/Constants.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.dexcom; 2 | 3 | public class Constants { 4 | public final static int MAX_COMMAND = 59; 5 | public final static int MAX_POSSIBLE_COMMAND = 255; 6 | public final static int EGV_VALUE_MASK = 1023; 7 | public final static int EGV_DISPLAY_ONLY_MASK = 32768; 8 | public final static int EGV_TREND_ARROW_MASK = 15; 9 | public final static int EGV_NOISE_MASK = 112; 10 | public final static float MG_DL_TO_MMOL_L = 0.05556f; 11 | public final static int CRC_LEN = 2; 12 | public static final float MMOL_L_TO_MG_DL = 18.0182f; 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/res/xml/app_tracker.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 300 5 | 6 | 7 | true 8 | 9 | false 10 | 11 | 12 | Main screen 13 | 14 | 15 | UA-54765135-1 16 | 17 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/model/GlucoseUnit.java: -------------------------------------------------------------------------------- 1 | // Code generated by Wire protocol buffer compiler, do not edit. 2 | // Source file: /Users/klee/Projects/Nightscout/android-uploader/core/src/main/java/com/nightscout/core/model/Download.proto 3 | package com.nightscout.core.model; 4 | 5 | import com.squareup.wire.ProtoEnum; 6 | 7 | public enum GlucoseUnit 8 | implements ProtoEnum { 9 | MGDL(0), 10 | MMOL(1); 11 | 12 | private final int value; 13 | 14 | private GlucoseUnit(int value) { 15 | this.value = value; 16 | } 17 | 18 | @Override 19 | public int getValue() { 20 | return value; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/nightscout/android/OnUpgradeReceiver.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.android; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | 7 | public class OnUpgradeReceiver extends BroadcastReceiver { 8 | @Override 9 | public void onReceive(final Context context, final Intent intent) { 10 | if (intent.getDataString().contains("com.nightscout.android")) { 11 | Intent mainActivity = new Intent(context, MainActivity.class); 12 | mainActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 13 | context.startActivity(mainActivity); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /core/src/test/java/com/nightscout/core/dexcom/CRC16Test.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.dexcom; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.hamcrest.Matchers.is; 6 | import static org.junit.Assert.assertThat; 7 | 8 | 9 | public class CRC16Test { 10 | 11 | @Test 12 | public void testCRC16() { 13 | // 01 07 00 10 04 8b b8 14 | byte[] testArray = new byte[]{(byte) 0x01, (byte) 0x07, (byte) 0x00, 15 | (byte) 0x10, (byte) 0x04}; 16 | byte[] expectedCrc = new byte[]{(byte) 0x8b, (byte) 0xb8}; 17 | byte[] calculatedCrc = CRC16.calculate(testArray, 0, testArray.length); 18 | assertThat(calculatedCrc, is(expectedCrc)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NightScout 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/dexcom/CRC16.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.dexcom; 2 | 3 | public final class CRC16 { 4 | public static byte[] calculate(byte[] buff, int start, int end) { 5 | int crcShort = 0; 6 | for (int i = start; i < end; i++) { 7 | crcShort = ((crcShort >>> 8) | (crcShort << 8) )& 0xffff; 8 | crcShort ^= (buff[i] & 0xff); 9 | crcShort ^= ((crcShort & 0xff) >> 4); 10 | crcShort ^= (crcShort << 12) & 0xffff; 11 | crcShort ^= ((crcShort & 0xFF) << 5) & 0xffff; 12 | } 13 | crcShort &= 0xffff; 14 | return new byte[] {(byte) (crcShort & 0xff), (byte) ((crcShort >> 8) & 0xff)}; 15 | } 16 | } -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/model/G4Noise.java: -------------------------------------------------------------------------------- 1 | // Code generated by Wire protocol buffer compiler, do not edit. 2 | // Source file: /Users/klee/Projects/Nightscout/android-uploader/core/src/main/java/com/nightscout/core/model/Download.proto 3 | package com.nightscout.core.model; 4 | 5 | import com.squareup.wire.ProtoEnum; 6 | 7 | public enum G4Noise 8 | implements ProtoEnum { 9 | NOISE_NONE(0), 10 | CLEAN(1), 11 | LIGHT(2), 12 | MEDIUM(3), 13 | HEAVY(4), 14 | NOT_COMPUTED(5), 15 | MAX(6); 16 | 17 | private final int value; 18 | 19 | private G4Noise(int value) { 20 | this.value = value; 21 | } 22 | 23 | @Override 24 | public int getValue() { 25 | return value; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ci/wait_for_emulator: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Originally written by Ralf Kistner , but placed in the public domain 4 | 5 | set +e 6 | 7 | bootanim="" 8 | failcounter=0 9 | timeout_in_sec=360 10 | 11 | until [[ "$bootanim" =~ "stopped" ]]; do 12 | bootanim=`adb -e shell getprop init.svc.bootanim 2>&1 &` 13 | if [[ "$bootanim" =~ "device not found" || "$bootanim" =~ "device offline" 14 | || "$bootanim" =~ "running" ]]; then 15 | let "failcounter += 1" 16 | echo "Waiting for emulator to start" 17 | if [[ $failcounter -gt timeout_in_sec ]]; then 18 | echo "Timeout ($timeout_in_sec seconds) reached; failed to start emulator" 19 | exit 1 20 | fi 21 | fi 22 | sleep 1 23 | done 24 | 25 | echo "Emulator is ready" 26 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/model/ReceiverStatus.java: -------------------------------------------------------------------------------- 1 | // Code generated by Wire protocol buffer compiler, do not edit. 2 | // Source file: /Users/klee/Projects/Nightscout/android-uploader/core/src/main/java/com/nightscout/core/model/Download.proto 3 | package com.nightscout.core.model; 4 | 5 | import com.squareup.wire.ProtoEnum; 6 | 7 | public enum ReceiverStatus 8 | implements ProtoEnum { 9 | RECEIVER_CONNECTED(0), 10 | /** 11 | * The receiver is connected to the uploader 12 | */ 13 | RECEIVER_DISCONNECTED(1); 14 | 15 | private final int value; 16 | 17 | private ReceiverStatus(int value) { 18 | this.value = value; 19 | } 20 | 21 | @Override 22 | public int getValue() { 23 | return value; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/model/DownloadStatus.java: -------------------------------------------------------------------------------- 1 | // Code generated by Wire protocol buffer compiler, do not edit. 2 | // Source file: /Users/klee/Projects/Nightscout/android-uploader/core/src/main/java/com/nightscout/core/model/Download.proto 3 | package com.nightscout.core.model; 4 | 5 | import com.squareup.wire.ProtoEnum; 6 | 7 | public enum DownloadStatus 8 | implements ProtoEnum { 9 | SUCCESS(0), 10 | NO_DATA(1), 11 | DEVICE_NOT_FOUND(2), 12 | IO_ERROR(3), 13 | APPLICATION_ERROR(4), 14 | UNKNOWN(5), 15 | NOT_APPLICABLE(6); 16 | 17 | private final int value; 18 | 19 | private DownloadStatus(int value) { 20 | this.value = value; 21 | } 22 | 23 | @Override 24 | public int getValue() { 25 | return value; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /core/src/test/java/com/nightscout/core/dexcom/SpecialValueTest.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.dexcom; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.hamcrest.Matchers.is; 6 | import static org.junit.Assert.assertThat; 7 | 8 | public class SpecialValueTest { 9 | 10 | @Test 11 | public void testIsSpecialValues() { 12 | int[] values = new int[]{0, 1, 2, 3, 5, 6, 9, 10, 12}; 13 | for (int aValue : values) { 14 | assertThat(SpecialValue.isSpecialValue(aValue), is(true)); 15 | } 16 | } 17 | 18 | @Test 19 | public void testIsNotSpecialValue() { 20 | int[] values = new int[]{11, 39, 100, 52, 250, 401, 72, 53, 80}; 21 | for (int aValue : values) { 22 | assertThat(SpecialValue.isSpecialValue(aValue), is(false)); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/battery.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 12 | 13 | 17 | 18 | 19 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/xml/device_filter.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | 3 | jdk: 4 | - oraclejdk7 5 | 6 | env: 7 | matrix: 8 | - ANDROID_TARGET=android-19 ANDROID_ABI=armeabi-v7a 9 | 10 | cache: 11 | directories: 12 | - $HOME/.gradle 13 | 14 | android: 15 | components: 16 | - build-tools-21.1.1 17 | - android-19 18 | - sysimg-19 19 | - extra-android-support 20 | 21 | licenses: 22 | - android-sdk-license-5be876d5 23 | - ".*intel.+" 24 | 25 | script: 26 | - TERM=dumb ./gradlew clean lint test jacocoTestReport 27 | 28 | deploy: 29 | provider: releases 30 | file: "./app/build/outputs/apk/app-debug.apk" 31 | skip_cleanup: true 32 | api_key: 33 | secure: Yv7qc91hNpvFZVjnl1fjA53SfH5kXyJPCabR6IIz2SbExDpstxStSQiUZydIgwrwtXtxliG8irhBdQSfYoYrHREbIZBCX0RkY1QxFpI7r3eLwIUW3SDjfvmbU7HwtYqHqG5gpr1qe81PEj5gFi49YsJYODw9WyaYiRqgE3JFUTs= 34 | on: 35 | repo: nightscout/android-uploader 36 | tags: true 37 | all_branches: true 38 | 39 | before_install: 40 | - sudo pip install codecov 41 | 42 | after_success: 43 | - cp app/build/jacoco/jacocoTestReport/*.xml jacoco.xml 44 | - cp app/build/jacoco/jacocoTestReport/*.xml jacoco2.xml 45 | - codecov 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/nightscout/android/ToastReceiver.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.android; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.widget.Toast; 7 | 8 | /** 9 | * Listens to Intents from non-UI threads, and shows a Toast for messages from them. 10 | */ 11 | public class ToastReceiver extends BroadcastReceiver { 12 | public static final String ACTION_SEND_NOTIFICATION = "nightscout_toast_intent"; 13 | public static final String TOAST_MESSAGE = "nightscout_toast_message"; 14 | 15 | @Override 16 | public void onReceive(Context context, Intent intent) { 17 | if (intent.getAction().equals(ACTION_SEND_NOTIFICATION)) { 18 | Toast.makeText(context, intent.getStringExtra(TOAST_MESSAGE), Toast.LENGTH_LONG).show(); 19 | } 20 | } 21 | 22 | public static Intent createIntent(Context context, int localizedErrorMessage) { 23 | Intent intent = new Intent(ACTION_SEND_NOTIFICATION); 24 | intent.putExtra(TOAST_MESSAGE, context.getString(localizedErrorMessage)); 25 | return intent; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/test/java/com/nightscout/robolectric/RobolectricGradleRunner.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.robolectric; 2 | 3 | import org.junit.runners.model.InitializationError; 4 | import org.robolectric.AndroidManifest; 5 | import org.robolectric.RobolectricTestRunner; 6 | import org.robolectric.annotation.Config; 7 | import org.robolectric.res.Fs; 8 | 9 | // Shamelessly stolen from: 10 | // http://blog.blundell-apps.com/android-gradle-app-with-robolectric-junit-tests/ 11 | public class RobolectricGradleRunner extends RobolectricTestRunner { 12 | private static final int MAX_SDK_SUPPORTED_BY_ROBOLECTRIC = 18; 13 | 14 | public RobolectricGradleRunner(Class testClass) throws InitializationError { 15 | super(testClass); 16 | } 17 | 18 | @Override 19 | protected AndroidManifest getAppManifest(Config config) { 20 | String manifestProperty = "src/main/AndroidManifest.xml"; 21 | String resProperty = "src/main/res"; 22 | return new AndroidManifest(Fs.fileFromPath(manifestProperty), Fs.fileFromPath(resProperty)) { 23 | @Override 24 | public int getTargetSdkVersion() { 25 | return MAX_SDK_SUPPORTED_BY_ROBOLECTRIC; 26 | } 27 | }; 28 | } 29 | } -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/drivers/AbstractDevice.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.drivers; 2 | 3 | import com.nightscout.core.model.DownloadResults; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | /** 9 | * This class is a representation for a device that we want information from e.g. pump or cgm 10 | */ 11 | abstract public class AbstractDevice { 12 | protected final Logger log = LoggerFactory.getLogger(this.getClass()); 13 | protected String deviceName = "Unknown"; 14 | protected DeviceTransport transport; 15 | 16 | public AbstractDevice(DeviceTransport transport) { 17 | this.transport = transport; 18 | } 19 | 20 | public abstract boolean isConnected(); 21 | 22 | // Not sure that I'll need this in the general device. This may be required for only push based 23 | // devices. 24 | protected void onDownload() { 25 | } 26 | 27 | public final DownloadResults download() { 28 | DownloadResults download = doDownload(); 29 | onDownload(); 30 | return download; 31 | } 32 | 33 | public String getDeviceName() { 34 | return deviceName; 35 | } 36 | 37 | abstract protected DownloadResults doDownload(); 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/nightscout/android/settings/SummaryBoundEditTextPreference.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.android.settings; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.os.Build; 6 | import android.preference.EditTextPreference; 7 | import android.util.AttributeSet; 8 | 9 | public class SummaryBoundEditTextPreference extends EditTextPreference { 10 | 11 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 12 | public SummaryBoundEditTextPreference(Context context, 13 | AttributeSet attrs, 14 | int defStyleAttr, int defStyleRes) { 15 | super(context, attrs, defStyleAttr, defStyleRes); 16 | } 17 | 18 | public SummaryBoundEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr) { 19 | super(context, attrs, defStyleAttr); 20 | } 21 | 22 | public SummaryBoundEditTextPreference(Context context, AttributeSet attrs) { 23 | super(context, attrs); 24 | } 25 | 26 | public SummaryBoundEditTextPreference(Context context) { 27 | super(context); 28 | } 29 | 30 | @Override 31 | public void setText(String text) { 32 | super.setText(text); 33 | setSummary(text); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/nightscout/android/preferences/PreferenceKeys.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.android.preferences; 2 | 3 | public final class PreferenceKeys { 4 | public static final String DATA_DONATE = "data_donate"; 5 | public static final String API_UPLOADER_ENABLED = "cloud_storage_api_enable"; 6 | public static final String API_URIS = "cloud_storage_api_base"; 7 | public static final String CAL_UPLOAD_ENABLED = "cloud_cal_data"; 8 | public static final String SENSOR_UPLOAD_ENABLED = "cloud_sensor_data"; 9 | public static final String MONGO_UPLOADER_ENABLED = "cloud_storage_mongodb_enable"; 10 | public static final String MONGO_URI = "cloud_storage_mongodb_uri"; 11 | public static final String MONGO_COLLECTION = "cloud_storage_mongodb_collection"; 12 | public static final String MONGO_DEVICE_STATUS_COLLECTION = 13 | "cloud_storage_mongodb_device_status_collection"; 14 | public static final String ROOT_ENABLED = "root_support_enabled"; 15 | public static final String I_UNDERSTAND = "i_understand"; 16 | public static final String PREFERRED_UNITS = "display_options_units"; 17 | public static final String PWD_NAME = "pwd_name"; 18 | public static final String DONATE_DATA_QUERY = "donate_data_query"; 19 | } 20 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/model/G4Trend.java: -------------------------------------------------------------------------------- 1 | // Code generated by Wire protocol buffer compiler, do not edit. 2 | // Source file: /Users/klee/Projects/Nightscout/android-uploader/core/src/main/java/com/nightscout/core/model/Download.proto 3 | package com.nightscout.core.model; 4 | 5 | import com.squareup.wire.ProtoEnum; 6 | 7 | public enum G4Trend 8 | implements ProtoEnum { 9 | TREND_NONE(0), 10 | DOUBLE_UP(1), 11 | /** 12 | * More than 3 mg/dL per minute 13 | */ 14 | SINGLE_UP(2), 15 | /** 16 | * +2 to +3 mg/dL per minute 17 | */ 18 | FORTY_FIVE_UP(3), 19 | /** 20 | * +1 to +2 mg/dL per minute 21 | */ 22 | FLAT(4), 23 | /** 24 | * +/- 1 mg/dL per minute 25 | */ 26 | FORTY_FIVE_DOWN(5), 27 | /** 28 | * -1 to -2 mg/dL per minute 29 | */ 30 | SINGLE_DOWN(6), 31 | /** 32 | * -2 to -3 mg/dL per minute 33 | */ 34 | DOUBLE_DOWN(7), 35 | /** 36 | * more than -3 mg/dL per minute 37 | */ 38 | NOT_COMPUTABLE(8), 39 | RATE_OUT_OF_RANGE(9); 40 | 41 | private final int value; 42 | 43 | private G4Trend(int value) { 44 | this.value = value; 45 | } 46 | 47 | @Override 48 | public int getValue() { 49 | return value; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/dexcom/TrendArrow.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.dexcom; 2 | 3 | 4 | import com.nightscout.core.model.G4Trend; 5 | 6 | public enum TrendArrow { 7 | NONE, 8 | DOUBLE_UP("\u21C8", "DoubleUp"), 9 | SINGLE_UP("\u2191", "SingleUp"), 10 | UP_45("\u2197", "FortyFiveUp"), 11 | FLAT("\u2192", "Flat"), 12 | DOWN_45("\u2198", "FortyFiveDown"), 13 | SINGLE_DOWN("\u2193", "SingleDown"), 14 | DOUBLE_DOWN("\u21CA", "DoubleDown"), 15 | NOT_COMPUTABLE, 16 | OUT_OF_RANGE; 17 | 18 | private String arrowSymbol; 19 | private String trendName; 20 | 21 | TrendArrow(String arrowSymbol, String trendName) { 22 | this.arrowSymbol = arrowSymbol; 23 | this.trendName = trendName; 24 | } 25 | 26 | TrendArrow() { 27 | this(null, null); 28 | } 29 | 30 | public String symbol() { 31 | if (arrowSymbol == null) { 32 | return "\u2194"; 33 | } else { 34 | return arrowSymbol; 35 | } 36 | } 37 | 38 | public String friendlyTrendName() { 39 | if (trendName == null) { 40 | return this.name().replace("_", " "); 41 | } else { 42 | return this.trendName; 43 | } 44 | } 45 | 46 | public G4Trend toProtobuf() { 47 | return G4Trend.values()[ordinal()]; 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/model/DownloadResults.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.model; 2 | 3 | import org.json.JSONArray; 4 | 5 | public class DownloadResults { 6 | private G4Download download; 7 | private long nextUploadTime; 8 | private JSONArray resultArray; 9 | private long displayTime; 10 | 11 | public DownloadResults(G4Download download, long nextUploadTime, 12 | JSONArray resultArray, long displayTime) { 13 | this.download = download; 14 | this.nextUploadTime = nextUploadTime; 15 | this.resultArray = resultArray; 16 | this.displayTime = displayTime; 17 | } 18 | 19 | public void setDownload(G4Download download) { 20 | this.download = download; 21 | } 22 | 23 | public void setNextUploadTime(long nextUploadTime) { 24 | this.nextUploadTime = nextUploadTime; 25 | } 26 | 27 | public void setResultArray(JSONArray resultArray) { 28 | this.resultArray = resultArray; 29 | } 30 | 31 | public G4Download getDownload() { 32 | return download; 33 | } 34 | 35 | public JSONArray getResultArray() { 36 | return resultArray; 37 | } 38 | 39 | public long getDisplayTime() { 40 | return displayTime; 41 | } 42 | 43 | public long getNextUploadTime() { 44 | return nextUploadTime; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/dexcom/ReadPacket.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.dexcom; 2 | 3 | import com.google.common.base.Optional; 4 | 5 | import java.util.Arrays; 6 | 7 | public class ReadPacket { 8 | private Command command; 9 | private byte[] data; 10 | private byte[] crc; 11 | private int OFFSET_CMD = 3; 12 | private int OFFSET_DATA = 4; 13 | private int CRC_LEN = 2; 14 | 15 | public ReadPacket(byte[] readPacket) { 16 | Optional optCmd = Command.getCommandByValue(readPacket[OFFSET_CMD]); 17 | if (optCmd.isPresent()) { 18 | this.command = optCmd.get(); 19 | } else { 20 | throw new IllegalArgumentException("Unknown command: " + readPacket[OFFSET_CMD]); 21 | } 22 | this.data = Arrays.copyOfRange(readPacket, OFFSET_DATA, readPacket.length - CRC_LEN); 23 | this.crc = Arrays.copyOfRange(readPacket, readPacket.length - CRC_LEN, readPacket.length); 24 | byte[] crc_calc = CRC16.calculate(readPacket, 0, readPacket.length - 2); 25 | if (!Arrays.equals(this.crc, crc_calc)) { 26 | throw new CRCFailError("CRC check failed. Was: " + Utils.bytesToHex(this.crc) + " Expected: " + Utils.bytesToHex(crc_calc)); 27 | } 28 | } 29 | 30 | public Command getCommand() { 31 | return command; 32 | } 33 | 34 | public byte[] getData() { 35 | return data; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /core/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | sourceCompatibility = JavaVersion.VERSION_1_7 4 | targetCompatibility = JavaVersion.VERSION_1_7 5 | 6 | dependencies { 7 | compile 'joda-time:joda-time:2.5' 8 | compile 'com.google.guava:guava:18.0' 9 | compile 'org.slf4j:slf4j-simple:1.7.7' 10 | compile 'org.mongodb:mongo-java-driver:2.10.1' 11 | /** Android-provided **/ 12 | // this is an old version of JSON, but it is the most compatible with the android version. 13 | // We exclude it in :app, because org.json is included in the adk. 14 | compile 'org.json:json:20090211' 15 | // as with JSON, this httpclient version is old, but hopefully matches what is in android. 16 | // We exclude it in :app 17 | compile 'org.apache.httpcomponents:httpclient:4.3' 18 | testCompile 'org.slf4j:slf4j-simple:1.7.7' 19 | testCompile 'junit:junit:4.11' 20 | testCompile 'org.hamcrest:hamcrest-library:1.3' 21 | testCompile 'org.mockito:mockito-core:1.9.5' 22 | compile 'com.squareup.wire:wire-runtime:1.6.0' 23 | } 24 | 25 | apply plugin: 'jacoco' 26 | 27 | jacoco { 28 | toolVersion = "0.7.1.201405082137" 29 | reportsDir = file("build/reports/jacoco/") 30 | } 31 | 32 | jacocoTestReport { 33 | additionalSourceDirs = files(sourceSets.main.allSource.srcDirs) 34 | sourceDirectories = files(sourceSets.main.allSource.srcDirs) 35 | classDirectories = files(sourceSets.main.output) 36 | reports { 37 | html.enabled = true 38 | xml.enabled = true 39 | csv.enabled = false 40 | } 41 | } -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/dexcom/SpecialValue.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.dexcom; 2 | 3 | import com.google.common.base.Optional; 4 | import com.nightscout.core.utils.GlucoseReading; 5 | 6 | public enum SpecialValue { 7 | NONE("??0", 0), 8 | SENSOR_NOT_ACTIVE("?SN", 1), 9 | MINIMALLY_EGV_AB("??2", 2), 10 | NO_ANTENNA("?NA", 3), 11 | SENSOR_OUT_OF_CAL("?NC", 5), 12 | COUNTS_AB("?CD", 6), 13 | ABSOLUTE_AB("?AD", 9), 14 | POWER_AB("???", 10), 15 | RF_BAD_STATUS("?RF", 12); 16 | 17 | 18 | private String name; 19 | private int val; 20 | 21 | SpecialValue(String s, int i) { 22 | name = s; 23 | val = i; 24 | } 25 | 26 | public int getValue() { 27 | return val; 28 | } 29 | 30 | public String toString() { 31 | return name; 32 | } 33 | 34 | public static Optional getEGVSpecialValue(int val) { 35 | for (SpecialValue e : values()) { 36 | if (e.getValue() == val) 37 | return Optional.of(e); 38 | } 39 | return Optional.absent(); 40 | } 41 | 42 | public static Optional getEGVSpecialValue(GlucoseReading reading) { 43 | return getEGVSpecialValue(reading.asMgdl()); 44 | } 45 | 46 | public static boolean isSpecialValue(GlucoseReading reading) { 47 | return isSpecialValue(reading.asMgdl()); 48 | } 49 | 50 | public static boolean isSpecialValue(int val) { 51 | return getEGVSpecialValue(val).isPresent(); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/nightscout/android/drivers/AndroidUploaderDevice.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.android.drivers; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.IntentFilter; 7 | 8 | import com.nightscout.core.drivers.AbstractUploaderDevice; 9 | 10 | public class AndroidUploaderDevice extends AbstractUploaderDevice { 11 | private int uploaderBattery; 12 | private Context context; 13 | 14 | private AndroidUploaderDevice(Context context) { 15 | IntentFilter deviceStatusFilter = new IntentFilter(); 16 | deviceStatusFilter.addAction(Intent.ACTION_BATTERY_CHANGED); 17 | this.context = context; 18 | this.context.registerReceiver(mDeviceStatusReceiver, deviceStatusFilter); 19 | } 20 | 21 | public int getBatteryLevel() { 22 | return uploaderBattery; 23 | } 24 | 25 | // TODO: This registers everytime. Need to fix 26 | public static AndroidUploaderDevice getUploaderDevice(Context context) { 27 | return new AndroidUploaderDevice(context); 28 | } 29 | 30 | public void close() { 31 | context.unregisterReceiver(mDeviceStatusReceiver); 32 | } 33 | 34 | BroadcastReceiver mDeviceStatusReceiver = new BroadcastReceiver() { 35 | public void onReceive(Context context, Intent intent) { 36 | String action = intent.getAction(); 37 | if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { 38 | uploaderBattery = intent.getIntExtra("level", 0); 39 | } 40 | } 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /app/src/test/java/com/nightscout/android/wearables/WearableTest.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.android.wearables; 2 | 3 | import android.content.Intent; 4 | 5 | import com.getpebble.android.kit.util.PebbleDictionary; 6 | import com.nightscout.android.MainActivity; 7 | import com.nightscout.android.test.RobolectricTestBase; 8 | import com.nightscout.core.dexcom.TrendArrow; 9 | 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.robolectric.Robolectric; 13 | 14 | import static org.hamcrest.Matchers.is; 15 | import static org.junit.Assert.assertThat; 16 | 17 | public class WearableTest extends RobolectricTestBase { 18 | MainActivity activity; 19 | 20 | @Before 21 | public void setUp() { 22 | activity = Robolectric.buildActivity(MainActivity.class).create().get(); 23 | } 24 | 25 | @Test 26 | public void pebbleShouldCreateDataReceiver() { 27 | Pebble pebble = new Pebble(activity.getApplicationContext()); 28 | Intent pebbleIntent = new Intent("com.getpebble.action.app.RECEIVE"); 29 | assertThat(getShadowApplication().hasReceiverForIntent(pebbleIntent), is(true)); 30 | } 31 | 32 | private PebbleDictionary createMockPebbleDictionary() { 33 | PebbleDictionary dict = new PebbleDictionary(); 34 | dict.addString(0, String.valueOf(TrendArrow.FLAT.ordinal())); 35 | dict.addString(1, "100"); 36 | dict.addUint32(2, 1417990743); 37 | dict.addUint32(3, 1417990743); 38 | dict.addString(4, "0"); 39 | dict.addString(5, "100"); 40 | dict.addString(6, "Bob"); 41 | return dict; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/preferences/NightscoutPreferences.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.preferences; 2 | 3 | import com.nightscout.core.model.GlucoseUnit; 4 | 5 | import java.util.List; 6 | 7 | public interface NightscoutPreferences { 8 | boolean isRestApiEnabled(); 9 | 10 | void setRestApiEnabled(boolean restApiEnabled); 11 | 12 | List getRestApiBaseUris(); 13 | 14 | void setRestApiBaseUris(List restApis); 15 | 16 | boolean isCalibrationUploadEnabled(); 17 | 18 | void setCalibrationUploadEnabled(boolean calibrationUploadEnabled); 19 | 20 | boolean isSensorUploadEnabled(); 21 | 22 | void setSensorUploadEnabled(boolean sensorUploadEnabled); 23 | 24 | boolean isMongoUploadEnabled(); 25 | 26 | void setMongoUploadEnabled(boolean mongoUploadEnabled); 27 | 28 | boolean isDataDonateEnabled(); 29 | 30 | void setDataDonateEnabled(boolean toDonate); 31 | 32 | String getMongoClientUri(); 33 | 34 | void setMongoClientUri(String client); 35 | 36 | String getMongoCollection(); 37 | 38 | void setMongoCollection(String mongoCollection); 39 | 40 | String getMongoDeviceStatusCollection(); 41 | 42 | void setMongoDeviceStatusCollection(String deviceStatusCollection); 43 | 44 | boolean getIUnderstand(); 45 | 46 | void setIUnderstand(boolean bool); 47 | 48 | GlucoseUnit getPreferredUnits(); 49 | 50 | void setPreferredUnits(GlucoseUnit units); 51 | 52 | String getPwdName(); 53 | 54 | void setPwdName(String pwdName); 55 | 56 | boolean hasAskedForData(); 57 | 58 | void setAskedForData(boolean askedForData); 59 | } 60 | -------------------------------------------------------------------------------- /app/src/test/java/com/nightscout/android/OnUpgradeReceiverTest.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.android; 2 | 3 | 4 | import android.content.Intent; 5 | import android.net.Uri; 6 | 7 | import com.nightscout.android.test.RobolectricTestBase; 8 | 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.robolectric.Robolectric; 12 | 13 | import static org.hamcrest.Matchers.is; 14 | import static org.junit.Assert.assertThat; 15 | 16 | public class OnUpgradeReceiverTest extends RobolectricTestBase { 17 | MainActivity activity; 18 | 19 | @Before 20 | public void setUp() { 21 | activity = Robolectric.buildActivity(MainActivity.class).create().get(); 22 | } 23 | 24 | @Test 25 | public void testOnCreate_ShouldHaveUpgradeReceiver() { 26 | Intent intent = new Intent(Intent.ACTION_PACKAGE_REPLACED); 27 | assertThat(getShadowApplication().hasReceiverForIntent(intent), is(true)); 28 | } 29 | 30 | @Test 31 | public void testOnUpgradeReceiverRestartsMainActivity() { 32 | Intent intent = new Intent(Intent.ACTION_PACKAGE_REPLACED); 33 | Uri dataUri = Uri.parse("package:com.nightscout.android"); 34 | intent.setData(dataUri); 35 | OnUpgradeReceiver onUpgradeReceiver = new OnUpgradeReceiver(); 36 | onUpgradeReceiver.onReceive(activity.getApplicationContext(),intent); 37 | Intent anIntent = getShadowApplication().getNextStartedActivity(); 38 | assertThat(anIntent.getComponent().getClassName(), is(MainActivity.class.getName())); 39 | } 40 | 41 | //TODO: need a test to make sure that activity is not started if another package is replaced 42 | } 43 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/utils/GlucoseReading.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.utils; 2 | 3 | import com.nightscout.core.dexcom.Constants; 4 | import com.nightscout.core.model.GlucoseUnit; 5 | 6 | public class GlucoseReading { 7 | private int valueMgdl; 8 | 9 | public GlucoseReading(float value, GlucoseUnit units) { 10 | this.valueMgdl = (units == GlucoseUnit.MGDL) ? 11 | Math.round(value) : Math.round(value * Constants.MMOL_L_TO_MG_DL); 12 | } 13 | 14 | public float asMmol() { 15 | return valueMgdl * Constants.MG_DL_TO_MMOL_L; 16 | } 17 | 18 | public String asMmolStr() { 19 | return String.format("%.1f", asMmol()); 20 | } 21 | 22 | public int asMgdl() { 23 | return valueMgdl; 24 | } 25 | 26 | public String asMgdlStr() { 27 | return String.valueOf(valueMgdl); 28 | } 29 | 30 | public float as(GlucoseUnit units) { 31 | return (units == GlucoseUnit.MGDL) ? asMgdl() : asMmol(); 32 | } 33 | 34 | public String asStr(GlucoseUnit units) { 35 | return (units == GlucoseUnit.MGDL) ? asMgdlStr() : asMmolStr(); 36 | } 37 | 38 | public GlucoseReading subtract(GlucoseReading reading) { 39 | return new GlucoseReading(valueMgdl - reading.asMgdl(), GlucoseUnit.MGDL); 40 | } 41 | 42 | @Override 43 | public boolean equals(Object o) { 44 | if (this == o) return true; 45 | if (o == null || getClass() != o.getClass()) return false; 46 | 47 | GlucoseReading that = (GlucoseReading) o; 48 | 49 | if (valueMgdl != that.valueMgdl) return false; 50 | 51 | return true; 52 | } 53 | 54 | @Override 55 | public int hashCode() { 56 | return valueMgdl; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | android-uploader 2 | ==================== 3 | [![Build Status](https://travis-ci.org/nightscout/cgm-remote-monitor.png)](https://travis-ci.org/nightscout/android-uploader?branch=dev) 4 | [![codecov.io](https://codecov.io/github/nightscout/android-uploader/coverage.svg?branch=dev)](https://codecov.io/github/nightscout/android-uploader?branch=dev) 5 | [![Gitter chat](https://badges.gitter.im/nightscout.png)](https://gitter.im/nightscout/public) 6 | 7 | Android app on Google Play 8 | 9 | 10 | Android Uploader for the Nightscout Project. 11 | 12 | 13 | ## [License - GPL V3](gpl-v3) 14 | [gpl-3]: http://www.gnu.org/licenses/gpl-3.0.txt 15 | 16 | android-uploader - Nightscout's open source MDDS CGM uploader and archiver 17 | Copyright (C) 2014 Nightscout contributors. See the COPYRIGHT file 18 | at the root directory of this distribution and at 19 | https://github.com/nightscout/android-uploader/blob/master/COPYRIGHT 20 | 21 | This program is free software: you can redistribute it and/or modify 22 | it under the terms of the GNU General Public License as published by 23 | the Free Software Foundation, either version 3 of the License, or 24 | (at your option) any later version. 25 | 26 | This program is distributed in the hope that it will be useful, 27 | but WITHOUT ANY WARRANTY; without even the implied warranty of 28 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 29 | GNU General Public License for more details. 30 | 31 | You should have received a copy of the GNU General Public License 32 | along with this program. If not, see . 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/nightscout/android/drivers/USB/USBPower.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.android.drivers.USB; 2 | 3 | import android.util.Log; 4 | 5 | import java.io.DataOutputStream; 6 | 7 | public class USBPower { 8 | 9 | private static final String TAG = USBPower.class.getSimpleName(); 10 | 11 | private static final String SET_POWER_ON_COMMAND = "echo 'on' > \"/sys/bus/usb/devices/1-1/power/control\""; 12 | private static final String SET_POWER_SUSPEND_COMMAND_A = "echo \"0\" > \"/sys/bus/usb/devices/1-1/power/autosuspend_delay_ms\""; 13 | private static final String SET_POWER_SUSPEND_COMMAND_B = "echo \"auto\" > \"/sys/bus/usb/devices/1-1/power/control\""; 14 | public static final int POWER_ON_DELAY = 5000; 15 | 16 | public static void powerOff() { 17 | try { 18 | runCommand(SET_POWER_SUSPEND_COMMAND_A); 19 | runCommand(SET_POWER_SUSPEND_COMMAND_B); 20 | Log.i(TAG, "powerOff USB complete"); 21 | } catch (Exception e) { 22 | Log.e(TAG, "Unable to powerOff USB"); 23 | } 24 | } 25 | 26 | public static void powerOn() { 27 | try { 28 | runCommand(SET_POWER_ON_COMMAND); 29 | Log.i(TAG, "powerOn USB complete"); 30 | Thread.sleep(POWER_ON_DELAY); 31 | } catch (Exception e) { 32 | Log.e(TAG, "Unable to powerOn USB"); 33 | } 34 | } 35 | 36 | private static void runCommand(String command) throws Exception { 37 | Process process = Runtime.getRuntime().exec("su"); 38 | DataOutputStream os = new DataOutputStream(process.getOutputStream()); 39 | os.writeBytes(command + "\n"); 40 | os.flush(); 41 | os.writeBytes("exit \n"); 42 | os.flush(); 43 | os.close(); 44 | process.waitFor(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/utils/RestUriUtils.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.utils; 2 | 3 | import com.google.common.base.Charsets; 4 | import com.google.common.base.Splitter; 5 | import com.google.common.base.Strings; 6 | import com.google.common.hash.Hashing; 7 | 8 | import java.net.URI; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | import static com.google.common.base.Preconditions.checkArgument; 13 | import static com.google.common.base.Preconditions.checkNotNull; 14 | 15 | public class RestUriUtils { 16 | public static boolean isV1Uri(URI uri) { 17 | return uri != null && (uri.getPath().endsWith("v1") || uri.getPath().endsWith("v1/")); 18 | } 19 | 20 | public static boolean hasToken(URI uri) { 21 | return !Strings.isNullOrEmpty(uri.getUserInfo()); 22 | } 23 | 24 | /** 25 | * Removes the token from the uri. 26 | * @param uri Non-null uri to strip the token from. 27 | * @return uri without token. 28 | */ 29 | public static URI removeToken(URI uri) { 30 | checkNotNull(uri); 31 | // This is gross, but I don't know a better way to do it. 32 | return URI.create(uri.toString().replaceFirst("//[^@]+@", "//")); 33 | } 34 | 35 | /** 36 | * Generates a secret from the given token. 37 | * @param secret Non-null, non-empty secret to generate the token from. 38 | * @return The generated token. 39 | */ 40 | public static String generateSecret(String secret) { 41 | checkArgument(!Strings.isNullOrEmpty(secret)); 42 | return Hashing.sha1().hashBytes(secret.getBytes(Charsets.UTF_8)).toString(); 43 | } 44 | 45 | public static List splitIntoMultipleUris(String combinedUris) { 46 | if (Strings.isNullOrEmpty(combinedUris)) { 47 | return new ArrayList<>(); 48 | } 49 | return Splitter.onPattern("\\s+").splitToList(combinedUris.trim()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /core/src/test/java/com/nightscout/core/dexcom/ReadPacketTest.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.dexcom; 2 | 3 | import com.google.common.primitives.UnsignedBytes; 4 | 5 | import org.junit.Test; 6 | 7 | import static org.hamcrest.Matchers.is; 8 | import static org.junit.Assert.assertThat; 9 | import static org.junit.Assert.fail; 10 | 11 | public class ReadPacketTest { 12 | 13 | byte[] testPacket = new byte[]{ 14 | /** HEADER **/0x1, 0x1, 0x1, 15 | /** COMMAND **/0x5, 16 | /** DATA **/0x10, 0x15, 17 | /** CRC */0x52, 0x33 18 | }; 19 | 20 | byte[] testPacketNoData = new byte[]{ 21 | /** HEADER **/0x1, 0x1, 0x1, 22 | /** COMMAND **/0x1A, 23 | /** CRC **/UnsignedBytes.checkedCast(0xCE), UnsignedBytes.checkedCast(0xC1) 24 | }; 25 | 26 | byte[] testPacketBadCrc = new byte[]{ 27 | /** HEADER **/0x1, 0x1, 0x1, 28 | /** COMMAND **/0x1A, 29 | /** CRC **/UnsignedBytes.checkedCast(0xCE), UnsignedBytes.checkedCast(0xC0) 30 | }; 31 | 32 | @Test 33 | public void testReadPacket_command() { 34 | assertThat(new ReadPacket(testPacket).getCommand().getValue(), is((byte) 0x05)); 35 | } 36 | 37 | @Test 38 | public void testReadPacket_data() { 39 | assertThat(new ReadPacket(testPacket).getData(), is(new byte[]{0x10, 0x15})); 40 | } 41 | 42 | @Test 43 | public void testReadPacket_noDataPacket_command() { 44 | assertThat(new ReadPacket(testPacketNoData).getCommand().getValue(), is((byte) 0x1A)); 45 | } 46 | 47 | @Test 48 | public void testReadPacket_noDataPacket_emptyData() { 49 | assertThat(new ReadPacket(testPacketNoData).getData(), is(new byte[]{})); 50 | } 51 | 52 | @Test 53 | public void testReadPacket_badCrc() throws Exception { 54 | try { 55 | new ReadPacket(testPacketBadCrc); 56 | fail("Should receive CRC error"); 57 | } catch (CRCFailError error) { 58 | // nom 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/test/java/com/nightscout/android/test/RobolectricTestBase.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.android.test; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.IntentFilter; 7 | 8 | import com.google.android.gms.analytics.GoogleAnalytics; 9 | import com.google.common.base.Function; 10 | import com.nightscout.robolectric.RobolectricGradleRunner; 11 | 12 | import org.junit.Before; 13 | import org.junit.runner.RunWith; 14 | import org.robolectric.Robolectric; 15 | import org.robolectric.shadows.ShadowApplication; 16 | 17 | import static org.hamcrest.Matchers.is; 18 | import static org.junit.Assert.assertThat; 19 | 20 | @RunWith(RobolectricGradleRunner.class) 21 | public class RobolectricTestBase { 22 | private final boolean[] intentSeen = {false}; 23 | 24 | @Before 25 | public final void setUpBase() { 26 | // NPEs happen when using Robolectric + GA for some reason. Disable them for now. 27 | // https://github.com/robolectric/robolectric/issues/1075 28 | getShadowApplication().declareActionUnbindable("com.google.android.gms.analytics.service.START"); 29 | GoogleAnalytics.getInstance(getContext()).setAppOptOut(true); 30 | } 31 | 32 | public void whenOnBroadcastReceived(String intentKey, final Function verifyCallback) { 33 | getShadowApplication().registerReceiver(new BroadcastReceiver() { 34 | @Override 35 | public void onReceive(Context context, Intent intent) { 36 | intentSeen[0] = true; 37 | verifyCallback.apply(intent); 38 | } 39 | }, new IntentFilter(intentKey)); 40 | } 41 | 42 | public void assertIntentSeen() { 43 | assertThat(intentSeen[0], is(true)); 44 | } 45 | 46 | public Context getContext() { 47 | return getShadowApplication().getApplicationContext(); 48 | } 49 | 50 | public ShadowApplication getShadowApplication() { 51 | return Robolectric.getShadowApplication(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/nightscout/android/Nightscout.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.android; 2 | 3 | import android.app.Application; 4 | import android.util.Log; 5 | 6 | import com.google.android.gms.analytics.GoogleAnalytics; 7 | import com.google.android.gms.analytics.Logger; 8 | import com.google.android.gms.analytics.Tracker; 9 | 10 | import net.danlew.android.joda.JodaTimeAndroid; 11 | 12 | import org.acra.ACRA; 13 | import org.acra.ReportingInteractionMode; 14 | import org.acra.annotation.ReportsCrashes; 15 | 16 | @ReportsCrashes( 17 | formUri = "https://collector.tracepot.com/a64e4a51", 18 | resToastText = R.string.crash_toast_text, 19 | resDialogText = R.string.feebback_dialog_text, 20 | resDialogIcon = R.drawable.ic_launcher, 21 | resDialogTitle = R.string.feedback_dialog_title, 22 | resDialogCommentPrompt = R.string.feedback_dialog_comment_prompt, 23 | resDialogOkToast = R.string.feedback_dialog_ok_toast, 24 | excludeMatchingSharedPreferencesKeys = {"cloud_storage_mongodb_uri", "cloud_storage_api_base"}, 25 | mode = ReportingInteractionMode.TOAST, 26 | logcatArguments = {"-t", "500", "-v", "time"} 27 | ) 28 | public class Nightscout extends Application { 29 | private final String TAG = MainActivity.class.getSimpleName(); 30 | private Tracker tracker = null; 31 | 32 | @Override 33 | public void onCreate() { 34 | super.onCreate(); 35 | ACRA.init(this); 36 | JodaTimeAndroid.init(this); 37 | } 38 | 39 | synchronized public Tracker getTracker() { 40 | Log.d(TAG, "getTracker called"); 41 | if (tracker == null) { 42 | Log.d(TAG, "tracker was null - returning new tracker"); 43 | GoogleAnalytics analytics = GoogleAnalytics.getInstance(this); 44 | analytics.setDryRun(false); 45 | analytics.getLogger().setLogLevel(Logger.LogLevel.WARNING); 46 | analytics.setLocalDispatchPeriod(7200); 47 | tracker = analytics.newTracker(R.xml.app_tracker); 48 | return tracker; 49 | } 50 | return tracker; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/dexcom/Command.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.dexcom; 2 | 3 | import com.google.common.base.CaseFormat; 4 | import com.google.common.base.Optional; 5 | 6 | public enum Command { 7 | NULL(0), 8 | ACK(1), 9 | NAK(2), 10 | INVALID_COMMAND(3), 11 | INVALID_PARAM(4), 12 | INCOMPLETE_PACKET_RECEIVED(5), 13 | RECEIVER_ERROR(6), 14 | INVALID_MODE(7), 15 | PING(10), 16 | READ_FIRMWARE_HEADER(11), 17 | READ_DATABASE_PARTITION_INFO(15), 18 | READ_DATABASE_PAGE_RANGE(16), 19 | READ_DATABASE_PAGES(17), 20 | READ_DATABASE_PAGE_HEADER(18), 21 | READ_TRANSMITTER_ID(25), 22 | WRITE_TRANSMITTER_ID(26), 23 | READ_LANGUAGE(27), 24 | WRITE_LANGUAGE(28), 25 | READ_DISPLAY_TIME_OFFSET(29), 26 | WRITE_DISPLAY_TIME_OFFSET(30), 27 | READ_RTC(31), 28 | RESET_RECEIVER(32), 29 | READ_BATTERY_LEVEL(33), 30 | READ_SYSTEM_TIME(34), 31 | READ_SYSTEM_TIME_OFFSET(35), 32 | WRITE_SYSTEM_TIME(36), 33 | READ_GLUCOSE_UNIT(37), 34 | WRITE_GLUCOSE_UNIT(38), 35 | READ_BLINDED_MODE(39), 36 | WRITE_BLINDED_MODE(40), 37 | READ_CLOCK_MODE(41), 38 | WRITE_CLOCK_MODE(42), 39 | READ_DEVICE_MODE(43), 40 | ERASE_DATABASE(45), 41 | SHUTDOWN_RECEIVER(46), 42 | WRITE_PC_PARAMETERS(47), 43 | READ_BATTERY_STATE(48), 44 | READ_HARDWARE_BOARD_ID(49), 45 | READ_FIRMWARE_SETTINGS(54), 46 | READ_ENABLE_SETUP_WIZARD_FLAG(55), 47 | READ_SETUP_WIZARD_STATE(57); 48 | 49 | private byte value; 50 | 51 | Command(int command){ 52 | value = (byte) command; 53 | } 54 | 55 | public byte getValue() { 56 | return value; 57 | } 58 | 59 | public String toString(){ 60 | return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL,this.name()).replace("_", " "); 61 | } 62 | 63 | public static Optional getCommandByValue(int value){ 64 | for(Command command:values()){ 65 | if (command.getValue() == value){ 66 | return Optional.of(command); 67 | } 68 | } 69 | return Optional.absent(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/drivers/DeviceTransport.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.drivers; 2 | 3 | import java.io.IOException; 4 | 5 | public interface DeviceTransport { 6 | /** 7 | * Opens and initializes the device as a USB serial device. Upon success, 8 | * caller must ensure that {@link #close()} is eventually called. 9 | * 10 | * @throws java.io.IOException on error opening or initializing the device. 11 | */ 12 | public void open() throws IOException; 13 | 14 | /** 15 | * Closes the serial device. 16 | * 17 | * @throws java.io.IOException on error closing the device. 18 | */ 19 | public void close() throws IOException; 20 | 21 | /** 22 | * Reads as many bytes as possible into the destination buffer. 23 | * 24 | * @param dest the destination byte buffer 25 | * @param timeoutMillis the timeout for reading 26 | * @return the actual number of bytes read 27 | * @throws java.io.IOException if an error occurred during reading 28 | */ 29 | public int read(final byte[] dest, final int timeoutMillis) throws IOException; 30 | 31 | /** 32 | * Reads as many bytes as possible into the destination buffer. 33 | * 34 | * @param size size to read 35 | * @param timeoutMillis the timeout for reading 36 | * @return the actual number of bytes read 37 | * @throws java.io.IOException if an error occurred during reading 38 | */ 39 | public byte[] read(int size, final int timeoutMillis) throws IOException; 40 | 41 | /** 42 | * Writes as many bytes as possible from the source buffer. 43 | * 44 | * @param src the source byte buffer 45 | * @param timeoutMillis the timeout for writing 46 | * @return the actual number of bytes written 47 | * @throws java.io.IOException if an error occurred during writing 48 | */ 49 | public int write(final byte[] src, final int timeoutMillis) throws IOException; 50 | 51 | public boolean isConnected(int vendorId, int productId, int deviceClass, int subClass, 52 | int protocol); 53 | 54 | } 55 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/upload/AbstractRestUploader.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.upload; 2 | 3 | import com.google.common.base.Joiner; 4 | import com.nightscout.core.preferences.NightscoutPreferences; 5 | 6 | import org.apache.http.HttpResponse; 7 | import org.apache.http.client.HttpClient; 8 | import org.apache.http.client.methods.HttpPost; 9 | import org.apache.http.entity.StringEntity; 10 | import org.apache.http.impl.client.DefaultHttpClient; 11 | import org.apache.http.message.AbstractHttpMessage; 12 | import org.json.JSONObject; 13 | 14 | import java.io.IOException; 15 | import java.net.URI; 16 | 17 | import static com.google.common.base.Preconditions.checkNotNull; 18 | 19 | public abstract class AbstractRestUploader extends BaseUploader { 20 | private final URI uri; 21 | private HttpClient client; 22 | 23 | public AbstractRestUploader(NightscoutPreferences preferences, URI baseUri) { 24 | super(preferences); 25 | checkNotNull(baseUri); 26 | this.uri = baseUri; 27 | } 28 | 29 | protected void setExtraHeaders(AbstractHttpMessage httpMessage) { } 30 | 31 | public URI getUri() { 32 | return uri; 33 | } 34 | 35 | public HttpClient getClient() { 36 | if (client != null) { 37 | return client; 38 | } 39 | client = new DefaultHttpClient(); 40 | return client; 41 | } 42 | 43 | public void setClient(HttpClient client) { 44 | this.client = client; 45 | } 46 | 47 | protected boolean doPost(String endpoint, JSONObject jsonObject) throws IOException { 48 | HttpPost httpPost = new HttpPost(Joiner.on('/').join(uri.toString(), endpoint)); 49 | httpPost.addHeader("Content-Type", "application/json"); 50 | httpPost.addHeader("Accept", "application/json"); 51 | setExtraHeaders(httpPost); 52 | httpPost.setEntity(new StringEntity(jsonObject.toString())); 53 | HttpResponse response = getClient().execute(httpPost); 54 | int statusCodeFamily = response.getStatusLine().getStatusCode() / 100; 55 | response.getEntity().consumeContent(); 56 | return statusCodeFamily == 2; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/nightscout/android/preferences/PreferencesValidator.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.android.preferences; 2 | 3 | import android.content.Context; 4 | 5 | import com.google.common.base.Optional; 6 | import com.google.common.base.Strings; 7 | import com.mongodb.MongoClientURI; 8 | import com.nightscout.android.R; 9 | import com.nightscout.core.utils.RestUriUtils; 10 | 11 | import java.net.URI; 12 | 13 | public class PreferencesValidator { 14 | /** 15 | * Validate the syntax of the given mongo uri. 16 | * @param mongoUriString String to validated. 17 | * @return Optional localized validation error, if one occurs. 18 | */ 19 | public static Optional validateMongoUriSyntax(Context context, String mongoUriString) { 20 | try { 21 | new MongoClientURI(mongoUriString); 22 | } catch (IllegalArgumentException e) { 23 | return Optional.of(context.getString(R.string.illegal_mongo_uri)); 24 | } 25 | return Optional.absent(); 26 | } 27 | 28 | /** 29 | * Validate the syntax of a single rest api uri. Can be either legacy or v1 format. 30 | * @param restApiUri Uri to validate. 31 | * @return Localized validation error, if one occurs. 32 | */ 33 | public static Optional validateRestApiUriSyntax(Context context, String restApiUri) { 34 | if (Strings.isNullOrEmpty(restApiUri)) { 35 | return Optional.of(context.getString(R.string.invalid_rest_uri, restApiUri)); 36 | } 37 | URI uri; 38 | try { 39 | uri = URI.create(restApiUri); 40 | } catch (NullPointerException e) { 41 | return Optional.of(context.getString(R.string.invalid_rest_uri, restApiUri)); 42 | } catch (IllegalArgumentException e) { 43 | return Optional.of(context.getString(R.string.invalid_rest_uri, restApiUri)); 44 | } 45 | if (RestUriUtils.isV1Uri(uri)) { 46 | if (!RestUriUtils.hasToken(uri)) { 47 | return Optional.of(context.getString(R.string.rest_uri_missing_token, restApiUri)); 48 | } 49 | } 50 | return Optional.absent(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/upload/RestLegacyUploader.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.upload; 2 | 3 | import com.nightscout.core.dexcom.records.GlucoseDataSet; 4 | import com.nightscout.core.drivers.AbstractUploaderDevice; 5 | import com.nightscout.core.preferences.NightscoutPreferences; 6 | 7 | import org.json.JSONException; 8 | import org.json.JSONObject; 9 | 10 | import java.io.IOException; 11 | import java.net.URI; 12 | 13 | public class RestLegacyUploader extends AbstractRestUploader { 14 | 15 | public RestLegacyUploader(NightscoutPreferences preferences, URI uri) { 16 | super(preferences, uri); 17 | } 18 | 19 | private JSONObject toJSONObject(GlucoseDataSet record) throws JSONException { 20 | JSONObject json = new JSONObject(); 21 | json.put("device", "dexcom"); 22 | json.put("date", record.getDisplayTime().getTime()); 23 | json.put("dateString", record.getDisplayTime().toString()); 24 | json.put("sgv", Integer.parseInt(String.valueOf(record.getBgMgdl()))); 25 | json.put("direction", record.getTrend().friendlyTrendName()); 26 | return json; 27 | } 28 | 29 | private JSONObject toJSONObject(AbstractUploaderDevice deviceStatus) throws JSONException { 30 | JSONObject json = new JSONObject(); 31 | json.put("uploaderBattery", deviceStatus.getBatteryLevel()); 32 | return json; 33 | } 34 | 35 | @Override 36 | protected boolean doUpload(GlucoseDataSet glucoseDataSet) throws IOException { 37 | try { 38 | return doPost("entries", toJSONObject(glucoseDataSet)); 39 | } catch (JSONException e) { 40 | log.error("Could not create JSON object for legacy rest glucose data set.", e); 41 | return false; 42 | } 43 | } 44 | 45 | // TODO(trhodeos): is devicestatus supported in legacy apis? 46 | @Override 47 | protected boolean doUpload(AbstractUploaderDevice deviceStatus) throws IOException { 48 | try { 49 | return doPost("devicestatus", toJSONObject(deviceStatus)); 50 | } catch (JSONException e) { 51 | log.error("Could not create JSON object for legacy rest device status.", e); 52 | return false; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/dexcom/PacketBuilder.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.dexcom; 2 | 3 | import com.google.common.primitives.Bytes; 4 | 5 | import java.util.ArrayList; 6 | 7 | public class PacketBuilder { 8 | public static final int MAX_PAYLOAD = 1584; 9 | public static final int MIN_LEN = 6; 10 | public static final int MAX_LEN = MAX_PAYLOAD + MIN_LEN; 11 | public static final byte SOF = 0x01; 12 | public static final int OFFSET_SOF = 0; 13 | public static final int OFFSET_LENGTH = 1; 14 | public static final int OFFSET_NULL = 2; 15 | public static final byte NULL = 0x00; 16 | public static final int OFFSET_CMD = 3; 17 | public static final int OFFSET_PAYLOAD = 4; 18 | public static final int CRC_LEN = 2; 19 | public static final int HEADER_LEN = 4; 20 | private ArrayList packet; 21 | private Command command; 22 | private ArrayList payload; 23 | 24 | public PacketBuilder(Command command){ 25 | this.command = command; 26 | } 27 | 28 | public PacketBuilder(Command command, ArrayList payload) { 29 | this.command = command; 30 | this.payload = payload; 31 | } 32 | 33 | public byte[] build() { 34 | packet = new ArrayList<>(); 35 | packet.add(OFFSET_SOF, SOF); 36 | packet.add(OFFSET_LENGTH, getLength()); 37 | packet.add(OFFSET_NULL, NULL); 38 | packet.add(OFFSET_CMD, (byte) command.getValue()); 39 | if (this.payload != null) { 40 | this.packet.addAll(OFFSET_PAYLOAD, this.payload); 41 | } 42 | byte[] crc16 = CRC16.calculate(toBytes(), 0, this.packet.size()); 43 | this.packet.add(crc16[0]); 44 | this.packet.add(crc16[1]); 45 | return this.toBytes(); 46 | } 47 | 48 | private byte getLength() { 49 | int packetSize = payload == null ? MIN_LEN : payload.size() + CRC_LEN + HEADER_LEN; 50 | 51 | if (packetSize > MAX_LEN) { 52 | throw new IndexOutOfBoundsException(packetSize + " bytes, but packet must between " 53 | + MIN_LEN + " and " + MAX_LEN + " bytes."); 54 | } 55 | 56 | return (byte) packetSize; 57 | } 58 | 59 | private byte[] toBytes() { 60 | return Bytes.toArray(this.packet); 61 | } 62 | } -------------------------------------------------------------------------------- /app/src/test/java/com/nightscout/android/preferences/PreferencesValidatorTest.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.android.preferences; 2 | 3 | import com.nightscout.android.R; 4 | import com.nightscout.android.test.RobolectricTestBase; 5 | 6 | import org.junit.Test; 7 | 8 | import static org.hamcrest.Matchers.is; 9 | import static org.junit.Assert.assertThat; 10 | 11 | public class PreferencesValidatorTest extends RobolectricTestBase { 12 | @Test 13 | public void testValidateMongoUriSyntax_Empty() { 14 | assertThat(PreferencesValidator.validateMongoUriSyntax(getContext(), "").get(), 15 | is(getContext().getString(R.string.illegal_mongo_uri, ""))); 16 | } 17 | 18 | @Test 19 | public void testValidateMongoUriSyntax_Invalid() { 20 | assertThat(PreferencesValidator.validateMongoUriSyntax(getContext(), "test/db").get(), 21 | is(getContext().getString(R.string.illegal_mongo_uri, "test/db"))); 22 | } 23 | 24 | @Test 25 | public void testValidateMongoUriSyntax_Valid() { 26 | assertThat(PreferencesValidator.validateMongoUriSyntax(getContext(), "mongodb://test/db") 27 | .isPresent(), 28 | is(false)); 29 | } 30 | 31 | @Test 32 | public void testValidateRestApiUriSyntax_Empty() { 33 | assertThat(PreferencesValidator.validateRestApiUriSyntax(getContext(), "").get(), 34 | is(getContext().getString(R.string.invalid_rest_uri, ""))); 35 | } 36 | 37 | @Test 38 | public void testValidateRestApiUriSyntax_Invalid() { 39 | assertThat(PreferencesValidator.validateRestApiUriSyntax(getContext(), "\\invalid").get(), 40 | is(getContext().getString(R.string.invalid_rest_uri, "\\invalid"))); 41 | } 42 | 43 | @Test 44 | public void testValidateRestApiUriSyntax_Valid() { 45 | assertThat(PreferencesValidator.validateRestApiUriSyntax(getContext(), "http://test.com") 46 | .isPresent(), 47 | is(false)); 48 | } 49 | 50 | @Test 51 | public void testValidateRestApiUriSyntax_V1NoToken() { 52 | assertThat(PreferencesValidator.validateRestApiUriSyntax(getContext(), "http://test.com/v1") 53 | .get(), 54 | is(getContext().getString(R.string.rest_uri_missing_token, "http://test.com/v1"))); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /core/src/test/java/com/nightscout/core/dexcom/SensorRecordTest.java: -------------------------------------------------------------------------------- 1 | // Sensor Record: 56301B0BF3DB1A0BC03B020050FD0100A600C7 2 | // Record Sensor filtered: 130384 unfiltered: 146368 RSSI: 166 display time: 1417102819000 system time: 186331222 3 | // Sensor Record: 82311B0B1FDD1A0B2058020080220200A30092 4 | // Record Sensor filtered: 139904 unfiltered: 153632 RSSI: 163 display time: 1417103119000 system time: 186331522 5 | 6 | 7 | package com.nightscout.core.dexcom; 8 | 9 | import com.nightscout.core.dexcom.records.SensorRecord; 10 | 11 | import org.junit.Test; 12 | 13 | import static org.hamcrest.Matchers.is; 14 | import static org.junit.Assert.assertThat; 15 | 16 | 17 | public class SensorRecordTest { 18 | 19 | @Test 20 | public void shouldParseSensorRecord() throws Exception { 21 | byte[] record = new byte[]{(byte) 0x56, (byte) 0x30, (byte) 0x1B, (byte) 0x0B, (byte) 0xF3, 22 | (byte) 0xDB, (byte) 0x1A, (byte) 0x0B, (byte) 0xC0, (byte) 0x3B, (byte) 0x02, 23 | (byte) 0x00, (byte) 0x50, (byte) 0xFD, (byte) 0x01, (byte) 0x00, (byte) 0xA6, 24 | (byte) 0x00, (byte) 0xC7}; 25 | SensorRecord sensorRecord = new SensorRecord(record); 26 | assertThat(sensorRecord.getUnfiltered(), is(146368L)); 27 | assertThat(sensorRecord.getFiltered(), is(130384L)); 28 | assertThat(sensorRecord.getRssi(), is(166)); 29 | assertThat(sensorRecord.getRawDisplayTimeSeconds(), is(186309619L)); 30 | assertThat(sensorRecord.getRawSystemTimeSeconds(), is(186331222L)); 31 | } 32 | 33 | @Test(expected = InvalidRecordLengthException.class) 34 | public void shouldNotParseSmallSensorRecord() throws Exception { 35 | byte[] record = new byte[]{(byte) 0x56, (byte) 0x30, (byte) 0x1B, (byte) 0x0B, (byte) 0xF3, 36 | (byte) 0xDB, (byte) 0x1A, (byte) 0x0B, (byte) 0xC0, (byte) 0x3B, (byte) 0x02, 37 | (byte) 0x00, (byte) 0x50, (byte) 0xFD, (byte) 0x01, (byte) 0x00, (byte) 0xA6, 38 | (byte) 0x00}; 39 | SensorRecord sensorRecord = new SensorRecord(record); 40 | } 41 | 42 | @Test(expected = InvalidRecordLengthException.class) 43 | public void shouldNotParseLargeSensorRecord() throws Exception { 44 | byte[] record = new byte[]{(byte) 0x56, (byte) 0x30, (byte) 0x1B, (byte) 0x0B, (byte) 0xF3, 45 | (byte) 0xDB, (byte) 0x1A, (byte) 0x0B, (byte) 0xC0, (byte) 0x3B, (byte) 0x02, 46 | (byte) 0x00, (byte) 0x50, (byte) 0xFD, (byte) 0x01, (byte) 0x00, (byte) 0xA6, 47 | (byte) 0x00, (byte) 0xC7, (byte) 0x00}; 48 | SensorRecord sensorRecord = new SensorRecord(record); 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /core/src/test/java/com/nightscout/core/dexcom/UtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.dexcom; 2 | 3 | import com.google.common.primitives.UnsignedBytes; 4 | 5 | import org.joda.time.DateTime; 6 | import org.joda.time.DateTimeZone; 7 | import org.joda.time.Period; 8 | import org.junit.Test; 9 | 10 | import static org.hamcrest.Matchers.is; 11 | import static org.junit.Assert.assertThat; 12 | 13 | public class UtilsTest { 14 | 15 | @Test 16 | public void testReceiverTimeToDateTime_epoch() { 17 | assertThat(Utils.receiverTimeToDateTime(0), 18 | is(Utils.DEXCOM_EPOCH.withZone(DateTimeZone.UTC))); 19 | } 20 | 21 | @Test 22 | public void testReceiverTimeToDateTime_positiveDelta() { 23 | int secondsDelta = 10; 24 | assertThat(Utils.receiverTimeToDateTime(secondsDelta), 25 | is(Utils.DEXCOM_EPOCH.plusSeconds(secondsDelta).withZone(DateTimeZone.UTC))); 26 | } 27 | 28 | @Test 29 | public void testReceiverTimeToDateTime_negativeDelta() { 30 | int secondsDelta = -10; 31 | assertThat(Utils.receiverTimeToDateTime(secondsDelta), 32 | is(Utils.DEXCOM_EPOCH.minusSeconds(10).withZone(DateTimeZone.UTC))); 33 | } 34 | 35 | @Test 36 | public void testGetTimeAgoString_ZeroDelta() { 37 | DateTime now = new DateTime(); 38 | assertThat(Utils.getTimeAgoString(new Period(now, now)), is("0 seconds ago")); 39 | } 40 | 41 | @Test 42 | public void testGetTimeAgoString_SecDelta() { 43 | DateTime now = new DateTime(); 44 | assertThat(Utils.getTimeAgoString(new Period(now, now.plusSeconds(1))), 45 | is("1 seconds ago")); 46 | } 47 | 48 | @Test 49 | public void testGetTimeAgoString_DayDelta() { 50 | DateTime now = new DateTime(); 51 | assertThat(Utils.getTimeAgoString(new Period(now, now.plusDays(1))), 52 | is("1 days ago")); 53 | } 54 | 55 | @Test 56 | public void testGetTimeAgoString_Multiple() { 57 | DateTime now = new DateTime(); 58 | assertThat(Utils.getTimeAgoString(new Period(now, 59 | now.plusMonths(1).plusDays(1).plusSeconds(3))), 60 | is("3 seconds, 1 days, and 1 months ago")); 61 | } 62 | 63 | @Test 64 | public void testBytesToHex_Simple() { 65 | assertThat(Utils.bytesToHex(new byte[]{0xA}), is("0A")); 66 | } 67 | 68 | @Test 69 | public void testBytesToHex_Multiple() { 70 | assertThat(Utils.bytesToHex(new byte[]{ 71 | UnsignedBytes.checkedCast(0xDE), 72 | UnsignedBytes.checkedCast(0xAD), 73 | UnsignedBytes.checkedCast(0xBE), 74 | UnsignedBytes.checkedCast(0xEF)}), 75 | is("DEADBEEF")); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/java/com/nightscout/android/settings/CustomSwitchPreference.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.android.settings; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.os.Build; 6 | import android.preference.SwitchPreference; 7 | import android.util.AttributeSet; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.Switch; 11 | 12 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) 13 | public class CustomSwitchPreference extends SwitchPreference { 14 | 15 | /** 16 | * Construct a new SwitchPreference with the given style options. 17 | * 18 | * @param context The Context that will style this preference 19 | * @param attrs Style attributes that differ from the default 20 | * @param defStyle Theme attribute defining the default style options 21 | */ 22 | public CustomSwitchPreference(Context context, AttributeSet attrs, int defStyle) { 23 | super(context, attrs, defStyle); 24 | } 25 | 26 | /** 27 | * Construct a new SwitchPreference with the given style options. 28 | * 29 | * @param context The Context that will style this preference 30 | * @param attrs Style attributes that differ from the default 31 | */ 32 | public CustomSwitchPreference(Context context, AttributeSet attrs) { 33 | super(context, attrs); 34 | } 35 | 36 | /** 37 | * Construct a new SwitchPreference with default style options. 38 | * 39 | * @param context The Context that will style this preference 40 | */ 41 | public CustomSwitchPreference(Context context) { 42 | super(context, null); 43 | } 44 | 45 | @Override 46 | protected void onBindView(View view) { 47 | // Clean listener before invoke SwitchPreference.onBindView 48 | ViewGroup viewGroup= (ViewGroup)view; 49 | clearListenerInViewGroup(viewGroup); 50 | super.onBindView(view); 51 | } 52 | 53 | /** 54 | * Clear listener in Switch for specify ViewGroup. 55 | * 56 | * @param viewGroup The ViewGroup that will need to clear the listener. 57 | */ 58 | private void clearListenerInViewGroup(ViewGroup viewGroup) { 59 | if (null == viewGroup) { 60 | return; 61 | } 62 | 63 | int count = viewGroup.getChildCount(); 64 | for(int n = 0; n < count; ++n) { 65 | View childView = viewGroup.getChildAt(n); 66 | if(childView instanceof Switch) { 67 | final Switch switchView = (Switch) childView; 68 | switchView.setOnCheckedChangeListener(null); 69 | return; 70 | } else if (childView instanceof ViewGroup){ 71 | ViewGroup childGroup = (ViewGroup)childView; 72 | clearListenerInViewGroup(childGroup); 73 | } 74 | } 75 | } 76 | 77 | } -------------------------------------------------------------------------------- /core/src/test/java/com/nightscout/core/dexcom/EgvRecordTest.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.dexcom; 2 | 3 | import com.nightscout.core.dexcom.records.EGVRecord; 4 | import com.nightscout.core.model.G4Noise; 5 | 6 | import org.json.JSONObject; 7 | import org.junit.Test; 8 | 9 | import static org.hamcrest.Matchers.is; 10 | import static org.junit.Assert.assertThat; 11 | 12 | public class EgvRecordTest { 13 | // EGV Record: C4881A0B61341A0B0500583E 14 | // EGV: 5 Trend: NOT_COMPUTABLE display time: 1417056321000, system time: 186288324, display time offset: 186266721, noise level: None 15 | // 16 | // EGV Record: 80BD1A0B1D691A0B7800217D 17 | // EGV: 120 Trend: DOUBLE_UP display time: 1417069821000 system time: 186301824 noise level: None 18 | 19 | @Test 20 | public void shouldParseEgvRecord() throws Exception { 21 | byte[] record = new byte[]{(byte) 0xC4, (byte) 0x88, (byte) 0x1A, (byte) 0x0B, (byte) 0x61, 22 | (byte) 0x34, (byte) 0x1A, (byte) 0x0B, (byte) 0x05, (byte) 0x00, (byte) 0x58, 23 | (byte) 0x3E}; 24 | EGVRecord egvRecord = new EGVRecord(record); 25 | assertThat(egvRecord.getBgMgdl(), is(5)); 26 | assertThat(egvRecord.getTrend(), is(TrendArrow.NOT_COMPUTABLE)); 27 | assertThat(egvRecord.getRawDisplayTimeSeconds(), is(186266721L)); 28 | assertThat(egvRecord.getRawSystemTimeSeconds(), is(186288324L)); 29 | assertThat(egvRecord.getNoiseMode(), is(G4Noise.NOT_COMPUTED)); 30 | } 31 | 32 | @Test(expected = InvalidRecordLengthException.class) 33 | public void shouldNotParseSmallEgvRecord() throws Exception { 34 | byte[] record = new byte[]{(byte) 0xC4, (byte) 0x88, (byte) 0x1A, (byte) 0x0B, (byte) 0x61, 35 | (byte) 0x34, (byte) 0x1A, (byte) 0x0B, (byte) 0x05, (byte) 0x00, (byte) 0x58}; 36 | EGVRecord egvRecord = new EGVRecord(record); 37 | } 38 | 39 | @Test(expected = InvalidRecordLengthException.class) 40 | public void shouldNotParseLargeEgvRecord() throws Exception { 41 | byte[] record = new byte[]{(byte) 0xC4, (byte) 0x88, (byte) 0x1A, (byte) 0x0B, (byte) 0x61, 42 | (byte) 0x34, (byte) 0x1A, (byte) 0x0B, (byte) 0x05, (byte) 0x00, (byte) 0x58, 43 | (byte) 0x3E, (byte) 0x00, (byte) 0x00}; 44 | EGVRecord egvRecord = new EGVRecord(record); 45 | } 46 | 47 | @Test 48 | public void shouldConvertToJsonString() throws Exception { 49 | byte[] record = new byte[]{(byte) 0xC4, (byte) 0x88, (byte) 0x1A, (byte) 0x0B, (byte) 0x61, 50 | (byte) 0x34, (byte) 0x1A, (byte) 0x0B, (byte) 0x05, (byte) 0x00, (byte) 0x58, 51 | (byte) 0x3E}; 52 | JSONObject obj = new JSONObject(); 53 | obj.put("sgv", 5); 54 | obj.put("date", Utils.receiverTimeToDate(186266721)); 55 | EGVRecord egvRecord = new EGVRecord(record); 56 | assertThat(egvRecord.toJSON().toString(), is(obj.toString())); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/model/ReceiverState.java: -------------------------------------------------------------------------------- 1 | // Code generated by Wire protocol buffer compiler, do not edit. 2 | // Source file: /Users/klee/Projects/Nightscout/android-uploader/core/src/main/java/com/nightscout/core/model/Download.proto 3 | package com.nightscout.core.model; 4 | 5 | import com.squareup.wire.Message; 6 | import com.squareup.wire.ProtoField; 7 | 8 | import java.util.Collections; 9 | import java.util.List; 10 | 11 | import static com.squareup.wire.Message.Datatype.ENUM; 12 | import static com.squareup.wire.Message.Datatype.UINT64; 13 | import static com.squareup.wire.Message.Label.REPEATED; 14 | import static com.squareup.wire.Message.Label.REQUIRED; 15 | 16 | public final class ReceiverState extends Message { 17 | 18 | public static final Long DEFAULT_TIMESTAMP_MS = 0L; 19 | public static final List DEFAULT_EVENT = Collections.emptyList(); 20 | 21 | @ProtoField(tag = 1, type = UINT64, label = REQUIRED) 22 | public final Long timestamp_ms; 23 | 24 | @ProtoField(tag = 2, type = ENUM, label = REPEATED) 25 | public final List event; 26 | 27 | public ReceiverState(Long timestamp_ms, List event) { 28 | this.timestamp_ms = timestamp_ms; 29 | this.event = immutableCopyOf(event); 30 | } 31 | 32 | private ReceiverState(Builder builder) { 33 | this(builder.timestamp_ms, builder.event); 34 | setBuilder(builder); 35 | } 36 | 37 | @Override 38 | public boolean equals(Object other) { 39 | if (other == this) return true; 40 | if (!(other instanceof ReceiverState)) return false; 41 | ReceiverState o = (ReceiverState) other; 42 | return equals(timestamp_ms, o.timestamp_ms) 43 | && equals(event, o.event); 44 | } 45 | 46 | @Override 47 | public int hashCode() { 48 | int result = hashCode; 49 | if (result == 0) { 50 | result = timestamp_ms != null ? timestamp_ms.hashCode() : 0; 51 | result = result * 37 + (event != null ? event.hashCode() : 1); 52 | hashCode = result; 53 | } 54 | return result; 55 | } 56 | 57 | public static final class Builder extends Message.Builder { 58 | 59 | public Long timestamp_ms; 60 | public List event; 61 | 62 | public Builder() { 63 | } 64 | 65 | public Builder(ReceiverState message) { 66 | super(message); 67 | if (message == null) return; 68 | this.timestamp_ms = message.timestamp_ms; 69 | this.event = copyOf(message.event); 70 | } 71 | 72 | public Builder timestamp_ms(Long timestamp_ms) { 73 | this.timestamp_ms = timestamp_ms; 74 | return this; 75 | } 76 | 77 | public Builder event(List event) { 78 | this.event = checkForNulls(event); 79 | return this; 80 | } 81 | 82 | @Override 83 | public ReceiverState build() { 84 | checkRequiredFields(); 85 | return new ReceiverState(this); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Nightscout 4 | 5 | Settings 6 | 7 | --- 8 | 9 | Oops! A crash has occurred and is being reported to nightscout so it can be fixed! 10 | Nightscout feedback 11 | Please let us know what you think. 12 | Please add your comments below: 13 | Thank you! 14 | 15 | Reading from device is disabled until you review and accept “I understand” in preferences 16 | Force sync 17 | Crash reporting is enabled 18 | Crash reporting is disabled 19 | Email 20 | This is the email address included in bug reports and automatic crash reports. This field is optional 21 | 22 | 23 | 24 | Close 25 | Feedback 26 | Gap sync 27 | Preferences 28 | USB 29 | Upload 30 | Dex Battery 31 | Time 32 | Error resolving Mongo host. Double check mongo url. 33 | Could not create API uploader. Check your settings. 34 | 35 | Donate data? 36 | Will you anonymously donate your CGM data for research in order to speed up diabetes innovation? 37 | Yes 38 | No 39 | Auto configure 40 | 41 | Time change detected 42 | Mongo uri has invalid syntax. Please double check. 43 | Cannot connect to %s, please check that you are connected to the internet, and that the url is correct. 44 | Invalid rest url %s, please double check formatting. 45 | Missing token for rest uri %s. 46 | Invalid Input 47 | OK 48 | Unknown version 49 | Camping 50 | Git hash 51 | 52 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/dexcom/records/GlucoseDataSet.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.dexcom.records; 2 | 3 | import com.nightscout.core.dexcom.TrendArrow; 4 | import com.nightscout.core.dexcom.Utils; 5 | import com.nightscout.core.model.GlucoseUnit; 6 | import com.nightscout.core.model.SensorEntry; 7 | import com.nightscout.core.model.SensorGlucoseValueEntry; 8 | import com.nightscout.core.utils.GlucoseReading; 9 | 10 | import java.util.Date; 11 | 12 | public class GlucoseDataSet { 13 | 14 | private Date systemTime; 15 | private Date displayTime; 16 | private GlucoseReading reading; 17 | private TrendArrow trend; 18 | private int noise; 19 | private long unfiltered; 20 | private long filtered; 21 | private int rssi; 22 | 23 | public GlucoseDataSet(SensorGlucoseValueEntry egvRecord) { 24 | systemTime = Utils.receiverTimeToDate(egvRecord.sys_timestamp_sec); 25 | displayTime = Utils.receiverTimeToDate(egvRecord.disp_timestamp_sec); 26 | reading = new GlucoseReading(egvRecord.sgv_mgdl, GlucoseUnit.MGDL); 27 | trend = TrendArrow.values()[egvRecord.trend.ordinal()]; 28 | noise = egvRecord.noise.ordinal(); 29 | } 30 | 31 | public GlucoseDataSet(EGVRecord egvRecord) { 32 | systemTime = egvRecord.getSystemTime(); 33 | displayTime = egvRecord.getDisplayTime(); 34 | reading = egvRecord.getReading(); 35 | trend = egvRecord.getTrend(); 36 | noise = egvRecord.getNoiseMode().ordinal(); 37 | } 38 | 39 | public GlucoseDataSet(EGVRecord egvRecord, SensorRecord sensorRecord) { 40 | this(egvRecord); 41 | // TODO check times match between record 42 | unfiltered = sensorRecord.getUnfiltered(); 43 | filtered = sensorRecord.getFiltered(); 44 | rssi = sensorRecord.getRssi(); 45 | } 46 | 47 | public GlucoseDataSet(SensorGlucoseValueEntry egvRecord, SensorEntry sensorRecord) { 48 | this.systemTime = Utils.receiverTimeToDate(egvRecord.sys_timestamp_sec); 49 | this.displayTime = Utils.receiverTimeToDate(egvRecord.disp_timestamp_sec); 50 | this.reading = new GlucoseReading(egvRecord.sgv_mgdl, GlucoseUnit.MGDL); 51 | this.trend = TrendArrow.values()[egvRecord.trend.ordinal()]; 52 | this.noise = egvRecord.noise.ordinal(); 53 | this.unfiltered = sensorRecord.unfiltered; 54 | this.filtered = sensorRecord.filtered; 55 | this.rssi = sensorRecord.rssi; 56 | } 57 | 58 | public Date getSystemTime() { 59 | return systemTime; 60 | } 61 | 62 | public Date getDisplayTime() { 63 | return displayTime; 64 | } 65 | 66 | public int getBgMgdl() { 67 | return reading.asMgdl(); 68 | } 69 | 70 | public GlucoseReading getReading() { 71 | return reading; 72 | } 73 | 74 | public TrendArrow getTrend() { 75 | return trend; 76 | } 77 | 78 | public String getTrendSymbol() { 79 | return trend.symbol(); 80 | } 81 | 82 | public int getNoise() { 83 | return noise; 84 | } 85 | 86 | public long getUnfiltered() { 87 | return unfiltered; 88 | } 89 | 90 | public long getFiltered() { 91 | return filtered; 92 | } 93 | 94 | public int getRssi() { 95 | return rssi; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/dexcom/records/GenericTimestampRecord.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.dexcom.records; 2 | 3 | import com.nightscout.core.dexcom.Utils; 4 | import com.squareup.wire.Message; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.nio.ByteBuffer; 10 | import java.nio.ByteOrder; 11 | import java.util.ArrayList; 12 | import java.util.Date; 13 | import java.util.List; 14 | 15 | abstract public class GenericTimestampRecord { 16 | protected final Logger log = LoggerFactory.getLogger(this.getClass()); 17 | protected final int OFFSET_SYS_TIME = 0; 18 | protected final int OFFSET_DISPLAY_TIME = 4; 19 | protected Date systemTime; 20 | protected long rawSystemTimeSeconds; 21 | protected Date displayTime; 22 | protected long rawDisplayTimeSeconds; 23 | 24 | public GenericTimestampRecord(byte[] packet) { 25 | rawSystemTimeSeconds = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(OFFSET_SYS_TIME); 26 | systemTime = Utils.receiverTimeToDate(rawSystemTimeSeconds); 27 | rawDisplayTimeSeconds = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(OFFSET_DISPLAY_TIME); 28 | displayTime = Utils.receiverTimeToDate(rawDisplayTimeSeconds); 29 | } 30 | 31 | public GenericTimestampRecord(Date displayTime, Date systemTime) { 32 | this.displayTime = displayTime; 33 | this.systemTime = systemTime; 34 | } 35 | 36 | public GenericTimestampRecord(long rawDisplayTimeSeconds, long rawSystemTimeSeconds) { 37 | this.rawDisplayTimeSeconds = rawDisplayTimeSeconds; 38 | this.rawSystemTimeSeconds = rawSystemTimeSeconds; 39 | this.systemTime = Utils.receiverTimeToDate(rawSystemTimeSeconds); 40 | this.displayTime = Utils.receiverTimeToDate(rawDisplayTimeSeconds); 41 | } 42 | 43 | public Date getSystemTime() { 44 | return systemTime; 45 | } 46 | 47 | public long getRawSystemTimeSeconds() { 48 | return rawSystemTimeSeconds; 49 | } 50 | 51 | public Date getDisplayTime() { 52 | return displayTime; 53 | } 54 | 55 | public long getRawDisplayTimeSeconds() { 56 | return rawDisplayTimeSeconds; 57 | } 58 | 59 | abstract protected Message toProtobuf(); 60 | 61 | public static List toProtobufList( 62 | List list, Class clazz) { 63 | List results = new ArrayList<>(); 64 | 65 | for (GenericTimestampRecord record : list) { 66 | results.add(clazz.cast(record.toProtobuf())); 67 | } 68 | return results; 69 | } 70 | 71 | @Override 72 | public boolean equals(Object o) { 73 | if (this == o) return true; 74 | if (o == null || getClass() != o.getClass()) return false; 75 | 76 | GenericTimestampRecord that = (GenericTimestampRecord) o; 77 | 78 | if (rawDisplayTimeSeconds != that.rawDisplayTimeSeconds) return false; 79 | if (rawSystemTimeSeconds != that.rawSystemTimeSeconds) return false; 80 | 81 | return true; 82 | } 83 | 84 | @Override 85 | public int hashCode() { 86 | int result = (int) (rawSystemTimeSeconds ^ (rawSystemTimeSeconds >>> 32)); 87 | result = 31 * result + (int) (rawDisplayTimeSeconds ^ (rawDisplayTimeSeconds >>> 32)); 88 | return result; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/src/main/res/values-it/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Nightscout 4 | 5 | Configurazione 6 | 7 | --- 8 | 9 | Oops! È crashato il software. Sto mandando un rapporto a Nightscout così magari lo possono sistemare! 10 | Feedback a Nightscout 11 | Per favore facci sapere cosa ne pensi 12 | Aggiungi i tuoi commenti qui sotto: 13 | Grazie! 14 | 15 | La lettura dal device è disabilitata finchè non leggi e accetti \"HO CAPITO\" nelle preferenze 16 | Forza invio dati ultimi 2 giorni 17 | Crash reporting is enabled 18 | Crash reporting is disabled 19 | Email 20 | This is the email address included in bug reports and automatic crash reports. This field is optional 21 | 22 | 23 | 24 | Esci 25 | Feedback 26 | Forza sincronizzazione 27 | Preferenze 28 | USB 29 | Upload 30 | 31 | Donate dati? 32 | Vuoi anonimo donare i vostri dati CGM per la ricerca , al fine di accelerare l\'innovazione del diabete ? 33 | 34 | No 35 | 36 | Error resolving Mongo host. Double check mongo url. 37 | Could not create API uploader. Check your settings. 38 | Auto configure 39 | 40 | Batteria Dex 41 | Ora 42 | Trovata modifica temporale 43 | Mongo uri ha sintassi non valida. Si prega di doppio controllo. 44 | Invalid Input 45 | Invalid rest url %s, si prega di controllare due volte la formattazione. 46 | Ok 47 | Missing token for rest uri %s. 48 | Impossibile connettersi a %s, si prega di verificare che si è connessi a Internet, e che l\'URL sia corretto. 49 | Versione sconosciuta 50 | Camping 51 | Git hash 52 | 53 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/dexcom/records/CalSubrecord.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.dexcom.records; 2 | 3 | import com.nightscout.core.dexcom.Utils; 4 | 5 | import java.nio.ByteBuffer; 6 | import java.nio.ByteOrder; 7 | import java.util.Date; 8 | 9 | public class CalSubrecord { 10 | private Date dateEntered; 11 | private int rawDateEntered; 12 | private int calBGL; 13 | private int calRaw; 14 | private Date dateApplied; 15 | private int rawDateApplied; 16 | private byte unk; 17 | 18 | public CalSubrecord(byte[] packet, long displayTimeOffset) { 19 | int delta = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(); 20 | rawDateEntered = delta; 21 | dateEntered = Utils.receiverTimeToDate(delta + displayTimeOffset); 22 | calBGL = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(4); 23 | calRaw = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(8); 24 | delta = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(12); 25 | rawDateApplied = delta; 26 | dateApplied = Utils.receiverTimeToDate(delta + displayTimeOffset); 27 | unk = packet[16]; 28 | } 29 | 30 | public CalSubrecord(int calBGL, int calRaw, Date dateApplied, Date dateEntered) { 31 | this.calBGL = calBGL; 32 | this.calRaw = calRaw; 33 | this.dateEntered = dateEntered; 34 | this.dateApplied = dateApplied; 35 | } 36 | 37 | public CalSubrecord(int calBGL, int calRaw, int dateApplied, int dateEntered) { 38 | this.calBGL = calBGL; 39 | this.calRaw = calRaw; 40 | this.rawDateEntered = dateEntered; 41 | this.rawDateApplied = dateApplied; 42 | } 43 | 44 | 45 | public Date getDateEntered() { 46 | return dateEntered; 47 | } 48 | 49 | public int getCalBGL() { 50 | return calBGL; 51 | } 52 | 53 | public int getCalRaw() { 54 | return calRaw; 55 | } 56 | 57 | public Date getDateApplied() { 58 | return dateApplied; 59 | } 60 | 61 | public byte getUnk() { 62 | return unk; 63 | } 64 | 65 | public int getRawDateEntered() { 66 | return rawDateEntered; 67 | } 68 | 69 | public void setRawDateEntered(int rawDateEntered) { 70 | this.rawDateEntered = rawDateEntered; 71 | } 72 | 73 | public int getRawDateApplied() { 74 | return rawDateApplied; 75 | } 76 | 77 | public void setRawDateApplied(int rawDateApplied) { 78 | this.rawDateApplied = rawDateApplied; 79 | } 80 | 81 | @Override 82 | public boolean equals(Object o) { 83 | if (this == o) return true; 84 | if (o == null || getClass() != o.getClass()) return false; 85 | 86 | CalSubrecord that = (CalSubrecord) o; 87 | 88 | if (calBGL != that.calBGL) return false; 89 | if (calRaw != that.calRaw) return false; 90 | if (rawDateApplied != that.rawDateApplied) return false; 91 | if (rawDateEntered != that.rawDateEntered) return false; 92 | 93 | return true; 94 | } 95 | 96 | @Override 97 | public int hashCode() { 98 | int result = rawDateEntered; 99 | result = 31 * result + calBGL; 100 | result = 31 * result + calRaw; 101 | result = 31 * result + rawDateApplied; 102 | return result; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /core/src/test/java/com/nightscout/core/dexcom/MeterRecordTest.java: -------------------------------------------------------------------------------- 1 | // Meter Record: 2880180BC52B180B71000A80180BAC 2 | // Record EGV: 113 Meter time: 186155018 display time: 1416926645000 system time: 186155048 3 | // Meter Record: 4CB51A0BE9601A0B46002EB51A0B73 4 | // Record EGV: 70 Meter time: 186299694 display time: 1417071321000 system time: 186299724 5 | // Meter Record: 63B51A0B00611A0B480045B51A0BB1 6 | // Record EGV: 72 Meter time: 186299717 display time: 1417071344000 system time: 186299747 7 | // Meter Record: 7CD01A0B1A7C1A0B06015ED01A0B06 8 | // Record EGV: 262 Meter time: 186306654 display time: 1417078282000 system time: 186306684 9 | // Meter Record: 2880180BC52B180B71000A80180BAC 10 | // Record EGV: 113 Meter time: 186155018 display time: 1416926645000 system time: 186155048 11 | // Meter Record: 4CB51A0BE9601A0B46002EB51A0B73 12 | // Record EGV: 70 Meter time: 186299694 display time: 1417071321000 system time: 186299724 13 | // Meter Record: 63B51A0B00611A0B480045B51A0BB1 14 | // Record EGV: 72 Meter time: 186299717 display time: 1417071344000 system time: 186299747 15 | // Meter Record: 7CD01A0B1A7C1A0B06015ED01A0B06 16 | // Record EGV: 262 Meter time: 186306654 display time: 1417078282000 system time: 186306684 17 | 18 | package com.nightscout.core.dexcom; 19 | 20 | import com.nightscout.core.dexcom.records.MeterRecord; 21 | 22 | import org.junit.Test; 23 | 24 | import static org.hamcrest.Matchers.is; 25 | import static org.junit.Assert.assertThat; 26 | 27 | public class MeterRecordTest { 28 | // Meter Record: 28 80 18 0B C5 2B 18 0B 71 00 0A 80 18 0B AC 29 | // Record EGV: 113 Meter time: 186155018 display time: 1416926645000 system time: 186155048 30 | // Meter Record: 7CD01A0B1A7C1A0B06015ED01A0B06 31 | // Record EGV: 262 Meter time: 186306654 display time: 1417078282000 system time: 186306684 32 | 33 | @Test 34 | public void shouldParseMeterRecord() throws Exception { 35 | byte[] record = new byte[]{(byte) 0x28, (byte) 0x80, (byte) 0x18, (byte) 0x0B, (byte) 0xC5, 36 | (byte) 0x2B, (byte) 0x18, (byte) 0x0B, (byte) 0x71, (byte) 0x00, (byte) 0x0A, 37 | (byte) 0x80, (byte) 0x18, (byte) 0x0B, (byte) 0xAC}; 38 | MeterRecord meterRecord = new MeterRecord(record); 39 | assertThat(meterRecord.getBgMgdl(), is(113)); 40 | assertThat(meterRecord.getRawDisplayTimeSeconds(), is(186133445L)); 41 | assertThat(meterRecord.getRawSystemTimeSeconds(), is(186155048L)); 42 | assertThat(meterRecord.getMeterTime(), is(186155018)); 43 | } 44 | 45 | @Test(expected = InvalidRecordLengthException.class) 46 | public void shouldNotParseSmallMeterRecord() throws Exception { 47 | byte[] record = new byte[]{(byte) 0x28, (byte) 0x80, (byte) 0x18, (byte) 0x0B, (byte) 0xC5, 48 | (byte) 0x2B, (byte) 0x18, (byte) 0x0B, (byte) 0x71, (byte) 0x00, (byte) 0x0A, 49 | (byte) 0x80, (byte) 0x18, (byte) 0x0B}; 50 | MeterRecord meterRecord = new MeterRecord(record); 51 | } 52 | 53 | @Test(expected = InvalidRecordLengthException.class) 54 | public void shouldNotParseLargeMeterRecord() throws Exception { 55 | byte[] record = new byte[]{(byte) 0x28, (byte) 0x80, (byte) 0x18, (byte) 0x0B, (byte) 0xC5, 56 | (byte) 0x2B, (byte) 0x18, (byte) 0x0B, (byte) 0x71, (byte) 0x00, (byte) 0x0A, 57 | (byte) 0x80, (byte) 0x18, (byte) 0x0B, (byte) 0x00, (byte) 0x00}; 58 | MeterRecord meterRecord = new MeterRecord(record); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 26 | 30 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 47 | 48 | 49 | 50 | 51 | 52 | 55 | 56 | 57 | 58 | 59 | 65 | 66 | 67 | 68 | 72 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/dexcom/records/SensorRecord.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.dexcom.records; 2 | 3 | import com.nightscout.core.dexcom.InvalidRecordLengthException; 4 | import com.nightscout.core.dexcom.Utils; 5 | import com.nightscout.core.model.SensorEntry; 6 | 7 | import java.nio.ByteBuffer; 8 | import java.nio.ByteOrder; 9 | import java.util.List; 10 | 11 | public class SensorRecord extends GenericTimestampRecord { 12 | public static final int RECORD_SIZE = 19; 13 | private long unfiltered; 14 | private long filtered; 15 | private int rssi; 16 | private int OFFSET_UNFILTERED = 8; 17 | private int OFFSET_FILTERED = 12; 18 | private int OFFSET_RSSI = 16; 19 | 20 | public SensorRecord(byte[] packet) { 21 | super(packet); 22 | if (packet.length != RECORD_SIZE) { 23 | throw new InvalidRecordLengthException("Unexpected record size: " + packet.length + 24 | ". Expected size: " + RECORD_SIZE + ". Unparsed record: " + Utils.bytesToHex(packet)); 25 | } 26 | unfiltered = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(OFFSET_UNFILTERED); 27 | filtered = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(OFFSET_FILTERED); 28 | rssi = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getShort(OFFSET_RSSI); 29 | } 30 | 31 | public SensorRecord(int filtered, int unfiltered, int rssi, long displayTime, int systemTime) { 32 | super(displayTime, systemTime); 33 | this.filtered = filtered; 34 | this.unfiltered = unfiltered; 35 | this.rssi = rssi; 36 | } 37 | 38 | public SensorRecord(SensorEntry sensor) { 39 | super(sensor.disp_timestamp_sec, sensor.sys_timestamp_sec); 40 | this.filtered = sensor.filtered; 41 | this.unfiltered = sensor.unfiltered; 42 | this.rssi = sensor.rssi; 43 | } 44 | 45 | public long getUnfiltered() { 46 | return unfiltered; 47 | } 48 | 49 | public long getFiltered() { 50 | return filtered; 51 | } 52 | 53 | public int getRssi() { 54 | return rssi; 55 | } 56 | 57 | @Override 58 | public SensorEntry toProtobuf() { 59 | SensorEntry.Builder builder = new SensorEntry.Builder(); 60 | return builder.sys_timestamp_sec(rawSystemTimeSeconds) 61 | .disp_timestamp_sec(rawDisplayTimeSeconds) 62 | .rssi(rssi) 63 | .filtered(filtered) 64 | .unfiltered(unfiltered) 65 | .build(); 66 | 67 | } 68 | 69 | public static List toProtobufList(List list) { 70 | return toProtobufList(list, SensorEntry.class); 71 | } 72 | 73 | @Override 74 | public boolean equals(Object o) { 75 | if (this == o) return true; 76 | if (o == null || getClass() != o.getClass()) return false; 77 | if (!super.equals(o)) return false; 78 | 79 | SensorRecord that = (SensorRecord) o; 80 | 81 | if (filtered != that.filtered) return false; 82 | if (rssi != that.rssi) return false; 83 | if (unfiltered != that.unfiltered) return false; 84 | 85 | return true; 86 | } 87 | 88 | @Override 89 | public int hashCode() { 90 | int result = super.hashCode(); 91 | result = 31 * result + (int) (unfiltered ^ (unfiltered >>> 32)); 92 | result = 31 * result + (int) (filtered ^ (filtered >>> 32)); 93 | result = 31 * result + rssi; 94 | return result; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/dexcom/records/PageHeader.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.dexcom.records; 2 | 3 | import com.nightscout.core.dexcom.CRC16; 4 | import com.nightscout.core.dexcom.CRCFailError; 5 | import com.nightscout.core.dexcom.Constants; 6 | import com.nightscout.core.dexcom.InvalidRecordLengthException; 7 | import com.nightscout.core.dexcom.RecordType; 8 | import com.nightscout.core.dexcom.Utils; 9 | 10 | import java.nio.ByteBuffer; 11 | import java.nio.ByteOrder; 12 | import java.util.Arrays; 13 | 14 | public class PageHeader { 15 | public static final int HEADER_SIZE=28; 16 | protected final int FIRSTRECORDINDEX_OFFSET=0; 17 | protected final int NUMRECS_OFFSET=4; 18 | protected final int RECTYPE_OFFSET=8; 19 | protected final int REV_OFFSET=9; 20 | protected final int PAGENUMBER_OFFSET=10; 21 | protected final int RESERVED2_OFFSET=14; 22 | protected final int RESERVED3_OFFSET=18; 23 | protected final int RESERVED4_OFFSET=22; 24 | 25 | protected int firstRecordIndex; 26 | protected int numOfRecords; 27 | protected RecordType recordType; 28 | protected byte revision; 29 | protected int pageNumber; 30 | protected int reserved2; 31 | protected int reserved3; 32 | protected int reserved4; 33 | protected byte[] crc=new byte[2]; 34 | 35 | 36 | public PageHeader(byte[] packet) { 37 | if (packet.length < HEADER_SIZE){ 38 | throw new InvalidRecordLengthException("Unexpected record size: " + packet.length + 39 | ". Expected size: " + HEADER_SIZE + ". Unparsed record: " + 40 | Utils.bytesToHex(packet)); 41 | } 42 | firstRecordIndex = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN) 43 | .getInt(FIRSTRECORDINDEX_OFFSET); 44 | numOfRecords = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(NUMRECS_OFFSET); 45 | recordType = RecordType.values()[packet[RECTYPE_OFFSET]]; 46 | revision = packet[REV_OFFSET]; 47 | pageNumber = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN) 48 | .getInt(PAGENUMBER_OFFSET); 49 | reserved2 = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(RESERVED2_OFFSET); 50 | reserved3 = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(RESERVED3_OFFSET); 51 | reserved4 = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(RESERVED4_OFFSET); 52 | System.arraycopy(packet, HEADER_SIZE-Constants.CRC_LEN, crc, 0, Constants.CRC_LEN); 53 | byte[] crc_calc = CRC16.calculate(packet, 0, HEADER_SIZE - Constants.CRC_LEN); 54 | if (!Arrays.equals(this.crc, crc_calc)) { 55 | throw new CRCFailError("CRC check failed. Was:" + Utils.bytesToHex(this.crc) + 56 | " Expected: " + Utils.bytesToHex(crc_calc)); 57 | } 58 | 59 | } 60 | 61 | public byte getRevision() { 62 | return revision; 63 | } 64 | 65 | public RecordType getRecordType() { 66 | return recordType; 67 | } 68 | 69 | public int getFirstRecordIndex() { 70 | return firstRecordIndex; 71 | } 72 | 73 | public int getNumOfRecords() { 74 | return numOfRecords; 75 | } 76 | 77 | public int getPageNumber() { 78 | return pageNumber; 79 | } 80 | 81 | public int getReserved2() { 82 | return reserved2; 83 | } 84 | 85 | public int getReserved3() { 86 | return reserved3; 87 | } 88 | 89 | public int getReserved4() { 90 | return reserved4; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/dexcom/records/MeterRecord.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.dexcom.records; 2 | 3 | import com.nightscout.core.dexcom.InvalidRecordLengthException; 4 | import com.nightscout.core.dexcom.Utils; 5 | import com.nightscout.core.model.GlucoseUnit; 6 | import com.nightscout.core.model.MeterEntry; 7 | import com.nightscout.core.utils.GlucoseReading; 8 | 9 | import java.nio.ByteBuffer; 10 | import java.nio.ByteOrder; 11 | import java.util.Date; 12 | import java.util.List; 13 | 14 | public class MeterRecord extends GenericTimestampRecord { 15 | public final static int RECORD_SIZE = 15; 16 | private int meterTime; 17 | private GlucoseReading reading; 18 | 19 | public MeterRecord(byte[] packet) { 20 | super(packet); 21 | if (packet.length != RECORD_SIZE) { 22 | throw new InvalidRecordLengthException("Unexpected record size: " + packet.length + 23 | ". Expected size: " + RECORD_SIZE + " record: " + Utils.bytesToHex(packet)); 24 | } 25 | int meterBG = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getShort(8); 26 | reading = new GlucoseReading(meterBG, GlucoseUnit.MGDL); 27 | meterTime = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(10); 28 | } 29 | 30 | public MeterRecord(int meterBgMgdl, int meterTime, Date displayTime, Date systemTime) { 31 | super(displayTime, systemTime); 32 | this.reading = new GlucoseReading(meterBgMgdl, GlucoseUnit.MGDL); 33 | this.meterTime = meterTime; 34 | } 35 | 36 | public MeterRecord(int meterBgMgdl, int meterTime, long displayTime, int systemTime) { 37 | super(displayTime, systemTime); 38 | this.reading = new GlucoseReading(meterBgMgdl, GlucoseUnit.MGDL); 39 | this.meterTime = meterTime; 40 | } 41 | 42 | public MeterRecord(MeterEntry meter) { 43 | super(meter.disp_timestamp_sec, meter.sys_timestamp_sec); 44 | this.reading = new GlucoseReading(meter.meter_bg_mgdl, GlucoseUnit.MGDL); 45 | this.meterTime = meter.meter_time; 46 | } 47 | 48 | public GlucoseReading getMeterBG() { 49 | return reading; 50 | } 51 | 52 | public int getBgMgdl() { 53 | return reading.asMgdl(); 54 | } 55 | 56 | public int getMeterTime() { 57 | return meterTime; 58 | } 59 | 60 | @Override 61 | public MeterEntry toProtobuf() { 62 | MeterEntry.Builder builder = new MeterEntry.Builder(); 63 | return builder.sys_timestamp_sec(rawSystemTimeSeconds) 64 | .disp_timestamp_sec(rawDisplayTimeSeconds) 65 | .meter_time(meterTime) 66 | .meter_bg_mgdl(reading.asMgdl()) 67 | .build(); 68 | } 69 | 70 | public static List toProtobufList(List list) { 71 | return toProtobufList(list, MeterEntry.class); 72 | } 73 | 74 | @Override 75 | public boolean equals(Object o) { 76 | if (this == o) return true; 77 | if (o == null || getClass() != o.getClass()) return false; 78 | if (!super.equals(o)) return false; 79 | 80 | MeterRecord that = (MeterRecord) o; 81 | 82 | if (meterTime != that.meterTime) return false; 83 | if (!reading.equals(that.reading)) return false; 84 | 85 | return true; 86 | } 87 | 88 | @Override 89 | public int hashCode() { 90 | int result = super.hashCode(); 91 | result = 31 * result + meterTime; 92 | result = 31 * result + reading.hashCode(); 93 | return result; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/src/test/java/com/nightscout/android/upload/UploaderTest.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.android.upload; 2 | 3 | import android.content.Intent; 4 | 5 | import com.google.common.base.Function; 6 | import com.google.common.collect.Lists; 7 | import com.nightscout.android.R; 8 | import com.nightscout.android.ToastReceiver; 9 | import com.nightscout.android.test.RobolectricTestBase; 10 | import com.nightscout.core.preferences.TestPreferences; 11 | 12 | import org.junit.Test; 13 | 14 | import static org.hamcrest.Matchers.is; 15 | import static org.hamcrest.Matchers.not; 16 | import static org.hamcrest.Matchers.nullValue; 17 | import static org.junit.Assert.assertThat; 18 | 19 | public class UploaderTest extends RobolectricTestBase { 20 | 21 | @Test 22 | public void shouldSendToastIntentOnInvalidMongoUri() throws Exception { 23 | TestPreferences prefs = new TestPreferences(); 24 | prefs.setMongoUploadEnabled(true); 25 | prefs.setMongoClientUri(null); 26 | 27 | whenOnBroadcastReceived(ToastReceiver.ACTION_SEND_NOTIFICATION, 28 | new Function() { 29 | @Override 30 | public Void apply(Intent input) { 31 | assertThat(input.getStringExtra(ToastReceiver.TOAST_MESSAGE), is(not(nullValue()))); 32 | assertThat(input.getStringExtra(ToastReceiver.TOAST_MESSAGE), 33 | is(getContext().getString(R.string.unknown_mongo_host))); 34 | return null; 35 | } 36 | }); 37 | 38 | new Uploader(getContext(), prefs); 39 | assertIntentSeen(); 40 | } 41 | 42 | @Test 43 | public void initializeShouldFailOnInvalidMongoUri() throws Exception { 44 | TestPreferences prefs = new TestPreferences(); 45 | prefs.setMongoUploadEnabled(true); 46 | prefs.setMongoClientUri("http://test.com"); 47 | Uploader uploader = new Uploader(getContext(), prefs); 48 | assertThat(uploader.areAllUploadersInitialized(), is(false)); 49 | } 50 | 51 | @Test 52 | public void shouldSendToastIntentOnInvalidRestv1Uri() throws Exception { 53 | TestPreferences prefs = new TestPreferences(); 54 | prefs.setRestApiEnabled(true); 55 | prefs.setRestApiBaseUris(Lists.newArrayList("http://test/v1")); 56 | 57 | whenOnBroadcastReceived(ToastReceiver.ACTION_SEND_NOTIFICATION, 58 | new Function() { 59 | @Override 60 | public Void apply(Intent input) { 61 | assertThat(input.getStringExtra(ToastReceiver.TOAST_MESSAGE), is(not(nullValue()))); 62 | assertThat(input.getStringExtra(ToastReceiver.TOAST_MESSAGE), 63 | is(getContext().getString(R.string.illegal_rest_url))); 64 | return null; 65 | } 66 | }); 67 | new Uploader(getContext(), prefs); 68 | assertIntentSeen(); 69 | } 70 | 71 | @Test 72 | public void shouldSendToastIntentOnInvalidRestUri() throws Exception { 73 | TestPreferences prefs = new TestPreferences(); 74 | prefs.setRestApiEnabled(true); 75 | prefs.setRestApiBaseUris(Lists.newArrayList("\\invalid")); 76 | 77 | whenOnBroadcastReceived(ToastReceiver.ACTION_SEND_NOTIFICATION, 78 | new Function() { 79 | @Override 80 | public Void apply(Intent input) { 81 | assertThat(input.getStringExtra(ToastReceiver.TOAST_MESSAGE), is(not(nullValue()))); 82 | assertThat(input.getStringExtra(ToastReceiver.TOAST_MESSAGE), 83 | is(getContext().getString(R.string.illegal_rest_url))); 84 | return null; 85 | } 86 | }); 87 | new Uploader(getContext(), prefs); 88 | assertIntentSeen(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/model/Download.proto: -------------------------------------------------------------------------------- 1 | option java_package = "com.nightscout.core.model"; 2 | 3 | option java_outer_classname = "G4Download"; 4 | 5 | message G4Download { 6 | repeated SensorGlucoseValueEntry sgv = 1; // Glucose records as reported by the G4 7 | optional GlucoseUnit units = 2; // Units reported by the device 8 | required string download_timestamp = 3; // ISO8601 timestamp reported by uploader 9 | optional uint64 receiver_system_time_sec = 4; // Raw value of the receiver's system time 10 | optional DownloadStatus download_status = 5 [default = NOT_APPLICABLE]; // Status of the download 11 | optional uint32 receiver_battery = 6; // Battery level as reported by the receiver 12 | optional uint32 uploader_battery = 7; // Battery level as reported by the uploader 13 | repeated MeterEntry meter = 8; 14 | repeated SensorEntry sensor = 9; 15 | repeated CalibrationEntry cal = 10; 16 | } 17 | 18 | enum GlucoseUnit { 19 | MGDL = 0; 20 | MMOL = 1; 21 | } 22 | 23 | enum DownloadStatus { 24 | SUCCESS = 0; 25 | NO_DATA = 1; 26 | DEVICE_NOT_FOUND = 2; 27 | IO_ERROR = 3; 28 | APPLICATION_ERROR = 4; 29 | UNKNOWN = 5; 30 | NOT_APPLICABLE = 6; 31 | } 32 | 33 | enum G4Trend { 34 | TREND_NONE = 0; 35 | DOUBLE_UP = 1; // More than 3 mg/dL per minute 36 | SINGLE_UP = 2; // +2 to +3 mg/dL per minute 37 | FORTY_FIVE_UP = 3; // +1 to +2 mg/dL per minute 38 | FLAT = 4; // +/- 1 mg/dL per minute 39 | FORTY_FIVE_DOWN = 5; // -1 to -2 mg/dL per minute 40 | SINGLE_DOWN = 6; // -2 to -3 mg/dL per minute 41 | DOUBLE_DOWN = 7; // more than -3 mg/dL per minute 42 | NOT_COMPUTABLE = 8; 43 | RATE_OUT_OF_RANGE = 9; 44 | } 45 | 46 | enum G4Noise { 47 | NOISE_NONE = 0; 48 | CLEAN = 1; 49 | LIGHT = 2; 50 | MEDIUM = 3; 51 | HEAVY = 4; 52 | NOT_COMPUTED = 5; 53 | MAX = 6; 54 | } 55 | 56 | enum ReceiverStatus { 57 | RECEIVER_CONNECTED = 0; // The receiver is connected to the uploader 58 | RECEIVER_DISCONNECTED = 1; // The receiver is not connected to the uploader 59 | } 60 | 61 | message SensorGlucoseValueEntry { 62 | required uint32 sgv_mgdl = 1; // Sensor Glucose Value 63 | optional uint64 sys_timestamp_sec = 2; // System timestamp - Timestamp representing the internal clock of the receiver 64 | optional uint64 disp_timestamp_sec = 3; // Display timestamp - Timestamp representing the user configured time displayed on the receiver 65 | optional G4Trend trend = 4; // G4 Glucose trend arrow 66 | optional G4Noise noise = 5; // Noise level that potentially affects the G4 sensor readings 67 | } 68 | 69 | message MeterEntry { 70 | required uint32 meter_bg_mgdl = 1; 71 | optional uint32 meter_time = 2; 72 | optional uint64 sys_timestamp_sec = 3; // System timestamp - Timestamp representing the internal clock of the receiver 73 | optional uint64 disp_timestamp_sec = 4; // Display timestamp - Timestamp representing the user configured time displayed on the receiver 74 | 75 | } 76 | 77 | message SensorEntry { 78 | required uint64 filtered = 1; 79 | optional uint64 unfiltered = 2; 80 | optional uint32 rssi = 3; 81 | optional uint64 sys_timestamp_sec = 4; // System timestamp - Timestamp representing the internal clock of the receiver 82 | optional uint64 disp_timestamp_sec = 5; // Display timestamp - Timestamp representing the user configured time displayed on the receiver 83 | 84 | } 85 | 86 | message CalibrationEntry { 87 | required double slope = 1; 88 | optional double intercept = 2; 89 | optional double scale = 3; 90 | optional double decay = 4; 91 | optional uint64 sys_timestamp_sec = 5; // System timestamp - Timestamp representing the internal clock of the receiver 92 | optional uint64 disp_timestamp_sec = 6; // Display timestamp - Timestamp representing the user configured time displayed on the receiver 93 | } 94 | 95 | message ReceiverState { 96 | required uint64 timestamp_ms = 1; 97 | repeated ReceiverStatus event = 2; 98 | } -------------------------------------------------------------------------------- /core/src/test/java/com/nightscout/core/utils/RestUriUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.utils; 2 | 3 | import org.junit.Test; 4 | 5 | import java.net.URI; 6 | import java.util.List; 7 | 8 | import static org.hamcrest.Matchers.is; 9 | import static org.junit.Assert.assertThat; 10 | 11 | public class RestUriUtilsTest { 12 | 13 | @Test 14 | public void testIsV1Uri_withV1Uri() { 15 | assertThat(RestUriUtils.isV1Uri(URI.create("http://example.com/v1")), is(true)); 16 | } 17 | 18 | @Test 19 | public void testIsV1Uri_withV1UriTrailingSlash() { 20 | assertThat(RestUriUtils.isV1Uri(URI.create("http://example.com/v1/")), is(true)); 21 | } 22 | 23 | @Test 24 | public void testIsV1Uri_withV1InPathButNotEnding() { 25 | assertThat(RestUriUtils.isV1Uri(URI.create("http://example.com/v1/test")), is(false)); 26 | } 27 | 28 | @Test 29 | public void testIsV1Uri_withV1NotInPath() { 30 | assertThat(RestUriUtils.isV1Uri(URI.create("http://example.v1/")), is(false)); 31 | } 32 | 33 | @Test 34 | public void testIsV1Uri_withLegacyUri() { 35 | assertThat(RestUriUtils.isV1Uri(URI.create("http://example.com/foo")), is(false)); 36 | } 37 | 38 | @Test 39 | public void testIsV1Uri_withNull() { 40 | assertThat(RestUriUtils.isV1Uri(null), is(false)); 41 | } 42 | 43 | @Test 44 | public void testHasToken_withNone() { 45 | assertThat(RestUriUtils.hasToken(URI.create("http://example.com")), is(false)); 46 | } 47 | 48 | @Test 49 | public void testHasToken_withOne() { 50 | assertThat(RestUriUtils.hasToken(URI.create("http://token@example.com")), is(true)); 51 | } 52 | 53 | @Test 54 | public void testRemoveToken_withToken() { 55 | assertThat(RestUriUtils.removeToken(URI.create("http://token@example.com")), 56 | is(URI.create("http://example.com"))); 57 | } 58 | 59 | @Test 60 | public void testRemoveToken_withoutToken() { 61 | assertThat(RestUriUtils.removeToken(URI.create("http://example.com")), 62 | is(URI.create("http://example.com"))); 63 | } 64 | 65 | @Test(expected = IllegalArgumentException.class) 66 | public void testGenerateSecret_withNull() { 67 | RestUriUtils.generateSecret(null); 68 | } 69 | 70 | @Test(expected = IllegalArgumentException.class) 71 | public void testGenerateSecret_withEmpty() { 72 | RestUriUtils.generateSecret(""); 73 | } 74 | 75 | @Test 76 | public void testGenerateSecret_withString() { 77 | assertThat(RestUriUtils.generateSecret("testingtesting"), is("b0212be2cc6081fba3e0b6f3dc6e0109d6f7b4cb")); 78 | } 79 | 80 | @Test 81 | public void testSplitIntoMultipleUris_Empty() { 82 | assertThat(RestUriUtils.splitIntoMultipleUris("").size(), is(0)); 83 | } 84 | 85 | @Test 86 | public void testSplitIntoMultipleUris_One() { 87 | List urls = RestUriUtils.splitIntoMultipleUris("one"); 88 | assertThat(urls.size(), is(1)); 89 | assertThat(urls.get(0), is("one")); 90 | } 91 | 92 | @Test 93 | public void testSplitIntoMultipleUris_ExtraWhitespace() { 94 | List urls = RestUriUtils.splitIntoMultipleUris("one \t\n"); 95 | assertThat(urls.size(), is(1)); 96 | assertThat(urls.get(0), is("one")); 97 | } 98 | 99 | @Test 100 | public void testSplitIntoMultipleUris_Multiple() { 101 | List urls = RestUriUtils.splitIntoMultipleUris("one two"); 102 | assertThat(urls.size(), is(2)); 103 | assertThat(urls.get(0), is("one")); 104 | assertThat(urls.get(1), is("two")); 105 | } 106 | 107 | @Test 108 | public void testSplitIntoMultipleUris_Whitespace() { 109 | List urls = RestUriUtils.splitIntoMultipleUris("one \t\ntwo"); 110 | assertThat(urls.size(), is(2)); 111 | assertThat(urls.get(0), is("one")); 112 | assertThat(urls.get(1), is("two")); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/preferences/TestPreferences.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.preferences; 2 | 3 | import com.nightscout.core.model.GlucoseUnit; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class TestPreferences implements NightscoutPreferences { 9 | private boolean restApiEnabled = false; 10 | private List restApiBaseUris = new ArrayList<>(); 11 | private boolean calibrationUploadEnabled = false; 12 | private boolean sensorUploadEnabled = false; 13 | private boolean mongoUploadEnabled = false; 14 | private String mongoClientUri = null; 15 | private String mongoCollection = "entries"; 16 | private String mongoDeviceStatusCollection = "devicestatus"; 17 | private boolean dataDonateEnabled; 18 | private GlucoseUnit units; 19 | private String pwdName; 20 | private boolean understand; 21 | private boolean askedForData; 22 | 23 | @Override 24 | public boolean isRestApiEnabled() { 25 | return restApiEnabled; 26 | } 27 | 28 | @Override 29 | public void setRestApiEnabled(boolean restApiEnabled) { 30 | this.restApiEnabled = restApiEnabled; 31 | } 32 | 33 | @Override 34 | public GlucoseUnit getPreferredUnits() { 35 | return units; 36 | } 37 | 38 | @Override 39 | public void setPreferredUnits(GlucoseUnit units) { 40 | this.units = units; 41 | } 42 | 43 | @Override 44 | public List getRestApiBaseUris() { 45 | return restApiBaseUris; 46 | } 47 | 48 | @Override 49 | public void setRestApiBaseUris(List restApiBaseUris) { 50 | this.restApiBaseUris = restApiBaseUris; 51 | } 52 | 53 | @Override 54 | public boolean isCalibrationUploadEnabled() { 55 | return calibrationUploadEnabled; 56 | } 57 | 58 | @Override 59 | public void setCalibrationUploadEnabled(boolean calibrationUploadEnabled) { 60 | this.calibrationUploadEnabled = calibrationUploadEnabled; 61 | } 62 | 63 | @Override 64 | public boolean isSensorUploadEnabled() { 65 | return sensorUploadEnabled; 66 | } 67 | 68 | @Override 69 | public void setSensorUploadEnabled(boolean sensorUploadEnabled) { 70 | this.sensorUploadEnabled = sensorUploadEnabled; 71 | } 72 | 73 | @Override 74 | public boolean isMongoUploadEnabled() { 75 | return mongoUploadEnabled; 76 | } 77 | 78 | @Override 79 | public boolean isDataDonateEnabled() { 80 | return dataDonateEnabled; 81 | } 82 | 83 | @Override 84 | public void setDataDonateEnabled(boolean toDonate) { 85 | this.dataDonateEnabled = toDonate; 86 | } 87 | 88 | @Override 89 | public void setMongoUploadEnabled(boolean mongoUploadEnabled) { 90 | this.mongoUploadEnabled = mongoUploadEnabled; 91 | } 92 | 93 | @Override 94 | public String getMongoClientUri() { 95 | return mongoClientUri; 96 | } 97 | 98 | @Override 99 | public void setMongoClientUri(String mongoClientUri) { 100 | this.mongoClientUri = mongoClientUri; 101 | } 102 | 103 | @Override 104 | public String getMongoCollection() { 105 | return mongoCollection; 106 | } 107 | 108 | @Override 109 | public void setMongoCollection(String mongoCollection) { 110 | this.mongoCollection = mongoCollection; 111 | } 112 | 113 | @Override 114 | public String getMongoDeviceStatusCollection() { 115 | return mongoDeviceStatusCollection; 116 | } 117 | 118 | @Override 119 | public boolean getIUnderstand() { 120 | return understand; 121 | } 122 | 123 | @Override 124 | public void setIUnderstand(boolean bool) { 125 | understand = bool; 126 | } 127 | 128 | @Override 129 | public void setMongoDeviceStatusCollection(String mongoDeviceStatusCollection) { 130 | this.mongoDeviceStatusCollection = mongoDeviceStatusCollection; 131 | } 132 | 133 | @Override 134 | public void setPwdName(String pwdName) { 135 | this.pwdName = pwdName; 136 | } 137 | 138 | @Override 139 | public boolean hasAskedForData() { 140 | return askedForData; 141 | } 142 | 143 | @Override 144 | public void setAskedForData(boolean askedForData) { 145 | this.askedForData = askedForData; 146 | } 147 | 148 | @Override 149 | public String getPwdName() { 150 | return pwdName; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /app/src/main/java/com/nightscout/android/drivers/USB/UsbSerialProber.java: -------------------------------------------------------------------------------- 1 | /* Copyright 2011 Google Inc. 2 | * 3 | * This library is free software; you can redistribute it and/or 4 | * modify it under the terms of the GNU Lesser General Public 5 | * License as published by the Free Software Foundation; either 6 | * version 2.1 of the License, or (at your option) any later version. 7 | * 8 | * This library is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | * Lesser General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU Lesser General Public 14 | * License along with this library; if not, write to the Free Software 15 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 16 | * USA. 17 | * 18 | * Project home page: http://code.google.com/p/usb-serial-for-android/ 19 | */ 20 | 21 | package com.nightscout.android.drivers.USB; 22 | 23 | import android.hardware.usb.UsbDevice; 24 | import android.hardware.usb.UsbDeviceConnection; 25 | import android.hardware.usb.UsbManager; 26 | import android.util.Log; 27 | 28 | /** 29 | * Helper class to assist in detecting and building {@link UsbSerialDriver} 30 | * instances from available hardware. 31 | * 32 | * @author mike wakerly (opensource@hoho.com) 33 | */ 34 | public enum UsbSerialProber { 35 | 36 | CDC_ACM_SERIAL { 37 | @Override 38 | public UsbSerialDriver getDevice(UsbManager manager, UsbDevice usbDevice) { 39 | final UsbDeviceConnection connection = manager.openDevice(usbDevice); 40 | if (connection == null) { 41 | return null; 42 | } 43 | return new CdcAcmSerialDriver(usbDevice, connection, manager); 44 | } 45 | }; 46 | 47 | private static final String TAG = UsbSerialProber.class.getSimpleName(); 48 | 49 | /** 50 | * Builds a new {@link UsbSerialDriver} instance from the raw device, or 51 | * returns null if it could not be built (for example, if the 52 | * probe failed). 53 | * 54 | * @param manager the {@link android.hardware.usb.UsbManager} to use 55 | * @param usbDevice the raw {@link android.hardware.usb.UsbDevice} to use 56 | * @return the first available {@link UsbSerialDriver}, or {@code null} if 57 | * no devices could be acquired 58 | */ 59 | public abstract UsbSerialDriver getDevice(final UsbManager manager, final UsbDevice usbDevice); 60 | 61 | /** 62 | * Acquires and returns the first available serial device among all 63 | * available {@link android.hardware.usb.UsbDevice}s, or returns {@code null} if no device could 64 | * be acquired. 65 | * 66 | * @param usbManager the {@link android.hardware.usb.UsbManager} to use 67 | * @return the first available {@link UsbSerialDriver}, or {@code null} if 68 | * no devices could be acquired 69 | */ 70 | public static UsbSerialDriver acquire(final UsbManager usbManager) { 71 | for (final UsbDevice usbDevice : usbManager.getDeviceList().values()) { 72 | final UsbSerialDriver probedDevice = acquire(usbManager, usbDevice); 73 | if (probedDevice != null) { 74 | return probedDevice; 75 | } 76 | } 77 | return null; 78 | } 79 | 80 | /** 81 | * Builds and returns a new {@link UsbSerialDriver} from the given 82 | * {@link android.hardware.usb.UsbDevice}, or returns {@code null} if no drivers supported this 83 | * device. 84 | * 85 | * @param usbManager the {@link android.hardware.usb.UsbManager} to use 86 | * @param usbDevice the {@link android.hardware.usb.UsbDevice} to use 87 | * @return a new {@link UsbSerialDriver}, or {@code null} if no devices 88 | * could be acquired 89 | */ 90 | public static UsbSerialDriver acquire(final UsbManager usbManager, final UsbDevice usbDevice) { 91 | if (!usbManager.hasPermission(usbDevice)) { 92 | Log.i(TAG, "No permission for " + usbDevice.getVendorId() + " " + usbDevice.getProductId()); 93 | return null; 94 | } 95 | for (final UsbSerialProber prober : values()) { 96 | final UsbSerialDriver probedDevice = prober.getDevice(usbManager, usbDevice); 97 | if (probedDevice != null) { 98 | return probedDevice; 99 | } 100 | } 101 | return null; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /core/src/test/java/com/nightscout/core/dexcom/PacketBuilderTest.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.dexcom; 2 | 3 | import com.google.common.collect.Lists; 4 | 5 | import org.junit.Test; 6 | 7 | import java.util.ArrayList; 8 | 9 | import static org.hamcrest.Matchers.is; 10 | import static org.junit.Assert.assertThat; 11 | 12 | public class PacketBuilderTest { 13 | 14 | @Test 15 | public void testPingCommand() { 16 | byte[] pingCommand = new byte[]{0x01, 0x06, 0x00, 0x0A, 0x5E, 0x65}; 17 | PacketBuilder builder = new PacketBuilder(Command.PING); 18 | assertThat(builder.build(), is(pingCommand)); 19 | } 20 | 21 | // Command: READ_DATABASE_PAGE_RANGE 22 | // Packet: 01070010048BB8 23 | // Command: READ_DATABASE_PAGE_RANGE 24 | // Packet: 010700100A4559 25 | // Command: READ_DATABASE_PAGE_RANGE 26 | // Packet: 01070010036CC8 27 | // Command: READ_DATABASE_PAGE_RANGE 28 | // Packet: 0107001005AAA8 29 | // Command: READ_DATABASE_PAGE_RANGE 30 | // Packet: 01070010048BB8 31 | // Command: READ_DATABASE_PAGE_RANGE 32 | // Packet: 010700100A4559 33 | // Command: READ_DATABASE_PAGE_RANGE 34 | // Packet: 01070010036CC8 35 | // Command: READ_DATABASE_PAGE_RANGE 36 | // Packet: 0107001005AAA8 37 | @Test 38 | public void testReadDatabasePageRangeCommand() { 39 | byte[] readDatabasePageRangeCommand = new byte[]{0x01, 0x07, 0x00, 0x10, 0x04, (byte) 0x8B, (byte) 0xB8}; 40 | PacketBuilder builder = new PacketBuilder(Command.READ_DATABASE_PAGE_RANGE, Lists.newArrayList((byte) 0x04)); 41 | assertThat(builder.build(), is(readDatabasePageRangeCommand)); 42 | } 43 | 44 | // Command: READ_DATABASE_PAGES 45 | // Packet: 010C00110488000000013263 46 | // Command: READ_DATABASE_PAGES 47 | // Packet: 010C001104890000000163C9 48 | // Command: READ_DATABASE_PAGES 49 | // Packet: 010C00110A01000000013D69 50 | // Command: READ_DATABASE_PAGES 51 | // Packet: 010C001103CE000000019E77 52 | // Command: READ_DATABASE_PAGES 53 | // Packet: 010C001103CF00000001CFDD 54 | // Command: READ_DATABASE_PAGES 55 | // Packet: 010C00110526000000015EC3 56 | // Command: READ_DATABASE_PAGES 57 | // Packet: 010C00110488000000013263 58 | // Command: READ_DATABASE_PAGES 59 | // Packet: 010C001104890000000163C9 60 | // Command: READ_DATABASE_PAGES 61 | // Packet: 010C00110A01000000013D69 62 | // Command: READ_DATABASE_PAGES 63 | // Packet: 010C001103CE000000019E77 64 | // Command: READ_DATABASE_PAGES 65 | // Packet: 010C001103CF00000001CFDD 66 | // Command: READ_DATABASE_PAGES 67 | // Packet: 010C00110526000000015EC3 68 | @Test 69 | public void testReadDatabasePagesCommand() { 70 | byte[] readDatabasePages = new byte[]{0x01, 0x0C, 0x00, 0x11, 0x05, 0x26, 0x00, 0x00, 0x00, 0x01, 0x5E, (byte) 0xC3}; 71 | ArrayList payload = Lists.newArrayList((byte) 0x05, (byte) 0x26, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01); 72 | PacketBuilder builder = new PacketBuilder(Command.READ_DATABASE_PAGES, payload); 73 | assertThat(builder.build(), is(readDatabasePages)); 74 | } 75 | 76 | // Command: READ_DISPLAY_TIME_OFFSET 77 | // Packet: 0106001D8807 78 | // Command: READ_DISPLAY_TIME_OFFSET 79 | // Packet: 0106001D8807 80 | @Test 81 | public void testReadDisplayTimeOffsetCommand() { 82 | byte[] readDatabasePages = new byte[]{0x01, 0x06, 0x00, 0x1D, (byte) 0x88, 0x07}; 83 | PacketBuilder builder = new PacketBuilder(Command.READ_DISPLAY_TIME_OFFSET); 84 | assertThat(builder.build(), is(readDatabasePages)); 85 | } 86 | 87 | // Command: READ_BATTERY_LEVEL 88 | // Packet: 0106002157F0 89 | @Test 90 | public void testReadBatteryLevelCommand() { 91 | byte[] readBatteryLevel = new byte[]{0x01, 0x06, 0x00, 0x21, 0x57, (byte) 0xF0}; 92 | PacketBuilder builder = new PacketBuilder(Command.READ_BATTERY_LEVEL); 93 | assertThat(builder.build(), is(readBatteryLevel)); 94 | } 95 | 96 | // Command: READ_SYSTEM_TIME 97 | // Packet: 0106002234C0 98 | // Command: READ_SYSTEM_TIME 99 | // Packet: 0106002234C0 100 | // Command: READ_SYSTEM_TIME 101 | // Packet: 0106002234C0 102 | // Command: READ_SYSTEM_TIME 103 | // Packet: 0106002234C0 104 | @Test 105 | public void testReadSystemTimeCommand() { 106 | byte[] readSystemTime = new byte[]{0x01, 0x06, 0x00, 0x22, 0x34, (byte) 0xC0}; 107 | PacketBuilder builder = new PacketBuilder(Command.READ_SYSTEM_TIME); 108 | assertThat(builder.build(), is(readSystemTime)); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/model/MeterEntry.java: -------------------------------------------------------------------------------- 1 | // Code generated by Wire protocol buffer compiler, do not edit. 2 | // Source file: /Users/klee/Projects/Nightscout/android-uploader/core/src/main/java/com/nightscout/core/model/Download.proto 3 | package com.nightscout.core.model; 4 | 5 | import com.squareup.wire.Message; 6 | import com.squareup.wire.ProtoField; 7 | 8 | import static com.squareup.wire.Message.Datatype.UINT32; 9 | import static com.squareup.wire.Message.Datatype.UINT64; 10 | import static com.squareup.wire.Message.Label.REQUIRED; 11 | 12 | public final class MeterEntry extends Message { 13 | 14 | public static final Integer DEFAULT_METER_BG_MGDL = 0; 15 | public static final Integer DEFAULT_METER_TIME = 0; 16 | public static final Long DEFAULT_SYS_TIMESTAMP_SEC = 0L; 17 | public static final Long DEFAULT_DISP_TIMESTAMP_SEC = 0L; 18 | 19 | @ProtoField(tag = 1, type = UINT32, label = REQUIRED) 20 | public final Integer meter_bg_mgdl; 21 | 22 | @ProtoField(tag = 2, type = UINT32) 23 | public final Integer meter_time; 24 | 25 | @ProtoField(tag = 3, type = UINT64) 26 | public final Long sys_timestamp_sec; 27 | 28 | /** 29 | * System timestamp - Timestamp representing the internal clock of the receiver 30 | */ 31 | @ProtoField(tag = 4, type = UINT64) 32 | public final Long disp_timestamp_sec; 33 | 34 | public MeterEntry(Integer meter_bg_mgdl, Integer meter_time, Long sys_timestamp_sec, Long disp_timestamp_sec) { 35 | this.meter_bg_mgdl = meter_bg_mgdl; 36 | this.meter_time = meter_time; 37 | this.sys_timestamp_sec = sys_timestamp_sec; 38 | this.disp_timestamp_sec = disp_timestamp_sec; 39 | } 40 | 41 | private MeterEntry(Builder builder) { 42 | this(builder.meter_bg_mgdl, builder.meter_time, builder.sys_timestamp_sec, builder.disp_timestamp_sec); 43 | setBuilder(builder); 44 | } 45 | 46 | @Override 47 | public boolean equals(Object other) { 48 | if (other == this) return true; 49 | if (!(other instanceof MeterEntry)) return false; 50 | MeterEntry o = (MeterEntry) other; 51 | return equals(meter_bg_mgdl, o.meter_bg_mgdl) 52 | && equals(meter_time, o.meter_time) 53 | && equals(sys_timestamp_sec, o.sys_timestamp_sec) 54 | && equals(disp_timestamp_sec, o.disp_timestamp_sec); 55 | } 56 | 57 | @Override 58 | public int hashCode() { 59 | int result = hashCode; 60 | if (result == 0) { 61 | result = meter_bg_mgdl != null ? meter_bg_mgdl.hashCode() : 0; 62 | result = result * 37 + (meter_time != null ? meter_time.hashCode() : 0); 63 | result = result * 37 + (sys_timestamp_sec != null ? sys_timestamp_sec.hashCode() : 0); 64 | result = result * 37 + (disp_timestamp_sec != null ? disp_timestamp_sec.hashCode() : 0); 65 | hashCode = result; 66 | } 67 | return result; 68 | } 69 | 70 | public static final class Builder extends Message.Builder { 71 | 72 | public Integer meter_bg_mgdl; 73 | public Integer meter_time; 74 | public Long sys_timestamp_sec; 75 | public Long disp_timestamp_sec; 76 | 77 | public Builder() { 78 | } 79 | 80 | public Builder(MeterEntry message) { 81 | super(message); 82 | if (message == null) return; 83 | this.meter_bg_mgdl = message.meter_bg_mgdl; 84 | this.meter_time = message.meter_time; 85 | this.sys_timestamp_sec = message.sys_timestamp_sec; 86 | this.disp_timestamp_sec = message.disp_timestamp_sec; 87 | } 88 | 89 | public Builder meter_bg_mgdl(Integer meter_bg_mgdl) { 90 | this.meter_bg_mgdl = meter_bg_mgdl; 91 | return this; 92 | } 93 | 94 | public Builder meter_time(Integer meter_time) { 95 | this.meter_time = meter_time; 96 | return this; 97 | } 98 | 99 | public Builder sys_timestamp_sec(Long sys_timestamp_sec) { 100 | this.sys_timestamp_sec = sys_timestamp_sec; 101 | return this; 102 | } 103 | 104 | /** 105 | * System timestamp - Timestamp representing the internal clock of the receiver 106 | */ 107 | public Builder disp_timestamp_sec(Long disp_timestamp_sec) { 108 | this.disp_timestamp_sec = disp_timestamp_sec; 109 | return this; 110 | } 111 | 112 | @Override 113 | public MeterEntry build() { 114 | checkRequiredFields(); 115 | return new MeterEntry(this); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/dexcom/records/EGVRecord.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.dexcom.records; 2 | 3 | import com.nightscout.core.dexcom.Constants; 4 | import com.nightscout.core.dexcom.InvalidRecordLengthException; 5 | import com.nightscout.core.dexcom.TrendArrow; 6 | import com.nightscout.core.dexcom.Utils; 7 | import com.nightscout.core.model.G4Noise; 8 | import com.nightscout.core.model.GlucoseUnit; 9 | import com.nightscout.core.model.SensorGlucoseValueEntry; 10 | import com.nightscout.core.utils.GlucoseReading; 11 | 12 | import org.json.JSONException; 13 | import org.json.JSONObject; 14 | 15 | import java.nio.ByteBuffer; 16 | import java.nio.ByteOrder; 17 | import java.util.Date; 18 | import java.util.List; 19 | 20 | public class EGVRecord extends GenericTimestampRecord { 21 | public final static int RECORD_SIZE = 12; 22 | private GlucoseReading reading; 23 | private TrendArrow trend; 24 | private G4Noise noiseMode; 25 | 26 | public EGVRecord(byte[] packet) { 27 | super(packet); 28 | if (packet.length != RECORD_SIZE) { 29 | throw new InvalidRecordLengthException("Unexpected record size: " + packet.length + 30 | ". Expected size: " + RECORD_SIZE + ". Unparsed record: " + Utils.bytesToHex(packet)); 31 | } 32 | int bGValue = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getShort(8) & Constants.EGV_VALUE_MASK; 33 | reading = new GlucoseReading(bGValue, GlucoseUnit.MGDL); 34 | byte trendAndNoise = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).get(10); 35 | int trendValue = trendAndNoise & Constants.EGV_TREND_ARROW_MASK; 36 | byte noiseValue = (byte) ((trendAndNoise & Constants.EGV_NOISE_MASK) >> 4); 37 | trend = TrendArrow.values()[trendValue]; 38 | noiseMode = G4Noise.values()[noiseValue]; 39 | } 40 | 41 | public EGVRecord(int bGValueMgdl, TrendArrow trend, Date displayTime, Date systemTime, G4Noise noise) { 42 | super(displayTime, systemTime); 43 | this.reading = new GlucoseReading(bGValueMgdl, GlucoseUnit.MGDL); 44 | this.trend = trend; 45 | this.noiseMode = noise; 46 | } 47 | 48 | public EGVRecord(int bGValueMgdl, TrendArrow trend, long displayTime, long systemTime, G4Noise noise) { 49 | super(displayTime, systemTime); 50 | this.reading = new GlucoseReading(bGValueMgdl, GlucoseUnit.MGDL); 51 | this.trend = trend; 52 | this.noiseMode = noise; 53 | } 54 | 55 | public EGVRecord(SensorGlucoseValueEntry sgv) { 56 | super(sgv.disp_timestamp_sec, sgv.sys_timestamp_sec); 57 | this.reading = new GlucoseReading(sgv.sgv_mgdl, GlucoseUnit.MGDL); 58 | this.trend = TrendArrow.values()[sgv.trend.ordinal()]; 59 | this.noiseMode = sgv.noise; 60 | } 61 | 62 | public int getBgMgdl() { 63 | return reading.asMgdl(); 64 | } 65 | 66 | public TrendArrow getTrend() { 67 | return trend; 68 | } 69 | 70 | public G4Noise getNoiseMode() { 71 | return noiseMode; 72 | } 73 | 74 | public JSONObject toJSON() throws JSONException { 75 | JSONObject obj = new JSONObject(); 76 | obj.put("sgv", getBgMgdl()); 77 | obj.put("date", getDisplayTime()); 78 | return obj; 79 | } 80 | 81 | @Override 82 | public SensorGlucoseValueEntry toProtobuf() { 83 | SensorGlucoseValueEntry.Builder builder = new SensorGlucoseValueEntry.Builder(); 84 | return builder.sys_timestamp_sec(rawSystemTimeSeconds) 85 | .disp_timestamp_sec(rawDisplayTimeSeconds) 86 | .sgv_mgdl(reading.asMgdl()) 87 | .trend(trend.toProtobuf()) 88 | .noise(noiseMode) 89 | .build(); 90 | } 91 | 92 | public static List toProtobufList(List list) { 93 | return toProtobufList(list, SensorGlucoseValueEntry.class); 94 | } 95 | 96 | public GlucoseReading getReading() { 97 | return reading; 98 | } 99 | 100 | @Override 101 | public boolean equals(Object o) { 102 | if (this == o) return true; 103 | if (o == null || getClass() != o.getClass()) return false; 104 | if (!super.equals(o)) return false; 105 | 106 | EGVRecord egvRecord = (EGVRecord) o; 107 | 108 | if (noiseMode != egvRecord.noiseMode) return false; 109 | if (!reading.equals(egvRecord.reading)) return false; 110 | if (trend != egvRecord.trend) return false; 111 | 112 | return true; 113 | } 114 | 115 | @Override 116 | public int hashCode() { 117 | int result = super.hashCode(); 118 | result = 31 * result + reading.hashCode(); 119 | result = 31 * result + trend.hashCode(); 120 | result = 31 * result + noiseMode.hashCode(); 121 | return result; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/barcode/NSBarcodeConfig.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.barcode; 2 | 3 | import com.google.common.base.Optional; 4 | 5 | import org.json.JSONArray; 6 | import org.json.JSONException; 7 | import org.json.JSONObject; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | /** 15 | * A class to manage barcode configuration of the uploader 16 | */ 17 | public class NSBarcodeConfig { 18 | protected static final Logger log = LoggerFactory.getLogger(NSBarcodeConfig.class); 19 | private JSONObject config = new JSONObject(); 20 | 21 | public NSBarcodeConfig(String decodeResults) { 22 | configureBarcode(decodeResults); 23 | } 24 | 25 | public void configureBarcode(String jsonConfig){ 26 | if (jsonConfig == null){ 27 | throw new IllegalArgumentException("Null barcode"); 28 | } 29 | try { 30 | this.config = new JSONObject(jsonConfig); 31 | } catch (JSONException e) { 32 | return; 33 | } 34 | } 35 | 36 | public Optional getMongoUri() { 37 | String mongoUri = null; 38 | try { 39 | if (hasMongoConfig()) { 40 | mongoUri = config.getJSONObject(NSBarcodeConfigKeys.MONGO_CONFIG).getString(NSBarcodeConfigKeys.MONGO_URI); 41 | } else { 42 | return Optional.absent(); 43 | } 44 | } catch (JSONException e) { 45 | return Optional.absent(); 46 | } 47 | return Optional.of(mongoUri); 48 | } 49 | 50 | public List getApiUris() { 51 | List apiUris = new ArrayList<>(); 52 | if (hasApiConfig()){ 53 | JSONArray jsonArray = null; 54 | try { 55 | jsonArray = config.getJSONObject(NSBarcodeConfigKeys.API_CONFIG) 56 | .getJSONArray(NSBarcodeConfigKeys.API_URI); 57 | } catch (JSONException e) { 58 | log.error("Invalid json array: " + config.toString()); 59 | return apiUris; 60 | } 61 | for (int index = 0; index < jsonArray.length(); index++) { 62 | try { 63 | apiUris.add(jsonArray.getString(index)); 64 | } catch (JSONException e) { 65 | log.error("Invalid child json object: " + config.toString()); 66 | } 67 | } 68 | } 69 | return apiUris; 70 | } 71 | 72 | public Optional getMongoCollection() { 73 | if (!hasMongoConfig()) { 74 | return Optional.absent(); 75 | } 76 | String mongoCollection = null; 77 | try { 78 | if (config.getJSONObject(NSBarcodeConfigKeys.MONGO_CONFIG).has(NSBarcodeConfigKeys.MONGO_COLLECTION)) { 79 | mongoCollection = config.getJSONObject(NSBarcodeConfigKeys.MONGO_CONFIG) 80 | .getString(NSBarcodeConfigKeys.MONGO_COLLECTION); 81 | } 82 | } catch (JSONException e) { 83 | // Should not see this 84 | log.warn("JSON exception: ", e); 85 | } 86 | return Optional.fromNullable(mongoCollection); 87 | } 88 | 89 | public Optional getMongoDeviceStatusCollection(){ 90 | if (! config.has(NSBarcodeConfigKeys.MONGO_CONFIG)) { 91 | return Optional.absent(); 92 | } 93 | String deviceStatusCollection = null; 94 | try { 95 | if (config.has(NSBarcodeConfigKeys.MONGO_CONFIG) && 96 | config.getJSONObject(NSBarcodeConfigKeys.MONGO_CONFIG).has(NSBarcodeConfigKeys.MONGO_COLLECTION)) { 97 | deviceStatusCollection = config.getJSONObject(NSBarcodeConfigKeys.MONGO_CONFIG) 98 | .getString(NSBarcodeConfigKeys.MONGO_DEVICE_STATUS_COLLECTION); 99 | } 100 | } catch (JSONException e) { 101 | // Should not see this 102 | log.warn("JSON exception: ", e); 103 | } 104 | return Optional.fromNullable(deviceStatusCollection); 105 | } 106 | 107 | public boolean hasMongoConfig(){ 108 | try { 109 | return config.has(NSBarcodeConfigKeys.MONGO_CONFIG) && 110 | config.getJSONObject(NSBarcodeConfigKeys.MONGO_CONFIG).has(NSBarcodeConfigKeys.MONGO_URI); 111 | } catch (JSONException e) { 112 | return false; 113 | } 114 | } 115 | 116 | public boolean hasApiConfig(){ 117 | try { 118 | return config.has(NSBarcodeConfigKeys.API_CONFIG) && 119 | config.getJSONObject(NSBarcodeConfigKeys.API_CONFIG) 120 | .getJSONArray(NSBarcodeConfigKeys.API_URI).length() > 0; 121 | } catch (JSONException e) { 122 | return false; 123 | } 124 | } 125 | } -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/model/SensorEntry.java: -------------------------------------------------------------------------------- 1 | // Code generated by Wire protocol buffer compiler, do not edit. 2 | // Source file: /Users/klee/Projects/Nightscout/android-uploader/core/src/main/java/com/nightscout/core/model/Download.proto 3 | package com.nightscout.core.model; 4 | 5 | import com.squareup.wire.Message; 6 | import com.squareup.wire.ProtoField; 7 | 8 | import static com.squareup.wire.Message.Datatype.UINT32; 9 | import static com.squareup.wire.Message.Datatype.UINT64; 10 | import static com.squareup.wire.Message.Label.REQUIRED; 11 | 12 | public final class SensorEntry extends Message { 13 | 14 | public static final Long DEFAULT_FILTERED = 0L; 15 | public static final Long DEFAULT_UNFILTERED = 0L; 16 | public static final Integer DEFAULT_RSSI = 0; 17 | public static final Long DEFAULT_SYS_TIMESTAMP_SEC = 0L; 18 | public static final Long DEFAULT_DISP_TIMESTAMP_SEC = 0L; 19 | 20 | @ProtoField(tag = 1, type = UINT64, label = REQUIRED) 21 | public final Long filtered; 22 | 23 | @ProtoField(tag = 2, type = UINT64) 24 | public final Long unfiltered; 25 | 26 | @ProtoField(tag = 3, type = UINT32) 27 | public final Integer rssi; 28 | 29 | @ProtoField(tag = 4, type = UINT64) 30 | public final Long sys_timestamp_sec; 31 | 32 | /** 33 | * System timestamp - Timestamp representing the internal clock of the receiver 34 | */ 35 | @ProtoField(tag = 5, type = UINT64) 36 | public final Long disp_timestamp_sec; 37 | 38 | public SensorEntry(Long filtered, Long unfiltered, Integer rssi, Long sys_timestamp_sec, Long disp_timestamp_sec) { 39 | this.filtered = filtered; 40 | this.unfiltered = unfiltered; 41 | this.rssi = rssi; 42 | this.sys_timestamp_sec = sys_timestamp_sec; 43 | this.disp_timestamp_sec = disp_timestamp_sec; 44 | } 45 | 46 | private SensorEntry(Builder builder) { 47 | this(builder.filtered, builder.unfiltered, builder.rssi, builder.sys_timestamp_sec, builder.disp_timestamp_sec); 48 | setBuilder(builder); 49 | } 50 | 51 | @Override 52 | public boolean equals(Object other) { 53 | if (other == this) return true; 54 | if (!(other instanceof SensorEntry)) return false; 55 | SensorEntry o = (SensorEntry) other; 56 | return equals(filtered, o.filtered) 57 | && equals(unfiltered, o.unfiltered) 58 | && equals(rssi, o.rssi) 59 | && equals(sys_timestamp_sec, o.sys_timestamp_sec) 60 | && equals(disp_timestamp_sec, o.disp_timestamp_sec); 61 | } 62 | 63 | @Override 64 | public int hashCode() { 65 | int result = hashCode; 66 | if (result == 0) { 67 | result = filtered != null ? filtered.hashCode() : 0; 68 | result = result * 37 + (unfiltered != null ? unfiltered.hashCode() : 0); 69 | result = result * 37 + (rssi != null ? rssi.hashCode() : 0); 70 | result = result * 37 + (sys_timestamp_sec != null ? sys_timestamp_sec.hashCode() : 0); 71 | result = result * 37 + (disp_timestamp_sec != null ? disp_timestamp_sec.hashCode() : 0); 72 | hashCode = result; 73 | } 74 | return result; 75 | } 76 | 77 | public static final class Builder extends Message.Builder { 78 | 79 | public Long filtered; 80 | public Long unfiltered; 81 | public Integer rssi; 82 | public Long sys_timestamp_sec; 83 | public Long disp_timestamp_sec; 84 | 85 | public Builder() { 86 | } 87 | 88 | public Builder(SensorEntry message) { 89 | super(message); 90 | if (message == null) return; 91 | this.filtered = message.filtered; 92 | this.unfiltered = message.unfiltered; 93 | this.rssi = message.rssi; 94 | this.sys_timestamp_sec = message.sys_timestamp_sec; 95 | this.disp_timestamp_sec = message.disp_timestamp_sec; 96 | } 97 | 98 | public Builder filtered(Long filtered) { 99 | this.filtered = filtered; 100 | return this; 101 | } 102 | 103 | public Builder unfiltered(Long unfiltered) { 104 | this.unfiltered = unfiltered; 105 | return this; 106 | } 107 | 108 | public Builder rssi(Integer rssi) { 109 | this.rssi = rssi; 110 | return this; 111 | } 112 | 113 | public Builder sys_timestamp_sec(Long sys_timestamp_sec) { 114 | this.sys_timestamp_sec = sys_timestamp_sec; 115 | return this; 116 | } 117 | 118 | /** 119 | * System timestamp - Timestamp representing the internal clock of the receiver 120 | */ 121 | public Builder disp_timestamp_sec(Long disp_timestamp_sec) { 122 | this.disp_timestamp_sec = disp_timestamp_sec; 123 | return this; 124 | } 125 | 126 | @Override 127 | public SensorEntry build() { 128 | checkRequiredFields(); 129 | return new SensorEntry(this); 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/dexcom/Utils.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.dexcom; 2 | 3 | import com.google.common.base.Strings; 4 | import com.google.common.hash.HashCode; 5 | import com.nightscout.core.dexcom.records.GlucoseDataSet; 6 | import com.nightscout.core.model.SensorEntry; 7 | import com.nightscout.core.model.SensorGlucoseValueEntry; 8 | 9 | import org.joda.time.DateTime; 10 | import org.joda.time.DateTimeZone; 11 | import org.joda.time.Instant; 12 | import org.joda.time.Period; 13 | import org.joda.time.format.PeriodFormatter; 14 | import org.joda.time.format.PeriodFormatterBuilder; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | import java.util.ArrayList; 19 | import java.util.Date; 20 | import java.util.List; 21 | 22 | import static com.google.common.base.Preconditions.checkNotNull; 23 | import static org.joda.time.Duration.standardSeconds; 24 | 25 | public final class Utils { 26 | protected static final Logger log = LoggerFactory.getLogger(Utils.class); 27 | 28 | public static final DateTime DEXCOM_EPOCH = new DateTime(2009, 1, 1, 0, 0, 0, 0).withZone(DateTimeZone.UTC); 29 | 30 | private static final String PRIMARY_SEPARATOR = ", "; 31 | private static final String SECONDARY_SEPARATOR = ", and "; 32 | private static final PeriodFormatter FORMATTER = new PeriodFormatterBuilder() 33 | .appendSeconds().appendSuffix(" seconds").appendSeparator(PRIMARY_SEPARATOR, SECONDARY_SEPARATOR) 34 | .appendMinutes().appendSuffix(" minutes").appendSeparator(PRIMARY_SEPARATOR, SECONDARY_SEPARATOR) 35 | .appendHours().appendSuffix(" hours").appendSeparator(PRIMARY_SEPARATOR, SECONDARY_SEPARATOR) 36 | .appendDays().appendSuffix(" days").appendSeparator(PRIMARY_SEPARATOR, SECONDARY_SEPARATOR) 37 | .appendWeeks().appendSuffix(" weeks").appendSeparator(PRIMARY_SEPARATOR, SECONDARY_SEPARATOR) 38 | .appendMonths().appendSuffix(" months").appendSeparator(PRIMARY_SEPARATOR, SECONDARY_SEPARATOR) 39 | .appendYears().appendSuffix(" years").appendLiteral(" ago") 40 | .printZeroNever() 41 | .toFormatter(); 42 | 43 | // TODO: probably not the right way to do this but it seems to do the trick. Need to revisit this to fully understand what is going on during DST change 44 | public static DateTime receiverTimeToDateTime(long deltaInSeconds) { 45 | int offset = DateTimeZone.getDefault().getOffset(DEXCOM_EPOCH) - DateTimeZone.getDefault().getOffset(Instant.now()); 46 | return DEXCOM_EPOCH.plus(offset).plus(standardSeconds(deltaInSeconds)).withZone(DateTimeZone.UTC); 47 | } 48 | 49 | public static Date receiverTimeToDate(long delta) { 50 | return receiverTimeToDateTime(delta).toDate(); 51 | } 52 | 53 | /** 54 | * Returns human-friendly string for the length of this duration, e.g. 4 seconds ago 55 | * or 4 days ago. 56 | * 57 | * @param period Non-null Period instance. 58 | * @return String human-friendly Period string, e.g. 4 seconds ago. 59 | */ 60 | public static String getTimeAgoString(Period period) { 61 | checkNotNull(period); 62 | String output = FORMATTER.print(period); 63 | if (Strings.isNullOrEmpty(output)) { 64 | return "--"; 65 | } 66 | return output; 67 | } 68 | 69 | public static String getTimeString(long timeDeltaMS) { 70 | long minutes = (timeDeltaMS / 1000) / 60; 71 | long hours = minutes / 60; 72 | long days = hours / 24; 73 | long weeks = days / 7; 74 | minutes = minutes - hours * 60; 75 | hours = hours - days * 24; 76 | days = days - weeks * 7; 77 | 78 | String timeAgoString = ""; 79 | if (weeks > 0) { 80 | timeAgoString += weeks + " weeks "; 81 | } 82 | if (days > 0) { 83 | timeAgoString += days + " days "; 84 | } 85 | if (hours > 0) { 86 | timeAgoString += hours + " hours "; 87 | } 88 | if (minutes >= 0) { 89 | timeAgoString += minutes + " min "; 90 | } 91 | 92 | return (timeAgoString.equals("") ? "--" : timeAgoString + "ago"); 93 | } 94 | 95 | public static List mergeGlucoseDataRecords(List egvRecords, 96 | List sensorRecords) { 97 | int egvLength = egvRecords.size(); 98 | int sensorLength = sensorRecords.size(); 99 | List glucoseDataSets = new ArrayList<>(); 100 | if (egvLength >= 0 && sensorLength == 0) { 101 | for (int i = 1; i <= egvLength; i++) { 102 | glucoseDataSets.add(new GlucoseDataSet(egvRecords.get(egvLength - i))); 103 | } 104 | return glucoseDataSets; 105 | } 106 | int smallerLength = egvLength < sensorLength ? egvLength : sensorLength; 107 | for (int i = 1; i <= smallerLength; i++) { 108 | glucoseDataSets.add(new GlucoseDataSet(egvRecords.get(egvLength - i), 109 | sensorRecords.get(sensorLength - i))); 110 | } 111 | return glucoseDataSets; 112 | } 113 | 114 | 115 | public static String bytesToHex(byte[] bytes) { 116 | return HashCode.fromBytes(bytes).toString().toUpperCase(); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/upload/BaseUploader.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.upload; 2 | 3 | import com.nightscout.core.dexcom.records.GlucoseDataSet; 4 | import com.nightscout.core.drivers.AbstractUploaderDevice; 5 | import com.nightscout.core.model.CalibrationEntry; 6 | import com.nightscout.core.model.MeterEntry; 7 | import com.nightscout.core.preferences.NightscoutPreferences; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.io.IOException; 13 | import java.util.List; 14 | 15 | import static com.google.common.base.Preconditions.checkNotNull; 16 | 17 | public abstract class BaseUploader { 18 | protected final Logger log = LoggerFactory.getLogger(this.getClass()); 19 | 20 | private final NightscoutPreferences preferences; 21 | 22 | protected abstract boolean doUpload(GlucoseDataSet glucoseDataSet) throws IOException; 23 | 24 | protected boolean doUpload(MeterEntry meterRecord) throws IOException { 25 | log.info("Meter record upload not supported."); 26 | return true; 27 | } 28 | 29 | protected boolean doUpload(CalibrationEntry calRecord) throws IOException { 30 | log.info("Cal record upload not supported."); 31 | return true; 32 | } 33 | 34 | protected boolean doUpload(AbstractUploaderDevice deviceStatus) throws IOException { 35 | log.info("Device status upload not supported."); 36 | return true; 37 | } 38 | 39 | public BaseUploader(NightscoutPreferences preferences) { 40 | checkNotNull(preferences); 41 | this.preferences = preferences; 42 | } 43 | 44 | // TODO(trhodeos): implement some sort of retry logic in all of these public functions. 45 | public final boolean uploadGlucoseDataSets(List glucoseDataSets) { 46 | if (glucoseDataSets == null) { 47 | return true; 48 | } 49 | boolean output = true; 50 | for (GlucoseDataSet glucoseDataSet : glucoseDataSets) { 51 | try { 52 | output &= doUpload(glucoseDataSet); 53 | } catch (IOException e) { 54 | log.error("Error uploading glucose data set.", e); 55 | output = false; 56 | } 57 | } 58 | return output; 59 | } 60 | 61 | /** 62 | * Uploads the meter records 63 | * 64 | * @param meterRecords 65 | * @return True if the upload was successful, false if the upload was unsuccessful 66 | */ 67 | public final boolean uploadMeterRecords(List meterRecords) { 68 | if (meterRecords == null) { 69 | return true; 70 | } 71 | boolean output = true; 72 | for (MeterEntry meterRecord : meterRecords) { 73 | try { 74 | output &= doUpload(meterRecord); 75 | } catch (IOException e) { 76 | log.error("Error uploading meter record.", e); 77 | output = false; 78 | } 79 | } 80 | return output; 81 | } 82 | 83 | /** 84 | * Uploads the calibration records 85 | * 86 | * @param calRecords 87 | * @return True if the upload was successful, false if the upload was unsuccessful 88 | */ 89 | public final boolean uploadCalRecords(List calRecords) { 90 | if (calRecords == null) { 91 | return true; 92 | } 93 | boolean output = true; 94 | if (getPreferences().isCalibrationUploadEnabled()) { 95 | for (CalibrationEntry calRecord : calRecords) { 96 | try { 97 | output &= doUpload(calRecord); 98 | } catch (IOException e) { 99 | log.error("Error uploading calibration record.", e); 100 | output = false; 101 | } 102 | } 103 | } 104 | return output; 105 | } 106 | 107 | /** 108 | * Uploads the device status 109 | * 110 | * @param deviceStatus 111 | * @return True if the upload was successful or False if the upload was unsuccessful 112 | */ 113 | public final boolean uploadDeviceStatus(AbstractUploaderDevice deviceStatus) { 114 | if (deviceStatus == null) { 115 | return true; 116 | } 117 | try { 118 | return doUpload(deviceStatus); 119 | } catch (IOException e) { 120 | log.error("Error uploading device status", e); 121 | return false; 122 | } 123 | } 124 | 125 | protected NightscoutPreferences getPreferences() { 126 | return this.preferences; 127 | } 128 | 129 | /** 130 | * Upload records, can be overridden to send all data in one batch. 131 | * @param glucoseDataSets 132 | * @param meterRecords 133 | * @param calRecords 134 | * @param deviceStatus 135 | * @return True if the (all) uploads was successful or False if at least one upload was unsuccessful. 136 | */ 137 | public boolean uploadRecords(List glucoseDataSets, List meterRecords, List calRecords, AbstractUploaderDevice deviceStatus) { 138 | boolean allSuccessful = uploadGlucoseDataSets(glucoseDataSets); 139 | allSuccessful &= uploadMeterRecords(meterRecords); 140 | allSuccessful &= uploadCalRecords(calRecords); 141 | allSuccessful &= uploadDeviceStatus(deviceStatus); 142 | return allSuccessful; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/upload/RestV1Uploader.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.upload; 2 | 3 | import com.nightscout.core.dexcom.Utils; 4 | import com.nightscout.core.dexcom.records.GlucoseDataSet; 5 | import com.nightscout.core.drivers.AbstractUploaderDevice; 6 | import com.nightscout.core.model.CalibrationEntry; 7 | import com.nightscout.core.model.MeterEntry; 8 | import com.nightscout.core.preferences.NightscoutPreferences; 9 | import com.nightscout.core.utils.RestUriUtils; 10 | 11 | import org.apache.http.message.AbstractHttpMessage; 12 | import org.json.JSONException; 13 | import org.json.JSONObject; 14 | 15 | import java.io.IOException; 16 | import java.net.URI; 17 | import java.util.Date; 18 | 19 | import static com.google.common.base.Preconditions.checkArgument; 20 | 21 | public class RestV1Uploader extends AbstractRestUploader { 22 | private final String secret; 23 | 24 | public RestV1Uploader(NightscoutPreferences preferences, URI uri) { 25 | super(preferences, RestUriUtils.removeToken(uri)); 26 | checkArgument(RestUriUtils.hasToken(uri), "Rest API v1 requires a token."); 27 | secret = RestUriUtils.generateSecret(uri.getUserInfo()); 28 | } 29 | 30 | protected String getSecret() { 31 | return secret; 32 | } 33 | 34 | @Override 35 | protected void setExtraHeaders(AbstractHttpMessage post) { 36 | post.setHeader("api-secret", secret); 37 | } 38 | 39 | private JSONObject toJSONObject(GlucoseDataSet record) throws JSONException { 40 | JSONObject json = new JSONObject(); 41 | json.put("device", "dexcom"); 42 | json.put("date", record.getDisplayTime().getTime()); 43 | json.put("dateString", record.getDisplayTime().toString()); 44 | json.put("sgv", Integer.parseInt(String.valueOf(record.getBgMgdl()))); 45 | json.put("direction", record.getTrend().friendlyTrendName()); 46 | json.put("type", "sgv"); 47 | if (getPreferences().isSensorUploadEnabled()) { 48 | json.put("filtered", record.getFiltered()); 49 | json.put("unfiltered", record.getUnfiltered()); 50 | json.put("rssi", record.getRssi()); 51 | json.put("noise", record.getNoise()); 52 | } 53 | return json; 54 | } 55 | 56 | private JSONObject toJSONObject(MeterEntry record) throws JSONException { 57 | JSONObject json = new JSONObject(); 58 | Date timestamp = Utils.receiverTimeToDate(record.disp_timestamp_sec); 59 | json.put("device", "dexcom"); 60 | json.put("type", "mbg"); 61 | json.put("date", timestamp.getTime()); 62 | json.put("dateString", timestamp.toString()); 63 | json.put("mbg", Integer.parseInt(String.valueOf(record.meter_bg_mgdl))); 64 | return json; 65 | } 66 | 67 | private JSONObject toJSONObject(CalibrationEntry record) throws JSONException { 68 | JSONObject json = new JSONObject(); 69 | Date timestamp = Utils.receiverTimeToDate(record.disp_timestamp_sec); 70 | json.put("device", "dexcom"); 71 | json.put("type", "cal"); 72 | json.put("date", timestamp.getTime()); 73 | json.put("dateString", timestamp.toString()); 74 | json.put("slope", record.slope); 75 | json.put("intercept", record.intercept); 76 | json.put("scale", record.scale); 77 | return json; 78 | } 79 | 80 | private JSONObject toJSONObject(AbstractUploaderDevice deviceStatus) throws JSONException { 81 | JSONObject json = new JSONObject(); 82 | json.put("uploaderBattery", deviceStatus.getBatteryLevel()); 83 | return json; 84 | } 85 | 86 | @Override 87 | protected boolean doUpload(GlucoseDataSet glucoseDataSet) throws IOException { 88 | try { 89 | return doPost("entries", toJSONObject(glucoseDataSet)); 90 | } catch (JSONException e) { 91 | log.error("Could not create JSON object for rest v1 glucose data set.", e); 92 | return false; 93 | } 94 | } 95 | 96 | @Override 97 | protected boolean doUpload(MeterEntry meterRecord) throws IOException { 98 | try { 99 | // TODO(trhodeos): in Uploader.java, this method still used 'entries' as the endpoint, 100 | // but this seems like a bug to me. 101 | return doPost("entries", toJSONObject(meterRecord)); 102 | } catch (JSONException e) { 103 | log.error("Could not create JSON object for rest v1 meter record.", e); 104 | return false; 105 | } 106 | } 107 | 108 | @Override 109 | protected boolean doUpload(CalibrationEntry calRecord) throws IOException { 110 | try { 111 | // TODO(trhodeos): in Uploader.java, this method still used 'entries' as the endpoint, 112 | // but this seems like a bug to me. 113 | return doPost("entries", toJSONObject(calRecord)); 114 | } catch (JSONException e) { 115 | log.error("Could not create JSON object for rest v1 cal record.", e); 116 | return false; 117 | } 118 | } 119 | 120 | @Override 121 | protected boolean doUpload(AbstractUploaderDevice deviceStatus) throws IOException { 122 | try { 123 | return doPost("devicestatus", toJSONObject(deviceStatus)); 124 | } catch (JSONException e) { 125 | log.error("Could not create JSON object for rest v1 device status.", e); 126 | return false; 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /core/src/test/java/com/nightscout/core/upload/RestLegacyUploaderTest.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.core.upload; 2 | 3 | import com.google.common.collect.Lists; 4 | import com.google.common.io.CharStreams; 5 | import com.nightscout.core.drivers.AbstractUploaderDevice; 6 | import com.nightscout.core.preferences.TestPreferences; 7 | 8 | import org.apache.http.HttpResponse; 9 | import org.apache.http.ProtocolVersion; 10 | import org.apache.http.client.HttpClient; 11 | import org.apache.http.client.methods.HttpPost; 12 | import org.apache.http.client.methods.HttpUriRequest; 13 | import org.apache.http.entity.StringEntity; 14 | import org.apache.http.message.BasicHttpResponse; 15 | import org.apache.http.message.BasicStatusLine; 16 | import org.json.JSONException; 17 | import org.json.JSONObject; 18 | import org.junit.Before; 19 | import org.junit.Test; 20 | import org.mockito.ArgumentCaptor; 21 | 22 | import java.io.IOException; 23 | import java.io.InputStreamReader; 24 | import java.net.URI; 25 | 26 | import static com.nightscout.core.test.MockFactory.mockCalRecord; 27 | import static com.nightscout.core.test.MockFactory.mockDeviceStatus; 28 | import static com.nightscout.core.test.MockFactory.mockGlucoseDataSet; 29 | import static com.nightscout.core.test.MockFactory.mockMeterRecord; 30 | import static org.hamcrest.MatcherAssert.assertThat; 31 | import static org.hamcrest.Matchers.containsString; 32 | import static org.hamcrest.Matchers.is; 33 | import static org.hamcrest.Matchers.not; 34 | import static org.hamcrest.Matchers.nullValue; 35 | import static org.mockito.Mockito.mock; 36 | import static org.mockito.Mockito.reset; 37 | import static org.mockito.Mockito.verifyNoMoreInteractions; 38 | import static org.mockito.Mockito.when; 39 | 40 | public class RestLegacyUploaderTest { 41 | RestLegacyUploader restUploader; 42 | HttpClient mockHttpClient; 43 | ArgumentCaptor captor; 44 | private TestPreferences preferences; 45 | 46 | @Before 47 | public void setUp() throws Exception { 48 | preferences = new TestPreferences(); 49 | restUploader = new RestLegacyUploader(preferences, URI.create("http://test.com/")); 50 | mockHttpClient = mock(HttpClient.class); 51 | restUploader.setClient(mockHttpClient); 52 | setUpExecuteCaptor(); 53 | } 54 | 55 | public void setUpExecuteCaptor() throws IOException { 56 | setUpExecuteCaptor(200); 57 | } 58 | 59 | public void setUpExecuteCaptor(int status) throws IOException { 60 | captor = ArgumentCaptor.forClass(HttpUriRequest.class); 61 | HttpResponse response = new BasicHttpResponse( 62 | new BasicStatusLine(new ProtocolVersion("mock", 1, 2), status, "")); 63 | response.setEntity(new StringEntity("")); 64 | when(mockHttpClient.execute(captor.capture())).thenReturn(response); 65 | } 66 | 67 | public static void verifyGlucoseDataSet(JSONObject jsonObject) 68 | throws JSONException { 69 | assertThat(jsonObject.getString("device"), is("dexcom")); 70 | assertThat(jsonObject.get("date"), is(not(nullValue()))); 71 | assertThat(jsonObject.get("dateString"), is(not(nullValue()))); 72 | assertThat(jsonObject.get("sgv"), is(not(nullValue()))); 73 | assertThat(jsonObject.get("direction"), is(not(nullValue()))); 74 | } 75 | 76 | public static void verifyDeviceStatus(JSONObject jsonObject, AbstractUploaderDevice deviceStatus) 77 | throws JSONException { 78 | assertThat(jsonObject.getInt("uploaderBattery"), is(deviceStatus.getBatteryLevel())); 79 | } 80 | 81 | @Test 82 | public void testGlucoseDataSet_Endpoint() throws Exception { 83 | restUploader.uploadGlucoseDataSets(Lists.newArrayList(mockGlucoseDataSet())); 84 | assertThat(captor.getValue().getURI().toString(), containsString("entries")); 85 | } 86 | 87 | @Test 88 | public void testGlucoseDataSet_Entity() throws Exception { 89 | restUploader.uploadGlucoseDataSets(Lists.newArrayList(mockGlucoseDataSet())); 90 | HttpPost post = (HttpPost) captor.getValue(); 91 | String entity = CharStreams.toString(new InputStreamReader(post.getEntity().getContent())); 92 | verifyGlucoseDataSet(new JSONObject(entity)); 93 | } 94 | 95 | @Test 96 | public void testMeterRecord_NoPost() throws Exception { 97 | reset(mockHttpClient); 98 | verifyNoMoreInteractions(mockHttpClient); 99 | restUploader.uploadMeterRecords(Lists.newArrayList(mockMeterRecord())); 100 | } 101 | 102 | @Test 103 | public void testCalRecord_NoPost() throws Exception { 104 | preferences.setCalibrationUploadEnabled(true); 105 | reset(mockHttpClient); 106 | verifyNoMoreInteractions(mockHttpClient); 107 | restUploader.uploadCalRecords(Lists.newArrayList(mockCalRecord())); 108 | } 109 | 110 | @Test 111 | public void testDeviceStatus_Endpoint() throws Exception { 112 | restUploader.uploadDeviceStatus(mockDeviceStatus()); 113 | assertThat(captor.getValue().getURI().toString(), containsString("devicestatus")); 114 | } 115 | 116 | @Test 117 | public void testDeviceStatus_Entity() throws Exception { 118 | AbstractUploaderDevice deviceStatus = mockDeviceStatus(); 119 | restUploader.uploadDeviceStatus(deviceStatus); 120 | HttpPost post = (HttpPost) captor.getValue(); 121 | String entity = CharStreams.toString(new InputStreamReader(post.getEntity().getContent())); 122 | verifyDeviceStatus(new JSONObject(entity), deviceStatus); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /app/src/test/java/com/nightscout/android/settings/SettingsActivityTest.java: -------------------------------------------------------------------------------- 1 | package com.nightscout.android.settings; 2 | 3 | import android.preference.EditTextPreference; 4 | import android.preference.PreferenceFragment; 5 | 6 | import com.nightscout.android.R; 7 | import com.nightscout.android.preferences.PreferenceKeys; 8 | import com.nightscout.android.test.RobolectricTestBase; 9 | 10 | import org.junit.Test; 11 | import org.robolectric.shadows.ShadowAlertDialog; 12 | import org.robolectric.util.FragmentTestUtil; 13 | 14 | import static org.hamcrest.Matchers.is; 15 | import static org.hamcrest.Matchers.not; 16 | import static org.hamcrest.Matchers.nullValue; 17 | import static org.junit.Assert.assertThat; 18 | 19 | public class SettingsActivityTest extends RobolectricTestBase { 20 | 21 | private PreferenceFragment setUpPreferenceFragment(Class clazz) { 22 | PreferenceFragment instance; 23 | try { 24 | instance = clazz.newInstance(); 25 | } catch (InstantiationException | IllegalAccessException e) { 26 | throw new RuntimeException(e); 27 | } 28 | FragmentTestUtil.startVisibleFragment(instance); 29 | return instance; 30 | } 31 | 32 | @Test 33 | public void testValidation_RestApi_Invalid() { 34 | EditTextPreference editTextPreference = (EditTextPreference) setUpPreferenceFragment( 35 | SettingsActivity.MainPreferenceFragment.class) 36 | .findPreference(PreferenceKeys.API_URIS); 37 | assertThat(editTextPreference.getOnPreferenceChangeListener() 38 | .onPreferenceChange(editTextPreference, "\\invalidUri"), is(false)); 39 | } 40 | 41 | @Test 42 | public void testValidation_RestApi_MultipleInvalid() { 43 | EditTextPreference editTextPreference = (EditTextPreference) setUpPreferenceFragment( 44 | SettingsActivity.MainPreferenceFragment.class) 45 | .findPreference(PreferenceKeys.API_URIS); 46 | assertThat(editTextPreference.getOnPreferenceChangeListener() 47 | .onPreferenceChange(editTextPreference, "http://example.com \\invalidUri"), 48 | is(false)); 49 | } 50 | 51 | @Test 52 | public void testValidation_RestApi_MultipleValid() { 53 | EditTextPreference editTextPreference = (EditTextPreference) setUpPreferenceFragment( 54 | SettingsActivity.MainPreferenceFragment.class) 55 | .findPreference(PreferenceKeys.API_URIS); 56 | assertThat(editTextPreference.getOnPreferenceChangeListener() 57 | .onPreferenceChange(editTextPreference, "http://example.com validUri.com"), 58 | is(true)); 59 | } 60 | 61 | @Test 62 | public void testValidation_RestApi_Valid() { 63 | EditTextPreference editTextPreference = (EditTextPreference) setUpPreferenceFragment( 64 | SettingsActivity.MainPreferenceFragment.class) 65 | .findPreference(PreferenceKeys.API_URIS); 66 | assertThat(editTextPreference.getOnPreferenceChangeListener() 67 | .onPreferenceChange(editTextPreference, "http://example.com"), is(true)); 68 | } 69 | 70 | @Test 71 | public void testAlert_RestApi_InvalidShowsDialog() { 72 | EditTextPreference editTextPreference = (EditTextPreference) setUpPreferenceFragment( 73 | SettingsActivity.MainPreferenceFragment.class) 74 | .findPreference(PreferenceKeys.API_URIS); 75 | editTextPreference.getOnPreferenceChangeListener() 76 | .onPreferenceChange(editTextPreference, "\\invalidUri"); 77 | ShadowAlertDialog alertDialog = getShadowApplication().getLatestAlertDialog(); 78 | assertThat(alertDialog, is(not(nullValue()))); 79 | assertThat(alertDialog.getMessage().toString(), 80 | is(getContext().getString(R.string.invalid_rest_uri, "\\invalidUri"))); 81 | } 82 | 83 | @Test 84 | public void testValidation_Mongo_Invalid() { 85 | EditTextPreference editTextPreference = (EditTextPreference) setUpPreferenceFragment( 86 | SettingsActivity.MainPreferenceFragment.class) 87 | .findPreference(PreferenceKeys.MONGO_URI); 88 | assertThat(editTextPreference.getOnPreferenceChangeListener() 89 | .onPreferenceChange(editTextPreference, "invalidMongo"), is(false)); 90 | } 91 | 92 | @Test 93 | public void testValidation_Mongo_Valid() { 94 | EditTextPreference editTextPreference = (EditTextPreference) setUpPreferenceFragment( 95 | SettingsActivity.MainPreferenceFragment.class) 96 | .findPreference(PreferenceKeys.MONGO_URI); 97 | assertThat(editTextPreference.getOnPreferenceChangeListener() 98 | .onPreferenceChange(editTextPreference, "mongodb://example.com"), is(true)); 99 | } 100 | 101 | @Test 102 | public void testAlert_Mongo_InvalidShowsDialog() { 103 | EditTextPreference editTextPreference = (EditTextPreference) setUpPreferenceFragment( 104 | SettingsActivity.MainPreferenceFragment.class) 105 | .findPreference(PreferenceKeys.MONGO_URI); 106 | editTextPreference.getOnPreferenceChangeListener() 107 | .onPreferenceChange(editTextPreference, "invalidMongo"); 108 | ShadowAlertDialog alertDialog = getShadowApplication().getLatestAlertDialog(); 109 | assertThat(alertDialog, is(not(nullValue()))); 110 | assertThat(alertDialog.getMessage().toString(), 111 | is(getContext().getString(R.string.illegal_mongo_uri))); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/model/CalibrationEntry.java: -------------------------------------------------------------------------------- 1 | // Code generated by Wire protocol buffer compiler, do not edit. 2 | // Source file: /Users/klee/Projects/Nightscout/android-uploader/core/src/main/java/com/nightscout/core/model/Download.proto 3 | package com.nightscout.core.model; 4 | 5 | import com.squareup.wire.Message; 6 | import com.squareup.wire.ProtoField; 7 | 8 | import static com.squareup.wire.Message.Datatype.DOUBLE; 9 | import static com.squareup.wire.Message.Datatype.UINT64; 10 | import static com.squareup.wire.Message.Label.REQUIRED; 11 | 12 | public final class CalibrationEntry extends Message { 13 | 14 | public static final Double DEFAULT_SLOPE = 0D; 15 | public static final Double DEFAULT_INTERCEPT = 0D; 16 | public static final Double DEFAULT_SCALE = 0D; 17 | public static final Double DEFAULT_DECAY = 0D; 18 | public static final Long DEFAULT_SYS_TIMESTAMP_SEC = 0L; 19 | public static final Long DEFAULT_DISP_TIMESTAMP_SEC = 0L; 20 | 21 | @ProtoField(tag = 1, type = DOUBLE, label = REQUIRED) 22 | public final Double slope; 23 | 24 | @ProtoField(tag = 2, type = DOUBLE) 25 | public final Double intercept; 26 | 27 | @ProtoField(tag = 3, type = DOUBLE) 28 | public final Double scale; 29 | 30 | @ProtoField(tag = 4, type = DOUBLE) 31 | public final Double decay; 32 | 33 | @ProtoField(tag = 5, type = UINT64) 34 | public final Long sys_timestamp_sec; 35 | 36 | /** 37 | * System timestamp - Timestamp representing the internal clock of the receiver 38 | */ 39 | @ProtoField(tag = 6, type = UINT64) 40 | public final Long disp_timestamp_sec; 41 | 42 | public CalibrationEntry(Double slope, Double intercept, Double scale, Double decay, Long sys_timestamp_sec, Long disp_timestamp_sec) { 43 | this.slope = slope; 44 | this.intercept = intercept; 45 | this.scale = scale; 46 | this.decay = decay; 47 | this.sys_timestamp_sec = sys_timestamp_sec; 48 | this.disp_timestamp_sec = disp_timestamp_sec; 49 | } 50 | 51 | private CalibrationEntry(Builder builder) { 52 | this(builder.slope, builder.intercept, builder.scale, builder.decay, builder.sys_timestamp_sec, builder.disp_timestamp_sec); 53 | setBuilder(builder); 54 | } 55 | 56 | @Override 57 | public boolean equals(Object other) { 58 | if (other == this) return true; 59 | if (!(other instanceof CalibrationEntry)) return false; 60 | CalibrationEntry o = (CalibrationEntry) other; 61 | return equals(slope, o.slope) 62 | && equals(intercept, o.intercept) 63 | && equals(scale, o.scale) 64 | && equals(decay, o.decay) 65 | && equals(sys_timestamp_sec, o.sys_timestamp_sec) 66 | && equals(disp_timestamp_sec, o.disp_timestamp_sec); 67 | } 68 | 69 | @Override 70 | public int hashCode() { 71 | int result = hashCode; 72 | if (result == 0) { 73 | result = slope != null ? slope.hashCode() : 0; 74 | result = result * 37 + (intercept != null ? intercept.hashCode() : 0); 75 | result = result * 37 + (scale != null ? scale.hashCode() : 0); 76 | result = result * 37 + (decay != null ? decay.hashCode() : 0); 77 | result = result * 37 + (sys_timestamp_sec != null ? sys_timestamp_sec.hashCode() : 0); 78 | result = result * 37 + (disp_timestamp_sec != null ? disp_timestamp_sec.hashCode() : 0); 79 | hashCode = result; 80 | } 81 | return result; 82 | } 83 | 84 | public static final class Builder extends Message.Builder { 85 | 86 | public Double slope; 87 | public Double intercept; 88 | public Double scale; 89 | public Double decay; 90 | public Long sys_timestamp_sec; 91 | public Long disp_timestamp_sec; 92 | 93 | public Builder() { 94 | } 95 | 96 | public Builder(CalibrationEntry message) { 97 | super(message); 98 | if (message == null) return; 99 | this.slope = message.slope; 100 | this.intercept = message.intercept; 101 | this.scale = message.scale; 102 | this.decay = message.decay; 103 | this.sys_timestamp_sec = message.sys_timestamp_sec; 104 | this.disp_timestamp_sec = message.disp_timestamp_sec; 105 | } 106 | 107 | public Builder slope(Double slope) { 108 | this.slope = slope; 109 | return this; 110 | } 111 | 112 | public Builder intercept(Double intercept) { 113 | this.intercept = intercept; 114 | return this; 115 | } 116 | 117 | public Builder scale(Double scale) { 118 | this.scale = scale; 119 | return this; 120 | } 121 | 122 | public Builder decay(Double decay) { 123 | this.decay = decay; 124 | return this; 125 | } 126 | 127 | public Builder sys_timestamp_sec(Long sys_timestamp_sec) { 128 | this.sys_timestamp_sec = sys_timestamp_sec; 129 | return this; 130 | } 131 | 132 | /** 133 | * System timestamp - Timestamp representing the internal clock of the receiver 134 | */ 135 | public Builder disp_timestamp_sec(Long disp_timestamp_sec) { 136 | this.disp_timestamp_sec = disp_timestamp_sec; 137 | return this; 138 | } 139 | 140 | @Override 141 | public CalibrationEntry build() { 142 | checkRequiredFields(); 143 | return new CalibrationEntry(this); 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | } 5 | 6 | dependencies { 7 | classpath 'org.robolectric:robolectric-gradle-plugin:0.14.0' 8 | } 9 | } 10 | 11 | apply plugin: 'com.android.application' 12 | apply plugin: 'robolectric' 13 | 14 | def gitSha = 'git rev-parse --short HEAD'.execute([], project.rootDir).text.trim() 15 | def buildTime = new Date().format("yyyy-MM-dd'T'HH:mm'Z'", TimeZone.getTimeZone("UTC")) 16 | def codeName = "Dreamsicle" 17 | 18 | android { 19 | compileSdkVersion 21 20 | buildToolsVersion '21.1.1' 21 | defaultConfig { 22 | applicationId "com.nightscout.android" 23 | minSdkVersion 12 24 | targetSdkVersion 18 25 | versionCode 22 26 | versionName "0.1.13" 27 | buildConfigField "String", "GIT_SHA", "\"${gitSha}\"" 28 | buildConfigField "String", "BUILD_TIME", "\"${buildTime}\"" 29 | buildConfigField "String", "VERSION_CODENAME", "\"${codeName}\"" 30 | } 31 | lintOptions { 32 | quiet false 33 | checkAllWarnings true 34 | warningsAsErrors false 35 | abortOnError true 36 | textReport true 37 | textOutput 'stdout' 38 | htmlOutput file("lint-report.html") 39 | htmlReport true 40 | checkReleaseBuilds true 41 | warning 'ProtectedPermissions', 'InvalidPackage' 42 | } 43 | packagingOptions { 44 | exclude 'META-INF/LICENSE' 45 | exclude 'META-INF/LICENSE.txt' 46 | } 47 | compileOptions { 48 | sourceCompatibility JavaVersion.VERSION_1_7 49 | targetCompatibility JavaVersion.VERSION_1_7 50 | } 51 | sourceSets { 52 | androidTest { 53 | setRoot('src/test') 54 | } 55 | } 56 | } 57 | 58 | robolectric { 59 | include '**/*Test.class' 60 | exclude '**/espresso/**/*.class' 61 | } 62 | 63 | repositories { 64 | maven { url "https://raw.github.com/embarkmobile/zxing-android-minimal/mvn-repo/maven-repository/" } 65 | maven { url "https://oss.sonatype.org/content/groups/public/" } 66 | } 67 | 68 | dependencies { 69 | compile(project(':core')) { 70 | // JSON already included by adk. 71 | exclude group: 'org.json' 72 | // HttpClient already included by adk. 73 | exclude group: 'org.apache.httpcomponents' 74 | exclude group: 'joda-time', module: 'joda-time' 75 | } 76 | compile 'ch.acra:acra:4.6.1' 77 | compile 'com.android.support:appcompat-v7:20.0.0' 78 | compile 'com.android.support:support-v4:20.0.0' 79 | compile 'com.google.android.gms:play-services-base:6.5.87' 80 | compile 'org.mongodb:mongo-java-driver:2.10.1' 81 | compile 'net.danlew:android.joda:2.6.0' 82 | compile 'com.google.guava:guava:18.0' 83 | // compile 'com.noveogroup.android:android-logger:1.3.4' 84 | compile 'com.embarkmobile:zxing-android-minimal:2.0.0@aar' 85 | compile 'com.embarkmobile:zxing-android-legacy:2.0.0@aar' 86 | compile 'com.embarkmobile:zxing-android-integration:2.0.0@aar' 87 | compile 'com.google.zxing:core:3.0.1' 88 | compile 'com.getpebble:pebblekit:2.6.0@aar' 89 | androidTestCompile 'org.hamcrest:hamcrest-library:1.3' 90 | androidTestCompile 'org.mockito:mockito-core:1.9.5' 91 | androidTestCompile('junit:junit:4.11') { 92 | exclude module: 'hamcrest-core' 93 | } 94 | androidTestCompile 'org.hamcrest:hamcrest-library:1.3' 95 | androidTestCompile 'org.mockito:mockito-core:1.9.5' 96 | androidTestCompile('org.robolectric:robolectric:2.4') { 97 | exclude module: 'classworlds' 98 | exclude module: 'commons-logging' 99 | exclude module: 'httpclient' 100 | exclude module: 'maven-artifact' 101 | exclude module: 'maven-artifact-manager' 102 | exclude module: 'maven-error-diagnostics' 103 | exclude module: 'maven-model' 104 | exclude module: 'maven-project' 105 | exclude module: 'maven-settings' 106 | exclude module: 'plexus-container-default' 107 | exclude module: 'plexus-interpolation' 108 | exclude module: 'plexus-utils' 109 | exclude module: 'wagon-file' 110 | exclude module: 'wagon-http-lightweight' 111 | exclude module: 'wagon-provider-api' 112 | } 113 | } 114 | 115 | apply plugin: 'idea' 116 | 117 | idea { 118 | module { 119 | testOutputDir = file('build/test-classes/debug') 120 | } 121 | } 122 | 123 | // Force 'app:test' to run after 'core:test'. This way, we fail fast if something is broken with 124 | // core. 125 | test.mustRunAfter ":core:test" 126 | 127 | apply plugin: 'jacoco' 128 | 129 | jacoco { 130 | toolVersion = "0.7.1.201405082137" 131 | reportsDir = file("$buildDir/jacoco") 132 | } 133 | 134 | sourceSets { 135 | main { 136 | java.srcDirs = ['src'] 137 | } 138 | } 139 | 140 | def coverageSourceDirs = [ 141 | '../app/src/main/java' 142 | ] 143 | 144 | task jacocoTestReport(type:JacocoReport, dependsOn: "testDebug") { 145 | group = "Reporting" 146 | 147 | description = "Generate Jacoco app coverage reports" 148 | 149 | classDirectories = fileTree( 150 | dir: '../app/build/intermediates/classes/debug', 151 | excludes: ['**/R.class', 152 | '**/R$*.class', 153 | '**/*$ViewInjector*.*', 154 | '**/BuildConfig.*', 155 | '**/USB/*.*', 156 | '**/proto/*.*', 157 | '**/Manifest*.*'] 158 | ) 159 | 160 | additionalSourceDirs = files(coverageSourceDirs) 161 | sourceDirectories = files(coverageSourceDirs) 162 | executionData = files('../app/build/jacoco/testDebug.exec') 163 | 164 | reports { 165 | xml.enabled = true 166 | html.enabled = true 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /core/src/main/java/com/nightscout/core/model/SensorGlucoseValueEntry.java: -------------------------------------------------------------------------------- 1 | // Code generated by Wire protocol buffer compiler, do not edit. 2 | // Source file: /Users/klee/Projects/Nightscout/android-uploader/core/src/main/java/com/nightscout/core/model/Download.proto 3 | package com.nightscout.core.model; 4 | 5 | import com.squareup.wire.Message; 6 | import com.squareup.wire.ProtoField; 7 | 8 | import static com.squareup.wire.Message.Datatype.ENUM; 9 | import static com.squareup.wire.Message.Datatype.UINT32; 10 | import static com.squareup.wire.Message.Datatype.UINT64; 11 | import static com.squareup.wire.Message.Label.REQUIRED; 12 | 13 | public final class SensorGlucoseValueEntry extends Message { 14 | 15 | public static final Integer DEFAULT_SGV_MGDL = 0; 16 | public static final Long DEFAULT_SYS_TIMESTAMP_SEC = 0L; 17 | public static final Long DEFAULT_DISP_TIMESTAMP_SEC = 0L; 18 | public static final G4Trend DEFAULT_TREND = G4Trend.TREND_NONE; 19 | public static final G4Noise DEFAULT_NOISE = G4Noise.NOISE_NONE; 20 | 21 | @ProtoField(tag = 1, type = UINT32, label = REQUIRED) 22 | public final Integer sgv_mgdl; 23 | 24 | /** 25 | * Sensor Glucose Value 26 | */ 27 | @ProtoField(tag = 2, type = UINT64) 28 | public final Long sys_timestamp_sec; 29 | 30 | /** 31 | * System timestamp - Timestamp representing the internal clock of the receiver 32 | */ 33 | @ProtoField(tag = 3, type = UINT64) 34 | public final Long disp_timestamp_sec; 35 | 36 | /** 37 | * Display timestamp - Timestamp representing the user configured time displayed on the receiver 38 | */ 39 | @ProtoField(tag = 4, type = ENUM) 40 | public final G4Trend trend; 41 | 42 | /** 43 | * G4 Glucose trend arrow 44 | */ 45 | @ProtoField(tag = 5, type = ENUM) 46 | public final G4Noise noise; 47 | 48 | public SensorGlucoseValueEntry(Integer sgv_mgdl, Long sys_timestamp_sec, Long disp_timestamp_sec, G4Trend trend, G4Noise noise) { 49 | this.sgv_mgdl = sgv_mgdl; 50 | this.sys_timestamp_sec = sys_timestamp_sec; 51 | this.disp_timestamp_sec = disp_timestamp_sec; 52 | this.trend = trend; 53 | this.noise = noise; 54 | } 55 | 56 | private SensorGlucoseValueEntry(Builder builder) { 57 | this(builder.sgv_mgdl, builder.sys_timestamp_sec, builder.disp_timestamp_sec, builder.trend, builder.noise); 58 | setBuilder(builder); 59 | } 60 | 61 | @Override 62 | public boolean equals(Object other) { 63 | if (other == this) return true; 64 | if (!(other instanceof SensorGlucoseValueEntry)) return false; 65 | SensorGlucoseValueEntry o = (SensorGlucoseValueEntry) other; 66 | return equals(sgv_mgdl, o.sgv_mgdl) 67 | && equals(sys_timestamp_sec, o.sys_timestamp_sec) 68 | && equals(disp_timestamp_sec, o.disp_timestamp_sec) 69 | && equals(trend, o.trend) 70 | && equals(noise, o.noise); 71 | } 72 | 73 | @Override 74 | public int hashCode() { 75 | int result = hashCode; 76 | if (result == 0) { 77 | result = sgv_mgdl != null ? sgv_mgdl.hashCode() : 0; 78 | result = result * 37 + (sys_timestamp_sec != null ? sys_timestamp_sec.hashCode() : 0); 79 | result = result * 37 + (disp_timestamp_sec != null ? disp_timestamp_sec.hashCode() : 0); 80 | result = result * 37 + (trend != null ? trend.hashCode() : 0); 81 | result = result * 37 + (noise != null ? noise.hashCode() : 0); 82 | hashCode = result; 83 | } 84 | return result; 85 | } 86 | 87 | public static final class Builder extends Message.Builder { 88 | 89 | public Integer sgv_mgdl; 90 | public Long sys_timestamp_sec; 91 | public Long disp_timestamp_sec; 92 | public G4Trend trend; 93 | public G4Noise noise; 94 | 95 | public Builder() { 96 | } 97 | 98 | public Builder(SensorGlucoseValueEntry message) { 99 | super(message); 100 | if (message == null) return; 101 | this.sgv_mgdl = message.sgv_mgdl; 102 | this.sys_timestamp_sec = message.sys_timestamp_sec; 103 | this.disp_timestamp_sec = message.disp_timestamp_sec; 104 | this.trend = message.trend; 105 | this.noise = message.noise; 106 | } 107 | 108 | public Builder sgv_mgdl(Integer sgv_mgdl) { 109 | this.sgv_mgdl = sgv_mgdl; 110 | return this; 111 | } 112 | 113 | /** 114 | * Sensor Glucose Value 115 | */ 116 | public Builder sys_timestamp_sec(Long sys_timestamp_sec) { 117 | this.sys_timestamp_sec = sys_timestamp_sec; 118 | return this; 119 | } 120 | 121 | /** 122 | * System timestamp - Timestamp representing the internal clock of the receiver 123 | */ 124 | public Builder disp_timestamp_sec(Long disp_timestamp_sec) { 125 | this.disp_timestamp_sec = disp_timestamp_sec; 126 | return this; 127 | } 128 | 129 | /** 130 | * Display timestamp - Timestamp representing the user configured time displayed on the receiver 131 | */ 132 | public Builder trend(G4Trend trend) { 133 | this.trend = trend; 134 | return this; 135 | } 136 | 137 | /** 138 | * G4 Glucose trend arrow 139 | */ 140 | public Builder noise(G4Noise noise) { 141 | this.noise = noise; 142 | return this; 143 | } 144 | 145 | @Override 146 | public SensorGlucoseValueEntry build() { 147 | checkRequiredFields(); 148 | return new SensorGlucoseValueEntry(this); 149 | } 150 | } 151 | } 152 | --------------------------------------------------------------------------------