1
0
mirror of https://github.com/oliexdev/openScale.git synced 2025-08-22 08:13:43 +02:00

Add bluetooth debug driver

The driver reads all readable characteristics and all descriptors from
the device when connected and logs them. The mode is enabled when
saving log to file is enabled and can be used on unsupported devices
as well.

This replaces the need to use the BLE scanner with something that
should be more easy to use.
This commit is contained in:
Erik Johansson
2018-04-24 00:16:05 +02:00
parent 17ec65b0bd
commit e07b9a8937
6 changed files with 285 additions and 10 deletions

View File

@@ -71,6 +71,7 @@ import java.util.TreeMap;
import timber.log.Timber;
public class OpenScale {
public static boolean DEBUG_MODE = false;
public static final String DATABASE_NAME = "openScale.db";
@@ -542,8 +543,18 @@ public class OpenScale {
return measurementDAO.getAllInRange(startCalender.getTime(), endCalender.getTime(), selectedUserId);
}
public void connectToBluetoothDeviceDebugMode(String hwAddress, Handler callbackBtHandler) {
Timber.d("Trying to connect to bluetooth device [%s] in debug mode", hwAddress);
disconnectFromBluetoothDevice();
btDeviceDriver = BluetoothFactory.createDebugDriver(context);
btDeviceDriver.registerCallbackHandler(callbackBtHandler);
btDeviceDriver.connect(hwAddress);
}
public boolean connectToBluetoothDevice(String deviceName, String hwAddress, Handler callbackBtHandler) {
Timber.d("Trying to connect to bluetooth device %s (%s)", hwAddress, deviceName);
Timber.d("Trying to connect to bluetooth device [%s] (%s)", hwAddress, deviceName);
disconnectFromBluetoothDevice();

View File

@@ -22,13 +22,16 @@ import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.os.Handler;
import com.health.openscale.core.datatypes.ScaleMeasurement;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.UUID;
@@ -68,6 +71,14 @@ public abstract class BluetoothCommunication {
connectionEstablished = false;
}
protected List<BluetoothGattService> getBluetoothGattServices() {
if (bluetoothGatt == null) {
return new ArrayList<>();
}
return bluetoothGatt.getServices();
}
/**
* Register a callback Bluetooth handler that notify any BT_STATUS_CODE changes for GUI/CORE.
*
@@ -233,6 +244,13 @@ public abstract class BluetoothCommunication {
bluetoothGatt.readCharacteristic(gattCharacteristic);
}
protected void readBytes(UUID service, UUID characteristic, UUID descriptor) {
BluetoothGattDescriptor gattDescriptor = bluetoothGatt.getService(service)
.getCharacteristic(characteristic).getDescriptor(descriptor);
bluetoothGatt.readDescriptor(gattDescriptor);
}
/**
* Set indication flag on for the Bluetooth device.
*
@@ -520,8 +538,8 @@ public abstract class BluetoothCommunication {
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
Timber.d("onCharacteristicRead %s: %s",
characteristic.getUuid(), byteInHex(characteristic.getValue()));
Timber.d("onCharacteristicRead %s (status=%d): %s",
characteristic.getUuid(), status, byteInHex(characteristic.getValue()));
synchronized (lock) {
onBluetoothDataRead(gatt, characteristic, status);
@@ -540,5 +558,19 @@ public abstract class BluetoothCommunication {
onBluetoothDataChange(gatt, characteristic);
}
}
@Override
public void onDescriptorRead(BluetoothGatt gatt,
BluetoothGattDescriptor descriptor,
int status) {
Timber.d("onDescriptorRead %s (status=%d): %s",
descriptor.getUuid(), status, byteInHex(descriptor.getValue()));
synchronized (lock) {
openRequest = false;
handleRequests();
}
}
}
}

View File

@@ -0,0 +1,168 @@
/* Copyright (C) 2018 Erik Johansson <erik@ejohansson.se>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package com.health.openscale.core.bluetooth;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.content.Context;
import java.util.HashMap;
import java.util.UUID;
import timber.log.Timber;
public class BluetoothDebug extends BluetoothCommunication {
HashMap<Integer, String> propertyString;
BluetoothDebug(Context context) {
super(context);
propertyString = new HashMap<>();
propertyString.put(BluetoothGattCharacteristic.PROPERTY_BROADCAST, "BROADCAST");
propertyString.put(BluetoothGattCharacteristic.PROPERTY_READ, "READ");
propertyString.put(BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE, "WRITE_NO_RESPONSE");
propertyString.put(BluetoothGattCharacteristic.PROPERTY_WRITE, "WRITE");
propertyString.put(BluetoothGattCharacteristic.PROPERTY_NOTIFY, "NOTIFY");
propertyString.put(BluetoothGattCharacteristic.PROPERTY_INDICATE, "INDICATE");
propertyString.put(BluetoothGattCharacteristic.PROPERTY_SIGNED_WRITE, "SIGNED_WRITE");
propertyString.put(BluetoothGattCharacteristic.PROPERTY_EXTENDED_PROPS, "EXTENDED_PROPS");
}
@Override
public String driverName() {
return "Debug";
}
private boolean isBlacklisted(BluetoothGattService service, BluetoothGattCharacteristic characteristic) {
// Reading this triggers a pairing request on Beurer BF710
if (service.getUuid().equals(UUID.fromString("0000ffe0-0000-1000-8000-00805f9b34fb"))
&& characteristic.getUuid().equals(UUID.fromString("0000ffe5-0000-1000-8000-00805f9b34fb"))) {
return true;
}
return false;
}
private String propertiesToString(int properties) {
StringBuilder names = new StringBuilder();
for (int property : propertyString.keySet()) {
if ((properties & property) != 0) {
names.append(propertyString.get(property));
names.append(", ");
}
}
if (names.length() == 0) {
return "<none>";
}
return names.substring(0, names.length() - 2);
}
private void logService(BluetoothGattService service, boolean included) {
Timber.d("Service %s%s", service.getUuid(), included ? " (included)" : "");
for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
Timber.d("|- characteristic %s (instance %d): %s (permissions=0x%x)",
characteristic.getUuid(), characteristic.getInstanceId(),
propertiesToString(characteristic.getProperties()), characteristic.getPermissions());
byte[] value = characteristic.getValue();
if (value != null && value.length > 0) {
Timber.d("|--> value: %s (%s)", byteInHex(value),
characteristic.getStringValue(0).replaceAll("\\p{Cntrl}", "?"));
}
for (BluetoothGattDescriptor descriptor : characteristic.getDescriptors()) {
Timber.d("|--- descriptor %s (permissions=0x%x)",
descriptor.getUuid(), descriptor.getPermissions());
value = descriptor.getValue();
if (value != null && value.length > 0) {
Timber.d("|-----> value: %s", byteInHex(value));
}
}
}
for (BluetoothGattService includedService : service.getIncludedServices()) {
logService(includedService, true);
}
}
private int readServiceCharacteristics(BluetoothGattService service, int offset) {
for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) != 0
&& !isBlacklisted(service, characteristic)) {
if (offset == 0) {
readBytes(service.getUuid(), characteristic.getUuid());
return -1;
}
offset -= 1;
}
for (BluetoothGattDescriptor descriptor : characteristic.getDescriptors()) {
if (offset == 0) {
readBytes(service.getUuid(), characteristic.getUuid(), descriptor.getUuid());
return -1;
}
offset -= 1;
}
}
for (BluetoothGattService included : service.getIncludedServices()) {
offset = readServiceCharacteristics(included, offset);
if (offset == -1) {
return offset;
}
}
return offset;
}
@Override
protected boolean nextInitCmd(int stateNr) {
int offset = stateNr;
for (BluetoothGattService service : getBluetoothGattServices()) {
offset = readServiceCharacteristics(service, offset);
if (offset == -1) {
return true;
}
}
return false;
}
@Override
protected boolean nextBluetoothCmd(int stateNr) {
for (BluetoothGattService service : getBluetoothGattServices()) {
logService(service, false);
}
disconnect(false);
setBtStatus(BT_STATUS_CODE.BT_CONNECTION_LOST);
return false;
}
@Override
protected boolean nextCleanUpCmd(int stateNr) {
return false;
}
}

View File

@@ -21,6 +21,10 @@ import android.content.Context;
import java.util.Locale;
public class BluetoothFactory {
public static BluetoothCommunication createDebugDriver(Context context) {
return new BluetoothDebug(context);
}
public static BluetoothCommunication createDeviceDriver(Context context, String deviceName) {
final String name = deviceName.toLowerCase(Locale.US);

View File

@@ -24,6 +24,7 @@ import android.util.Log;
import com.health.openscale.BuildConfig;
import com.health.openscale.R;
import com.health.openscale.core.OpenScale;
import java.io.IOException;
import java.io.OutputStream;
@@ -110,6 +111,7 @@ public class AboutPreferences extends PreferenceFragment {
tree.close();
Timber.uproot(tree);
preference.setSummary(R.string.info_is_not_enable);
OpenScale.DEBUG_MODE = false;
return true;
}
@@ -133,6 +135,7 @@ public class AboutPreferences extends PreferenceFragment {
OutputStream output = getActivity().getContentResolver().openOutputStream(uri);
Timber.plant(new FileDebugTree(output));
findPreference(KEY_DEBUG_LOG).setSummary(R.string.info_is_enable);
OpenScale.DEBUG_MODE = true;
Timber.d("Debug log enabled (%s v%s (%d))",
getResources().getString(R.string.app_name),
BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE);

View File

@@ -16,6 +16,7 @@
package com.health.openscale.gui.preferences;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Fragment;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
@@ -29,6 +30,7 @@ import android.graphics.PorterDuff;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
@@ -45,6 +47,8 @@ import com.health.openscale.gui.utils.PermissionHelper;
import java.util.HashMap;
import java.util.Map;
import timber.log.Timber;
public class BluetoothPreferences extends PreferenceFragment {
private static final String PREFERENCE_KEY_BLUETOOTH_SCANNER = "btScanner";
@@ -145,9 +149,9 @@ public class BluetoothPreferences extends PreferenceFragment {
}
private void stopDiscoveryAndLeScan() {
Timber.d("Stop discovery and LE scan");
if (handler != null) {
Timber.d("Stop discovery");
handler.removeCallbacksAndMessages(null);
handler = null;
@@ -156,6 +160,8 @@ public class BluetoothPreferences extends PreferenceFragment {
}
if (leScanCallback != null) {
Timber.d("Stop LE scan");
btAdapter.stopLeScan(leScanCallback);
leScanCallback = null;
}
@@ -165,7 +171,7 @@ public class BluetoothPreferences extends PreferenceFragment {
}
}
private void onDeviceFound(BluetoothDevice device) {
private void onDeviceFound(final BluetoothDevice device) {
if (device.getName() == null || foundDevices.containsKey(device.getAddress())) {
return;
}
@@ -191,9 +197,20 @@ public class BluetoothPreferences extends PreferenceFragment {
prefBtDevice.setIcon(R.drawable.ic_bluetooth_disabled);
prefBtDevice.setSummary(R.string.label_bt_device_no_support);
prefBtDevice.setEnabled(false);
if (OpenScale.DEBUG_MODE && device.getType() == BluetoothDevice.DEVICE_TYPE_LE) {
prefBtDevice.setEnabled(true);
prefBtDevice.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
getDebugInfo(device);
return false;
}
});
}
}
foundDevices.put(device.getAddress(), prefBtDevice.isEnabled() ? device : null);
foundDevices.put(device.getAddress(), btDevice != null ? device : null);
btScanner.addPreference(prefBtDevice);
}
@@ -287,6 +304,37 @@ public class BluetoothPreferences extends PreferenceFragment {
}
}
private void getDebugInfo(final BluetoothDevice device) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("Fetching info")
.setMessage("Please wait while we fetch extended info from the device...")
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
OpenScale.getInstance().disconnectFromBluetoothDevice();
dialog.dismiss();
}
});
final AlertDialog dialog = builder.create();
Handler btHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (BluetoothCommunication.BT_STATUS_CODE.values()[msg.what]) {
case BT_CONNECTION_LOST:
OpenScale.getInstance().disconnectFromBluetoothDevice();
dialog.dismiss();
break;
}
}
};
dialog.show();
OpenScale.getInstance().connectToBluetoothDeviceDebugMode(
device.getAddress(), btHandler);
}
private class onClickListenerDeviceSelect implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(final Preference preference) {
@@ -301,12 +349,21 @@ public class BluetoothPreferences extends PreferenceFragment {
btScanner.setSummary(preference.getTitle());
((BaseAdapter)getPreferenceScreen().getRootAdapter()).notifyDataSetChanged();
stopDiscoveryAndLeScan();
btScanner.getDialog().dismiss();
// Perform an explicit bonding with classic devices
if (device.getType() == BluetoothDevice.DEVICE_TYPE_CLASSIC
&& device.getBondState() == BluetoothDevice.BOND_NONE) {
device.createBond();
switch (device.getType()) {
case BluetoothDevice.DEVICE_TYPE_CLASSIC:
if (device.getBondState() == BluetoothDevice.BOND_NONE) {
device.createBond();
}
break;
case BluetoothDevice.DEVICE_TYPE_LE:
if (OpenScale.DEBUG_MODE) {
getDebugInfo(device);
}
break;
}
return true;
}