From 849dbb9591bc51caa0db9b278796a0a4eae19bbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Jur=C3=ADk?= Date: Tue, 17 Oct 2017 22:02:10 +0200 Subject: [PATCH 01/11] Updated slovak translation Fixed some mismatches in slovak translation --- android_app/app/src/main/res/values-sk/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android_app/app/src/main/res/values-sk/strings.xml b/android_app/app/src/main/res/values-sk/strings.xml index 7adf126c..9a7b7292 100644 --- a/android_app/app/src/main/res/values-sk/strings.xml +++ b/android_app/app/src/main/res/values-sk/strings.xml @@ -104,7 +104,7 @@ Bod na údajový bod Potvrďte vymazanie Pripomienka - Pracovné dni + Dni v týždni Čas Text upozornenia Čas na váženie @@ -114,7 +114,7 @@ Vaše percento svalov v tele bolo Váš obvod pása bol Váš obvod bokov bol - do + v deň Pondelok Utorok Streda From 22d812a2ccd97bfbd407997959fbb4ed20532804 Mon Sep 17 00:00:00 2001 From: oliexdev Date: Sun, 22 Oct 2017 11:22:57 +0200 Subject: [PATCH 02/11] add increment and decrement button to data edit mode --- .../openscale/gui/views/MeasurementView.java | 153 +++++++++++++++++- .../gui/views/TimeMeasurementView.java | 2 +- .../gui/views/WeightMeasurementView.java | 4 - 3 files changed, 152 insertions(+), 7 deletions(-) diff --git a/android_app/app/src/main/java/com/health/openscale/gui/views/MeasurementView.java b/android_app/app/src/main/java/com/health/openscale/gui/views/MeasurementView.java index 662e11ab..62a71db4 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/views/MeasurementView.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/views/MeasurementView.java @@ -26,15 +26,23 @@ import android.text.Html; import android.text.InputType; import android.util.TypedValue; import android.view.Gravity; +import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.Space; import android.widget.TableLayout; import android.widget.TableRow; import android.widget.TextView; +import android.os.Handler; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnTouchListener; import com.health.openscale.R; import com.health.openscale.core.OpenScale; @@ -59,6 +67,9 @@ public abstract class MeasurementView extends TableLayout { private ImageView iconView; private TextView nameView; private TextView valueView; + private LinearLayout incdecLayout; + private Button incView; + private Button decView; private ImageView editModeView; private ImageView indicatorView; @@ -90,17 +101,22 @@ public abstract class MeasurementView extends TableLayout { iconView = new ImageView(context); nameView = new TextView(context); valueView = new TextView(context); + incView = new Button(context); + decView = new Button(context); editModeView = new ImageView(context); indicatorView = new ImageView(context); evaluatorRow = new TableRow(context); evaluatorView = new LinearGaugeView(context); + incdecLayout = new LinearLayout(context); + measurementRow.setLayoutParams(new TableRow.LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT, 1.0f)); measurementRow.setGravity(Gravity.CENTER); measurementRow.addView(iconView); measurementRow.addView(nameView); measurementRow.addView(valueView); + measurementRow.addView(incdecLayout); measurementRow.addView(editModeView); measurementRow.addView(indicatorView); @@ -113,7 +129,7 @@ public abstract class MeasurementView extends TableLayout { nameView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 15); nameView.setTextColor(Color.BLACK); nameView.setLines(2); - nameView.setLayoutParams(new TableRow.LayoutParams(0, LayoutParams.WRAP_CONTENT, 0.60f)); + nameView.setLayoutParams(new TableRow.LayoutParams(0, LayoutParams.WRAP_CONTENT, 0.55f)); valueView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 15); valueView.setTextColor(Color.BLACK); @@ -121,6 +137,50 @@ public abstract class MeasurementView extends TableLayout { valueView.setPadding(0,0,20,0); valueView.setLayoutParams(new TableRow.LayoutParams(0, LayoutParams.MATCH_PARENT, 0.29f)); + incdecLayout.setOrientation(VERTICAL); + incdecLayout.addView(incView); + incdecLayout.addView(decView); + incdecLayout.setVisibility(View.GONE); + incdecLayout.setPadding(0,0,0,0); + incdecLayout.setLayoutParams(new TableRow.LayoutParams(0, LayoutParams.MATCH_PARENT, 0.05f)); + + incView.setText("+"); + incView.setBackgroundColor(Color.TRANSPARENT); + incView.setPadding(0,0,0,0); + incView.setLayoutParams(new TableRow.LayoutParams(LayoutParams.MATCH_PARENT, 0, 0.50f)); + incView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + incValue(); + } + }); + incView.setOnTouchListener(new RepeatListener(400, 100, new OnClickListener() { + @Override + public void onClick(View view) { + incValue(); + } + })); + incView.setVisibility(View.GONE); + + decView.setText("-"); + decView.setBackgroundColor(Color.TRANSPARENT); + decView.setPadding(0,0,0,0); + decView.setLayoutParams(new TableRow.LayoutParams(LayoutParams.MATCH_PARENT, 0, 0.50f)); + decView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + decValue(); + } + }); + + decView.setOnTouchListener(new RepeatListener(400, 100, new OnClickListener() { + @Override + public void onClick(View view) { + decValue(); + } + })); + decView.setVisibility(View.GONE); + editModeView.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_editable)); editModeView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); editModeView.setVisibility(View.GONE); @@ -157,6 +217,22 @@ public abstract class MeasurementView extends TableLayout { return Float.valueOf(value); } + public void incValue() { + float incValue = getValue() + 0.1f; + + if (incValue <= getMaxValue()) { + setValueOnView(incValue); + } + } + + public void decValue() { + float decValue = getValue() - 0.1f; + + if (decValue >= 0) { + setValueOnView(decValue); + } + } + public String getValueAsString() { return value; } @@ -176,13 +252,26 @@ public abstract class MeasurementView extends TableLayout { case VIEW: indicatorView.setVisibility(View.VISIBLE); editModeView.setVisibility(View.GONE); + incdecLayout.setVisibility(View.GONE); + incView.setVisibility(View.GONE); + decView.setVisibility(View.GONE); break; case EDIT: case ADD: editModeView.setVisibility(View.VISIBLE); + incView.setVisibility(View.VISIBLE); + decView.setVisibility(View.VISIBLE); + incdecLayout.setVisibility(View.VISIBLE); if (!isEditable()) { editModeView.setImageDrawable(ContextCompat.getDrawable(getContext(), R.drawable.ic_noteeditable)); + incView.setVisibility(View.GONE); + decView.setVisibility(View.GONE); + } + + if (getUnit() == null) { + incView.setVisibility(View.GONE); + decView.setVisibility(View.GONE); } indicatorView.setVisibility(View.GONE); @@ -334,7 +423,7 @@ public abstract class MeasurementView extends TableLayout { input.setInputType(getInputType()); input.setHint(getHintText()); input.setText(value); - input.setSelection(value.length()); + input.setSelectAllOnFocus(true); builder.setView(input); builder.setPositiveButton(getResources().getString(R.string.label_ok), null); @@ -396,5 +485,65 @@ public abstract class MeasurementView extends TableLayout { } } } + + private class RepeatListener implements OnTouchListener { + + private Handler handler = new Handler(); + + private int initialInterval; + private final int normalInterval; + private final OnClickListener clickListener; + + private Runnable handlerRunnable = new Runnable() { + @Override + public void run() { + handler.postDelayed(this, normalInterval); + clickListener.onClick(downView); + } + }; + + private View downView; + + /** + * RepeatListener cyclically runs a clickListener, emulating keyboard-like behaviour. First + * click is fired immediately, next one after the initialInterval, and subsequent ones after the normalInterval. + * + * @param initialInterval The interval after first click event + * @param normalInterval The interval after second and subsequent click events + * @param clickListener The OnClickListener, that will be called periodically + */ + public RepeatListener(int initialInterval, int normalInterval, + OnClickListener clickListener) { + if (clickListener == null) + throw new IllegalArgumentException("null runnable"); + if (initialInterval < 0 || normalInterval < 0) + throw new IllegalArgumentException("negative interval"); + + this.initialInterval = initialInterval; + this.normalInterval = normalInterval; + this.clickListener = clickListener; + } + + public boolean onTouch(View view, MotionEvent motionEvent) { + switch (motionEvent.getAction()) { + case MotionEvent.ACTION_DOWN: + handler.removeCallbacks(handlerRunnable); + handler.postDelayed(handlerRunnable, initialInterval); + downView = view; + downView.setPressed(true); + clickListener.onClick(view); + return true; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + handler.removeCallbacks(handlerRunnable); + downView.setPressed(false); + downView = null; + return true; + } + + return false; + } + + } } diff --git a/android_app/app/src/main/java/com/health/openscale/gui/views/TimeMeasurementView.java b/android_app/app/src/main/java/com/health/openscale/gui/views/TimeMeasurementView.java index 73def5ff..9b1bd472 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/views/TimeMeasurementView.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/views/TimeMeasurementView.java @@ -70,7 +70,7 @@ public class TimeMeasurementView extends MeasurementView { @Override public String getUnit() { - return ""; + return null; } @Override diff --git a/android_app/app/src/main/java/com/health/openscale/gui/views/WeightMeasurementView.java b/android_app/app/src/main/java/com/health/openscale/gui/views/WeightMeasurementView.java index 658ef3a3..be7c1a32 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/views/WeightMeasurementView.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/views/WeightMeasurementView.java @@ -34,10 +34,6 @@ public class WeightMeasurementView extends MeasurementView { @Override public void updateValue(ScaleData updateData) { setValueOnView(updateData.getConvertedWeight(getScaleUser().scale_unit)); - - if (getMeasurementMode() == MeasurementViewMode.ADD) { - getInputDialog().show(); - } } @Override From b2ec8599b7b17ee6c39dc0550ec0be1e42688fc7 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Thu, 26 Oct 2017 23:07:57 +0200 Subject: [PATCH 03/11] Initial working support for Mi Scale v2 Notes: - This is basically a copy of BluetoothMiScale.java with some data offset adjustments - The absent realtime measurement characteristic disabled - No extra body fat etc infos are parsed, just the same data as on the v1 scale are processed - The scale is not yet switched to kg, it is still operated in catty and there is no HW switch! --- .../bluetooth/BluetoothCommunication.java | 4 +- .../core/bluetooth/BluetoothMiScale2.java | 288 ++++++++++++++++++ 2 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMiScale2.java diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothCommunication.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothCommunication.java index 071514e7..e36d311c 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothCommunication.java +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothCommunication.java @@ -38,7 +38,7 @@ public abstract class BluetoothCommunication { BT_CONNECTION_LOST, BT_NO_DEVICE_FOUND, BT_UNEXPECTED_ERROR, BT_SCALE_MESSAGE }; public enum BT_MACHINE_STATE {BT_INIT_STATE, BT_CMD_STATE, BT_CLEANUP_STATE} - public enum BT_DEVICE_ID {CUSTOM_OPENSCALE, MI_SCALE_V1, SANITAS_SBF70, MEDISANA_BS444, DIGOO_DGS038H, EXCELVANT_CF369BLE, YUNMAI_MINI} + public enum BT_DEVICE_ID {CUSTOM_OPENSCALE, MI_SCALE_V1, SANITAS_SBF70, MEDISANA_BS444, DIGOO_DGS038H, EXCELVANT_CF369BLE, YUNMAI_MINI, MI_SCALE_V2} protected Context context; @@ -81,6 +81,8 @@ public abstract class BluetoothCommunication { return new BluetoothCustomOpenScale(context); case MI_SCALE_V1: return new BluetoothMiScale(context); + case MI_SCALE_V2: + return new BluetoothMiScale2(context); case SANITAS_SBF70: return new BluetoothSanitasSbf70(context); case MEDISANA_BS444: diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMiScale2.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMiScale2.java new file mode 100644 index 00000000..3471c04e --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMiScale2.java @@ -0,0 +1,288 @@ +/* Copyright (C) 2014 olie.xdev +* +* 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 +*/ + +package com.health.openscale.core.bluetooth; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.util.Log; + +import com.health.openscale.core.datatypes.ScaleData; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.Random; +import java.util.UUID; + +import static com.health.openscale.core.bluetooth.BluetoothCommunication.BT_STATUS_CODE.BT_UNEXPECTED_ERROR; + +public class BluetoothMiScale2 extends BluetoothCommunication { + private final UUID WEIGHT_MEASUREMENT_SERVICE = UUID.fromString("0000181b-0000-1000-8000-00805f9b34fb"); + private final UUID WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC = UUID.fromString("00002a2f-0000-3512-2118-0009af100700"); + private final UUID WEIGHT_MEASUREMENT_TIME_CHARACTERISTIC = UUID.fromString("00002a2b-0000-1000-8000-00805f9b34fb"); + private final UUID WEIGHT_MEASUREMENT_CONFIG = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); + + public BluetoothMiScale2(Context context) { + super(context); + } + + @Override + public String deviceName() { + return "Xiaomi Mi Scale v2"; + } + + @Override + public String defaultDeviceName() { + return "MIBCS"; + } + + @Override + public void onBluetoothDataRead(BluetoothGatt bluetoothGatt, BluetoothGattCharacteristic gattCharacteristic, int status) { + byte[] data = gattCharacteristic.getValue(); + + int currentYear = Calendar.getInstance().get(Calendar.YEAR); + int currentMonth = Calendar.getInstance().get(Calendar.MONTH)+1; + int currentDay = Calendar.getInstance().get(Calendar.DAY_OF_MONTH); + int scaleYear = ((data[1] & 0xFF) << 8) | (data[0] & 0xFF); + int scaleMonth = (int) data[2]; + int scaleDay = (int) data[3]; + + if (currentYear == scaleYear && currentMonth == scaleMonth && currentDay == scaleDay) { + setBtMachineState(BT_MACHINE_STATE.BT_CMD_STATE); + } else { + Log.d("BluetoothMiScale", "Current year and scale year is different"); + } + } + + @Override + public void onBluetoothDataChange(BluetoothGatt bluetoothGatt, BluetoothGattCharacteristic gattCharacteristic) { + final byte[] data = gattCharacteristic.getValue(); + + if (data != null && data.length > 0) { + // Stop command from mi scale received + if (data[0] == 0x03) { + setBtMachineState(BT_MACHINE_STATE.BT_CLEANUP_STATE); + } + + if (data.length == 26) { + final byte[] firstWeight = Arrays.copyOfRange(data, 0, 10); + final byte[] secondWeight = Arrays.copyOfRange(data, 10, 20); + parseBytes(firstWeight); + parseBytes(secondWeight); + } + + if (data.length == 13) { + parseBytes(data); + } + + } + } + + + @Override + boolean nextInitCmd(int stateNr) { + switch (stateNr) { + case 0: + // read device time + readBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_TIME_CHARACTERISTIC); + break; + case 1: + // set current time + Calendar currentDateTime = Calendar.getInstance(); + int year = currentDateTime.get(Calendar.YEAR); + byte month = (byte)(currentDateTime.get(Calendar.MONTH)+1); + byte day = (byte)currentDateTime.get(Calendar.DAY_OF_MONTH); + byte hour = (byte)currentDateTime.get(Calendar.HOUR_OF_DAY); + byte min = (byte)currentDateTime.get(Calendar.MINUTE); + byte sec = (byte)currentDateTime.get(Calendar.SECOND); + + byte[] dateTimeByte = {(byte)(year), (byte)(year >> 8), month, day, hour, min, sec, 0x03, 0x00, 0x00}; + + writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_TIME_CHARACTERISTIC, dateTimeByte); + break; + case 2: + // set notification on for weight measurement history + setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, WEIGHT_MEASUREMENT_CONFIG); + break; + case 3: + // Set on history weight measurement + byte[] magicBytes = new byte[]{(byte)0x01, (byte)0x96, (byte)0x8a, (byte)0xbd, (byte)0x62}; + + writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, magicBytes); + break; + default: + return false; + } + + return true; + } + + @Override + boolean nextBluetoothCmd(int stateNr) { + switch (stateNr) { + case 0: + // set notification on for weight measurement history + setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, WEIGHT_MEASUREMENT_CONFIG); + break; + case 1: + // set notification on for weight measurement + // FIXME: replacement characteristic for realtime measurements on Mi Scale 2? + //setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_CHARACTERISTIC, WEIGHT_MEASUREMENT_CONFIG); + break; + case 2: + // configure scale to get only last measurements + int uniqueNumber = getUniqueNumber(); + + byte[] userIdentifier = new byte[]{(byte)0x01, (byte)0xFF, (byte)0xFF, (byte) ((uniqueNumber & 0xFF00) >> 8), (byte) ((uniqueNumber & 0xFF) >> 0)}; + writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, userIdentifier); + break; + case 3: + // set notification off for weight measurement history + setNotificationOff(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, WEIGHT_MEASUREMENT_CONFIG); + break; + case 4: + // set notification on for weight measurement history + setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, WEIGHT_MEASUREMENT_CONFIG); + break; + case 5: + // invoke receiving history data + writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, new byte[]{0x02}); + break; + default: + return false; + } + + return true; + } + + @Override + boolean nextCleanUpCmd(int stateNr) { + + switch (stateNr) { + case 0: + // send stop command to mi scale + writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, new byte[]{0x03}); + break; + case 1: + // acknowledge that you received the last history data + int uniqueNumber = getUniqueNumber(); + + byte[] userIdentifier = new byte[]{(byte)0x04, (byte)0xFF, (byte)0xFF, (byte) ((uniqueNumber & 0xFF00) >> 8), (byte) ((uniqueNumber & 0xFF) >> 0)}; + writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, userIdentifier); + break; + default: + return false; + } + + return true; + } + + private void parseBytes(byte[] weightBytes) { + try { + float weight = 0.0f; + + final byte ctrlByte = weightBytes[1]; + + final boolean isWeightRemoved = isBitSet(ctrlByte, 7); + final boolean isStabilized = isBitSet(ctrlByte, 5); + final boolean isLBSUnit = isBitSet(ctrlByte, 0); + final boolean isCattyUnit = isBitSet(ctrlByte, 4); + + /*Log.d("GattCallback", "IsWeightRemoved: " + isBitSet(ctrlByte, 7)); + Log.d("GattCallback", "6 LSB Unknown: " + isBitSet(ctrlByte, 6)); + Log.d("GattCallback", "IsStabilized: " + isBitSet(ctrlByte, 5)); + Log.d("GattCallback", "IsCattyOrKg: " + isBitSet(ctrlByte, 4)); + Log.d("GattCallback", "3 LSB Unknown: " + isBitSet(ctrlByte, 3)); + Log.d("GattCallback", "2 LSB Unknown: " + isBitSet(ctrlByte, 2)); + Log.d("GattCallback", "1 LSB Unknown: " + isBitSet(ctrlByte, 1)); + Log.d("GattCallback", "IsLBS: " + isBitSet(ctrlByte, 0));*/ + + // Only if the value is stabilized and the weight is *not* removed, the date is valid + if (isStabilized && !isWeightRemoved) { + + final int year = ((weightBytes[3] & 0xFF) << 8) | (weightBytes[2] & 0xFF); + final int month = (int) weightBytes[4]; + final int day = (int) weightBytes[5]; + final int hours = (int) weightBytes[6]; + final int min = (int) weightBytes[7]; + final int sec = (int) weightBytes[8]; + + if (isLBSUnit || isCattyUnit) { + weight = (float) (((weightBytes[12] & 0xFF) << 8) | (weightBytes[11] & 0xFF)) / 100.0f; + } else { + weight = (float) (((weightBytes[12] & 0xFF) << 8) | (weightBytes[11] & 0xFF)) / 200.0f; + } + + String date_string = year + "/" + month + "/" + day + "/" + hours + "/" + min; + Date date_time = new SimpleDateFormat("yyyy/MM/dd/HH/mm").parse(date_string); + + // Is the year plausible? Check if the year is in the range of 20 years... + if (validateDate(date_time, 20)) { + ScaleData scaleBtData = new ScaleData(); + + scaleBtData.setWeight(weight); + scaleBtData.setDateTime(date_time); + + addScaleData(scaleBtData); + } else { + Log.e("BluetoothMiScale", "Invalid Mi scale weight year " + year); + } + } + } catch (ParseException e) { + setBtStatus(BT_UNEXPECTED_ERROR, "Error while decoding bluetooth date string (" + e.getMessage() + ")"); + } + } + + private boolean validateDate(Date weightDate, int range) { + + Calendar currentDatePos = Calendar.getInstance(); + currentDatePos.add(Calendar.YEAR, range); + + Calendar currentDateNeg = Calendar.getInstance(); + currentDateNeg.add(Calendar.YEAR, -range); + + if (weightDate.before(currentDatePos.getTime()) && weightDate.after(currentDateNeg.getTime())) { + return true; + } + + return false; + } + + private int getUniqueNumber() { + int uniqueNumber; + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + uniqueNumber = prefs.getInt("uniqueNumber", 0x00); + + if (uniqueNumber == 0x00) { + Random r = new Random(); + uniqueNumber = r.nextInt(65535 - 100 + 1) + 100; + + prefs.edit().putInt("uniqueNumber", uniqueNumber).commit(); + } + + int userId = prefs.getInt("selectedUserId", -1); + + return uniqueNumber + userId; + } +} From e44fe05894195a7ed711249b5c9d70ca64fadadd Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Sat, 28 Oct 2017 00:27:15 +0200 Subject: [PATCH 04/11] Set scale units to user configured units (kg/lbs/st) --- .../openscale/core/bluetooth/BluetoothMiScale2.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMiScale2.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMiScale2.java index 3471c04e..8aef698f 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMiScale2.java +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMiScale2.java @@ -23,7 +23,9 @@ import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.util.Log; +import com.health.openscale.core.OpenScale; import com.health.openscale.core.datatypes.ScaleData; +import com.health.openscale.core.datatypes.ScaleUser; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -41,6 +43,9 @@ public class BluetoothMiScale2 extends BluetoothCommunication { private final UUID WEIGHT_MEASUREMENT_TIME_CHARACTERISTIC = UUID.fromString("00002a2b-0000-1000-8000-00805f9b34fb"); private final UUID WEIGHT_MEASUREMENT_CONFIG = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); + private final UUID WEIGHT_CUSTOM_SERVICE = UUID.fromString("00001530-0000-3512-2118-0009af100700"); + private final UUID WEIGHT_CUSTOM_CONFIG = UUID.fromString("00001542-0000-3512-2118-0009af100700"); + public BluetoothMiScale2(Context context) { super(context); } @@ -167,6 +172,12 @@ public class BluetoothMiScale2 extends BluetoothCommunication { // invoke receiving history data writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, new byte[]{0x02}); break; + case 6: + // set scale units + final ScaleUser selectedUser = OpenScale.getInstance(context).getSelectedScaleUser(); + byte[] setUnitCmd = new byte[]{(byte)0x06, (byte)0x04, (byte)0x00, (byte) selectedUser.scale_unit}; + writeBytes(WEIGHT_CUSTOM_SERVICE, WEIGHT_CUSTOM_CONFIG, setUnitCmd); + break; default: return false; } From 3ccc4b627ab7d32018dad9501399dc50e7f67e3c Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Sat, 28 Oct 2017 01:07:31 +0200 Subject: [PATCH 05/11] fix lbs and catty unit detection --- .../core/bluetooth/BluetoothMiScale2.java | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMiScale2.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMiScale2.java index 8aef698f..59c960a3 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMiScale2.java +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMiScale2.java @@ -211,21 +211,13 @@ public class BluetoothMiScale2 extends BluetoothCommunication { try { float weight = 0.0f; - final byte ctrlByte = weightBytes[1]; + final byte ctrlByte0 = weightBytes[0]; + final byte ctrlByte1 = weightBytes[1]; - final boolean isWeightRemoved = isBitSet(ctrlByte, 7); - final boolean isStabilized = isBitSet(ctrlByte, 5); - final boolean isLBSUnit = isBitSet(ctrlByte, 0); - final boolean isCattyUnit = isBitSet(ctrlByte, 4); - - /*Log.d("GattCallback", "IsWeightRemoved: " + isBitSet(ctrlByte, 7)); - Log.d("GattCallback", "6 LSB Unknown: " + isBitSet(ctrlByte, 6)); - Log.d("GattCallback", "IsStabilized: " + isBitSet(ctrlByte, 5)); - Log.d("GattCallback", "IsCattyOrKg: " + isBitSet(ctrlByte, 4)); - Log.d("GattCallback", "3 LSB Unknown: " + isBitSet(ctrlByte, 3)); - Log.d("GattCallback", "2 LSB Unknown: " + isBitSet(ctrlByte, 2)); - Log.d("GattCallback", "1 LSB Unknown: " + isBitSet(ctrlByte, 1)); - Log.d("GattCallback", "IsLBS: " + isBitSet(ctrlByte, 0));*/ + final boolean isWeightRemoved = isBitSet(ctrlByte1, 7); // FIXME: unconfirmed + final boolean isStabilized = isBitSet(ctrlByte1, 5); // FIXME: unconfirmed + final boolean isLBSUnit = isBitSet(ctrlByte0, 0); + final boolean isCattyUnit = isBitSet(ctrlByte1, 6); // Only if the value is stabilized and the weight is *not* removed, the date is valid if (isStabilized && !isWeightRemoved) { From 8a245a915577b4df3e60e9020e0488ee2fa8a2b6 Mon Sep 17 00:00:00 2001 From: OliE Date: Tue, 31 Oct 2017 14:12:23 +0100 Subject: [PATCH 06/11] convert initial weight into user scale unit --- .../com/health/openscale/core/OpenScale.java | 6 +-- .../core/database/ScaleUserDatabase.java | 6 +-- .../openscale/core/datatypes/ScaleUser.java | 45 ++++++++++++++++++- .../gui/activities/UserSettingsActivity.java | 2 +- 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/android_app/app/src/main/java/com/health/openscale/core/OpenScale.java b/android_app/app/src/main/java/com/health/openscale/core/OpenScale.java index 8a897493..7bca1430 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/OpenScale.java +++ b/android_app/app/src/main/java/com/health/openscale/core/OpenScale.java @@ -93,7 +93,7 @@ public class OpenScale { scaleUser.body_height = body_height; scaleUser.scale_unit = scale_unit; scaleUser.gender = gender; - scaleUser.initial_weight = initial_weight; + scaleUser.setConvertedInitialWeight(initial_weight); scaleUser.goal_weight = goal_weight; scaleUser.goal_date = new SimpleDateFormat("dd.MM.yyyy").parse(goal_date); @@ -150,7 +150,7 @@ public class OpenScale { scaleUser.body_height = body_height; scaleUser.scale_unit = scale_unit; scaleUser.gender = gender; - scaleUser.initial_weight = initial_weight; + scaleUser.setConvertedInitialWeight(initial_weight); scaleUser.goal_weight = goal_weight; scaleUser.goal_date = new SimpleDateFormat("dd.MM.yyyy").parse(goal_date); } catch (ParseException e) { @@ -212,7 +212,7 @@ public class OpenScale { if (scaleUserData.size() > 0) { lastWeight = scaleUserData.get(0).getWeight(); } else { - lastWeight = scaleUser.get(i).initial_weight; + lastWeight = scaleUser.get(i).getInitialWeight(); } if ((lastWeight - range) <= weight && (lastWeight + range) >= weight) { diff --git a/android_app/app/src/main/java/com/health/openscale/core/database/ScaleUserDatabase.java b/android_app/app/src/main/java/com/health/openscale/core/database/ScaleUserDatabase.java index f7294be2..4131a399 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/database/ScaleUserDatabase.java +++ b/android_app/app/src/main/java/com/health/openscale/core/database/ScaleUserDatabase.java @@ -113,7 +113,7 @@ public class ScaleUserDatabase extends SQLiteOpenHelper { values.put(COLUMN_NAME_BODY_HEIGHT, scaleUser.body_height); values.put(COLUMN_NAME_SCALE_UNIT, scaleUser.scale_unit); values.put(COLUMN_NAME_GENDER, scaleUser.gender); - values.put(COLUMN_NAME_INITIAL_WEIGHT, scaleUser.initial_weight); + values.put(COLUMN_NAME_INITIAL_WEIGHT, scaleUser.getInitialWeight()); values.put(COLUMN_NAME_GOAL_WEIGHT, scaleUser.goal_weight); values.put(COLUMN_NAME_GOAL_DATE, formatDateTime.format(scaleUser.goal_date)); @@ -146,7 +146,7 @@ public class ScaleUserDatabase extends SQLiteOpenHelper { values.put(COLUMN_NAME_BODY_HEIGHT, scaleUser.body_height); values.put(COLUMN_NAME_SCALE_UNIT, scaleUser.scale_unit); values.put(COLUMN_NAME_GENDER, scaleUser.gender); - values.put(COLUMN_NAME_INITIAL_WEIGHT, scaleUser.initial_weight); + values.put(COLUMN_NAME_INITIAL_WEIGHT, scaleUser.getInitialWeight()); values.put(COLUMN_NAME_GOAL_WEIGHT, scaleUser.goal_weight); values.put(COLUMN_NAME_GOAL_DATE, formatDateTime.format(scaleUser.goal_date)); @@ -222,7 +222,7 @@ public class ScaleUserDatabase extends SQLiteOpenHelper { scaleUser.birthday = formatDateTime.parse(birthday); scaleUser.goal_date = formatDateTime.parse(goal_date); - scaleUser.initial_weight = Math.round(initial_weight * 100.0f) / 100.0f; + scaleUser.setInitialWeight(Math.round(initial_weight * 100.0f) / 100.0f); scaleUser.goal_weight = Math.round(goal_weight * 100.0f) / 100.0f; } catch (ParseException ex) { Log.e("ScaleDatabase", "Can't parse the date time string: " + ex.getMessage()); diff --git a/android_app/app/src/main/java/com/health/openscale/core/datatypes/ScaleUser.java b/android_app/app/src/main/java/com/health/openscale/core/datatypes/ScaleUser.java index 7f0d5c88..d541685c 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/datatypes/ScaleUser.java +++ b/android_app/app/src/main/java/com/health/openscale/core/datatypes/ScaleUser.java @@ -21,6 +21,8 @@ import java.util.Date; public class ScaleUser { public static final String[] UNIT_STRING = new String[] {"kg", "lb", "st"}; + private static float KG_LB = 2.20462f; + private static float KG_ST = 0.157473f; public int id; public String user_name; @@ -28,7 +30,7 @@ public class ScaleUser { public int body_height; public int scale_unit; public int gender; - public float initial_weight; + private float initial_weight; public float goal_weight; public Date goal_date; @@ -62,6 +64,47 @@ public class ScaleUser { return userAge; } + public void setInitialWeight(float weight) { + this.initial_weight = weight; + + } + + public void setConvertedInitialWeight(float weight) { + switch (ScaleUser.UNIT_STRING[scale_unit]) { + case "kg": + this.initial_weight = weight; + break; + case "lb": + this.initial_weight = weight / KG_LB; + break; + case "st": + this.initial_weight = weight / KG_ST; + break; + } + } + + public float getInitialWeight() { + return initial_weight; + } + + public float getConvertedInitialWeight() { + float converted_weight = 0.0f; + + switch (ScaleUser.UNIT_STRING[scale_unit]) { + case "kg": + converted_weight = initial_weight; + break; + case "lb": + converted_weight = initial_weight * KG_LB; + break; + case "st": + converted_weight = initial_weight * KG_ST; + break; + } + + return converted_weight; + } + @Override public String toString() { diff --git a/android_app/app/src/main/java/com/health/openscale/gui/activities/UserSettingsActivity.java b/android_app/app/src/main/java/com/health/openscale/gui/activities/UserSettingsActivity.java index b208383b..ac99cc9a 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/activities/UserSettingsActivity.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/activities/UserSettingsActivity.java @@ -133,7 +133,7 @@ public class UserSettingsActivity extends Activity { txtBodyHeight.setText(Integer.toString(scaleUser.body_height)); txtBirthday.setText(dateFormat.format(scaleUser.birthday)); txtGoalDate.setText(dateFormat.format(scaleUser.goal_date)); - txtInitialWeight.setText(scaleUser.initial_weight+""); + txtInitialWeight.setText(Math.round(scaleUser.getConvertedInitialWeight()*100.0f)/100.0f + ""); txtGoalWeight.setText(scaleUser.goal_weight+""); switch (scaleUser.scale_unit) From 896985152a289da6031d011b31aa89c87b8a39d9 Mon Sep 17 00:00:00 2001 From: OliE Date: Tue, 31 Oct 2017 18:49:47 +0100 Subject: [PATCH 07/11] receive weight while on miscale v2 scale --- .../core/bluetooth/BluetoothMiScale2.java | 66 +++++-------------- 1 file changed, 18 insertions(+), 48 deletions(-) diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMiScale2.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMiScale2.java index 59c960a3..56338c04 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMiScale2.java +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMiScale2.java @@ -42,6 +42,8 @@ public class BluetoothMiScale2 extends BluetoothCommunication { private final UUID WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC = UUID.fromString("00002a2f-0000-3512-2118-0009af100700"); private final UUID WEIGHT_MEASUREMENT_TIME_CHARACTERISTIC = UUID.fromString("00002a2b-0000-1000-8000-00805f9b34fb"); private final UUID WEIGHT_MEASUREMENT_CONFIG = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); + private final UUID WEIGHT_MEASUREMENT_BODY_COMPOSITION_FEATURE = UUID.fromString("00002a9b-0000-1000-8000-00805f9b34fb"); + private final UUID WEIGHT_MEASUREMENT_BODY_COMPOSITION_MEASUREMENT = UUID.fromString("00002a9c-0000-1000-8000-00805f9b34fb"); private final UUID WEIGHT_CUSTOM_SERVICE = UUID.fromString("00001530-0000-3512-2118-0009af100700"); private final UUID WEIGHT_CUSTOM_CONFIG = UUID.fromString("00001542-0000-3512-2118-0009af100700"); @@ -60,29 +62,12 @@ public class BluetoothMiScale2 extends BluetoothCommunication { return "MIBCS"; } - @Override - public void onBluetoothDataRead(BluetoothGatt bluetoothGatt, BluetoothGattCharacteristic gattCharacteristic, int status) { - byte[] data = gattCharacteristic.getValue(); - - int currentYear = Calendar.getInstance().get(Calendar.YEAR); - int currentMonth = Calendar.getInstance().get(Calendar.MONTH)+1; - int currentDay = Calendar.getInstance().get(Calendar.DAY_OF_MONTH); - int scaleYear = ((data[1] & 0xFF) << 8) | (data[0] & 0xFF); - int scaleMonth = (int) data[2]; - int scaleDay = (int) data[3]; - - if (currentYear == scaleYear && currentMonth == scaleMonth && currentDay == scaleDay) { - setBtMachineState(BT_MACHINE_STATE.BT_CMD_STATE); - } else { - Log.d("BluetoothMiScale", "Current year and scale year is different"); - } - } - @Override public void onBluetoothDataChange(BluetoothGatt bluetoothGatt, BluetoothGattCharacteristic gattCharacteristic) { final byte[] data = gattCharacteristic.getValue(); if (data != null && data.length > 0) { + // Stop command from mi scale received if (data[0] == 0x03) { setBtMachineState(BT_MACHINE_STATE.BT_CLEANUP_STATE); @@ -107,8 +92,10 @@ public class BluetoothMiScale2 extends BluetoothCommunication { boolean nextInitCmd(int stateNr) { switch (stateNr) { case 0: - // read device time - readBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_TIME_CHARACTERISTIC); + // set scale units + final ScaleUser selectedUser = OpenScale.getInstance(context).getSelectedScaleUser(); + byte[] setUnitCmd = new byte[]{(byte)0x06, (byte)0x04, (byte)0x00, (byte) selectedUser.scale_unit}; + writeBytes(WEIGHT_CUSTOM_SERVICE, WEIGHT_CUSTOM_CONFIG, setUnitCmd); break; case 1: // set current time @@ -128,12 +115,6 @@ public class BluetoothMiScale2 extends BluetoothCommunication { // set notification on for weight measurement history setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, WEIGHT_MEASUREMENT_CONFIG); break; - case 3: - // Set on history weight measurement - byte[] magicBytes = new byte[]{(byte)0x01, (byte)0x96, (byte)0x8a, (byte)0xbd, (byte)0x62}; - - writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, magicBytes); - break; default: return false; } @@ -145,39 +126,24 @@ public class BluetoothMiScale2 extends BluetoothCommunication { boolean nextBluetoothCmd(int stateNr) { switch (stateNr) { case 0: - // set notification on for weight measurement history - setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, WEIGHT_MEASUREMENT_CONFIG); - break; - case 1: - // set notification on for weight measurement - // FIXME: replacement characteristic for realtime measurements on Mi Scale 2? - //setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_CHARACTERISTIC, WEIGHT_MEASUREMENT_CONFIG); - break; - case 2: // configure scale to get only last measurements int uniqueNumber = getUniqueNumber(); byte[] userIdentifier = new byte[]{(byte)0x01, (byte)0xFF, (byte)0xFF, (byte) ((uniqueNumber & 0xFF00) >> 8), (byte) ((uniqueNumber & 0xFF) >> 0)}; writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, userIdentifier); break; - case 3: + case 1: // set notification off for weight measurement history setNotificationOff(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, WEIGHT_MEASUREMENT_CONFIG); break; - case 4: + case 2: // set notification on for weight measurement history setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, WEIGHT_MEASUREMENT_CONFIG); break; - case 5: + case 3: // invoke receiving history data writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, new byte[]{0x02}); break; - case 6: - // set scale units - final ScaleUser selectedUser = OpenScale.getInstance(context).getSelectedScaleUser(); - byte[] setUnitCmd = new byte[]{(byte)0x06, (byte)0x04, (byte)0x00, (byte) selectedUser.scale_unit}; - writeBytes(WEIGHT_CUSTOM_SERVICE, WEIGHT_CUSTOM_CONFIG, setUnitCmd); - break; default: return false; } @@ -200,6 +166,10 @@ public class BluetoothMiScale2 extends BluetoothCommunication { byte[] userIdentifier = new byte[]{(byte)0x04, (byte)0xFF, (byte)0xFF, (byte) ((uniqueNumber & 0xFF00) >> 8), (byte) ((uniqueNumber & 0xFF) >> 0)}; writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, userIdentifier); break; + case 2: + // set notification on for body composition measurement + setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_BODY_COMPOSITION_MEASUREMENT, WEIGHT_MEASUREMENT_CONFIG); + break; default: return false; } @@ -214,13 +184,13 @@ public class BluetoothMiScale2 extends BluetoothCommunication { final byte ctrlByte0 = weightBytes[0]; final byte ctrlByte1 = weightBytes[1]; - final boolean isWeightRemoved = isBitSet(ctrlByte1, 7); // FIXME: unconfirmed - final boolean isStabilized = isBitSet(ctrlByte1, 5); // FIXME: unconfirmed + final boolean isWeightRemoved = isBitSet(ctrlByte1, 7); + final boolean isDateInvalid = isBitSet(ctrlByte1, 6); + final boolean isStabilized = isBitSet(ctrlByte1, 5); final boolean isLBSUnit = isBitSet(ctrlByte0, 0); final boolean isCattyUnit = isBitSet(ctrlByte1, 6); - // Only if the value is stabilized and the weight is *not* removed, the date is valid - if (isStabilized && !isWeightRemoved) { + if (isStabilized && !isWeightRemoved && !isDateInvalid) { final int year = ((weightBytes[3] & 0xFF) << 8) | (weightBytes[2] & 0xFF); final int month = (int) weightBytes[4]; From 5556d20cc48899123e94a19d438979da45c6900d Mon Sep 17 00:00:00 2001 From: OliE Date: Tue, 31 Oct 2017 19:05:26 +0100 Subject: [PATCH 08/11] added support for Yunmai SE scale --- .../bluetooth/BluetoothCommunication.java | 4 +- .../core/bluetooth/BluetoothYunmaiSE.java | 168 ++++++++++++++++++ 2 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothYunmaiSE.java diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothCommunication.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothCommunication.java index e36d311c..2921dee7 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothCommunication.java +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothCommunication.java @@ -38,7 +38,7 @@ public abstract class BluetoothCommunication { BT_CONNECTION_LOST, BT_NO_DEVICE_FOUND, BT_UNEXPECTED_ERROR, BT_SCALE_MESSAGE }; public enum BT_MACHINE_STATE {BT_INIT_STATE, BT_CMD_STATE, BT_CLEANUP_STATE} - public enum BT_DEVICE_ID {CUSTOM_OPENSCALE, MI_SCALE_V1, SANITAS_SBF70, MEDISANA_BS444, DIGOO_DGS038H, EXCELVANT_CF369BLE, YUNMAI_MINI, MI_SCALE_V2} + public enum BT_DEVICE_ID {CUSTOM_OPENSCALE, MI_SCALE_V1, MI_SCALE_V2, SANITAS_SBF70, MEDISANA_BS444, DIGOO_DGS038H, EXCELVANT_CF369BLE, YUNMAI_MINI, YUNMAI_SE} protected Context context; @@ -93,6 +93,8 @@ public abstract class BluetoothCommunication { return new BluetoothExcelvanCF369BLE(context); case YUNMAI_MINI: return new BluetoothYunmaiMini(context); + case YUNMAI_SE: + return new BluetoothYunmaiSE(context); } return null; diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothYunmaiSE.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothYunmaiSE.java new file mode 100644 index 00000000..67916b55 --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothYunmaiSE.java @@ -0,0 +1,168 @@ +/* Copyright (C) 2017 olie.xdev +* +* 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 +*/ + +package com.health.openscale.core.bluetooth; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +import com.health.openscale.core.OpenScale; +import com.health.openscale.core.datatypes.ScaleData; +import com.health.openscale.core.datatypes.ScaleUser; + +import java.util.Date; +import java.util.Random; +import java.util.UUID; + +public class BluetoothYunmaiSE extends BluetoothCommunication { + private final UUID WEIGHT_MEASUREMENT_SERVICE = UUID.fromString("0000ffe0-0000-1000-8000-00805f9b34fb"); + private final UUID WEIGHT_MEASUREMENT_CHARACTERISTIC = UUID.fromString("0000ffe4-0000-1000-8000-00805f9b34fb"); + private final UUID WEIGHT_CMD_SERVICE = UUID.fromString("0000ffe5-0000-1000-8000-00805f9b34fb"); + private final UUID WEIGHT_CMD_CHARACTERISTIC = UUID.fromString("0000ffe9-0000-1000-8000-00805f9b34fb"); + + private final UUID WEIGHT_MEASUREMENT_CONFIG = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); + + public BluetoothYunmaiSE(Context context) { + super(context); + } + + @Override + public String deviceName() { + return "Yunmai SE"; + } + + @Override + public String defaultDeviceName() { + return "YUNMAI-ISSE-US"; + } + + @Override + public boolean checkDeviceName(String btDeviceName) { + if (btDeviceName.startsWith("YUNMAI-ISSE")) { + return true; + } + + return false; + } + + @Override + boolean nextInitCmd(int stateNr) { + switch (stateNr) { + case 0: + int user_id = getUniqueNumber(); + + final ScaleUser selectedUser = OpenScale.getInstance(context).getSelectedScaleUser(); + byte sex = selectedUser.isMale() ? (byte)0x01 : (byte)0x02; + byte display_unit = selectedUser.scale_unit == 0 ? (byte) 0x01 : (byte) 0x02; + + byte[] user_add_or_query = new byte[]{(byte)0x0d, (byte)0x12, (byte)0x10, (byte)0x01, (byte)0x00, (byte) 0x00, (byte) ((user_id & 0xFF00) >> 8), (byte) ((user_id & 0xFF) >> 0), (byte)selectedUser.body_height, (byte)sex, (byte) selectedUser.getAge(), (byte) 0x55, (byte) 0x5a, (byte) 0x00, (byte)0x00, (byte) display_unit, (byte) 0x03, (byte) 0x00 }; + user_add_or_query[17] = xor_checksum(user_add_or_query); + writeBytes(WEIGHT_CMD_SERVICE, WEIGHT_CMD_CHARACTERISTIC, user_add_or_query); + break; + case 1: + long unix_time = new Date().getTime() / 1000; + + byte[] set_time = new byte[]{(byte)0x0d, (byte) 0x0d, (byte) 0x11, (byte)((unix_time & 0xFF000000) >> 32), (byte)((unix_time & 0xFF0000) >> 16), (byte)((unix_time & 0xFF00) >> 8), (byte)((unix_time & 0xFF) >> 0), (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00}; + + set_time[12] = xor_checksum(set_time); + writeBytes(WEIGHT_CMD_SERVICE, WEIGHT_CMD_CHARACTERISTIC, set_time); + break; + case 2: + setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_CHARACTERISTIC, WEIGHT_MEASUREMENT_CONFIG); + break; + case 3: + byte[] magic_bytes = new byte[]{(byte)0x0d, (byte)0x05, (byte)0x13, (byte)0x00, (byte)0x16}; + writeBytes(WEIGHT_CMD_SERVICE, WEIGHT_CMD_CHARACTERISTIC, magic_bytes); + break; + default: + return false; + } + + return true; + } + + @Override + boolean nextBluetoothCmd(int stateNr) { + return false; + } + + @Override + boolean nextCleanUpCmd(int stateNr) { + return false; + } + + @Override + public void onBluetoothDataChange(BluetoothGatt bluetoothGatt, BluetoothGattCharacteristic gattCharacteristic) { + final byte[] data = gattCharacteristic.getValue(); + + if (data != null && data.length > 0) { + + // if finished weighting? + if (data[3] == 0x02) { + parseBytes(data); + } + } + } + + private void parseBytes(byte[] weightBytes) { + long unix_timestamp = ((weightBytes[5] & 0xFF) << 24) | ((weightBytes[6] & 0xFF) << 16) | ((weightBytes[7] & 0xFF) << 8) | (weightBytes[8] & 0xFF); + Date btDate = new Date(); + btDate.setTime(unix_timestamp*1000); + + float weight = (float) (((weightBytes[13] & 0xFF) << 8) | (weightBytes[14] & 0xFF)) / 100.0f; + + ScaleData scaleBtData = new ScaleData(); + + final ScaleUser selectedUser = OpenScale.getInstance(context).getSelectedScaleUser(); + + scaleBtData.setConvertedWeight(weight, selectedUser.scale_unit); + scaleBtData.setDateTime(btDate); + + addScaleData(scaleBtData); + } + + private int getUniqueNumber() { + int uniqueNumber; + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + uniqueNumber = prefs.getInt("uniqueNumber", 0x00); + + if (uniqueNumber == 0x00) { + Random r = new Random(); + uniqueNumber = r.nextInt(65535 - 100 + 1) + 100; + + prefs.edit().putInt("uniqueNumber", uniqueNumber).commit(); + } + + int userId = prefs.getInt("selectedUserId", -1); + + return uniqueNumber + userId; + } + + private byte xor_checksum(byte[] data) { + byte checksum = 0x00; + + for (int i=0; i Date: Tue, 31 Oct 2017 19:22:45 +0100 Subject: [PATCH 09/11] added debug information for mi scale v2 --- .../com/health/openscale/core/bluetooth/BluetoothMiScale2.java | 1 + 1 file changed, 1 insertion(+) diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMiScale2.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMiScale2.java index 56338c04..8590e267 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMiScale2.java +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMiScale2.java @@ -67,6 +67,7 @@ public class BluetoothMiScale2 extends BluetoothCommunication { final byte[] data = gattCharacteristic.getValue(); if (data != null && data.length > 0) { + Log.d("MIScale_v2", "DataChange hex data: "+ byteInHex(data)); // Stop command from mi scale received if (data[0] == 0x03) { From 987efab026e75db8d4c85abe94f80623928d23e6 Mon Sep 17 00:00:00 2001 From: OliE Date: Wed, 1 Nov 2017 18:17:49 +0100 Subject: [PATCH 10/11] added options to estimate body fat and body water --- .../com/health/openscale/core/OpenScale.java | 14 ++ .../openscale/core/bodymetric/BFBJoN.java | 35 +++++ .../core/bodymetric/BFDeurenberg.java | 35 +++++ .../openscale/core/bodymetric/BFEddy.java | 35 +++++ .../core/bodymetric/BFGallagher.java | 37 ++++++ .../core/bodymetric/BFGallagherAsian.java | 37 ++++++ .../core/bodymetric/EstimatedFatMetric.java | 43 ++++++ .../core/bodymetric/EstimatedWaterMetric.java | 41 ++++++ .../openscale/core/bodymetric/TBWBehnke.java | 35 +++++ .../core/bodymetric/TBWDelwaideCrenier.java | 31 +++++ .../core/bodymetric/TBWHumeWeyers.java | 35 +++++ .../core/bodymetric/TBWLeeSongKim.java | 35 +++++ .../preferences/MeasurementPreferences.java | 124 +++++++++++++++++- .../gui/views/FatMeasurementView.java | 17 ++- .../openscale/gui/views/MeasurementView.java | 14 +- .../gui/views/WaterMeasurementView.java | 17 ++- .../app/src/main/res/values/strings.xml | 7 + .../main/res/xml/measurement_preferences.xml | 4 + 18 files changed, 585 insertions(+), 11 deletions(-) create mode 100644 android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFBJoN.java create mode 100644 android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFDeurenberg.java create mode 100644 android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFEddy.java create mode 100644 android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFGallagher.java create mode 100644 android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFGallagherAsian.java create mode 100644 android_app/app/src/main/java/com/health/openscale/core/bodymetric/EstimatedFatMetric.java create mode 100644 android_app/app/src/main/java/com/health/openscale/core/bodymetric/EstimatedWaterMetric.java create mode 100644 android_app/app/src/main/java/com/health/openscale/core/bodymetric/TBWBehnke.java create mode 100644 android_app/app/src/main/java/com/health/openscale/core/bodymetric/TBWDelwaideCrenier.java create mode 100644 android_app/app/src/main/java/com/health/openscale/core/bodymetric/TBWHumeWeyers.java create mode 100644 android_app/app/src/main/java/com/health/openscale/core/bodymetric/TBWLeeSongKim.java diff --git a/android_app/app/src/main/java/com/health/openscale/core/OpenScale.java b/android_app/app/src/main/java/com/health/openscale/core/OpenScale.java index 7bca1430..56107b9b 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/OpenScale.java +++ b/android_app/app/src/main/java/com/health/openscale/core/OpenScale.java @@ -27,6 +27,8 @@ import android.widget.Toast; import com.health.openscale.R; import com.health.openscale.core.alarm.AlarmHandler; import com.health.openscale.core.bluetooth.BluetoothCommunication; +import com.health.openscale.core.bodymetric.EstimatedFatMetric; +import com.health.openscale.core.bodymetric.EstimatedWaterMetric; import com.health.openscale.core.database.ScaleDatabase; import com.health.openscale.core.database.ScaleUserDatabase; import com.health.openscale.core.datatypes.ScaleData; @@ -188,6 +190,18 @@ public class OpenScale { } } + if (prefs.getBoolean("estimateFatEnable", false)) { + EstimatedFatMetric fatMetric = EstimatedFatMetric.getEstimatedFatMetric(EstimatedFatMetric.FORMULA_FAT.valueOf(prefs.getString("estimateFatFormula", "BF_DEURENBERG_II"))); + + scaleData.setFat(fatMetric.getFat(getScaleUser(scaleData.getUserId()), scaleData)); + } + + if (prefs.getBoolean("estimateWaterEnable", false)) { + EstimatedWaterMetric waterMetric = EstimatedWaterMetric.getEstimatedWaterMetric(EstimatedWaterMetric.FORMULA_WATER.valueOf(prefs.getString("estimateWaterFormula", "TBW_BEHNKE"))); + + scaleData.setWater(waterMetric.getWater(getScaleUser(scaleData.getUserId()), scaleData)); + } + if (scaleDB.insertEntry(scaleData)) { ScaleUser scaleUser = getScaleUser(scaleData.getUserId()); diff --git a/android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFBJoN.java b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFBJoN.java new file mode 100644 index 00000000..a7d30212 --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFBJoN.java @@ -0,0 +1,35 @@ +/* Copyright (C) 2017 olie.xdev +* +* 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 +*/ +package com.health.openscale.core.bodymetric; + +import com.health.openscale.core.datatypes.ScaleData; +import com.health.openscale.core.datatypes.ScaleUser; + +public class BFBJoN extends EstimatedFatMetric { + @Override + public String getName() { + return "British Journal of Nutrition (1991)"; + } + + @Override + public float getFat(ScaleUser user, ScaleData data) { + if (user.isMale()) { + return (data.getBMI(user.body_height) * 1.2f) + (user.getAge() * 0.23f) - 16.2f; + } + + return (data.getBMI(user.body_height) * 1.2f) + (user.getAge() * 0.23f) - 5.4f; + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFDeurenberg.java b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFDeurenberg.java new file mode 100644 index 00000000..35b76bed --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFDeurenberg.java @@ -0,0 +1,35 @@ +/* Copyright (C) 2017 olie.xdev +* +* 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 +*/ +package com.health.openscale.core.bodymetric; + +import com.health.openscale.core.datatypes.ScaleData; +import com.health.openscale.core.datatypes.ScaleUser; + +public class BFDeurenberg extends EstimatedFatMetric { + @Override + public String getName() { + return "Deurenberg et. al (1998)"; + } + + @Override + public float getFat(ScaleUser user, ScaleData data) { + if (user.getAge() >= 16) { + return (1.2f * data.getBMI(user.body_height)) + (0.23f*user.getAge()) - (10.8f*(1-user.gender)) - 5.4f; + } + + return (1.294f * data.getBMI(user.body_height)) + (0.20f*user.getAge()) - (11.4f*(1-user.gender)) - 8.0f; + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFEddy.java b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFEddy.java new file mode 100644 index 00000000..81e2aa45 --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFEddy.java @@ -0,0 +1,35 @@ +/* Copyright (C) 2017 olie.xdev +* +* 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 +*/ +package com.health.openscale.core.bodymetric; + +import com.health.openscale.core.datatypes.ScaleData; +import com.health.openscale.core.datatypes.ScaleUser; + +public class BFEddy extends EstimatedFatMetric { + @Override + public String getName() { + return "Eddy et. al (1976)"; + } + + @Override + public float getFat(ScaleUser user, ScaleData data) { + if (user.isMale()) { + return (1.281f* data.getBMI(user.body_height)) - 10.13f; + } + + return (1.48f* data.getBMI(user.body_height)) - 7.0f; + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFGallagher.java b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFGallagher.java new file mode 100644 index 00000000..f0f31a8b --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFGallagher.java @@ -0,0 +1,37 @@ +/* Copyright (C) 2017 olie.xdev +* +* 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 +*/ +package com.health.openscale.core.bodymetric; + +import com.health.openscale.core.datatypes.ScaleData; +import com.health.openscale.core.datatypes.ScaleUser; + +public class BFGallagher extends EstimatedFatMetric { + @Override + public String getName() { + return "Gallagher et. al [non-asian] (2000)"; + } + + @Override + public float getFat(ScaleUser user, ScaleData data) { + if (user.isMale()) { + // non-asian male + return 64.5f - 848.0f * (1.0f / data.getBMI(user.body_height)) + 0.079f * user.getAge() - 16.4f + 0.05f * user.getAge() + 39.0f * (1.0f / data.getBMI(user.body_height)); + } + + // non-asian female + return 64.5f - 848.0f * (1.0f / data.getBMI(user.body_height)) + 0.079f * user.getAge(); + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFGallagherAsian.java b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFGallagherAsian.java new file mode 100644 index 00000000..a6a986e2 --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFGallagherAsian.java @@ -0,0 +1,37 @@ +/* Copyright (C) 2017 olie.xdev +* +* 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 +*/ +package com.health.openscale.core.bodymetric; + +import com.health.openscale.core.datatypes.ScaleData; +import com.health.openscale.core.datatypes.ScaleUser; + +public class BFGallagherAsian extends EstimatedFatMetric { + @Override + public String getName() { + return "Gallagher et. al [asian] (2000)"; + } + + @Override + public float getFat(ScaleUser user, ScaleData data) { + if (user.isMale()) { + // asian male + return 51.9f - 740.0f * (1.0f / data.getBMI(user.body_height)) + 0.029f * user.getAge(); + } + + // asian female + return 64.8f - 752.0f * (1.0f / data.getBMI(user.body_height)) + 0.016f * user.getAge(); + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/core/bodymetric/EstimatedFatMetric.java b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/EstimatedFatMetric.java new file mode 100644 index 00000000..6749a65e --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/EstimatedFatMetric.java @@ -0,0 +1,43 @@ +/* Copyright (C) 2017 olie.xdev +* +* 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 +*/ +package com.health.openscale.core.bodymetric; + +import com.health.openscale.core.datatypes.ScaleData; +import com.health.openscale.core.datatypes.ScaleUser; + +public abstract class EstimatedFatMetric { + public enum FORMULA_FAT { BF_DEURENBERG, BF_BJoN, BF_EDDY, BF_GALLAGHER, BF_GALLAGHER_ASIAN }; + + public static EstimatedFatMetric getEstimatedFatMetric( FORMULA_FAT fatMetric) { + switch (fatMetric) { + case BF_DEURENBERG: + return new BFDeurenberg(); + case BF_BJoN: + return new BFBJoN(); + case BF_EDDY: + return new BFEddy(); + case BF_GALLAGHER: + return new BFGallagher(); + case BF_GALLAGHER_ASIAN: + return new BFGallagherAsian(); + } + + return null; + } + + public abstract String getName(); + public abstract float getFat(ScaleUser user, ScaleData data); +} diff --git a/android_app/app/src/main/java/com/health/openscale/core/bodymetric/EstimatedWaterMetric.java b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/EstimatedWaterMetric.java new file mode 100644 index 00000000..a32e48db --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/EstimatedWaterMetric.java @@ -0,0 +1,41 @@ +/* Copyright (C) 2017 olie.xdev +* +* 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 +*/ +package com.health.openscale.core.bodymetric; + +import com.health.openscale.core.datatypes.ScaleData; +import com.health.openscale.core.datatypes.ScaleUser; + +public abstract class EstimatedWaterMetric { + public enum FORMULA_WATER { TBW_BEHNKE, TBW_DELWAIDECRENIER, TBW_HUMEWEYERS, TBW_LEESONGKIM }; + + public static EstimatedWaterMetric getEstimatedWaterMetric( FORMULA_WATER waterMetric) { + switch (waterMetric) { + case TBW_BEHNKE: + return new TBWBehnke(); + case TBW_DELWAIDECRENIER: + return new TBWDelwaideCrenier(); + case TBW_HUMEWEYERS: + return new TBWHumeWeyers(); + case TBW_LEESONGKIM: + return new TBWLeeSongKim(); + } + + return null; + } + + public abstract String getName(); + public abstract float getWater(ScaleUser user, ScaleData data); +} diff --git a/android_app/app/src/main/java/com/health/openscale/core/bodymetric/TBWBehnke.java b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/TBWBehnke.java new file mode 100644 index 00000000..53a6ba54 --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/TBWBehnke.java @@ -0,0 +1,35 @@ +/* Copyright (C) 2017 olie.xdev +* +* 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 +*/ +package com.health.openscale.core.bodymetric; + +import com.health.openscale.core.datatypes.ScaleData; +import com.health.openscale.core.datatypes.ScaleUser; + +public class TBWBehnke extends EstimatedWaterMetric { + @Override + public String getName() { + return "Behnke et. al (1963)"; + } + + @Override + public float getWater(ScaleUser user, ScaleData data) { + if (user.isMale()) { + return 0.72f * (0.204f * user.body_height * user.body_height) / 100.0f; + } + + return 0.72f * (0.18f * user.body_height * user.body_height) / 100.0f; + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/core/bodymetric/TBWDelwaideCrenier.java b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/TBWDelwaideCrenier.java new file mode 100644 index 00000000..83667a04 --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/TBWDelwaideCrenier.java @@ -0,0 +1,31 @@ +/* Copyright (C) 2017 olie.xdev +* +* 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 +*/ +package com.health.openscale.core.bodymetric; + +import com.health.openscale.core.datatypes.ScaleData; +import com.health.openscale.core.datatypes.ScaleUser; + +public class TBWDelwaideCrenier extends EstimatedWaterMetric { + @Override + public String getName() { + return "Delwaide-Crenier et. al (1973)"; + } + + @Override + public float getWater(ScaleUser user, ScaleData data) { + return 0.72f * (-1.976f + 0.907f * data.getWeight()); + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/core/bodymetric/TBWHumeWeyers.java b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/TBWHumeWeyers.java new file mode 100644 index 00000000..59d53c2d --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/TBWHumeWeyers.java @@ -0,0 +1,35 @@ +/* Copyright (C) 2017 olie.xdev +* +* 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 +*/ +package com.health.openscale.core.bodymetric; + +import com.health.openscale.core.datatypes.ScaleData; +import com.health.openscale.core.datatypes.ScaleUser; + +public class TBWHumeWeyers extends EstimatedWaterMetric { + @Override + public String getName() { + return "Hume & Weyers (1971)"; + } + + @Override + public float getWater(ScaleUser user, ScaleData data) { + if (user.isMale()) { + return (0.194786f * user.body_height) + (0.296785f * data.getWeight()) - 14.012934f; + } + + return (0.34454f * user.body_height) + (0.183809f * data.getWeight()) - 35.270121f; + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/core/bodymetric/TBWLeeSongKim.java b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/TBWLeeSongKim.java new file mode 100644 index 00000000..0f578345 --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/TBWLeeSongKim.java @@ -0,0 +1,35 @@ +/* Copyright (C) 2017 olie.xdev +* +* 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 +*/ +package com.health.openscale.core.bodymetric; + +import com.health.openscale.core.datatypes.ScaleData; +import com.health.openscale.core.datatypes.ScaleUser; + +public class TBWLeeSongKim extends EstimatedWaterMetric { + @Override + public String getName() { + return "Lee, Song, Kim, Lee et. al (1999)"; + } + + @Override + public float getWater(ScaleUser user, ScaleData data) { + if (user.isMale()) { + return -28.3497f + (0.243057f * user.body_height) + (0.366248f * data.getWeight()); + } + + return -26.6224f + (0.262513f * user.body_height) + (0.232948f * data.getWeight()); + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/gui/preferences/MeasurementPreferences.java b/android_app/app/src/main/java/com/health/openscale/gui/preferences/MeasurementPreferences.java index 03de804f..ef6c025b 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/preferences/MeasurementPreferences.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/preferences/MeasurementPreferences.java @@ -19,18 +19,37 @@ import android.app.AlertDialog; import android.content.DialogInterface; import android.content.SharedPreferences; import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.EditTextPreference; +import android.preference.ListPreference; +import android.preference.MultiSelectListPreference; import android.preference.Preference; import android.preference.PreferenceFragment; +import android.preference.PreferenceGroup; import android.preference.PreferenceManager; import android.widget.Toast; import com.health.openscale.R; import com.health.openscale.core.OpenScale; +import com.health.openscale.core.bodymetric.EstimatedFatMetric; +import com.health.openscale.core.bodymetric.EstimatedWaterMetric; -public class MeasurementPreferences extends PreferenceFragment { +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class MeasurementPreferences extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener { public static final String PREFERENCE_KEY_DELETE_ALL = "deleteAll"; + public static final String PREFERENCE_KEY_ESTIMATE_FAT = "estimateFatEnable"; + public static final String PREFERENCE_KEY_ESTIMATE_FAT_FORMULA = "estimateFatFormula"; + public static final String PREFERENCE_KEY_ESTIMATE_WATER = "estimateWaterEnable"; + public static final String PREFERENCE_KEY_ESTIMATE_WATER_FORMULA = "estimateWaterFormula"; private Preference deleteAll; + private CheckBoxPreference estimateFatEnable; + private ListPreference estimateFatFormula; + private CheckBoxPreference estimateWaterEnable; + private ListPreference estimateWaterFormula; @Override public void onCreate(Bundle savedInstanceState) { @@ -40,6 +59,109 @@ public class MeasurementPreferences extends PreferenceFragment { deleteAll = (Preference) findPreference(PREFERENCE_KEY_DELETE_ALL); deleteAll.setOnPreferenceClickListener(new onClickListenerDeleteAll()); + + estimateFatEnable = (CheckBoxPreference) findPreference(PREFERENCE_KEY_ESTIMATE_FAT); + estimateFatFormula = (ListPreference) findPreference(PREFERENCE_KEY_ESTIMATE_FAT_FORMULA); + estimateWaterEnable = (CheckBoxPreference) findPreference(PREFERENCE_KEY_ESTIMATE_WATER); + estimateWaterFormula = (ListPreference) findPreference(PREFERENCE_KEY_ESTIMATE_WATER_FORMULA); + + updateFatListPreferences(); + updateWaterListPreferences(); + initSummary(getPreferenceScreen()); + } + + public void updateFatListPreferences() { + ArrayList listEntries = new ArrayList(); + ArrayList listEntryValues = new ArrayList(); + + for (EstimatedFatMetric.FORMULA_FAT formulaFat : EstimatedFatMetric.FORMULA_FAT.values()) { + EstimatedFatMetric fatMetric = EstimatedFatMetric.getEstimatedFatMetric(formulaFat); + + listEntries.add(fatMetric.getName()); + listEntryValues.add(formulaFat.toString()); + } + + estimateFatFormula.setEntries(listEntries.toArray(new CharSequence[listEntries.size()])); + estimateFatFormula.setEntryValues(listEntryValues.toArray(new CharSequence[listEntryValues.size()])); + } + + public void updateWaterListPreferences() { + ArrayList listEntries = new ArrayList(); + ArrayList listEntryValues = new ArrayList(); + + for (EstimatedWaterMetric.FORMULA_WATER formulaWater : EstimatedWaterMetric.FORMULA_WATER.values()) { + EstimatedWaterMetric waterMetric = EstimatedWaterMetric.getEstimatedWaterMetric(formulaWater); + + listEntries.add(waterMetric.getName()); + listEntryValues.add(formulaWater.toString()); + } + + estimateWaterFormula.setEntries(listEntries.toArray(new CharSequence[listEntries.size()])); + estimateWaterFormula.setEntryValues(listEntryValues.toArray(new CharSequence[listEntryValues.size()])); + } + + private void initSummary(Preference p) { + if (p instanceof PreferenceGroup) { + PreferenceGroup pGrp = (PreferenceGroup) p; + for (int i = 0; i < pGrp.getPreferenceCount(); i++) { + initSummary(pGrp.getPreference(i)); + } + } else { + updatePrefSummary(p); + } + } + + @Override + public void onResume() { + super.onResume(); + getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + updatePrefSummary(findPreference(key)); + } + + private void updatePrefSummary(Preference p) { + if (estimateFatEnable.isChecked()) { + estimateFatFormula.setEnabled(true); + } else { + estimateFatFormula.setEnabled(false); + } + + if (estimateWaterEnable.isChecked()) { + estimateWaterFormula.setEnabled(true); + } else { + estimateWaterFormula.setEnabled(false); + } + + estimateFatFormula.setSummary(EstimatedFatMetric.getEstimatedFatMetric(EstimatedFatMetric.FORMULA_FAT.valueOf(estimateFatFormula.getValue())).getName()); + estimateWaterFormula.setSummary(EstimatedWaterMetric.getEstimatedWaterMetric(EstimatedWaterMetric.FORMULA_WATER.valueOf(estimateWaterFormula.getValue())).getName()); + + if (p instanceof EditTextPreference) { + EditTextPreference editTextPref = (EditTextPreference) p; + if (p.getTitle().toString().contains("assword")) + { + p.setSummary("******"); + } else { + p.setSummary(editTextPref.getText()); + } + } + + if (p instanceof MultiSelectListPreference) { + MultiSelectListPreference editMultiListPref = (MultiSelectListPreference) p; + + CharSequence[] entries = editMultiListPref.getEntries(); + CharSequence[] entryValues = editMultiListPref.getEntryValues(); + List currentEntries = new ArrayList<>(); + Set currentEntryValues = editMultiListPref.getValues(); + + for (int i = 0; i < entries.length; i++) + if (currentEntryValues.contains(entryValues[i])) + currentEntries.add(entries[i].toString()); + + p.setSummary(currentEntries.toString()); + } } private class onClickListenerDeleteAll implements Preference.OnPreferenceClickListener { diff --git a/android_app/app/src/main/java/com/health/openscale/gui/views/FatMeasurementView.java b/android_app/app/src/main/java/com/health/openscale/gui/views/FatMeasurementView.java index 447ba2ac..0d14a6a0 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/views/FatMeasurementView.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/views/FatMeasurementView.java @@ -26,13 +26,27 @@ import com.health.openscale.core.evaluation.EvaluationSheet; public class FatMeasurementView extends MeasurementView { + private boolean estimateFatEnable; + public FatMeasurementView(Context context) { super(context, context.getResources().getString(R.string.label_fat), ContextCompat.getDrawable(context, R.drawable.ic_fat)); } + @Override + public boolean isEditable() { + if (estimateFatEnable && getMeasurementMode() == MeasurementViewMode.ADD) { + return false; + } + return true; + } + @Override public void updateValue(ScaleData updateData) { - setValueOnView(updateData.getFat()); + if (estimateFatEnable && getMeasurementMode() == MeasurementViewMode.ADD) { + setValueOnView((getContext().getString(R.string.label_automatic))); + } else { + setValueOnView(updateData.getFat()); + } } @Override @@ -48,6 +62,7 @@ public class FatMeasurementView extends MeasurementView { @Override public void updatePreferences(SharedPreferences preferences) { setVisible(preferences.getBoolean("fatEnable", true)); + estimateFatEnable = preferences.getBoolean("estimateFatEnable", false); } @Override diff --git a/android_app/app/src/main/java/com/health/openscale/gui/views/MeasurementView.java b/android_app/app/src/main/java/com/health/openscale/gui/views/MeasurementView.java index 62a71db4..09635f65 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/views/MeasurementView.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/views/MeasurementView.java @@ -21,6 +21,7 @@ import android.content.DialogInterface; import android.content.SharedPreferences; import android.graphics.Color; import android.graphics.drawable.Drawable; +import android.os.Handler; import android.support.v4.content.ContextCompat; import android.text.Html; import android.text.InputType; @@ -28,7 +29,6 @@ import android.util.TypedValue; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; -import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; @@ -38,11 +38,6 @@ import android.widget.Space; import android.widget.TableLayout; import android.widget.TableRow; import android.widget.TextView; -import android.os.Handler; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.View.OnTouchListener; import com.health.openscale.R; import com.health.openscale.core.OpenScale; @@ -213,8 +208,11 @@ public abstract class MeasurementView extends TableLayout { if (value.length() == 0) { return -1; } - - return Float.valueOf(value); + try { + return Float.valueOf(value); + } catch (NumberFormatException e) { + return -1; + } } public void incValue() { diff --git a/android_app/app/src/main/java/com/health/openscale/gui/views/WaterMeasurementView.java b/android_app/app/src/main/java/com/health/openscale/gui/views/WaterMeasurementView.java index a153aad8..54ec2de1 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/views/WaterMeasurementView.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/views/WaterMeasurementView.java @@ -26,13 +26,27 @@ import com.health.openscale.core.evaluation.EvaluationSheet; public class WaterMeasurementView extends MeasurementView { + private boolean estimateWaterEnable; + public WaterMeasurementView(Context context) { super(context, context.getResources().getString(R.string.label_water), ContextCompat.getDrawable(context, R.drawable.ic_water)); } + @Override + public boolean isEditable() { + if (estimateWaterEnable && getMeasurementMode() == MeasurementViewMode.ADD) { + return false; + } + return true; + } + @Override public void updateValue(ScaleData updateData) { - setValueOnView(updateData.getWater()); + if (estimateWaterEnable && getMeasurementMode() == MeasurementViewMode.ADD) { + setValueOnView((getContext().getString(R.string.label_automatic))); + } else { + setValueOnView(updateData.getWater()); + } } @Override @@ -48,6 +62,7 @@ public class WaterMeasurementView extends MeasurementView { @Override public void updatePreferences(SharedPreferences preferences) { setVisible(preferences.getBoolean("waterEnable", true)); + estimateWaterEnable = preferences.getBoolean("estimateWaterEnable", false); } @Override diff --git a/android_app/app/src/main/res/values/strings.xml b/android_app/app/src/main/res/values/strings.xml index e649b79b..425027c0 100644 --- a/android_app/app/src/main/res/values/strings.xml +++ b/android_app/app/src/main/res/values/strings.xml @@ -120,6 +120,13 @@ Delete confirmation + Estimate body fat + Estimate body water + + Body fat formula + Body water formula + auto + Reminder Weekdays Time diff --git a/android_app/app/src/main/res/xml/measurement_preferences.xml b/android_app/app/src/main/res/xml/measurement_preferences.xml index daa046dd..6c873f85 100644 --- a/android_app/app/src/main/res/xml/measurement_preferences.xml +++ b/android_app/app/src/main/res/xml/measurement_preferences.xml @@ -6,5 +6,9 @@ + + + + \ No newline at end of file From 2e7434df9c57276d9101e7297fe359e8499a8681 Mon Sep 17 00:00:00 2001 From: OliE Date: Wed, 1 Nov 2017 19:00:03 +0100 Subject: [PATCH 11/11] added preference categories. --- .../app/src/main/res/values-de/strings.xml | 11 ++++++++ .../app/src/main/res/values/strings.xml | 5 ++++ .../src/main/res/xml/graph_preferences.xml | 18 +++++++----- .../main/res/xml/measurement_preferences.xml | 28 +++++++++++-------- 4 files changed, 44 insertions(+), 18 deletions(-) diff --git a/android_app/app/src/main/res/values-de/strings.xml b/android_app/app/src/main/res/values-de/strings.xml index 4c2d4e59..c46a696b 100644 --- a/android_app/app/src/main/res/values-de/strings.xml +++ b/android_app/app/src/main/res/values-de/strings.xml @@ -135,4 +135,15 @@ Grundumsatz (BMR) Messe Gewicht: %.2f %1$.2f%2$s [%3$s] zu %4$s hinzugefügt + Maximale Anzahl gleichzeitiger Benutzer erreicht. + Bitte steigen Sie barfuß auf die Waage zur Referenzmessung + auto + Körpermetriken abschätzen + Anzeige + Messwertedatenbank + Verschiedenes + Körperfettschätzung + Körperfettformel + Körperwasserschätzung + Körperwasserformel \ No newline at end of file diff --git a/android_app/app/src/main/res/values/strings.xml b/android_app/app/src/main/res/values/strings.xml index 425027c0..be1460db 100644 --- a/android_app/app/src/main/res/values/strings.xml +++ b/android_app/app/src/main/res/values/strings.xml @@ -123,6 +123,11 @@ Estimate body fat Estimate body water + Display + Body metrics estimation + Measurement database + Miscellaneous + Body fat formula Body water formula auto diff --git a/android_app/app/src/main/res/xml/graph_preferences.xml b/android_app/app/src/main/res/xml/graph_preferences.xml index 4fa78e1a..327f2348 100644 --- a/android_app/app/src/main/res/xml/graph_preferences.xml +++ b/android_app/app/src/main/res/xml/graph_preferences.xml @@ -1,10 +1,14 @@ - - - - - - - + + + + + + + + + + + \ No newline at end of file diff --git a/android_app/app/src/main/res/xml/measurement_preferences.xml b/android_app/app/src/main/res/xml/measurement_preferences.xml index 6c873f85..492a3bfb 100644 --- a/android_app/app/src/main/res/xml/measurement_preferences.xml +++ b/android_app/app/src/main/res/xml/measurement_preferences.xml @@ -1,14 +1,20 @@ - - - - - - - - - - - + + + + + + + + + + + + + + + + + \ No newline at end of file