From 29e67659a57b7ac5367424bbe8b9fb29200c47da Mon Sep 17 00:00:00 2001 From: OliE Date: Sun, 4 Jun 2017 16:03:05 +0200 Subject: [PATCH] added support for Medisana BS 444 Connect scale set default device name on Bluetooth type change --- .../com/health/openscale/core/OpenScale.java | 5 + .../bluetooth/BluetoothCommunication.java | 1 + .../bluetooth/BluetoothMedisanaBS444.java | 250 ++++++++++++++++++ .../core/bluetooth/BluetoothSanitasSbf70.java | 18 +- .../health/openscale/gui/MainActivity.java | 3 +- .../gui/preferences/BluetoothPreferences.java | 7 + .../src/main/res/values/type_btdevices.xml | 14 +- 7 files changed, 287 insertions(+), 11 deletions(-) create mode 100644 android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMedisanaBS444.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 c83d6d3d..9987e1d6 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 @@ -26,6 +26,7 @@ import android.util.Log; import com.health.openscale.core.alarm.AlarmHandler; import com.health.openscale.core.bluetooth.BluetoothCommunication; import com.health.openscale.core.bluetooth.BluetoothCustomOpenScale; +import com.health.openscale.core.bluetooth.BluetoothMedisanaBS444; import com.health.openscale.core.bluetooth.BluetoothMiScale; import com.health.openscale.core.bluetooth.BluetoothSanitasSbf70; import com.health.openscale.core.database.ScaleDatabase; @@ -47,6 +48,7 @@ import java.util.ArrayList; import java.util.Map; import java.util.TreeMap; +import static com.health.openscale.core.bluetooth.BluetoothCommunication.BT_MEDISANA_BS444; import static com.health.openscale.core.bluetooth.BluetoothCommunication.BT_MI_SCALE; import static com.health.openscale.core.bluetooth.BluetoothCommunication.BT_OPEN_SCALE; import static com.health.openscale.core.bluetooth.BluetoothCommunication.BT_SANITAS_SBF70; @@ -345,6 +347,9 @@ public class OpenScale { case BT_SANITAS_SBF70: btCom = new BluetoothSanitasSbf70(context); break; + case BT_MEDISANA_BS444: + btCom = new BluetoothMedisanaBS444(context); + break; default: Log.e("OpenScale", "No valid scale type selected"); return; 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 a970e079..3dc0dc58 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 @@ -23,6 +23,7 @@ public abstract class BluetoothCommunication { public static final int BT_MI_SCALE = 0; public static final int BT_OPEN_SCALE = 1; public static final int BT_SANITAS_SBF70 = 2; + public static final int BT_MEDISANA_BS444 = 3; public static final int BT_RETRIEVE_SCALE_DATA = 0; public static final int BT_INIT_PROCESS = 1; diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMedisanaBS444.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMedisanaBS444.java new file mode 100644 index 00000000..d065a242 --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMedisanaBS444.java @@ -0,0 +1,250 @@ +/* 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.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.os.Handler; +import android.util.Log; + +import com.health.openscale.core.datatypes.ScaleData; + +import java.util.Date; +import java.util.UUID; + +public class BluetoothMedisanaBS444 extends BluetoothCommunication { + private BluetoothGatt bluetoothGatt; + private BluetoothAdapter.LeScanCallback scanCallback; + + private final UUID WEIGHT_MEASUREMENT_SERVICE = UUID.fromString("000078b2-0000-1000-8000-00805f9b34fb"); + private final UUID WEIGHT_MEASUREMENT_CHARACTERISTIC = UUID.fromString("00008a21-0000-1000-8000-00805f9b34fb"); // indication, read-only + private final UUID FEATURE_MEASUREMENT_CHARACTERISTIC = UUID.fromString("00008a22-0000-1000-8000-00805f9b34fb"); // indication, read-only + private final UUID CUSTOM3_MEASUREMENT_CHARACTERISTIC = UUID.fromString("00008a20-0000-1000-8000-00805f9b34fb"); // read-only + private final UUID CMD_MEASUREMENT_CHARACTERISTIC = UUID.fromString("00008a81-0000-1000-8000-00805f9b34fb"); // write-only + private final UUID CUSTOM5_MEASUREMENT_CHARACTERISTIC = UUID.fromString("00008a82-0000-1000-8000-00805f9b34fb"); // indication, read-only + private final UUID WEIGHT_MEASUREMENT_CONFIG = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); + + private String btDeviceName; + private Handler searchHandler; + private Context context; + private ScaleData btScaleData; + private int nextCmdState; + + public BluetoothMedisanaBS444(Context context) { + this.context = context; + searchHandler = new Handler(); + btScaleData = new ScaleData(); + scanCallback = null; + } + + @Override + public void startSearching(String deviceName) { + btDeviceName = deviceName; + + if (scanCallback == null) { + scanCallback = new BluetoothAdapter.LeScanCallback() { + @Override + public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) { + if (device.getAddress().replace(":", "").toUpperCase().startsWith("E454EB")) { + //if (device.getName().equals(btDeviceName)) { + bluetoothGatt = device.connectGatt(context, false, gattCallback); + + searchHandler.removeCallbacksAndMessages(null); + btAdapter.stopLeScan(scanCallback); + //} + } + } + }; + } + + searchHandler.postDelayed(new Runnable() + { + @Override + public void run() + { + btAdapter.stopLeScan(scanCallback); + callbackBtHandler.obtainMessage(BluetoothCommunication.BT_NO_DEVICE_FOUND).sendToTarget(); + } + }, 10000); + + btAdapter.startLeScan(scanCallback); + } + + @Override + public void stopSearching() { + if (bluetoothGatt != null) + { + bluetoothGatt.close(); + bluetoothGatt = null; + } + + searchHandler.removeCallbacksAndMessages(null); + btAdapter.stopLeScan(scanCallback); + } + + private BluetoothGattCallback gattCallback= new BluetoothGattCallback() { + @Override + public void onConnectionStateChange(final BluetoothGatt gatt, int status, int newState) { + if (newState == BluetoothProfile.STATE_CONNECTED) { + callbackBtHandler.obtainMessage(BluetoothCommunication.BT_CONNECTION_ESTABLISHED).sendToTarget(); + gatt.discoverServices(); + } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { + callbackBtHandler.obtainMessage(BluetoothCommunication.BT_CONNECTION_LOST).sendToTarget(); + stopSearching(); + } + } + + @Override + public void onServicesDiscovered(final BluetoothGatt gatt, int status) { + nextCmdState = 0; + + invokeNextBluetoothCmd(gatt); + } + + private void invokeNextBluetoothCmd(BluetoothGatt gatt) { + BluetoothGattCharacteristic characteristic; + BluetoothGattDescriptor descriptor; + + switch (nextCmdState) { + case 0: + // set indication on for feature characteristic + characteristic = gatt.getService(WEIGHT_MEASUREMENT_SERVICE) + .getCharacteristic(FEATURE_MEASUREMENT_CHARACTERISTIC); + + gatt.setCharacteristicNotification(characteristic, true); + + descriptor = characteristic.getDescriptor(WEIGHT_MEASUREMENT_CONFIG); + descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE); + + gatt.writeDescriptor(descriptor); + break; + case 1: + // set indication on for weight measurement + characteristic = gatt.getService(WEIGHT_MEASUREMENT_SERVICE) + .getCharacteristic(WEIGHT_MEASUREMENT_CHARACTERISTIC); + + gatt.setCharacteristicNotification(characteristic, true); + + descriptor = characteristic.getDescriptor(WEIGHT_MEASUREMENT_CONFIG); + descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE); + gatt.writeDescriptor(descriptor); + break; + case 2: + // set indication on for custom5 measurement + characteristic = gatt.getService(WEIGHT_MEASUREMENT_SERVICE) + .getCharacteristic(CUSTOM5_MEASUREMENT_CHARACTERISTIC); + + gatt.setCharacteristicNotification(characteristic, true); + + descriptor = characteristic.getDescriptor(WEIGHT_MEASUREMENT_CONFIG); + descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE); + gatt.writeDescriptor(descriptor); + break; + case 3: + // send magic number to receive weight data + characteristic = gatt.getService(WEIGHT_MEASUREMENT_SERVICE) + .getCharacteristic(CMD_MEASUREMENT_CHARACTERISTIC); + + characteristic.setValue(new byte[]{(byte)0x02, (byte)0x7B, (byte)0x7B, (byte)0xF6, (byte)0x0D}); // 02:7b:7b:f6:0d + gatt.writeCharacteristic(characteristic); + break; + case 4: + // end of command mode + break; + default: + Log.e("BluetoothMedisanaScale", "Error invalid Bluetooth State"); + break; + } + } + + @Override + public void onDescriptorWrite(BluetoothGatt gatt, + BluetoothGattDescriptor descriptor, + int status) { + nextCmdState++; + invokeNextBluetoothCmd(gatt); + } + + @Override + public void onCharacteristicWrite (BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic, + int status) { + nextCmdState++; + invokeNextBluetoothCmd(gatt); + } + + @Override + public void onCharacteristicChanged(BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic) { + final byte[] data = characteristic.getValue(); + + if (characteristic.getUuid().equals(WEIGHT_MEASUREMENT_CHARACTERISTIC)) { + parseWeightData(data); + } + + if (characteristic.getUuid().equals(FEATURE_MEASUREMENT_CHARACTERISTIC)) { + parseFeatureData(data); + + callbackBtHandler.obtainMessage(BluetoothCommunication.BT_RETRIEVE_SCALE_DATA, btScaleData).sendToTarget(); + } + } + }; + + + private void parseWeightData(byte[] weightData) { + float weight = (float)(((weightData[2] & 0xFF) << 8) | (weightData[1] & 0xFF)) / 100.0f; + long unix_timestamp = ((weightData[8] & 0xFF) << 24) | ((weightData[7] & 0xFF) << 16) | ((weightData[6] & 0xFF) << 8) | (weightData[5] & 0xFF); // elapsed time in seconds since 2010 + + Date btDate = new Date(); + unix_timestamp += 1262304000; // +40 years because unix time starts in year 1970 + btDate.setTime(unix_timestamp*1000); // multiply with 1000 to get milliseconds + + btScaleData.setDateTime(btDate); + btScaleData.setWeight(weight); + } + + private void parseFeatureData(byte[] featureData) { + //btScaleData.setKCal(((featureData[7] & 0xFF) << 8) | (featureData[6] & 0xFF)); + btScaleData.setFat(decodeFeature(featureData[8], featureData[9])); + btScaleData.setWater(decodeFeature(featureData[10], featureData[11])); + btScaleData.setMuscle(decodeFeature(featureData[12], featureData[13])); + //btScaleData.setBone(decodeFeature(featureData[14], featureData[15])); + } + + private float decodeFeature(byte highByte, byte lowByte) { + return (float)(((lowByte& 0x0F) << 8) | (highByte & 0xFF)) / 10.0f; + } + + private void printByteInHex(byte[] data) { + if (data == null) { + Log.e("BluetoothMedisanaScale", "Data is null"); + return; + } + + final StringBuilder stringBuilder = new StringBuilder(data.length); + for(byte byteChar : data) { + stringBuilder.append(String.format("%02X ", byteChar)); + } + + Log.d("BluetoothMedisanaScale", "Raw hex data: " + stringBuilder); + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothSanitasSbf70.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothSanitasSbf70.java index 7891c6be..8cc98200 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothSanitasSbf70.java +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothSanitasSbf70.java @@ -385,16 +385,18 @@ public class BluetoothSanitasSbf70 extends BluetoothCommunication { + device.getAddress() + " : " + device.getName() ); // Texas Instrument (Sanitas) - if (!device.getAddress().replace(":", "").toUpperCase().startsWith("C4BE84")) - return; - if (!device.getName().toLowerCase().equals(btDeviceName.toLowerCase())) - return; - Log.d(TAG, "Sanitas scale found. Connecting..."); + if (device.getAddress().replace(":", "").toUpperCase().startsWith("C4BE84") || + device.getAddress().replace(":", "").toUpperCase().startsWith("209148")) { - bluetoothGatt = device.connectGatt(context, false, gattCallback); + if (!device.getName().toLowerCase().equals(btDeviceName.toLowerCase())) + return; + Log.d(TAG, "Sanitas scale found. Connecting..."); - searchHandler.removeCallbacksAndMessages(null); - btAdapter.stopLeScan(scanCallback); + bluetoothGatt = device.connectGatt(context, false, gattCallback); + + searchHandler.removeCallbacksAndMessages(null); + btAdapter.stopLeScan(scanCallback); + } } }; } diff --git a/android_app/app/src/main/java/com/health/openscale/gui/MainActivity.java b/android_app/app/src/main/java/com/health/openscale/gui/MainActivity.java index ac4d89a9..1a9b3d96 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/MainActivity.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/MainActivity.java @@ -187,8 +187,7 @@ public class MainActivity extends ActionBarActivity implements String deviceType = prefs.getString("btDeviceTypes", "0"); // Check if Bluetooth 4.x is available - if (Integer.parseInt(deviceType) == BluetoothCommunication.BT_MI_SCALE - || Integer.parseInt(deviceType) == BluetoothCommunication.BT_SANITAS_SBF70) { + if (Integer.parseInt(deviceType) != BluetoothCommunication.BT_OPEN_SCALE) { if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { setBluetoothStatusIcon(R.drawable.bluetooth_disabled); Toast.makeText(getApplicationContext(), "Bluetooth 4.x " + getResources().getString(R.string.info_is_not_available), Toast.LENGTH_SHORT).show(); diff --git a/android_app/app/src/main/java/com/health/openscale/gui/preferences/BluetoothPreferences.java b/android_app/app/src/main/java/com/health/openscale/gui/preferences/BluetoothPreferences.java index c5a85de2..d04618e2 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/preferences/BluetoothPreferences.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/preferences/BluetoothPreferences.java @@ -36,6 +36,7 @@ public class BluetoothPreferences extends PreferenceFragment implements SharedPr String[] btDeviceSupportInit; String[] btDeviceSupportDataTransfer; String[] btDeviceSupportDataHistory; + String[] btDeviceDefaultName; @Override public void onCreate(Bundle savedInstanceState) { @@ -46,6 +47,7 @@ public class BluetoothPreferences extends PreferenceFragment implements SharedPr btDeviceSupportInit = getResources().getStringArray(R.array.bt_device_support_initializing); btDeviceSupportDataTransfer = getResources().getStringArray(R.array.bt_device_support_data_transfer); btDeviceSupportDataHistory = getResources().getStringArray(R.array.bt_device_support_data_history); + btDeviceDefaultName = getResources().getStringArray(R.array.bt_device_default_name); initSummary(getPreferenceScreen()); } @@ -91,6 +93,11 @@ public class BluetoothPreferences extends PreferenceFragment implements SharedPr getResources().getString(R.string.label_bt_device_data_transfer) + ": " + btDeviceSupportDataTransfer[i] + "
" + getResources().getString(R.string.label_bt_device_data_history) + ": " + btDeviceSupportDataHistory[i] )); + + getPreferenceManager().getDefaultSharedPreferences(getActivity().getApplicationContext()).edit().putString("btDeviceName", btDeviceDefaultName[i]).commit(); + EditTextPreference prefDeviceName = (EditTextPreference)findPreference("btDeviceName"); + prefDeviceName.setSummary(btDeviceDefaultName[i]); + prefDeviceName.setText(btDeviceDefaultName[i]); } if (p instanceof EditTextPreference) { diff --git a/android_app/app/src/main/res/values/type_btdevices.xml b/android_app/app/src/main/res/values/type_btdevices.xml index ff1afb0d..f2530968 100644 --- a/android_app/app/src/main/res/values/type_btdevices.xml +++ b/android_app/app/src/main/res/values/type_btdevices.xml @@ -3,31 +3,43 @@ Xiaomi Mi Scale v1 openScale Custom Scale - Sanitas SBF-70 + Sanitas SBF-70# + Medisana BS444 Connect 0 1 2 + 3 + + + + MI_SCALE + openScale_MCU + SANITAS SBF70 + Medisana BS444 @string/label_yes @string/label_yes @string/label_no + @string/label_no @string/label_yes @string/label_yes @string/label_yes + @string/label_yes @string/label_yes @string/label_yes @string/label_no + @string/label_yes \ No newline at end of file