diff --git a/android_app/app/src/androidTest/java/com/health/openscale/DatabaseMigrationTest.java b/android_app/app/src/androidTest/java/com/health/openscale/DatabaseMigrationTest.java index e294bb00..6945ca03 100644 --- a/android_app/app/src/androidTest/java/com/health/openscale/DatabaseMigrationTest.java +++ b/android_app/app/src/androidTest/java/com/health/openscale/DatabaseMigrationTest.java @@ -31,8 +31,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.Date; - import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotSame; import static junit.framework.Assert.assertTrue; diff --git a/android_app/app/src/androidTest/java/com/health/openscale/gui/AddMeasurementTest.java b/android_app/app/src/androidTest/java/com/health/openscale/gui/AddMeasurementTest.java index 3ef62437..d7917afe 100644 --- a/android_app/app/src/androidTest/java/com/health/openscale/gui/AddMeasurementTest.java +++ b/android_app/app/src/androidTest/java/com/health/openscale/gui/AddMeasurementTest.java @@ -44,7 +44,6 @@ import com.health.openscale.gui.views.DateMeasurementView; import com.health.openscale.gui.views.FatMeasurementView; import com.health.openscale.gui.views.HipMeasurementView; import com.health.openscale.gui.views.LBMMeasurementView; -import com.health.openscale.gui.views.MeasurementView; import com.health.openscale.gui.views.MuscleMeasurementView; import com.health.openscale.gui.views.NeckMeasurementView; import com.health.openscale.gui.views.ThighMeasurementView; @@ -55,7 +54,6 @@ import com.health.openscale.gui.views.WaterMeasurementView; import com.health.openscale.gui.views.WeightMeasurementView; import org.hamcrest.Matchers; -import org.junit.After; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Rule; @@ -67,7 +65,6 @@ import java.util.List; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.action.ViewActions.click; -import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard; import static android.support.test.espresso.action.ViewActions.replaceText; import static android.support.test.espresso.action.ViewActions.scrollTo; import static android.support.test.espresso.matcher.ViewMatchers.withClassName; diff --git a/android_app/app/src/androidTest/java/com/health/openscale/gui/AddUserTest.java b/android_app/app/src/androidTest/java/com/health/openscale/gui/AddUserTest.java index 79da11bf..52108e90 100644 --- a/android_app/app/src/androidTest/java/com/health/openscale/gui/AddUserTest.java +++ b/android_app/app/src/androidTest/java/com/health/openscale/gui/AddUserTest.java @@ -17,8 +17,6 @@ package com.health.openscale.gui; import android.content.Context; import android.content.SharedPreferences; -import android.content.res.Configuration; -import android.content.res.Resources; import android.preference.PreferenceManager; import android.support.test.InstrumentationRegistry; import android.support.test.espresso.ViewInteraction; @@ -47,7 +45,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.util.Calendar; -import java.util.Locale; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.action.ViewActions.click; diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothBeurerSanitas.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothBeurerSanitas.java index 2b646a6d..17313ef4 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothBeurerSanitas.java +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothBeurerSanitas.java @@ -588,7 +588,7 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication { private void updateDateTime() { // Update date/time of the scale long unixTime = System.currentTimeMillis() / 1000L; - byte[] unixTimeBytes = Converters.toUnsignedInt32Be(unixTime); + byte[] unixTimeBytes = Converters.toInt32Be(unixTime); Timber.d("Write new Date/Time: %d (%s)", unixTime, byteInHex(unixTimeBytes)); writeBytes(new byte[]{(byte) getAlternativeStartByte(9), diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothDigooDGSO38H.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothDigooDGSO38H.java index eeecd0da..a8200df4 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothDigooDGSO38H.java +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothDigooDGSO38H.java @@ -23,7 +23,6 @@ import android.content.Context; import com.health.openscale.core.OpenScale; import com.health.openscale.core.datatypes.ScaleMeasurement; import com.health.openscale.core.datatypes.ScaleUser; -import com.health.openscale.core.utils.Converters; import java.util.Date; import java.util.UUID; diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothExcelvanCF36xBLE.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothExcelvanCF36xBLE.java index aa93327f..4d675d6c 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothExcelvanCF36xBLE.java +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothExcelvanCF36xBLE.java @@ -20,7 +20,6 @@ import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; import android.content.Context; -import com.health.openscale.R; import com.health.openscale.core.OpenScale; import com.health.openscale.core.datatypes.ScaleMeasurement; import com.health.openscale.core.datatypes.ScaleUser; diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothExingtechY1.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothExingtechY1.java index 7e169a2e..e7ed5301 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothExingtechY1.java +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothExingtechY1.java @@ -25,7 +25,6 @@ import com.health.openscale.core.datatypes.ScaleMeasurement; import com.health.openscale.core.datatypes.ScaleUser; import com.health.openscale.core.utils.Converters; -import java.util.Calendar; import java.util.Date; import java.util.UUID; diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMedisanaBS44x.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMedisanaBS44x.java index f80ae6bb..f496b8df 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMedisanaBS44x.java +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMedisanaBS44x.java @@ -73,7 +73,7 @@ public class BluetoothMedisanaBS44x extends BluetoothCommunication { // send magic number to receive weight data long timestamp = new Date().getTime() / 1000; timestamp -= SCALE_UNIX_TIMESTAMP_OFFSET; - byte[] date = Converters.toUnsignedInt32Le(timestamp); + byte[] date = Converters.toInt32Le(timestamp); byte[] magicBytes = new byte[] {(byte)0x02, date[0], date[1], date[2], date[3]}; diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothTrisaBodyAnalyze.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothTrisaBodyAnalyze.java index e549e934..978eca49 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothTrisaBodyAnalyze.java +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothTrisaBodyAnalyze.java @@ -23,14 +23,16 @@ import android.preference.PreferenceManager; import android.support.annotation.Nullable; import com.health.openscale.R; +import com.health.openscale.core.OpenScale; import com.health.openscale.core.datatypes.ScaleMeasurement; +import com.health.openscale.core.datatypes.ScaleUser; +import com.health.openscale.core.utils.Converters; import java.util.UUID; import timber.log.Timber; import static com.health.openscale.core.bluetooth.lib.TrisaBodyAnalyzeLib.convertJavaTimestampToDevice; -import static com.health.openscale.core.bluetooth.lib.TrisaBodyAnalyzeLib.getInt32; import static com.health.openscale.core.bluetooth.lib.TrisaBodyAnalyzeLib.parseScaleMeasurementData; /** @@ -216,7 +218,7 @@ public class BluetoothTrisaBodyAnalyze extends BluetoothCommunication { Timber.e("Password data too short"); return; } - password = getInt32(data, 1); + password = Converters.fromSignedInt32Le(data, 1); if (deviceId == null) { Timber.e("Can't save password: device id not set!"); } else { @@ -244,7 +246,7 @@ public class BluetoothTrisaBodyAnalyze extends BluetoothCommunication { disconnect(true); return; } - int challenge = getInt32(data, 1); + int challenge = Converters.fromSignedInt32Le(data, 1); int response = challenge ^ password; writeCommand(DOWNLOAD_INFORMATION_RESULT_COMMAND, response); int deviceTimestamp = convertJavaTimestampToDevice(System.currentTimeMillis()); @@ -252,7 +254,8 @@ public class BluetoothTrisaBodyAnalyze extends BluetoothCommunication { } private void onScaleMeasurumentReceived(byte[] data) { - ScaleMeasurement scaleMeasurement = parseScaleMeasurementData(data); + ScaleUser user = OpenScale.getInstance().getSelectedScaleUser(); + ScaleMeasurement scaleMeasurement = parseScaleMeasurementData(data, user); if (scaleMeasurement == null) { Timber.e("Failed to parse scale measure measurement data: %s", byteInHex(data)); return; @@ -272,13 +275,10 @@ public class BluetoothTrisaBodyAnalyze extends BluetoothCommunication { * encoded in little-endian byte order.
*/ private void writeCommand(byte commandByte, int argument) { - writeCommandBytes(new byte[]{ - commandByte, - (byte) (argument >> 0), - (byte) (argument >> 8), - (byte) (argument >> 16), - (byte) (argument >> 24), - }); + byte[] bytes = new byte[5]; + bytes[0] = commandByte; + Converters.toInt32Le(bytes, 1, argument); + writeCommandBytes(bytes); } private void writeCommandBytes(byte[] bytes) { diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothYunmaiSE_Mini.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothYunmaiSE_Mini.java index fcd2993d..4fdc7b65 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothYunmaiSE_Mini.java +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothYunmaiSE_Mini.java @@ -58,7 +58,7 @@ public class BluetoothYunmaiSE_Mini extends BluetoothCommunication { protected boolean nextInitCmd(int stateNr) { switch (stateNr) { case 0: - byte[] userId = Converters.toUnsignedInt16Be(getUniqueNumber()); + byte[] userId = Converters.toInt16Be(getUniqueNumber()); final ScaleUser selectedUser = OpenScale.getInstance().getSelectedScaleUser(); byte sex = selectedUser.getGender().isMale() ? (byte)0x01 : (byte)0x02; @@ -74,7 +74,7 @@ public class BluetoothYunmaiSE_Mini extends BluetoothCommunication { writeBytes(WEIGHT_CMD_SERVICE, WEIGHT_CMD_CHARACTERISTIC, user_add_or_query); break; case 1: - byte[] unixTime = Converters.toUnsignedInt32Be(new Date().getTime() / 1000); + byte[] unixTime = Converters.toInt32Be(new Date().getTime() / 1000); byte[] set_time = new byte[]{(byte)0x0d, (byte) 0x0d, (byte) 0x11, unixTime[0], unixTime[1], unixTime[2], unixTime[3], diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/lib/TrisaBodyAnalyzeLib.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/lib/TrisaBodyAnalyzeLib.java index ccff9009..862ef23a 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/lib/TrisaBodyAnalyzeLib.java +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/lib/TrisaBodyAnalyzeLib.java @@ -18,6 +18,8 @@ package com.health.openscale.core.bluetooth.lib; import android.support.annotation.Nullable; import com.health.openscale.core.datatypes.ScaleMeasurement; +import com.health.openscale.core.datatypes.ScaleUser; +import com.health.openscale.core.utils.Converters; import java.util.Date; @@ -31,16 +33,6 @@ public class TrisaBodyAnalyzeLib { // Timestamp of 2010-01-01 00:00:00 UTC (or local time?) private static final long TIMESTAMP_OFFSET_SECONDS = 1262304000L; - /** - * Converts 4 little-endian bytes to a 32-bit integer, starting from {@code offset}. - * - * @throws IndexOutOfBoundsException if {@code offset < 0} or {@code offset + 4> data.length} - */ - public static int getInt32(byte[] data, int offset) { - return (data[offset] & 0xff) | ((data[offset + 1] & 0xff) << 8) | - ((data[offset + 2] & 0xff) << 16) | ((data[offset + 3] & 0xff) << 24); - } - /** Converts 4 bytes to a floating point number, starting from {@code offset}. * *The first three little-endian bytes form the 24-bit mantissa. The last byte contains the @@ -49,8 +41,7 @@ public class TrisaBodyAnalyzeLib { * @throws IndexOutOfBoundsException if {@code offset < 0} or {@code offset + 4> data.length} */ public static double getBase10Float(byte[] data, int offset) { - int mantissa = (data[offset] & 0xff) | ((data[offset + 1] & 0xff) << 8) | - ((data[offset + 2] & 0xff) << 16); + int mantissa = Converters.fromUnsignedInt24Le(data, offset); int exponent = data[offset + 3]; // note: byte is signed. return mantissa * Math.pow(10, exponent); } @@ -64,25 +55,75 @@ public class TrisaBodyAnalyzeLib { } @Nullable - public static ScaleMeasurement parseScaleMeasurementData(byte[] data) { - // Byte 0 contains info. - // Byte 1-4 contains weight. - // Byte 5-8 contains timestamp, if bit 0 in info byte is set. + public static ScaleMeasurement parseScaleMeasurementData(byte[] data, @Nullable ScaleUser user) { + // data contains: + // + // 1 byte: info about presence of other fields: + // bit 0: timestamp + // bit 1: resistance1 + // bit 2: resistance2 + // (other bits aren't used here) + // 4 bytes: weight + // 4 bytes: timestamp (if info bit 0 is set) + // 4 bytes: resistance1 (if info bit 1 is set) + // 4 bytes: resistance2 (if info bit 2 is set) + // (following fields aren't used here) + // Check that we have at least weight & timestamp, which is the minimum information that // ScaleMeasurement needs. - if (data.length < 9 || (data[0] & 1) == 0) { + if (data.length < 9) { + return null; // data is too short + } + byte infoByte = data[0]; + boolean hasTimestamp = (infoByte & 1) == 1; + boolean hasResistance1 = (infoByte & 2) == 2; + boolean hasResistance2 = (infoByte & 4) == 4; + if (!hasTimestamp) { return null; } - - double weight = getBase10Float(data, 1); - int deviceTimestamp = getInt32(data, 5); + double weightKg = getBase10Float(data, 1); + int deviceTimestamp = Converters.fromSignedInt32Le(data, 5); ScaleMeasurement measurement = new ScaleMeasurement(); measurement.setDateTime(new Date(convertDeviceTimestampToJava(deviceTimestamp))); - measurement.setWeight((float)weight); - // TODO: calculate body composition (if possible) and set those fields too + measurement.setWeight((float) weightKg); + + // Only resistance 2 is used; resistance 1 is 0, even if it is present. + int resistance2Offset = 9 + (hasResistance1 ? 4 : 0); + if (hasResistance2 && resistance2Offset + 4 <= data.length && isValidUser(user)) { + // Calculate body composition statistics from measured weight & resistance, combined + // with age, height and sex from the user profile. The accuracy of the resulting figures + // is questionable, but it's better than nothing. Even if the absolute numbers aren't + // very meaningful, it might still be useful to track changes over time. + double resistance2 = getBase10Float(data, resistance2Offset); + int ageYears = user.getAge(); + double heightCm = Converters.toCentimeter(user.getBodyHeight(), user.getMeasureUnit()); + boolean isMale = user.getGender().isMale(); + double impedance = resistance2 < 410 ? 3.0 : 0.3 * (resistance2 - 400); + double bmi = weightKg * 1e4 / (heightCm * heightCm); + double fat = isMale + ? bmi * (1.479 + 4.4e-4 * impedance) + 0.1 * ageYears - 21.764 + : bmi * (1.506 + 3.908e-4 * impedance) + 0.1 * ageYears - 12.834; + double water = isMale + ? 87.51 + (-1.162 * bmi - 0.00813 * impedance + 0.07594 * ageYears) + : 77.721 + (-1.148 * bmi - 0.00573 * impedance + 0.06448 * ageYears); + double muscle = isMale + ? 74.627 + (-0.811 * bmi - 0.00565 * impedance - 0.367 * ageYears) + : 57.0 + (-0.694 * bmi - 0.00344 * impedance - 0.255 * ageYears); + double bone = isMale + ? 7.829 + (-0.0855 * bmi - 5.92e-4 * impedance - 0.0389 * ageYears) + : 7.98 + (-0.0973 * bmi - 4.84e-4 * impedance - 0.036 * ageYears); + measurement.setFat((float) fat); + measurement.setWater((float) water); + measurement.setMuscle((float) muscle); + measurement.setBone((float) bone); + } return measurement; } + private static boolean isValidUser(@Nullable ScaleUser user) { + return user != null && user.getAge() > 0 && user.getBodyHeight() > 0; + } + private TrisaBodyAnalyzeLib() {} } diff --git a/android_app/app/src/main/java/com/health/openscale/core/datatypes/ScaleMeasurement.java b/android_app/app/src/main/java/com/health/openscale/core/datatypes/ScaleMeasurement.java index 2634425f..0cbf9cec 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/datatypes/ScaleMeasurement.java +++ b/android_app/app/src/main/java/com/health/openscale/core/datatypes/ScaleMeasurement.java @@ -22,8 +22,6 @@ import android.arch.persistence.room.ForeignKey; import android.arch.persistence.room.Index; import android.arch.persistence.room.PrimaryKey; -import com.health.openscale.R; -import com.health.openscale.core.utils.Converters; import com.j256.simplecsv.common.CsvColumn; import java.lang.reflect.Field; diff --git a/android_app/app/src/main/java/com/health/openscale/core/utils/Converters.java b/android_app/app/src/main/java/com/health/openscale/core/utils/Converters.java index 1f755885..e0d69a3e 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/utils/Converters.java +++ b/android_app/app/src/main/java/com/health/openscale/core/utils/Converters.java @@ -235,63 +235,106 @@ public class Converters { return kg; } - public static int fromUnsignedInt16Le(byte[] data, int offset) { - int value = (data[offset + 1] & 0xFF) << 8; + public static int fromSignedInt16Le(byte[] data, int offset) { + int value = data[offset + 1] << 8; value += data[offset] & 0xFF; return value; } - public static int fromUnsignedInt16Be(byte[] data, int offset) { - int value = (data[offset] & 0xFF) << 8; + public static int fromSignedInt16Be(byte[] data, int offset) { + int value = data[offset] << 8; value += data[offset + 1] & 0xFF; return value; } - public static byte[] toUnsignedInt16Be(int value) { + public static int fromUnsignedInt16Le(byte[] data, int offset) { + return fromSignedInt16Le(data, offset) & 0xFFFF; + } + + public static int fromUnsignedInt16Be(byte[] data, int offset) { + return fromSignedInt16Be(data, offset) & 0xFFFF; + } + + public static void toInt16Le(byte[] data, int offset, int value) { + data[offset + 0] = (byte) (value & 0xFF); + data[offset + 1] = (byte) ((value >> 8) & 0xFF); + } + + public static void toInt16Be(byte[] data, int offset, int value) { + data[offset + 0] = (byte) ((value >> 8) & 0xFF); + data[offset + 1] = (byte) (value & 0xFF); + } + + public static byte[] toInt16Le(int value) { byte[] data = new byte[2]; - data[0] = (byte) ((value >> 8) & 0xFF); - data[1] = (byte) (value & 0xFF); + toInt16Le(data, 0, value); return data; } - public static int fromUnsignedInt24Le(byte[] data, int offset) { - int value = (data[offset + 2] & 0xFF) << 16; + public static byte[] toInt16Be(int value) { + byte[] data = new byte[2]; + toInt16Be(data, 0, value); + return data; + } + + public static int fromSignedInt24Le(byte[] data, int offset) { + int value = data[offset + 2] << 16; value += (data[offset + 1] & 0xFF) << 8; value += data[offset] & 0xFF; return value; } - public static long fromUnsignedInt32Le(byte[] data, int offset) { - long value = (long) (data[offset + 3] & 0xFF) << 24; + public static int fromUnsignedInt24Le(byte[] data, int offset) { + return fromSignedInt24Le(data, offset) & 0xFFFFFF; + } + + public static int fromSignedInt32Le(byte[] data, int offset) { + int value = data[offset + 3] << 24; value += (data[offset + 2] & 0xFF) << 16; value += (data[offset + 1] & 0xFF) << 8; value += data[offset] & 0xFF; return value; } - public static long fromUnsignedInt32Be(byte[] data, int offset) { - long value = (long) (data[offset] & 0xFF) << 24; + public static int fromSignedInt32Be(byte[] data, int offset) { + int value = data[offset] << 24; value += (data[offset + 1] & 0xFF) << 16; value += (data[offset + 2] & 0xFF) << 8; value += data[offset + 3] & 0xFF; return value; } - public static byte[] toUnsignedInt32Le(long value) { + public static long fromUnsignedInt32Le(byte[] data, int offset) { + return (long) fromSignedInt32Le(data, offset) & 0xFFFFFFFFL; + } + + public static long fromUnsignedInt32Be(byte[] data, int offset) { + return (long) fromSignedInt32Be(data, offset) & 0xFFFFFFFFL; + } + + public static void toInt32Le(byte[] data, int offset, long value) { + data[offset + 3] = (byte) ((value >> 24) & 0xFF); + data[offset + 2] = (byte) ((value >> 16) & 0xFF); + data[offset + 1] = (byte) ((value >> 8) & 0xFF); + data[offset + 0] = (byte) (value & 0xFF); + } + + public static void toInt32Be(byte[] data, int offset, long value) { + data[offset + 0] = (byte) ((value >> 24) & 0xFF); + data[offset + 1] = (byte) ((value >> 16) & 0xFF); + data[offset + 2] = (byte) ((value >> 8) & 0xFF); + data[offset + 3] = (byte) (value & 0xFF); + } + + public static byte[] toInt32Le(long value) { byte[] data = new byte[4]; - data[3] = (byte) ((value >> 24) & 0xFF); - data[2] = (byte) ((value >> 16) & 0xFF); - data[1] = (byte) ((value >> 8) & 0xFF); - data[0] = (byte) (value & 0xFF); + toInt32Le(data, 0, value); return data; } - public static byte[] toUnsignedInt32Be(long value) { + public static byte[] toInt32Be(long value) { byte[] data = new byte[4]; - data[0] = (byte) ((value >> 24) & 0xFF); - data[1] = (byte) ((value >> 16) & 0xFF); - data[2] = (byte) ((value >> 8) & 0xFF); - data[3] = (byte) (value & 0xFF); + toInt32Be(data, 0, value); return data; } } diff --git a/android_app/app/src/main/java/com/health/openscale/gui/MainActivity.java b/android_app/app/src/main/java/com/health/openscale/gui/MainActivity.java index 7cbe29a4..9557b4b6 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/MainActivity.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/MainActivity.java @@ -16,7 +16,6 @@ package com.health.openscale.gui; -import android.annotation.SuppressLint; import android.app.AlertDialog; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothManager; @@ -31,8 +30,6 @@ import android.os.Handler; import android.os.Message; import android.preference.PreferenceManager; import android.support.annotation.NonNull; -import android.support.design.internal.BottomNavigationItemView; -import android.support.design.internal.BottomNavigationMenuView; import android.support.design.widget.BottomNavigationView; import android.support.design.widget.NavigationView; import android.support.v4.app.Fragment; @@ -64,7 +61,6 @@ import com.health.openscale.gui.fragments.TableFragment; import com.health.openscale.gui.preferences.BluetoothPreferences; import java.io.File; -import java.lang.reflect.Field; import java.util.List; import cat.ereza.customactivityoncrash.config.CaocConfig; diff --git a/android_app/app/src/main/java/com/health/openscale/gui/views/BMRMeasurementView.java b/android_app/app/src/main/java/com/health/openscale/gui/views/BMRMeasurementView.java index 87c9008e..5d28c611 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/views/BMRMeasurementView.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/views/BMRMeasurementView.java @@ -23,8 +23,6 @@ import com.health.openscale.core.datatypes.ScaleMeasurement; import com.health.openscale.core.evaluation.EvaluationResult; import com.health.openscale.core.evaluation.EvaluationSheet; -import java.util.Locale; - public class BMRMeasurementView extends FloatMeasurementView { // Don't change key value, it may be stored persistent in preferences public static final String KEY = "bmr"; diff --git a/android_app/app/src/test/java/com/health/openscale/ConvertersTest.java b/android_app/app/src/test/java/com/health/openscale/ConvertersTest.java index 4b95155c..066ffcad 100644 --- a/android_app/app/src/test/java/com/health/openscale/ConvertersTest.java +++ b/android_app/app/src/test/java/com/health/openscale/ConvertersTest.java @@ -82,61 +82,117 @@ public class ConvertersTest { } @Test - public void unsignedInt16Converters() throws Exception { + public void fromInt16Converters() throws Exception { byte[] data = new byte[]{(byte) 0xfd, (byte) 0xfe, (byte) 0xfc, (byte) 0x10, (byte) 0x7f}; + assertEquals(0xfffffefd, Converters.fromSignedInt16Le(data, 0)); + assertEquals(0xfffffcfe, Converters.fromSignedInt16Le(data, 1)); + assertEquals(0x000010fc, Converters.fromSignedInt16Le(data, 2)); + assertEquals(0x00007f10, Converters.fromSignedInt16Le(data, 3)); + assertEquals(0xfefd, Converters.fromUnsignedInt16Le(data, 0)); assertEquals(0xfcfe, Converters.fromUnsignedInt16Le(data, 1)); assertEquals(0x10fc, Converters.fromUnsignedInt16Le(data, 2)); assertEquals(0x7f10, Converters.fromUnsignedInt16Le(data, 3)); + assertEquals(0xfffffdfe, Converters.fromSignedInt16Be(data, 0)); + assertEquals(0xfffffefc, Converters.fromSignedInt16Be(data, 1)); + assertEquals(0xfffffc10, Converters.fromSignedInt16Be(data, 2)); + assertEquals(0x0000107f, Converters.fromSignedInt16Be(data, 3)); + assertEquals(0xfdfe, Converters.fromUnsignedInt16Be(data, 0)); assertEquals(0xfefc, Converters.fromUnsignedInt16Be(data, 1)); assertEquals(0xfc10, Converters.fromUnsignedInt16Be(data, 2)); assertEquals(0x107f, Converters.fromUnsignedInt16Be(data, 3)); - data = new byte[]{(byte) 0xff, (byte) 0xfe}; - assertArrayEquals(data, Converters.toUnsignedInt16Be(0xfffe)); - assertEquals(0xffff, - Converters.fromUnsignedInt16Be( - Converters.toUnsignedInt16Be(0xffff), 0)); + assertEquals(-12345, + Converters.fromSignedInt16Le(Converters.toInt16Le(-12345), 0)); + assertEquals(-12345, + Converters.fromSignedInt16Be(Converters.toInt16Be(-12345), 0)); } @Test - public void unsignedInt24Converters() throws Exception { + public void toInt16Converters() throws Exception { + assertArrayEquals(new byte[]{(byte) 0x12, (byte) 0x34}, Converters.toInt16Be(0x1234)); + assertArrayEquals(new byte[]{(byte) 0xff, (byte) 0xfe}, Converters.toInt16Be(0xfffe)); + assertArrayEquals(new byte[]{(byte) 0x34, (byte) 0x12}, Converters.toInt16Le(0x1234)); + assertArrayEquals(new byte[]{(byte) 0xfe, (byte) 0xff}, Converters.toInt16Le(0xfffe)); + + byte[] data = new byte[6]; + Converters.toInt16Be(data, 1, 0x0102); + Converters.toInt16Be(data, 3, 0x0304); + Converters.toInt16Le(data, 2, 0x0506); + assertArrayEquals(new byte[]{ 0, 1, 6, 5, 4, 0}, data); + } + + @Test + public void fromInt24Converters() throws Exception { byte[] data = new byte[]{(byte) 0xfd, (byte) 0xfe, (byte) 0xfc, (byte) 0x10, (byte) 0x7f}; + assertEquals(0xfffcfefd, Converters.fromSignedInt24Le(data, 0)); + assertEquals(0x0010fcfe, Converters.fromSignedInt24Le(data, 1)); + assertEquals(0x007f10fc, Converters.fromSignedInt24Le(data, 2)); + assertEquals(0xfcfefd, Converters.fromUnsignedInt24Le(data, 0)); assertEquals(0x10fcfe, Converters.fromUnsignedInt24Le(data, 1)); assertEquals(0x7f10fc, Converters.fromUnsignedInt24Le(data, 2)); + + assertEquals(-1234567, + Converters.fromSignedInt24Le(Converters.toInt32Le(-1234567), 0)); } @Test - public void unsignedInt32Converters() throws Exception { + public void fromInt32Converters() throws Exception { byte[] data = new byte[]{(byte) 0xf1, (byte) 0xf2, (byte) 0xf3, (byte) 0x7f, (byte) 0x7e}; + assertEquals(0x7ff3f2f1, Converters.fromSignedInt32Le(data, 0)); + assertEquals(0x7e7ff3f2, Converters.fromSignedInt32Le(data, 1)); + assertEquals(0x7ff3f2f1, Converters.fromUnsignedInt32Le(data, 0)); assertEquals(0x7e7ff3f2, Converters.fromUnsignedInt32Le(data, 1)); + assertEquals(0xf1f2f37f, Converters.fromSignedInt32Be(data, 0)); + assertEquals(0xf2f37f7e, Converters.fromSignedInt32Be(data, 1)); + assertEquals(0xf1f2f37fL, Converters.fromUnsignedInt32Be(data, 0)); assertEquals(0xf2f37f7eL, Converters.fromUnsignedInt32Be(data, 1)); data = new byte[]{(byte) 0x80, (byte) 0x00, (byte) 0x01, (byte) 0xff, (byte) 0x00}; + assertEquals(0xff010080, Converters.fromSignedInt32Le(data, 0)); + assertEquals(0xff0100, Converters.fromSignedInt32Le(data, 1)); + assertEquals(0xff010080L, Converters.fromUnsignedInt32Le(data, 0)); assertEquals(0xff0100, Converters.fromUnsignedInt32Le(data, 1)); + assertEquals(0x800001ff, Converters.fromSignedInt32Be(data, 0)); + assertEquals(0x1ff00, Converters.fromSignedInt32Be(data, 1)); + assertEquals(0x800001ffL, Converters.fromUnsignedInt32Be(data, 0)); assertEquals(0x1ff00L, Converters.fromUnsignedInt32Be(data, 1)); - data = new byte[]{(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc}; - assertArrayEquals(data, Converters.toUnsignedInt32Le(0xfcfdfeffL)); - assertArrayEquals(data, Converters.toUnsignedInt32Be(0xfffefdfcL)); + assertEquals(-1234567890, + Converters.fromSignedInt32Le(Converters.toInt32Le(-1234567890), 0)); + assertEquals(-1234567890, + Converters.fromSignedInt32Be(Converters.toInt32Be(-1234567890), 0)); + } + + @Test + public void toInt32Converters() throws Exception { + byte[] data = new byte[]{(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc}; + assertArrayEquals(data, Converters.toInt32Le(0xfcfdfeffL)); + assertArrayEquals(data, Converters.toInt32Be(0xfffefdfcL)); assertEquals(0xffffffffL, Converters.fromUnsignedInt32Le( - Converters.toUnsignedInt32Le(0xffffffffL), 0)); + Converters.toInt32Le(0xffffffffL), 0)); assertEquals(0xffffffffL, Converters.fromUnsignedInt32Be( - Converters.toUnsignedInt32Be(0xffffffffL), 0)); + Converters.toInt32Be(0xffffffffL), 0)); + + data = new byte[10]; + Converters.toInt32Be(data, 1, 0x01020304); + Converters.toInt32Be(data, 6, 0x05060708); + Converters.toInt32Le(data, 3, 0x090a0b0c); + assertArrayEquals(new byte[]{ 0, 1, 2, 12, 11, 10, 9, 6, 7, 8}, data); } } diff --git a/android_app/app/src/test/java/com/health/openscale/TrisaBodyAnalyzeLibTest.java b/android_app/app/src/test/java/com/health/openscale/TrisaBodyAnalyzeLibTest.java index 4c98e64f..ad9cfe96 100644 --- a/android_app/app/src/test/java/com/health/openscale/TrisaBodyAnalyzeLibTest.java +++ b/android_app/app/src/test/java/com/health/openscale/TrisaBodyAnalyzeLibTest.java @@ -1,37 +1,26 @@ package com.health.openscale; import com.health.openscale.core.datatypes.ScaleMeasurement; +import com.health.openscale.core.datatypes.ScaleUser; +import com.health.openscale.core.utils.Converters; import junit.framework.Assert; import org.junit.Test; +import java.util.Calendar; import java.util.Date; +import java.util.GregorianCalendar; import static com.health.openscale.core.bluetooth.lib.TrisaBodyAnalyzeLib.convertDeviceTimestampToJava; import static com.health.openscale.core.bluetooth.lib.TrisaBodyAnalyzeLib.convertJavaTimestampToDevice; import static com.health.openscale.core.bluetooth.lib.TrisaBodyAnalyzeLib.getBase10Float; -import static com.health.openscale.core.bluetooth.lib.TrisaBodyAnalyzeLib.getInt32; import static com.health.openscale.core.bluetooth.lib.TrisaBodyAnalyzeLib.parseScaleMeasurementData; import static junit.framework.Assert.assertEquals; /** Unit tests for {@link com.health.openscale.core.bluetooth.lib.TrisaBodyAnalyzeLib}.*/ public class TrisaBodyAnalyzeLibTest { - @Test - public void getInt32Tests() { - byte[] data = new byte[]{1, 2, 3, 4, 5, 6}; - assertEquals(0x04030201, getInt32(data, 0)); - assertEquals(0x05040302, getInt32(data, 1)); - assertEquals(0x06050403, getInt32(data, 2)); - - assertEquals(0xa7bdd385, getInt32(new byte[]{-123, -45, -67, -89}, 0)); - - assertThrows(IndexOutOfBoundsException.class, getInt32Runnable(data, -1)); - assertThrows(IndexOutOfBoundsException.class, getInt32Runnable(data, 5)); - assertThrows(IndexOutOfBoundsException.class, getInt32Runnable(new byte[]{1,2,3}, 0)); - } - @Test public void getBase10FloatTests() { double eps = 1e-9; // margin of error for inexact floating point comparisons @@ -68,27 +57,49 @@ public class TrisaBodyAnalyzeLibTest { } @Test - public void parseScaleMeasurementDataTests() { + public void parseScaleMeasurementData_validUserData() { + long expected_timestamp_seconds = 1539205852L; // Wed Oct 10 21:10:52 UTC 2018 + byte[] bytes = hexToBytes("9f:b0:1d:00:fe:dc:2f:81:10:00:00:00:ff:0a:15:00:ff:00:09:00"); + + ScaleUser user = new ScaleUser(); + user.setGender(Converters.Gender.MALE); + user.setBirthday(ageToBirthday(36)); + user.setBodyHeight(186); + user.setMeasureUnit(Converters.MeasureUnit.CM); + + ScaleMeasurement measurement = parseScaleMeasurementData(bytes, user); + + float eps = 1e-3f; + assertEquals(76.0f, measurement.getWeight(), eps); + assertEquals(new Date(expected_timestamp_seconds * 1000), measurement.getDateTime()); + assertEquals(14.728368f, measurement.getFat(), eps); + assertEquals(64.37914f, measurement.getWater(), eps); + assertEquals(43.36414f, measurement.getMuscle(), eps); + assertEquals(4.525733f, measurement.getBone()); + } + + @Test + public void parseScaleMeasurementData_missingUserData() { long expected_timestamp_seconds = 1538156082L; // Fri Sep 28 17:34:42 UTC 2018 byte[] bytes = hexToBytes("9f:ba:1d:00:fe:32:2b:71:10:00:00:00:ff:8d:14:00:ff:00:09:00"); - ScaleMeasurement measurement = parseScaleMeasurementData(bytes); + ScaleMeasurement measurement = parseScaleMeasurementData(bytes, null); - assertEquals(measurement.getWeight(), 76.1f, 1e-6f); + assertEquals(76.1f, measurement.getWeight(), 1e-3f); assertEquals(new Date(expected_timestamp_seconds * 1000), measurement.getDateTime()); + assertEquals(0f, measurement.getFat()); } - /** - * Creates a {@link Runnable} that will call getInt32(). In Java 8, this can be done more - * easily with a lambda expression at the call site, but we are using Java 7. - */ - private static Runnable getInt32Runnable(final byte[] data, final int offset) { - return new Runnable() { - @Override - public void run() { - getInt32(data, offset); - } - }; + @Test + public void parseScaleMeasurementData_invalidUserData() { + long expected_timestamp_seconds = 1538156082L; // Fri Sep 28 17:34:42 UTC 2018 + byte[] bytes = hexToBytes("9f:ba:1d:00:fe:32:2b:71:10:00:00:00:ff:8d:14:00:ff:00:09:00"); + + ScaleMeasurement measurement = parseScaleMeasurementData(bytes, new ScaleUser()); + + assertEquals(76.1f, measurement.getWeight(), 1e-3f); + assertEquals(new Date(expected_timestamp_seconds * 1000), measurement.getDateTime()); + assertEquals(0f, measurement.getFat()); } /** @@ -124,7 +135,7 @@ public class TrisaBodyAnalyzeLibTest { } /** Parses a colon-separated hex-encoded string like "aa:bb:cc:dd" into an array of bytes. */ - private byte[] hexToBytes(String s) { + private static byte[] hexToBytes(String s) { String[] parts = s.split(":"); byte[] bytes = new byte[parts.length]; for (int i = 0; i < bytes.length; ++i) { @@ -135,4 +146,9 @@ public class TrisaBodyAnalyzeLibTest { } return bytes; } + + private static Date ageToBirthday(int years) { + int currentYear = GregorianCalendar.getInstance().get(Calendar.YEAR); + return new GregorianCalendar(currentYear - years, Calendar.JANUARY, 1).getTime(); + } }