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:
@@ -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();
|
||||
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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);
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -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,13 +349,22 @@ 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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user