From d2678fca853343b86141e976150c65c940f05797 Mon Sep 17 00:00:00 2001 From: oliexdev Date: Mon, 4 Mar 2019 18:41:23 +0100 Subject: [PATCH] added reversed engineered mi scale v2 library to support body measurements --- .../core/bluetooth/BluetoothMiScale2.java | 72 ++++---- .../core/bluetooth/lib/MiScaleLib.java | 164 ++++++++++++++++++ 2 files changed, 202 insertions(+), 34 deletions(-) create mode 100644 android_app/app/src/main/java/com/health/openscale/core/bluetooth/lib/MiScaleLib.java 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 d962420d..62fe5bf9 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 @@ -21,16 +21,10 @@ import android.content.SharedPreferences; import android.preference.PreferenceManager; import com.health.openscale.core.OpenScale; -import com.health.openscale.core.bodymetric.EstimatedFatMetric; -import com.health.openscale.core.bodymetric.EstimatedLBMMetric; -import com.health.openscale.core.bodymetric.EstimatedWaterMetric; +import com.health.openscale.core.bluetooth.lib.MiScaleLib; import com.health.openscale.core.datatypes.ScaleMeasurement; import com.health.openscale.core.datatypes.ScaleUser; import com.health.openscale.core.utils.Converters; -import com.health.openscale.gui.views.FatMeasurementView; -import com.health.openscale.gui.views.LBMMeasurementView; -import com.health.openscale.gui.views.MeasurementViewSettings; -import com.health.openscale.gui.views.WaterMeasurementView; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -143,31 +137,39 @@ public class BluetoothMiScale2 extends BluetoothCommunication { return true; } - private void parseBytes(byte[] weightBytes) { + private void parseBytes(byte[] data) { try { - final byte ctrlByte0 = weightBytes[0]; - final byte ctrlByte1 = weightBytes[1]; + final byte ctrlByte0 = data[0]; + final byte ctrlByte1 = data[1]; 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); + final boolean isImpedance = isBitSet(ctrlByte1, 1); if (isStabilized && !isWeightRemoved && !isDateInvalid) { - 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]; + final int year = ((data[3] & 0xFF) << 8) | (data[2] & 0xFF); + final int month = (int) data[4]; + final int day = (int) data[5]; + final int hours = (int) data[6]; + final int min = (int) data[7]; + final int sec = (int) data[8]; float weight; + float impedance = 0.0f; + if (isLBSUnit || isCattyUnit) { - weight = (float) (((weightBytes[12] & 0xFF) << 8) | (weightBytes[11] & 0xFF)) / 100.0f; + weight = (float) (((data[12] & 0xFF) << 8) | (data[11] & 0xFF)) / 100.0f; } else { - weight = (float) (((weightBytes[12] & 0xFF) << 8) | (weightBytes[11] & 0xFF)) / 200.0f; + weight = (float) (((data[12] & 0xFF) << 8) | (data[11] & 0xFF)) / 200.0f; + } + + if (isImpedance) { + impedance = ((data[10] & 0xFF) << 8) | (data[9] & 0xFF); + Timber.d("impedance value is " + impedance); } String date_string = year + "/" + month + "/" + day + "/" + hours + "/" + min; @@ -175,29 +177,31 @@ public class BluetoothMiScale2 extends BluetoothCommunication { // Is the year plausible? Check if the year is in the range of 20 years... if (validateDate(date_time, 20)) { - final ScaleUser selectedUser = OpenScale.getInstance().getSelectedScaleUser(); + final ScaleUser scaleUser = OpenScale.getInstance().getSelectedScaleUser(); ScaleMeasurement scaleBtData = new ScaleMeasurement(); - scaleBtData.setWeight(Converters.toKilogram(weight, selectedUser.getScaleUnit())); + scaleBtData.setWeight(Converters.toKilogram(weight, scaleUser.getScaleUnit())); scaleBtData.setDateTime(date_time); - // estimate fat, water and LBM until library is reversed engineered - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + int sex; - MeasurementViewSettings settings = new MeasurementViewSettings(prefs, WaterMeasurementView.KEY); - EstimatedWaterMetric waterMetric = EstimatedWaterMetric.getEstimatedMetric( - EstimatedWaterMetric.FORMULA.valueOf(settings.getEstimationFormula())); - scaleBtData.setWater(waterMetric.getWater(selectedUser, scaleBtData)); + if (scaleUser.getGender() == Converters.Gender.MALE) { + sex = 1; + } else { + sex = 0; + } - settings = new MeasurementViewSettings(prefs, FatMeasurementView.KEY); - EstimatedFatMetric fatMetric = EstimatedFatMetric.getEstimatedMetric( - EstimatedFatMetric.FORMULA.valueOf(settings.getEstimationFormula())); - scaleBtData.setFat(fatMetric.getFat(selectedUser, scaleBtData)); + if (impedance != 0.0f) { + MiScaleLib miScaleLib = new MiScaleLib(sex, scaleUser.getAge(), scaleUser.getBodyHeight()); - settings = new MeasurementViewSettings(prefs, LBMMeasurementView.KEY); - EstimatedLBMMetric lbmMetric = EstimatedLBMMetric.getEstimatedMetric( - EstimatedLBMMetric.FORMULA.valueOf(settings.getEstimationFormula())); - scaleBtData.setLbm(lbmMetric.getLBM(selectedUser, scaleBtData)); + scaleBtData.setWater(miScaleLib.getWater(weight, impedance)); + scaleBtData.setVisceralFat(miScaleLib.getVisceralFat(weight)); + scaleBtData.setFat(miScaleLib.getBodyFat(weight, impedance)); + scaleBtData.setMuscle(miScaleLib.getMuscle(weight, impedance)); + scaleBtData.setBone(miScaleLib.getBoneMass(weight, impedance)); + } else { + Timber.d("Impedance value is zero"); + } addScaleMeasurement(scaleBtData); } else { diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/lib/MiScaleLib.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/lib/MiScaleLib.java new file mode 100644 index 00000000..e9a4cd2f --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/lib/MiScaleLib.java @@ -0,0 +1,164 @@ +/* Copyright (C) 2019 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 + */ + +/** + * based on https://github.com/prototux/MIBCS-reverse-engineering by prototux + */ + +package com.health.openscale.core.bluetooth.lib; + +public class MiScaleLib { + private int sex; // male = 1; female = 0 + private int age; + private float height; + + public MiScaleLib(int sex, int age, float height) { + this.sex = sex; + this.age = age; + this.height = height; + } + + private float getLBMCoefficient(float weight, float impedance) { + float lbm = (height * 9.058f / 100.0f) * (height / 100.0f); + lbm += weight * 0.32f + 12.226f; + lbm -= impedance * 0.0068f; + lbm -= age * 0.0542f; + + return lbm; + } + + public float getBMI(float weight) { + return weight / (((height * height) / 100.0f) / 100.0f); + } + + public float getMuscle(float weight, float impedance) { + float muscleMass = weight - ((getBodyFat(weight, impedance) * 0.01f) * weight) - getBoneMass(weight, impedance); + + if (sex == 0 && muscleMass >= 84.0f) { + muscleMass = 120.0f; + } + else if (sex == 1 && muscleMass >= 93.5f) { + muscleMass = 120.0f; + } + + return muscleMass; + } + + public float getWater(float weight, float impedance) { + float coeff; + float water = (100.0f - getBodyFat(weight, impedance)) * 0.7f; + + if (water < 50) { + coeff = 1.02f; + } else { + coeff = 0.98f; + } + + return coeff * water; + } + + public float getBoneMass(float weight, float impedance) { + float boneMass; + float base; + + if (sex == 0) { + base = 0.245691014f; + } + else { + base = 0.18016894f; + } + + boneMass = (base - (getLBMCoefficient(weight, impedance) * 0.05158f)) * -1.0f; + + if (boneMass > 2.2f) { + boneMass += 0.1f; + } + else { + boneMass -= 0.1f; + } + + if (sex == 0 && boneMass > 5.1f) { + boneMass = 8.0f; + } + else if (sex == 1 && boneMass > 5.2f) { + boneMass = 8.0f; + } + + return boneMass; + } + + public float getVisceralFat(float weight) { + float visceralFat = 0.0f; + if (sex == 0) { + if (weight > (13.0f - (height * 0.5f)) * -1.0f) { + float subsubcalc = ((height * 1.45f) + (height * 0.1158f) * height) - 120.0f; + float subcalc = weight * 500.0f / subsubcalc; + visceralFat = (subcalc - 6.0f) + (age * 0.07f); + } + else { + float subcalc = 0.691f + (height * -0.0024f) + (height * -0.0024f); + visceralFat = (((height * 0.027f) - (subcalc * weight)) * -1.0f) + (age * 0.07f) - age; + } + } + else { + if (height < weight * 1.6f) { + float subcalc = ((height * 0.4f) - (height * (height * 0.0826f))) * -1.0f; + visceralFat = ((weight * 305.0f) / (subcalc + 48.0f)) - 2.9f + (age * 0.15f); + } + else { + float subcalc = 0.765f + height * -0.0015f; + visceralFat = (((height * 0.143f) - (weight * subcalc)) * -1.0f) + (age * 0.15f) - 5.0f; + } + } + + return visceralFat; + } + + public float getBodyFat(float weight, float impedance) { + float bodyFat = 0.0f; + float lbmSub = 0.8f; + + if (sex == 0 && age <= 49) { + lbmSub = 9.25f; + } else if (sex == 1 && age > 49) { + lbmSub = 7.25f; + } + + float lbmCoeff = getLBMCoefficient(weight, impedance); + + if (sex == 1 && weight < 61) { + bodyFat = (1.0f - (((lbmCoeff - lbmSub) * 0.98f) / weight)) * 100.0f; + } + else if (sex == 0 && weight < 50) { + bodyFat = (1.0f - (((lbmCoeff - lbmSub) * 1.02f) / weight)) * 100.0f; + } + else if (sex == 0 && weight < 60) { + bodyFat = (1.0f - (((lbmCoeff - lbmSub) * 0.96f) / weight)) * 100.0f; + } + else if (sex == 0 && height < 160) { + bodyFat = (1.0f - (((lbmCoeff - lbmSub) * 1.03f) / weight)) * 100.0f; + } else { + bodyFat = (1.0f - ((lbmCoeff - lbmSub) / weight)) * 100.0f; + } + + if (bodyFat > 63.0f) { + bodyFat = 75.0f; + } + + return bodyFat; + } +} +