mirror of
https://github.com/oliexdev/openScale.git
synced 2025-08-29 11:10:35 +02:00
Merge branch 'master' into funkwit-master
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
/* Copyright (C) 2018 olie.xdev <olie.xdev@googlemail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
package com.health.openscale.gui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.espresso.ViewInteraction;
|
||||
import android.support.test.espresso.contrib.PickerActions;
|
||||
import android.support.test.rule.ActivityTestRule;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.LargeTest;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
import android.widget.DatePicker;
|
||||
|
||||
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.gui.activities.BaseAppCompatActivity;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
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;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class AddMeasurementTest {
|
||||
private static final double DELTA = 1e-15;
|
||||
|
||||
private static Context context;
|
||||
private static OpenScale openScale;
|
||||
|
||||
private static final ScaleUser male = TestData.getMaleUser();
|
||||
private static final ScaleUser female = TestData.getFemaleUser();
|
||||
|
||||
@Rule
|
||||
public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class, false, true);
|
||||
|
||||
@BeforeClass
|
||||
public static void initTest() {
|
||||
context = InstrumentationRegistry.getTargetContext();
|
||||
|
||||
openScale = OpenScale.getInstance();
|
||||
|
||||
openScale.addScaleUser(male);
|
||||
openScale.addScaleUser(female);
|
||||
|
||||
// Set first start to true to get the user add dialog
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
prefs.edit()
|
||||
.putBoolean("firstStart", false)
|
||||
.putString(BaseAppCompatActivity.PREFERENCE_LANGUAGE, "en")
|
||||
.putInt("selectedUserId", male.getId())
|
||||
.commit();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void addMeasurementVerification() {
|
||||
openScale.deleteScaleUser(male.getId());
|
||||
openScale.deleteScaleUser(female.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addMeasurementTest() {
|
||||
onView(withId(R.id.action_add_measurement)).perform(click());
|
||||
|
||||
ScaleMeasurement measurement = TestData.getMeasurement(1);
|
||||
}
|
||||
}
|
@@ -61,7 +61,7 @@ import static org.junit.Assert.assertEquals;
|
||||
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class UserAddTest {
|
||||
public class AddUserTest {
|
||||
private static final double DELTA = 1e-15;
|
||||
|
||||
private Context context;
|
||||
@@ -82,7 +82,7 @@ public class UserAddTest {
|
||||
}
|
||||
|
||||
@After
|
||||
public void verifyUserAdd() {
|
||||
public void addUserVerification() {
|
||||
ScaleUser user = OpenScale.getInstance().getSelectedScaleUser();
|
||||
|
||||
assertEquals("test", user.getUserName());
|
||||
@@ -107,10 +107,12 @@ public class UserAddTest {
|
||||
goalDate.set(Calendar.HOUR_OF_DAY, 0);
|
||||
|
||||
assertEquals(goalDate.getTime().getTime(), user.getGoalDate().getTime());
|
||||
|
||||
OpenScale.getInstance().deleteScaleUser(user.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void userAddTest() {
|
||||
public void addUserTest() {
|
||||
mActivityTestRule.launchActivity(null);
|
||||
|
||||
ViewInteraction editText = onView(
|
@@ -0,0 +1,108 @@
|
||||
/* Copyright (C) 2018 olie.xdev <olie.xdev@googlemail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
package com.health.openscale.gui;
|
||||
|
||||
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.Random;
|
||||
|
||||
public class TestData {
|
||||
private static Random rand = new Random();
|
||||
|
||||
public static ScaleUser getMaleUser() {
|
||||
ScaleUser male = new ScaleUser();
|
||||
|
||||
male.setUserName("Bob");
|
||||
male.setGender(Converters.Gender.MALE);
|
||||
male.setInitialWeight(80.0f);
|
||||
male.setScaleUnit(Converters.WeightUnit.KG);
|
||||
male.setActivityLevel(Converters.ActivityLevel.MILD);
|
||||
male.setBodyHeight(180.0f);
|
||||
male.setGoalWeight(60.0f);
|
||||
male.setMeasureUnit(Converters.MeasureUnit.CM);
|
||||
male.setBirthday(getDateFromYears(-20));
|
||||
male.setGoalDate(getDateFromYears(2));
|
||||
|
||||
return male;
|
||||
}
|
||||
|
||||
public static ScaleUser getFemaleUser() {
|
||||
ScaleUser female = new ScaleUser();
|
||||
|
||||
female.setUserName("Alice");
|
||||
female.setGender(Converters.Gender.FEMALE);
|
||||
female.setInitialWeight(70.0f);
|
||||
female.setScaleUnit(Converters.WeightUnit.LB);
|
||||
female.setActivityLevel(Converters.ActivityLevel.EXTREME);
|
||||
female.setBodyHeight(160.0f);
|
||||
female.setGoalWeight(50.0f);
|
||||
female.setMeasureUnit(Converters.MeasureUnit.INCH);
|
||||
female.setBirthday(getDateFromYears(-25));
|
||||
female.setGoalDate(getDateFromYears(1));
|
||||
|
||||
return female;
|
||||
}
|
||||
|
||||
public static ScaleMeasurement getMeasurement(int nr) {
|
||||
ScaleMeasurement measurement = new ScaleMeasurement();
|
||||
|
||||
rand.setSeed(nr);
|
||||
|
||||
measurement.setDateTime(getDateFromDays(nr));
|
||||
measurement.setWeight(100.0f + getRandNumberInRange(0,50));
|
||||
measurement.setFat(30.0f + getRandNumberInRange(0,30));
|
||||
measurement.setWater(50.0f + getRandNumberInRange(0,20));
|
||||
measurement.setMuscle(40.0f + getRandNumberInRange(0,15));
|
||||
measurement.setLbm(20.0f + getRandNumberInRange(0,10));
|
||||
measurement.setBone(8.0f + getRandNumberInRange(0,50));
|
||||
measurement.setWaist(50.0f + getRandNumberInRange(0,50));
|
||||
measurement.setHip(60.0f + getRandNumberInRange(0,50));
|
||||
measurement.setChest(80.0f + getRandNumberInRange(0,50));
|
||||
measurement.setThigh(40.0f + getRandNumberInRange(0,50));
|
||||
measurement.setBiceps(30.0f + getRandNumberInRange(0,50));
|
||||
measurement.setNeck(15.0f + getRandNumberInRange(0,50));
|
||||
measurement.setCaliper1(5.0f + getRandNumberInRange(0,10));
|
||||
measurement.setCaliper2(10.0f + getRandNumberInRange(0,10));
|
||||
measurement.setCaliper3(7.0f + getRandNumberInRange(0,10));
|
||||
measurement.setComment("my comment " + nr);
|
||||
|
||||
return measurement;
|
||||
}
|
||||
|
||||
private static Date getDateFromYears(int years) {
|
||||
Calendar currentTime = Calendar.getInstance();
|
||||
|
||||
currentTime.add(Calendar.YEAR, years);
|
||||
|
||||
return currentTime.getTime();
|
||||
}
|
||||
|
||||
private static Date getDateFromDays(int days) {
|
||||
Calendar currentTime = Calendar.getInstance();
|
||||
|
||||
currentTime.add(Calendar.DAY_OF_YEAR, days);
|
||||
|
||||
return currentTime.getTime();
|
||||
}
|
||||
|
||||
private static float getRandNumberInRange(int min, int max) {
|
||||
return (float)(rand.nextInt(max*10 - min*10) + min*10) / 10.0f;
|
||||
}
|
||||
}
|
@@ -101,6 +101,10 @@ public abstract class BluetoothCommunication {
|
||||
return bluetoothGatt.getServices();
|
||||
}
|
||||
|
||||
protected boolean hasBluetoothGattService(UUID service) {
|
||||
return bluetoothGatt != null && bluetoothGatt.getService(service) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback Bluetooth handler that notify any BT_STATUS_CODE changes for GUI/CORE.
|
||||
*
|
||||
@@ -271,6 +275,7 @@ public abstract class BluetoothCommunication {
|
||||
BluetoothGattCharacteristic gattCharacteristic = bluetoothGatt.getService(service)
|
||||
.getCharacteristic(characteristic);
|
||||
|
||||
Timber.d("Read characteristic %s", characteristic);
|
||||
bluetoothGatt.readCharacteristic(gattCharacteristic);
|
||||
}
|
||||
|
||||
@@ -278,6 +283,7 @@ public abstract class BluetoothCommunication {
|
||||
BluetoothGattDescriptor gattDescriptor = bluetoothGatt.getService(service)
|
||||
.getCharacteristic(characteristic).getDescriptor(descriptor);
|
||||
|
||||
Timber.d("Read descriptor %s", descriptor);
|
||||
bluetoothGatt.readDescriptor(gattDescriptor);
|
||||
}
|
||||
|
||||
@@ -369,12 +375,16 @@ public abstract class BluetoothCommunication {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (data.length == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
final StringBuilder stringBuilder = new StringBuilder(3 * data.length);
|
||||
for (byte byteChar : data) {
|
||||
stringBuilder.append(String.format("%02X ", byteChar));
|
||||
}
|
||||
|
||||
return stringBuilder.toString();
|
||||
return stringBuilder.substring(0, stringBuilder.length() - 1);
|
||||
}
|
||||
|
||||
protected byte xorChecksum(byte[] data, int offset, int length) {
|
||||
@@ -654,7 +664,8 @@ public abstract class BluetoothCommunication {
|
||||
|
||||
@Override
|
||||
public void onServicesDiscovered(final BluetoothGatt gatt, int status) {
|
||||
Timber.d("onServicesDiscovered: status=%d", status);
|
||||
Timber.d("onServicesDiscovered: status=%d (%d services)",
|
||||
status, gatt.getServices().size());
|
||||
|
||||
synchronized (lock) {
|
||||
cmdStepNr = 0;
|
||||
@@ -681,24 +692,32 @@ public abstract class BluetoothCommunication {
|
||||
setBtMachineState(BT_MACHINE_STATE.BT_INIT_STATE);
|
||||
}
|
||||
|
||||
private void postDelayedHandleRequests() {
|
||||
// Wait a short while before starting the next operation as suggested
|
||||
// on the android.jlelse.eu link above.
|
||||
handler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (lock) {
|
||||
openRequest = false;
|
||||
handleRequests();
|
||||
}
|
||||
}
|
||||
}, 60);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDescriptorWrite(BluetoothGatt gatt,
|
||||
BluetoothGattDescriptor descriptor,
|
||||
int status) {
|
||||
synchronized (lock) {
|
||||
openRequest = false;
|
||||
handleRequests();
|
||||
}
|
||||
postDelayedHandleRequests();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicWrite(BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic,
|
||||
int status) {
|
||||
synchronized (lock) {
|
||||
openRequest = false;
|
||||
handleRequests();
|
||||
}
|
||||
postDelayedHandleRequests();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -710,8 +729,7 @@ public abstract class BluetoothCommunication {
|
||||
|
||||
synchronized (lock) {
|
||||
onBluetoothDataRead(gatt, characteristic, status);
|
||||
openRequest = false;
|
||||
handleRequests();
|
||||
postDelayedHandleRequests();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -733,10 +751,7 @@ public abstract class BluetoothCommunication {
|
||||
Timber.d("onDescriptorRead %s (status=%d): %s",
|
||||
descriptor.getUuid(), status, byteInHex(descriptor.getValue()));
|
||||
|
||||
synchronized (lock) {
|
||||
openRequest = false;
|
||||
handleRequests();
|
||||
}
|
||||
postDelayedHandleRequests();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -156,8 +156,8 @@ public class BluetoothDebug extends BluetoothCommunication {
|
||||
logService(service, false);
|
||||
}
|
||||
|
||||
disconnect(false);
|
||||
setBtStatus(BT_STATUS_CODE.BT_CONNECTION_LOST);
|
||||
disconnect(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@@ -31,7 +31,9 @@ import timber.log.Timber;
|
||||
|
||||
public class BluetoothOneByone extends BluetoothCommunication {
|
||||
private final UUID WEIGHT_MEASUREMENT_SERVICE_BODY_COMPOSITION = UUID.fromString("0000181B-0000-1000-8000-00805f9b34fb");
|
||||
|
||||
private final UUID WEIGHT_MEASUREMENT_CHARACTERISTIC_BODY_COMPOSITION = UUID.fromString("00002A9C-0000-1000-8000-00805f9b34fb"); // read, indication
|
||||
private final UUID WEIGHT_MEASUREMENT_CHARACTERISTIC_BODY_COMPOSITION_ALT = UUID.fromString("0000fff4-0000-1000-8000-00805f9b34fb"); // notify
|
||||
|
||||
private final UUID WEIGHT_MEASUREMENT_SERVICE = UUID.fromString("0000fff0-0000-1000-8000-00805f9b34fb");
|
||||
private final UUID CMD_MEASUREMENT_CHARACTERISTIC = UUID.fromString("0000fff1-0000-1000-8000-00805f9b34fb"); // write only
|
||||
@@ -53,7 +55,17 @@ public class BluetoothOneByone extends BluetoothCommunication {
|
||||
switch (stateNr) {
|
||||
case 0:
|
||||
lastWeight = 0;
|
||||
setIndicationOn(WEIGHT_MEASUREMENT_SERVICE_BODY_COMPOSITION, WEIGHT_MEASUREMENT_CHARACTERISTIC_BODY_COMPOSITION, WEIGHT_MEASUREMENT_CONFIG);
|
||||
|
||||
if (hasBluetoothGattService(WEIGHT_MEASUREMENT_SERVICE_BODY_COMPOSITION)) {
|
||||
setIndicationOn(WEIGHT_MEASUREMENT_SERVICE_BODY_COMPOSITION,
|
||||
WEIGHT_MEASUREMENT_CHARACTERISTIC_BODY_COMPOSITION,
|
||||
WEIGHT_MEASUREMENT_CONFIG);
|
||||
}
|
||||
else {
|
||||
setNotificationOn(WEIGHT_MEASUREMENT_SERVICE,
|
||||
WEIGHT_MEASUREMENT_CHARACTERISTIC_BODY_COMPOSITION_ALT,
|
||||
WEIGHT_MEASUREMENT_CONFIG);
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
ScaleUser currentUser = OpenScale.getInstance().getSelectedScaleUser();
|
||||
@@ -94,11 +106,20 @@ public class BluetoothOneByone extends BluetoothCommunication {
|
||||
@Override
|
||||
public void onBluetoothDataChange(BluetoothGatt bluetoothGatt, BluetoothGattCharacteristic gattCharacteristic) {
|
||||
final byte[] data = gattCharacteristic.getValue();
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final UUID uuid = gattCharacteristic.getUuid();
|
||||
// if data is valid data
|
||||
if (data != null && data.length == 20) {
|
||||
if (data.length == 20
|
||||
&& uuid.equals(WEIGHT_MEASUREMENT_CHARACTERISTIC_BODY_COMPOSITION)) {
|
||||
parseBytes(data);
|
||||
}
|
||||
else if (data.length == 11
|
||||
&& uuid.equals(WEIGHT_MEASUREMENT_CHARACTERISTIC_BODY_COMPOSITION_ALT)) {
|
||||
parseBytesAlt(data);
|
||||
}
|
||||
}
|
||||
|
||||
private void parseBytes(byte[] weightBytes) {
|
||||
@@ -117,4 +138,21 @@ public class BluetoothOneByone extends BluetoothCommunication {
|
||||
addScaleData(scaleBtData);
|
||||
}
|
||||
}
|
||||
|
||||
private void parseBytesAlt(byte[] weightBytes) {
|
||||
float weight = Converters.fromUnsignedInt16Le(weightBytes, 3) / 100.0f;
|
||||
boolean done = (weightBytes[9] & 0xff) == 0;
|
||||
|
||||
Timber.d("weight: %.2f%s", weight, done ? " (done)" : "");
|
||||
|
||||
// This check should be a bit more elaborate, but it works for now...
|
||||
if (done && weight != lastWeight) {
|
||||
lastWeight = weight;
|
||||
|
||||
ScaleMeasurement scaleBtData = new ScaleMeasurement();
|
||||
scaleBtData.setWeight(weight);
|
||||
|
||||
addScaleData(scaleBtData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -37,6 +37,7 @@ import android.widget.LinearLayout;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.health.openscale.R;
|
||||
import com.health.openscale.core.OpenScale;
|
||||
@@ -321,9 +322,16 @@ public class GraphFragment extends Fragment implements FragmentUpdateListener {
|
||||
|
||||
floatingActionBar.removeAllViews();
|
||||
|
||||
PolynomialFitter polyFitter = new PolynomialFitter(
|
||||
Math.min(Integer.parseInt(prefs.getString("regressionLineOrder", "1")),
|
||||
100));
|
||||
int regressLineOrder = 1;
|
||||
|
||||
try {
|
||||
regressLineOrder = Integer.parseInt(prefs.getString("regressionLineOrder", "1"));
|
||||
} catch (NumberFormatException e) {
|
||||
Toast.makeText(getContext(), getString(R.string.error_value_required) + ":" + e.getMessage(), Toast.LENGTH_LONG).show();
|
||||
prefs.edit().putString("regressionLineOrder", "1").commit();
|
||||
}
|
||||
|
||||
PolynomialFitter polyFitter = new PolynomialFitter(Math.min(regressLineOrder, 100));
|
||||
|
||||
for (MeasurementView view : measurementViews) {
|
||||
if (view instanceof FloatMeasurementView) {
|
||||
|
@@ -307,7 +307,7 @@ public class BluetoothPreferences extends PreferenceFragment {
|
||||
super.onStart();
|
||||
|
||||
// Restart discovery after e.g. orientation change
|
||||
if (btScanner.getDialog() != null) {
|
||||
if (btScanner.getDialog() != null && btScanner.getDialog().isShowing()) {
|
||||
startBluetoothDiscovery();
|
||||
}
|
||||
}
|
||||
|
BIN
doc/scales/excelvan_cf366ble.jpg
Normal file
BIN
doc/scales/excelvan_cf366ble.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 131 KiB |
Reference in New Issue
Block a user