mirror of
https://github.com/oliexdev/openScale.git
synced 2025-08-29 11:10:35 +02:00
Replaced Bluetooth backend to Blessed android library (#469)
* replaced RxAndroidBlw with blesses-android library as Bluetooth backend * remove license commands * uncomment test debug devices * use blessed-android library for discovering Bluetooth devices * add support for Bluetooth Standard Weight Profile scales (e.g. Beurer BF600 and Beurer B850 scales) * stop scan on Bluetooth discovery * upgrade Blesses-Android to 0.6
This commit is contained in:
@@ -5,7 +5,7 @@ android {
|
||||
defaultConfig {
|
||||
applicationId "com.health.openscale"
|
||||
testApplicationId "com.health.openscale.test"
|
||||
minSdkVersion 19
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 28
|
||||
versionCode 44
|
||||
versionName "2.0.3"
|
||||
@@ -59,7 +59,7 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.material:material:1.1.0-alpha04'
|
||||
implementation 'com.google.android.material:material:1.1.0-alpha07'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.0.0'
|
||||
@@ -67,27 +67,24 @@ dependencies {
|
||||
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
|
||||
// Simple CSV
|
||||
implementation 'com.j256.simplecsv:simplecsv:2.3'
|
||||
// RxAndroidBle
|
||||
implementation 'com.polidea.rxandroidble2:rxandroidble:1.8.2'
|
||||
implementation 'io.reactivex.rxjava2:rxjava:2.2.7'
|
||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
|
||||
implementation 'com.jakewharton.rx2:replaying-share:2.1.0'
|
||||
// Blessed Android
|
||||
implementation 'com.github.weliem:blessed-android:0.6'
|
||||
// CustomActivityOnCrash
|
||||
implementation 'cat.ereza:customactivityoncrash:2.2.0'
|
||||
// Room
|
||||
implementation 'androidx.room:room-runtime:2.1.0-alpha04'
|
||||
annotationProcessor 'androidx.room:room-compiler:2.1.0-alpha04'
|
||||
androidTestImplementation 'androidx.room:room-testing:2.1.0-alpha04'
|
||||
implementation 'androidx.room:room-runtime:2.1.0-rc01'
|
||||
annotationProcessor 'androidx.room:room-compiler:2.1.0-rc01'
|
||||
androidTestImplementation 'androidx.room:room-testing:2.1.0-rc01'
|
||||
// Timber
|
||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||
// Local unit tests
|
||||
testImplementation 'junit:junit:4.12'
|
||||
// Instrumented unit tests
|
||||
androidTestImplementation 'androidx.annotation:annotation:1.0.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
|
||||
androidTestImplementation 'androidx.test:rules:1.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.1.1'
|
||||
androidTestImplementation 'androidx.annotation:annotation:1.0.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
androidTestImplementation 'androidx.test:rules:1.2.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.2.0'
|
||||
}
|
||||
|
||||
tasks.withType(Test) {
|
||||
|
@@ -57,8 +57,6 @@ import com.health.openscale.gui.views.LBMMeasurementView;
|
||||
import com.health.openscale.gui.views.MeasurementViewSettings;
|
||||
import com.health.openscale.gui.views.WaterMeasurementView;
|
||||
import com.health.openscale.gui.widget.WidgetProvider;
|
||||
import com.polidea.rxandroidble2.RxBleClient;
|
||||
import com.polidea.rxandroidble2.internal.RxBleLog;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
@@ -92,7 +90,6 @@ public class OpenScale {
|
||||
private List<ScaleMeasurement> scaleMeasurementList;
|
||||
|
||||
private BluetoothCommunication btDeviceDriver;
|
||||
private RxBleClient bleClient;
|
||||
private AlarmHandler alarmHandler;
|
||||
|
||||
private Context context;
|
||||
@@ -104,10 +101,6 @@ public class OpenScale {
|
||||
alarmHandler = new AlarmHandler();
|
||||
btDeviceDriver = null;
|
||||
fragmentList = new ArrayList<>();
|
||||
bleClient = RxBleClient.create(context);
|
||||
|
||||
RxBleClient.setLogLevel(RxBleLog.VERBOSE);
|
||||
RxBleLog.setLogger((level, tag, msg) -> Timber.tag(tag).log(level, msg));
|
||||
|
||||
reopenDatabase(false);
|
||||
|
||||
@@ -130,10 +123,6 @@ public class OpenScale {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public RxBleClient getBleClient() {
|
||||
return bleClient;
|
||||
}
|
||||
|
||||
public void reopenDatabase(boolean truncate) throws SQLiteDatabaseCorruptException {
|
||||
if (appDB != null) {
|
||||
appDB.close();
|
||||
|
@@ -172,7 +172,7 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
|
||||
switch (stepNr) {
|
||||
case 0:
|
||||
// Setup notification
|
||||
setNotificationOn(CUSTOM_CHARACTERISTIC_WEIGHT);
|
||||
setNotificationOn(CUSTOM_SERVICE_1, CUSTOM_CHARACTERISTIC_WEIGHT);
|
||||
break;
|
||||
case 1:
|
||||
// Say "Hello" to the scale and wait for ack
|
||||
@@ -566,7 +566,7 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
|
||||
}
|
||||
|
||||
private void writeBytes(byte[] data) {
|
||||
writeBytes(CUSTOM_CHARACTERISTIC_WEIGHT, data);
|
||||
writeBytes(CUSTOM_SERVICE_1, CUSTOM_CHARACTERISTIC_WEIGHT, data);
|
||||
}
|
||||
|
||||
private void sendCommand(byte command, byte... parameters) {
|
||||
|
@@ -17,39 +17,29 @@
|
||||
package com.health.openscale.core.bluetooth;
|
||||
|
||||
import android.Manifest;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.bluetooth.le.ScanResult;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Handler;
|
||||
|
||||
import com.health.openscale.core.OpenScale;
|
||||
import com.health.openscale.core.datatypes.ScaleMeasurement;
|
||||
import com.jakewharton.rx.ReplayingShare;
|
||||
import com.polidea.rxandroidble2.RxBleClient;
|
||||
import com.polidea.rxandroidble2.RxBleConnection;
|
||||
import com.polidea.rxandroidble2.RxBleDevice;
|
||||
import com.polidea.rxandroidble2.RxBleDeviceServices;
|
||||
import com.polidea.rxandroidble2.exceptions.BleException;
|
||||
import com.polidea.rxandroidble2.scan.ScanSettings;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.SocketException;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.exceptions.UndeliverableException;
|
||||
import io.reactivex.plugins.RxJavaPlugins;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import io.reactivex.subjects.PublishSubject;
|
||||
|
||||
import com.health.openscale.core.datatypes.ScaleMeasurement;
|
||||
import com.welie.blessed.BluetoothCentral;
|
||||
import com.welie.blessed.BluetoothCentralCallback;
|
||||
import com.welie.blessed.BluetoothPeripheral;
|
||||
import com.welie.blessed.BluetoothPeripheralCallback;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
import static android.bluetooth.BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT;
|
||||
import static android.content.Context.LOCATION_SERVICE;
|
||||
import static com.welie.blessed.BluetoothPeripheral.GATT_SUCCESS;
|
||||
|
||||
public abstract class BluetoothCommunication {
|
||||
public enum BT_STATUS {
|
||||
@@ -69,55 +59,19 @@ public abstract class BluetoothCommunication {
|
||||
|
||||
protected Context context;
|
||||
|
||||
private final int BT_RETRY_TIMES_ON_ERROR = 3;
|
||||
private final int BT_DELAY_MS = 10;
|
||||
|
||||
private RxBleClient bleClient;
|
||||
private RxBleDevice bleDevice;
|
||||
private Observable<RxBleConnection> connectionObservable;
|
||||
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||
private Disposable scanSubscription;
|
||||
private PublishSubject<Boolean> disconnectTriggerSubject = PublishSubject.create();
|
||||
|
||||
private Handler callbackBtHandler;
|
||||
private Handler disconnectHandler;
|
||||
|
||||
private BluetoothCentral central;
|
||||
private BluetoothPeripheral btPeripheral;
|
||||
|
||||
public BluetoothCommunication(Context context)
|
||||
{
|
||||
this.context = context;
|
||||
this.bleClient = OpenScale.getInstance().getBleClient();
|
||||
this.scanSubscription = null;
|
||||
this.disconnectHandler = new Handler();
|
||||
this.stepNr = 0;
|
||||
this.stopped = false;
|
||||
|
||||
RxJavaPlugins.setErrorHandler(e -> {
|
||||
if (e instanceof UndeliverableException && e.getCause() instanceof BleException) {
|
||||
return; // ignore BleExceptions as they were surely delivered at least once
|
||||
}
|
||||
if (e instanceof UndeliverableException) {
|
||||
onError(e);
|
||||
}
|
||||
if ((e instanceof IOException) || (e instanceof SocketException)) {
|
||||
// fine, irrelevant network problem or API that throws on cancellation
|
||||
return;
|
||||
}
|
||||
if (e instanceof InterruptedException) {
|
||||
// fine, some blocking code was interrupted by a dispose call
|
||||
return;
|
||||
}
|
||||
if ((e instanceof NullPointerException) || (e instanceof IllegalArgumentException)) {
|
||||
// that's likely a bug in the application
|
||||
onError(e);
|
||||
return;
|
||||
}
|
||||
if (e instanceof IllegalStateException) {
|
||||
// that's a bug in RxJava or in a custom operator
|
||||
onError(e);
|
||||
return;
|
||||
}
|
||||
onError(e);
|
||||
});
|
||||
this.central = new BluetoothCentral(context, bluetoothCentralCallback, new Handler(Looper.getMainLooper()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -191,14 +145,6 @@ public abstract class BluetoothCommunication {
|
||||
*/
|
||||
abstract protected boolean onNextStep(int stepNr);
|
||||
|
||||
/**
|
||||
* Method is triggered if a Bluetooth data is read from a device.
|
||||
*
|
||||
* @param characteristic
|
||||
* @param value
|
||||
*/
|
||||
protected void onBluetoothRead(UUID characteristic, byte[] value) {}
|
||||
|
||||
/**
|
||||
* Method is triggered if a Bluetooth data from a device is notified or indicated.
|
||||
*
|
||||
@@ -210,9 +156,9 @@ public abstract class BluetoothCommunication {
|
||||
/**
|
||||
* Method is triggered if a Bluetooth services from a device is discovered.
|
||||
*
|
||||
* @param rxBleDeviceServices
|
||||
* @param peripheral
|
||||
*/
|
||||
protected void onBluetoothDiscovery(RxBleDeviceServices rxBleDeviceServices) { }
|
||||
protected void onBluetoothDiscovery(BluetoothPeripheral peripheral) { }
|
||||
|
||||
protected synchronized void stopMachineState() {
|
||||
Timber.d("Stop machine state");
|
||||
@@ -235,26 +181,9 @@ public abstract class BluetoothCommunication {
|
||||
* @param characteristic the Bluetooth UUID characteristic
|
||||
* @param bytes the bytes that should be write
|
||||
*/
|
||||
protected Observable<byte[]> writeBytes(UUID characteristic, byte[] bytes) {
|
||||
protected void writeBytes(UUID service, UUID characteristic, byte[] bytes) {
|
||||
Timber.d("Invoke write bytes [" + byteInHex(bytes) + "] on " + BluetoothGattUuid.prettyPrint(characteristic));
|
||||
Observable<byte[]> observable = connectionObservable
|
||||
.flatMapSingle(rxBleConnection -> rxBleConnection.writeCharacteristic(characteristic, bytes))
|
||||
.subscribeOn(Schedulers.trampoline())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.delay(BT_DELAY_MS, TimeUnit.MILLISECONDS)
|
||||
.retry(BT_RETRY_TIMES_ON_ERROR);
|
||||
|
||||
compositeDisposable.add(observable.subscribe(
|
||||
value -> {
|
||||
Timber.d("Write characteristic %s: %s",
|
||||
BluetoothGattUuid.prettyPrint(characteristic),
|
||||
byteInHex(value));
|
||||
},
|
||||
throwable -> onError(throwable)
|
||||
)
|
||||
);
|
||||
|
||||
return observable;
|
||||
btPeripheral.writeCharacteristic(btPeripheral.getCharacteristic(service, characteristic), bytes, WRITE_TYPE_DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -263,26 +192,10 @@ public abstract class BluetoothCommunication {
|
||||
* @note onBluetoothRead() will be triggered if read command was successful. nextMachineStep() needs to manually called!
|
||||
*@param characteristic the Bluetooth UUID characteristic
|
||||
*/
|
||||
protected Single<byte[]> readBytes(UUID characteristic) {
|
||||
void readBytes(UUID service, UUID characteristic) {
|
||||
Timber.d("Invoke read bytes on " + BluetoothGattUuid.prettyPrint(characteristic));
|
||||
Single<byte[]> observable = connectionObservable
|
||||
.firstOrError()
|
||||
.flatMap(rxBleConnection -> rxBleConnection.readCharacteristic(characteristic))
|
||||
.subscribeOn(Schedulers.trampoline())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.delay(BT_DELAY_MS, TimeUnit.MILLISECONDS)
|
||||
.retry(BT_RETRY_TIMES_ON_ERROR);
|
||||
|
||||
compositeDisposable.add(observable
|
||||
.subscribe(bytes -> {
|
||||
Timber.d("Read characteristic %s", BluetoothGattUuid.prettyPrint(characteristic));
|
||||
onBluetoothRead(characteristic, bytes);
|
||||
},
|
||||
throwable -> onError(throwable)
|
||||
)
|
||||
);
|
||||
|
||||
return observable;
|
||||
btPeripheral.readCharacteristic(btPeripheral.getCharacteristic(service, characteristic));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -290,33 +203,13 @@ public abstract class BluetoothCommunication {
|
||||
*
|
||||
* @param characteristic the Bluetooth UUID characteristic
|
||||
*/
|
||||
protected Observable<byte[]> setIndicationOn(UUID characteristic) {
|
||||
protected void setIndicationOn(UUID service, UUID characteristic) {
|
||||
Timber.d("Invoke set indication on " + BluetoothGattUuid.prettyPrint(characteristic));
|
||||
Observable<byte[]> observable = connectionObservable
|
||||
.flatMap(rxBleConnection -> rxBleConnection.setupIndication(characteristic))
|
||||
.doOnNext(notificationObservable -> {
|
||||
Timber.d("Successful set indication on for %s", BluetoothGattUuid.prettyPrint(characteristic));
|
||||
}
|
||||
)
|
||||
.flatMap(indicationObservable -> indicationObservable)
|
||||
.subscribeOn(Schedulers.trampoline())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.delay(BT_DELAY_MS, TimeUnit.MILLISECONDS)
|
||||
.retry(BT_RETRY_TIMES_ON_ERROR);
|
||||
|
||||
compositeDisposable.add(observable.subscribe(
|
||||
bytes -> {
|
||||
Timber.d("onCharacteristicChanged %s: %s",
|
||||
BluetoothGattUuid.prettyPrint(characteristic),
|
||||
byteInHex(bytes));
|
||||
onBluetoothNotify(characteristic, bytes);
|
||||
resetDisconnectTimer();
|
||||
},
|
||||
throwable -> onError(throwable)
|
||||
)
|
||||
);
|
||||
|
||||
return observable;
|
||||
if(btPeripheral.getService(service) != null) {
|
||||
stopMachineState();
|
||||
BluetoothGattCharacteristic currentTimeCharacteristic = btPeripheral.getCharacteristic(service, characteristic);
|
||||
btPeripheral.setNotify(currentTimeCharacteristic, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -324,56 +217,13 @@ public abstract class BluetoothCommunication {
|
||||
*
|
||||
* @param characteristic the Bluetooth UUID characteristic
|
||||
*/
|
||||
protected Observable<byte[]> setNotificationOn(UUID characteristic) {
|
||||
protected void setNotificationOn(UUID service, UUID characteristic) {
|
||||
Timber.d("Invoke set notification on " + BluetoothGattUuid.prettyPrint(characteristic));
|
||||
stopped = true;
|
||||
Observable<byte[]> observable = connectionObservable
|
||||
.flatMap(rxBleConnection -> rxBleConnection.setupNotification(characteristic))
|
||||
.doOnNext(notificationObservable -> {
|
||||
Timber.d("Successful set notification on for %s", BluetoothGattUuid.prettyPrint(characteristic));
|
||||
stopped = false;
|
||||
nextMachineStep();
|
||||
}
|
||||
)
|
||||
.flatMap(notificationObservable -> notificationObservable)
|
||||
.subscribeOn(Schedulers.trampoline())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.delay(BT_DELAY_MS, TimeUnit.MILLISECONDS)
|
||||
.retry(BT_RETRY_TIMES_ON_ERROR);
|
||||
|
||||
compositeDisposable.add(observable.subscribe(
|
||||
bytes -> {
|
||||
Timber.d("onCharacteristicChanged %s: %s",
|
||||
BluetoothGattUuid.prettyPrint(characteristic),
|
||||
byteInHex(bytes));
|
||||
onBluetoothNotify(characteristic, bytes);
|
||||
resetDisconnectTimer();
|
||||
},
|
||||
throwable -> onError(throwable)
|
||||
));
|
||||
|
||||
return observable;
|
||||
}
|
||||
|
||||
protected Observable<RxBleDeviceServices> discoverBluetoothServices() {
|
||||
Timber.d("Invoke discover Bluetooth services");
|
||||
final Observable<RxBleDeviceServices> observable = connectionObservable
|
||||
.flatMapSingle(RxBleConnection::discoverServices)
|
||||
.subscribeOn(Schedulers.trampoline())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.delay(BT_DELAY_MS, TimeUnit.MILLISECONDS)
|
||||
.retry(BT_RETRY_TIMES_ON_ERROR);
|
||||
|
||||
compositeDisposable.add(observable.subscribe(
|
||||
deviceServices -> {
|
||||
Timber.d("Successful Bluetooth services discovered");
|
||||
onBluetoothDiscovery(deviceServices);
|
||||
},
|
||||
throwable -> onError(throwable)
|
||||
)
|
||||
);
|
||||
|
||||
return observable;
|
||||
if(btPeripheral.getService(service) != null) {
|
||||
stopMachineState();
|
||||
BluetoothGattCharacteristic currentTimeCharacteristic = btPeripheral.getCharacteristic(service, characteristic);
|
||||
btPeripheral.setNotify(currentTimeCharacteristic, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -382,13 +232,12 @@ public abstract class BluetoothCommunication {
|
||||
public void disconnect() {
|
||||
Timber.d("Bluetooth disconnect");
|
||||
setBluetoothStatus(BT_STATUS.CONNECTION_DISCONNECT);
|
||||
if (scanSubscription != null) {
|
||||
scanSubscription.dispose();
|
||||
central.stopScan();
|
||||
if (btPeripheral != null) {
|
||||
central.cancelConnection(btPeripheral);
|
||||
}
|
||||
callbackBtHandler = null;
|
||||
disconnectHandler.removeCallbacksAndMessages(null);
|
||||
disconnectTriggerSubject.onNext(true);
|
||||
compositeDisposable.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -452,6 +301,74 @@ public abstract class BluetoothCommunication {
|
||||
return (value & (1 << bit)) != 0;
|
||||
}
|
||||
|
||||
private final BluetoothPeripheralCallback peripheralCallback = new BluetoothPeripheralCallback() {
|
||||
@Override
|
||||
public void onServicesDiscovered(BluetoothPeripheral peripheral) {
|
||||
Timber.d("Successful Bluetooth services discovered");
|
||||
onBluetoothDiscovery(peripheral);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationStateUpdate(BluetoothPeripheral peripheral, BluetoothGattCharacteristic characteristic, int status) {
|
||||
if( status == GATT_SUCCESS) {
|
||||
if(peripheral.isNotifying(characteristic)) {
|
||||
Timber.d(String.format("SUCCESS: Notify set for %s", characteristic.getUuid()));
|
||||
resumeMachineState();
|
||||
}
|
||||
} else {
|
||||
Timber.e(String.format("ERROR: Changing notification state failed for %s", characteristic.getUuid()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicWrite(BluetoothPeripheral peripheral, byte[] value, BluetoothGattCharacteristic characteristic, int status) {
|
||||
if( status == GATT_SUCCESS) {
|
||||
Timber.d(String.format("SUCCESS: Writing <%s> to <%s>", byteInHex(value), characteristic.getUuid().toString()));
|
||||
nextMachineStep();
|
||||
|
||||
} else {
|
||||
Timber.e(String.format("ERROR: Failed writing <%s> to <%s>", byteInHex(value), characteristic.getUuid().toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicUpdate(BluetoothPeripheral peripheral, byte[] value, BluetoothGattCharacteristic characteristic) {
|
||||
resetDisconnectTimer();
|
||||
onBluetoothNotify(characteristic.getUuid(), value);
|
||||
}
|
||||
};
|
||||
|
||||
// Callback for central
|
||||
private final BluetoothCentralCallback bluetoothCentralCallback = new BluetoothCentralCallback() {
|
||||
|
||||
@Override
|
||||
public void onConnectedPeripheral(BluetoothPeripheral peripheral) {
|
||||
Timber.d(String.format("connected to '%s'", peripheral.getName()));
|
||||
setBluetoothStatus(BT_STATUS.CONNECTION_ESTABLISHED);
|
||||
btPeripheral = peripheral;
|
||||
nextMachineStep();
|
||||
resetDisconnectTimer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionFailed(BluetoothPeripheral peripheral, final int status) {
|
||||
Timber.e(String.format("connection '%s' failed with status %d", peripheral.getName(), status ));
|
||||
setBluetoothStatus(BT_STATUS.CONNECTION_LOST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnectedPeripheral(final BluetoothPeripheral peripheral, final int status) {
|
||||
Timber.d(String.format("disconnected '%s' with status %d", peripheral.getName(), status));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDiscoveredPeripheral(BluetoothPeripheral peripheral, ScanResult scanResult) {
|
||||
Timber.d(String.format("Found peripheral '%s'", peripheral.getName()));
|
||||
central.stopScan();
|
||||
connectToDevice(peripheral);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Connect to a Bluetooth device.
|
||||
*
|
||||
@@ -461,8 +378,6 @@ public abstract class BluetoothCommunication {
|
||||
* @param macAddress the Bluetooth address to connect to
|
||||
*/
|
||||
public void connect(String macAddress) {
|
||||
bleDevice = bleClient.getBleDevice(macAddress);
|
||||
|
||||
// Running an LE scan during connect improves connectivity on some phones
|
||||
// (e.g. Sony Xperia Z5 compact, Android 7.1.1). For some scales (e.g. Medisana BS444)
|
||||
// it seems to be a requirement that the scale is discovered before connecting to it.
|
||||
@@ -474,95 +389,30 @@ public abstract class BluetoothCommunication {
|
||||
|| locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER))
|
||||
) {
|
||||
Timber.d("Do LE scan before connecting to device");
|
||||
disconnectWithDelay();
|
||||
scanSubscription = bleClient.scanBleDevices(
|
||||
new ScanSettings.Builder()
|
||||
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
|
||||
//.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
|
||||
.build()
|
||||
)
|
||||
.subscribeOn(Schedulers.trampoline())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(bleScanResult -> {
|
||||
if (bleScanResult.getBleDevice().getMacAddress().equals(macAddress)) {
|
||||
connectToDevice(macAddress);
|
||||
}}, throwable -> setBluetoothStatus(BT_STATUS.NO_DEVICE_FOUND));
|
||||
central.scanForPeripheralsWithAddresses(new String[]{macAddress});
|
||||
}
|
||||
else {
|
||||
Timber.d("No location permission, connecting without LE scan");
|
||||
connectToDevice(macAddress);
|
||||
BluetoothPeripheral peripheral = central.getPeripheral(macAddress);
|
||||
connectToDevice(peripheral);
|
||||
}
|
||||
}
|
||||
|
||||
private void connectToDevice(String macAddress) {
|
||||
|
||||
// stop LE scan before connecting to device
|
||||
if (scanSubscription != null) {
|
||||
Timber.d("Stop Le san");
|
||||
scanSubscription.dispose();
|
||||
scanSubscription = null;
|
||||
}
|
||||
private void connectToDevice(BluetoothPeripheral peripheral) {
|
||||
|
||||
Handler handler = new Handler();
|
||||
handler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Timber.d("Try to connect to BLE device " + macAddress);
|
||||
Timber.d("Try to connect to BLE device " + peripheral.getAddress());
|
||||
|
||||
connectionObservable = bleDevice
|
||||
.establishConnection(false)
|
||||
.takeUntil(disconnectTriggerSubject)
|
||||
.doOnError(throwable -> setBluetoothStatus(BT_STATUS.CONNECTION_RETRYING))
|
||||
.subscribeOn(Schedulers.trampoline())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.compose(ReplayingShare.instance());
|
||||
stepNr = 0;
|
||||
|
||||
if (isConnected()) {
|
||||
disconnect();
|
||||
} else {
|
||||
stepNr = 0;
|
||||
|
||||
setBtMonitoringOn();
|
||||
nextMachineStep();
|
||||
resetDisconnectTimer();
|
||||
}
|
||||
central.connectPeripheral(peripheral, peripheralCallback);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
private void setBtMonitoringOn() {
|
||||
final Disposable disposableConnectionState = bleDevice.observeConnectionStateChanges()
|
||||
.subscribe(
|
||||
connectionState -> {
|
||||
switch (connectionState) {
|
||||
case CONNECTED:
|
||||
setBluetoothStatus(BT_STATUS.CONNECTION_ESTABLISHED);
|
||||
break;
|
||||
case CONNECTING:
|
||||
// empty
|
||||
break;
|
||||
case DISCONNECTING:
|
||||
// empty
|
||||
break;
|
||||
case DISCONNECTED:
|
||||
// setBluetoothStatus(BT_STATUS.CONNECTION_LOST);
|
||||
break;
|
||||
}
|
||||
},
|
||||
throwable -> onError(throwable)
|
||||
);
|
||||
|
||||
compositeDisposable.add(disposableConnectionState);
|
||||
}
|
||||
|
||||
protected void onError(Throwable throwable) {
|
||||
setBluetoothStatus(BT_STATUS.UNEXPECTED_ERROR, throwable.getMessage());
|
||||
}
|
||||
|
||||
private boolean isConnected() {
|
||||
return bleDevice.getConnectionState() == RxBleConnection.RxBleConnectionState.CONNECTED;
|
||||
}
|
||||
|
||||
private void resetDisconnectTimer() {
|
||||
disconnectHandler.removeCallbacksAndMessages(null);
|
||||
disconnectWithDelay();
|
||||
|
@@ -46,8 +46,7 @@ public class BluetoothCustomOpenScale extends BluetoothCommunication {
|
||||
protected boolean onNextStep(int stepNr) {
|
||||
switch (stepNr) {
|
||||
case 0:
|
||||
setNotificationOn(WEIGHT_MEASUREMENT_CHARACTERISTIC
|
||||
);
|
||||
setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_CHARACTERISTIC);
|
||||
break;
|
||||
case 1:
|
||||
Calendar cal = Calendar.getInstance();
|
||||
@@ -60,7 +59,7 @@ public class BluetoothCustomOpenScale extends BluetoothCommunication {
|
||||
cal.get(Calendar.MINUTE),
|
||||
cal.get(Calendar.SECOND));
|
||||
|
||||
writeBytes(WEIGHT_MEASUREMENT_CHARACTERISTIC, date_time.getBytes());
|
||||
writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_CHARACTERISTIC, date_time.getBytes());
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
@@ -72,7 +71,7 @@ public class BluetoothCustomOpenScale extends BluetoothCommunication {
|
||||
public void clearEEPROM()
|
||||
{
|
||||
byte[] cmd = {(byte)'9'};
|
||||
writeBytes(WEIGHT_MEASUREMENT_CHARACTERISTIC, cmd);
|
||||
writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_CHARACTERISTIC, cmd);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -21,15 +21,14 @@ import android.bluetooth.BluetoothGattDescriptor;
|
||||
import android.bluetooth.BluetoothGattService;
|
||||
import android.content.Context;
|
||||
|
||||
import com.polidea.rxandroidble2.RxBleDeviceServices;
|
||||
import com.welie.blessed.BluetoothPeripheral;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class BluetoothDebug extends BluetoothCommunication {
|
||||
private HashMap<Integer, String> propertyString;
|
||||
private RxBleDeviceServices rxBleDeviceServices;
|
||||
HashMap<Integer, String> propertyString;
|
||||
|
||||
BluetoothDebug(Context context) {
|
||||
super(context);
|
||||
@@ -53,7 +52,7 @@ public class BluetoothDebug extends BluetoothCommunication {
|
||||
private boolean isBlacklisted(BluetoothGattService service, BluetoothGattCharacteristic characteristic) {
|
||||
// Reading this triggers a pairing request on Beurer BF710
|
||||
if (service.getUuid().equals(BluetoothGattUuid.fromShortCode(0xffe0))
|
||||
&& characteristic.getUuid().equals(BluetoothGattUuid.fromShortCode(0xffe5))) {
|
||||
&& characteristic.getUuid().equals(BluetoothGattUuid.fromShortCode(0xffe5))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -137,10 +136,19 @@ public class BluetoothDebug extends BluetoothCommunication {
|
||||
private int readServiceCharacteristics(BluetoothGattService service, int offset) {
|
||||
for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
|
||||
if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) != 0
|
||||
&& !isBlacklisted(service, characteristic)) {
|
||||
&& !isBlacklisted(service, characteristic)) {
|
||||
|
||||
if (offset == 0) {
|
||||
readBytes(characteristic.getUuid());
|
||||
readBytes(service.getUuid(), characteristic.getUuid());
|
||||
return -1;
|
||||
}
|
||||
|
||||
offset -= 1;
|
||||
}
|
||||
|
||||
for (BluetoothGattDescriptor descriptor : characteristic.getDescriptors()) {
|
||||
if (offset == 0) {
|
||||
readBytes(service.getUuid(), characteristic.getUuid());
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -159,39 +167,25 @@ public class BluetoothDebug extends BluetoothCommunication {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBluetoothDiscovery(RxBleDeviceServices rxBleDeviceServices) {
|
||||
this.rxBleDeviceServices = rxBleDeviceServices;
|
||||
resumeMachineState();
|
||||
}
|
||||
protected void onBluetoothDiscovery(BluetoothPeripheral peripheral) {
|
||||
int offset = 0;
|
||||
|
||||
@Override
|
||||
protected boolean onNextStep(int stepNr) {
|
||||
switch (stepNr)
|
||||
{
|
||||
case 0:
|
||||
discoverBluetoothServices();
|
||||
stopMachineState();
|
||||
break;
|
||||
case 1:
|
||||
int offset = stepNr;
|
||||
|
||||
for (BluetoothGattService service : rxBleDeviceServices.getBluetoothGattServices()) {
|
||||
offset = readServiceCharacteristics(service, offset);
|
||||
}
|
||||
|
||||
for (BluetoothGattService service : rxBleDeviceServices.getBluetoothGattServices()) {
|
||||
logService(service, false);
|
||||
}
|
||||
|
||||
setBluetoothStatus(BT_STATUS.CONNECTION_LOST);
|
||||
break;
|
||||
case 2:
|
||||
disconnect();
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
for (BluetoothGattService service : peripheral.getServices()) {
|
||||
offset = readServiceCharacteristics(service, offset);
|
||||
}
|
||||
|
||||
return true;
|
||||
for (BluetoothGattService service : peripheral.getServices()) {
|
||||
logService(service, false);
|
||||
}
|
||||
|
||||
setBluetoothStatus(BT_STATUS.CONNECTION_LOST);
|
||||
disconnect();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean onNextStep(int stateNr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -60,7 +60,7 @@ public class BluetoothDigooDGSO38H extends BluetoothCommunication {
|
||||
switch (stepNr) {
|
||||
case 0:
|
||||
//Tell device to send us weight measurements
|
||||
setNotificationOn(WEIGHT_MEASUREMENT_CHARACTERISTIC);
|
||||
setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_CHARACTERISTIC);
|
||||
break;
|
||||
case 1:
|
||||
sendMessage(R.string.info_step_on_scale, 0);
|
||||
@@ -102,7 +102,7 @@ public class BluetoothDigooDGSO38H extends BluetoothCommunication {
|
||||
checksum += configBytes[i];
|
||||
}
|
||||
configBytes[15] = (byte)(checksum & 0xFF);
|
||||
writeBytes(EXTRA_MEASUREMENT_CHARACTERISTIC, configBytes);
|
||||
writeBytes(WEIGHT_MEASUREMENT_SERVICE, EXTRA_MEASUREMENT_CHARACTERISTIC, configBytes);
|
||||
} else if (allValues) {
|
||||
ScaleMeasurement scaleBtData = new ScaleMeasurement();
|
||||
weight = (float) (((weightBytes[3] & 0xFF) << 8) | (weightBytes[4] & 0xFF)) / 100.0f;
|
||||
|
@@ -86,10 +86,10 @@ public class BluetoothExcelvanCF36xBLE extends BluetoothCommunication {
|
||||
configBytes[configBytes.length - 1] =
|
||||
xorChecksum(configBytes, 1, configBytes.length - 2);
|
||||
|
||||
writeBytes(WEIGHT_MEASUREMENT_CHARACTERISTIC, configBytes);
|
||||
writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_CHARACTERISTIC, configBytes);
|
||||
break;
|
||||
case 1:
|
||||
setNotificationOn(WEIGHT_CUSTOM0_CHARACTERISTIC);
|
||||
setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_CUSTOM0_CHARACTERISTIC);
|
||||
break;
|
||||
case 2:
|
||||
sendMessage(R.string.info_step_on_scale, 0);
|
||||
|
@@ -45,7 +45,7 @@ public class BluetoothExingtechY1 extends BluetoothCommunication {
|
||||
protected boolean onNextStep(int stepNr) {
|
||||
switch (stepNr) {
|
||||
case 0:
|
||||
setNotificationOn(WEIGHT_MEASUREMENT_CHARACTERISTIC);
|
||||
setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_CHARACTERISTIC);
|
||||
break;
|
||||
case 1:
|
||||
final ScaleUser selectedUser = OpenScale.getInstance().getSelectedScaleUser();
|
||||
@@ -58,7 +58,7 @@ public class BluetoothExingtechY1 extends BluetoothCommunication {
|
||||
|
||||
byte cmdByte[] = {(byte)0x10, (byte)userId, gender, age, height};
|
||||
|
||||
writeBytes(CMD_MEASUREMENT_CHARACTERISTIC, cmdByte);
|
||||
writeBytes(WEIGHT_MEASUREMENT_SERVICE, CMD_MEASUREMENT_CHARACTERISTIC, cmdByte);
|
||||
break;
|
||||
case 2:
|
||||
sendMessage(R.string.info_step_on_scale, 0);
|
||||
|
@@ -18,8 +18,6 @@ package com.health.openscale.core.bluetooth;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.polidea.rxandroidble2.RxBleClient;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class BluetoothFactory {
|
||||
@@ -42,6 +40,14 @@ public class BluetoothFactory {
|
||||
|| name.equals("BF700".toLowerCase(Locale.US))) {
|
||||
return new BluetoothBeurerSanitas(context, BluetoothBeurerSanitas.DeviceType.BEURER_BF710);
|
||||
}
|
||||
if (name.startsWith("BEURER BF600".toLowerCase(Locale.US))
|
||||
|| name.startsWith("BEURER BF850".toLowerCase(Locale.US))
|
||||
|| name.startsWith("BF600".toLowerCase(Locale.US))
|
||||
|| name.startsWith("BF850".toLowerCase(Locale.US))
|
||||
|| name.startsWith("BF-600".toLowerCase(Locale.US))
|
||||
|| name.startsWith("BF-850".toLowerCase(Locale.US))) {
|
||||
return new BluetoothStandardWeightProfile(context);
|
||||
}
|
||||
if (name.equals("openScale".toLowerCase(Locale.US))) {
|
||||
return new BluetoothCustomOpenScale(context);
|
||||
}
|
||||
|
@@ -66,6 +66,9 @@ public class BluetoothGattUuid {
|
||||
public static final UUID SERVICE_GENERIC_ACCESS = fromShortCode(0x1800);
|
||||
public static final UUID SERVICE_GENERIC_ATTRIBUTE = fromShortCode(0x1801);
|
||||
public static final UUID SERVICE_WEIGHT_SCALE = fromShortCode(0x181d);
|
||||
public static final UUID SERVICE_CURRENT_TIME = fromShortCode(0x1805);
|
||||
public static final UUID SERVICE_USER_DATA = fromShortCode(0x181C);
|
||||
public static final UUID SERVICE_BATTERY_LEVEL = fromShortCode(0x180F);
|
||||
|
||||
// https://www.bluetooth.com/specifications/gatt/characteristics
|
||||
public static final UUID CHARACTERISTIC_APPEARANCE = fromShortCode(0x2a01);
|
||||
@@ -86,6 +89,9 @@ public class BluetoothGattUuid {
|
||||
public static final UUID CHARACTERISTIC_SOFTWARE_REVISION_STRING = fromShortCode(0x2a28);
|
||||
public static final UUID CHARACTERISTIC_SYSTEM_ID = fromShortCode(0x2a23);
|
||||
public static final UUID CHARACTERISTIC_WEIGHT_MEASUREMENT = fromShortCode(0x2a9d);
|
||||
public static final UUID CHARACTERISTIC_BATTERY_LEVEL = fromShortCode(0x2A19);
|
||||
public static final UUID CHARACTERISTIC_CHANGE_INCREMENT = fromShortCode(0x2a99);
|
||||
public static final UUID CHARACTERISTIC_USER_CONTROL_POINT = fromShortCode(0x2A9F);
|
||||
|
||||
// https://www.bluetooth.com/specifications/gatt/descriptors
|
||||
public static final UUID DESCRIPTOR_CLIENT_CHARACTERISTIC_CONFIGURATION = fromShortCode(0x2902);
|
||||
|
@@ -42,11 +42,11 @@ public class BluetoothHesley extends BluetoothCommunication {
|
||||
protected boolean onNextStep(int stepNr) {
|
||||
switch (stepNr) {
|
||||
case 0:
|
||||
setNotificationOn(WEIGHT_MEASUREMENT_CHARACTERISTIC);
|
||||
setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_CHARACTERISTIC);
|
||||
break;
|
||||
case 1:
|
||||
byte[] magicBytes = {(byte)0xa5, (byte)0x01, (byte)0x2c, (byte)0xab, (byte)0x50, (byte)0x5a, (byte)0x29};
|
||||
writeBytes(CMD_MEASUREMENT_CHARACTERISTIC, magicBytes);
|
||||
writeBytes(WEIGHT_MEASUREMENT_SERVICE, CMD_MEASUREMENT_CHARACTERISTIC, magicBytes);
|
||||
break;
|
||||
case 2:
|
||||
sendMessage(R.string.info_step_on_scale, 0);
|
||||
|
@@ -60,7 +60,7 @@ public class BluetoothInlife extends BluetoothCommunication {
|
||||
data[i++] = parameter;
|
||||
}
|
||||
data[data.length - 2] = xorChecksum(data, 1, data.length - 3);
|
||||
writeBytes(WEIGHT_CMD_CHARACTERISTIC, data);
|
||||
writeBytes(WEIGHT_SERVICE, WEIGHT_CMD_CHARACTERISTIC, data);
|
||||
}
|
||||
|
||||
public BluetoothInlife(Context context) {
|
||||
@@ -76,7 +76,7 @@ public class BluetoothInlife extends BluetoothCommunication {
|
||||
protected boolean onNextStep(int stepNr) {
|
||||
switch (stepNr) {
|
||||
case 0:
|
||||
setNotificationOn(WEIGHT_MEASUREMENT_CHARACTERISTIC);
|
||||
setNotificationOn(WEIGHT_SERVICE, WEIGHT_MEASUREMENT_CHARACTERISTIC);
|
||||
break;
|
||||
case 1:
|
||||
ScaleUser scaleUser = OpenScale.getInstance().getSelectedScaleUser();
|
||||
|
@@ -68,7 +68,7 @@ public class BluetoothMGB extends BluetoothCommunication {
|
||||
buf[6] = (byte)0xCC;
|
||||
buf[7] = (byte)((buf[2] + buf[3] + buf[4] + buf[5] + buf[6]) & 0xFF);
|
||||
|
||||
writeBytes(uuid_char_cfg, buf);
|
||||
writeBytes(uuid_service, uuid_char_cfg, buf);
|
||||
}
|
||||
|
||||
|
||||
@@ -85,8 +85,7 @@ public class BluetoothMGB extends BluetoothCommunication {
|
||||
protected boolean onNextStep(int stepNr) {
|
||||
switch (stepNr) {
|
||||
case 0:
|
||||
setNotificationOn(uuid_char_ctrl
|
||||
);
|
||||
setNotificationOn(uuid_service, uuid_char_ctrl);
|
||||
now = Calendar.getInstance();
|
||||
user = OpenScale.getInstance().getSelectedScaleUser();
|
||||
break;
|
||||
|
@@ -55,15 +55,15 @@ public class BluetoothMedisanaBS44x extends BluetoothCommunication {
|
||||
switch (stepNr) {
|
||||
case 0:
|
||||
// set indication on for feature characteristic
|
||||
setIndicationOn(FEATURE_MEASUREMENT_CHARACTERISTIC);
|
||||
setIndicationOn(WEIGHT_MEASUREMENT_SERVICE, FEATURE_MEASUREMENT_CHARACTERISTIC);
|
||||
break;
|
||||
case 1:
|
||||
// set indication on for weight measurement
|
||||
setIndicationOn(WEIGHT_MEASUREMENT_CHARACTERISTIC);
|
||||
setIndicationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_CHARACTERISTIC);
|
||||
break;
|
||||
case 2:
|
||||
// set indication on for custom5 measurement
|
||||
setIndicationOn(CUSTOM5_MEASUREMENT_CHARACTERISTIC);
|
||||
setIndicationOn(WEIGHT_MEASUREMENT_SERVICE, CUSTOM5_MEASUREMENT_CHARACTERISTIC);
|
||||
break;
|
||||
case 3:
|
||||
// send magic number to receive weight data
|
||||
@@ -75,7 +75,7 @@ public class BluetoothMedisanaBS44x extends BluetoothCommunication {
|
||||
|
||||
byte[] magicBytes = new byte[] {(byte)0x02, date[0], date[1], date[2], date[3]};
|
||||
|
||||
writeBytes(CMD_MEASUREMENT_CHARACTERISTIC, magicBytes);
|
||||
writeBytes(WEIGHT_MEASUREMENT_SERVICE, CMD_MEASUREMENT_CHARACTERISTIC, magicBytes);
|
||||
break;
|
||||
case 4:
|
||||
sendMessage(R.string.info_step_on_scale, 0);
|
||||
|
@@ -38,6 +38,7 @@ import timber.log.Timber;
|
||||
import static com.health.openscale.core.bluetooth.BluetoothCommunication.BT_STATUS.UNEXPECTED_ERROR;
|
||||
|
||||
public class BluetoothMiScale extends BluetoothCommunication {
|
||||
private final UUID WEIGHT_MEASUREMENT_SERVICE = UUID.fromString("0000181d-0000-1000-8000-00805f9b34fb");
|
||||
private final UUID WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC = UUID.fromString("00002a2f-0000-3512-2118-0009af100700");
|
||||
|
||||
public BluetoothMiScale(Context context) {
|
||||
@@ -49,65 +50,66 @@ public class BluetoothMiScale extends BluetoothCommunication {
|
||||
return "Xiaomi Mi Scale v1";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBluetoothRead(UUID characteristic, byte[] value) {
|
||||
byte[] data = value;
|
||||
|
||||
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)) {
|
||||
Timber.d("Current year and scale year is different");
|
||||
|
||||
// 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(BluetoothGattUuid.CHARACTERISTIC_CURRENT_TIME, dateTimeByte);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBluetoothNotify(UUID characteristic, byte[] value) {
|
||||
final byte[] data = value;
|
||||
|
||||
if (data != null && data.length > 0) {
|
||||
if (characteristic.equals(BluetoothGattUuid.CHARACTERISTIC_CURRENT_TIME)) {
|
||||
byte[] data = value;
|
||||
|
||||
// Stop command from mi scale received
|
||||
if (data[0] == 0x03) {
|
||||
// send stop command to mi scale
|
||||
writeBytes(WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, new byte[]{0x03});
|
||||
// acknowledge that you received the last history data
|
||||
int uniqueNumber = getUniqueNumber();
|
||||
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];
|
||||
|
||||
byte[] userIdentifier = new byte[]{(byte)0x04, (byte)0xFF, (byte)0xFF, (byte) ((uniqueNumber & 0xFF00) >> 8), (byte) ((uniqueNumber & 0xFF) >> 0)};
|
||||
writeBytes(WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, userIdentifier);
|
||||
if (!(currentYear == scaleYear && currentMonth == scaleMonth && currentDay == scaleDay)) {
|
||||
Timber.d("Current year and scale year is different");
|
||||
|
||||
resumeMachineState();
|
||||
// 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, BluetoothGattUuid.CHARACTERISTIC_CURRENT_TIME, dateTimeByte);
|
||||
}
|
||||
} else {
|
||||
final byte[] data = value;
|
||||
|
||||
if (data != null && data.length > 0) {
|
||||
|
||||
// Stop command from mi scale received
|
||||
if (data[0] == 0x03) {
|
||||
// send stop command to mi scale
|
||||
writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, new byte[]{0x03});
|
||||
// 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);
|
||||
|
||||
resumeMachineState();
|
||||
}
|
||||
|
||||
if (data.length == 20) {
|
||||
final byte[] firstWeight = Arrays.copyOfRange(data, 0, 10);
|
||||
final byte[] secondWeight = Arrays.copyOfRange(data, 10, 20);
|
||||
parseBytes(firstWeight);
|
||||
parseBytes(secondWeight);
|
||||
}
|
||||
|
||||
if (data.length == 10) {
|
||||
parseBytes(data);
|
||||
}
|
||||
|
||||
if (data.length == 20) {
|
||||
final byte[] firstWeight = Arrays.copyOfRange(data, 0, 10);
|
||||
final byte[] secondWeight = Arrays.copyOfRange(data, 10, 20);
|
||||
parseBytes(firstWeight);
|
||||
parseBytes(secondWeight);
|
||||
}
|
||||
|
||||
if (data.length == 10) {
|
||||
parseBytes(data);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,32 +119,32 @@ public class BluetoothMiScale extends BluetoothCommunication {
|
||||
switch (stepNr) {
|
||||
case 0:
|
||||
// read device time
|
||||
readBytes(BluetoothGattUuid.CHARACTERISTIC_CURRENT_TIME);
|
||||
readBytes(WEIGHT_MEASUREMENT_SERVICE, BluetoothGattUuid.CHARACTERISTIC_CURRENT_TIME);
|
||||
break;
|
||||
case 1:
|
||||
// Set on history weight measurement
|
||||
byte[] magicBytes = new byte[]{(byte)0x01, (byte)0x96, (byte)0x8a, (byte)0xbd, (byte)0x62};
|
||||
|
||||
writeBytes(WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, magicBytes);
|
||||
writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, magicBytes);
|
||||
break;
|
||||
case 2:
|
||||
// set notification on for weight measurement history
|
||||
setNotificationOn(WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC);
|
||||
setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC);
|
||||
break;
|
||||
case 3:
|
||||
// set notification on for weight measurement
|
||||
setNotificationOn(BluetoothGattUuid.CHARACTERISTIC_WEIGHT_MEASUREMENT);
|
||||
setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, BluetoothGattUuid.CHARACTERISTIC_WEIGHT_MEASUREMENT);
|
||||
break;
|
||||
case 4:
|
||||
// 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_HISTORY_CHARACTERISTIC, userIdentifier);
|
||||
writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, userIdentifier);
|
||||
break;
|
||||
case 5:
|
||||
// invoke receiving history data
|
||||
writeBytes(WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, new byte[]{0x02});
|
||||
writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, new byte[]{0x02});
|
||||
stopMachineState();
|
||||
break;
|
||||
default:
|
||||
|
@@ -64,12 +64,12 @@ public class BluetoothMiScale2 extends BluetoothCommunication {
|
||||
if (data[0] == 0x03) {
|
||||
Timber.d("Scale stop byte received");
|
||||
// send stop command to mi scale
|
||||
writeBytes(WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, new byte[]{0x03});
|
||||
writeBytes(BluetoothGattUuid.SERVICE_BODY_COMPOSITION, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, new byte[]{0x03});
|
||||
// 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_HISTORY_CHARACTERISTIC, userIdentifier);
|
||||
writeBytes(BluetoothGattUuid.SERVICE_BODY_COMPOSITION, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, userIdentifier);
|
||||
|
||||
disconnect();
|
||||
|
||||
@@ -98,7 +98,7 @@ public class BluetoothMiScale2 extends BluetoothCommunication {
|
||||
// set scale units
|
||||
final ScaleUser selectedUser = OpenScale.getInstance().getSelectedScaleUser();
|
||||
byte[] setUnitCmd = new byte[]{(byte)0x06, (byte)0x04, (byte)0x00, (byte) selectedUser.getScaleUnit().toInt()};
|
||||
writeBytes(WEIGHT_CUSTOM_CONFIG, setUnitCmd);
|
||||
writeBytes(WEIGHT_CUSTOM_SERVICE, WEIGHT_CUSTOM_CONFIG, setUnitCmd);
|
||||
break;
|
||||
case 1:
|
||||
// set current time
|
||||
@@ -112,22 +112,22 @@ public class BluetoothMiScale2 extends BluetoothCommunication {
|
||||
|
||||
byte[] dateTimeByte = {(byte)(year), (byte)(year >> 8), month, day, hour, min, sec, 0x03, 0x00, 0x00};
|
||||
|
||||
writeBytes(BluetoothGattUuid.CHARACTERISTIC_CURRENT_TIME, dateTimeByte);
|
||||
writeBytes(BluetoothGattUuid.SERVICE_BODY_COMPOSITION, BluetoothGattUuid.CHARACTERISTIC_CURRENT_TIME, dateTimeByte);
|
||||
break;
|
||||
case 2:
|
||||
// set notification on for weight measurement history
|
||||
setNotificationOn(WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC);
|
||||
setNotificationOn(BluetoothGattUuid.SERVICE_BODY_COMPOSITION, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC);
|
||||
break;
|
||||
case 3:
|
||||
// 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_HISTORY_CHARACTERISTIC, userIdentifier);
|
||||
writeBytes(BluetoothGattUuid.SERVICE_BODY_COMPOSITION, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, userIdentifier);
|
||||
break;
|
||||
case 4:
|
||||
// invoke receiving history data
|
||||
writeBytes(WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, new byte[]{0x02});
|
||||
writeBytes(BluetoothGattUuid.SERVICE_BODY_COMPOSITION, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, new byte[]{0x02});
|
||||
stopMachineState();
|
||||
break;
|
||||
default:
|
||||
|
@@ -50,7 +50,7 @@ public class BluetoothOneByone extends BluetoothCommunication {
|
||||
protected boolean onNextStep(int stepNr) {
|
||||
switch (stepNr) {
|
||||
case 0:
|
||||
setNotificationOn(WEIGHT_MEASUREMENT_CHARACTERISTIC_BODY_COMPOSITION);
|
||||
setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_CHARACTERISTIC_BODY_COMPOSITION);
|
||||
break;
|
||||
case 1:
|
||||
ScaleUser currentUser = OpenScale.getInstance().getSelectedScaleUser();
|
||||
@@ -69,7 +69,7 @@ public class BluetoothOneByone extends BluetoothCommunication {
|
||||
(byte)0x00, (byte)0x00, (byte)0x00};
|
||||
magicBytes[magicBytes.length - 1] =
|
||||
xorChecksum(magicBytes, 0, magicBytes.length - 1);
|
||||
writeBytes(CMD_MEASUREMENT_CHARACTERISTIC, magicBytes);
|
||||
writeBytes(WEIGHT_MEASUREMENT_SERVICE, CMD_MEASUREMENT_CHARACTERISTIC, magicBytes);
|
||||
break;
|
||||
case 2:
|
||||
sendMessage(R.string.info_step_on_scale, 0);
|
||||
|
@@ -88,11 +88,11 @@ public class BluetoothQNScale extends BluetoothCommunication {
|
||||
switch (stepNr) {
|
||||
case 0:
|
||||
// set notification on for custom characteristic 1 (weight, time, and others)
|
||||
setNotificationOn(CUSTOM1_MEASUREMENT_CHARACTERISTIC);
|
||||
setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, CUSTOM1_MEASUREMENT_CHARACTERISTIC);
|
||||
break;
|
||||
case 1:
|
||||
// set indication on for weight measurement
|
||||
setIndicationOn(CUSTOM2_MEASUREMENT_CHARACTERISTIC);
|
||||
setIndicationOn(WEIGHT_MEASUREMENT_SERVICE, CUSTOM2_MEASUREMENT_CHARACTERISTIC);
|
||||
break;
|
||||
case 2:
|
||||
final ScaleUser scaleUser = OpenScale.getInstance().getSelectedScaleUser();
|
||||
@@ -108,7 +108,7 @@ public class BluetoothQNScale extends BluetoothCommunication {
|
||||
byte[] ffe3magicBytes = new byte[]{(byte) 0x13, (byte) 0x09, (byte) 0x15, weightUnitByte, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00};
|
||||
// Set last byte to be checksum
|
||||
ffe3magicBytes[ffe3magicBytes.length -1] = sumChecksum(ffe3magicBytes, 0, ffe3magicBytes.length - 1);
|
||||
writeBytes(CUSTOM3_MEASUREMENT_CHARACTERISTIC, ffe3magicBytes);
|
||||
writeBytes(WEIGHT_MEASUREMENT_SERVICE, CUSTOM3_MEASUREMENT_CHARACTERISTIC, ffe3magicBytes);
|
||||
break;
|
||||
case 3:
|
||||
// send time magic number to receive weight data
|
||||
@@ -117,7 +117,7 @@ public class BluetoothQNScale extends BluetoothCommunication {
|
||||
byte[] date = new byte[4];
|
||||
Converters.toInt32Le(date, 0, timestamp);
|
||||
byte[] timeMagicBytes = new byte[]{(byte) 0x02, date[0], date[1], date[2], date[3]};
|
||||
writeBytes(CUSTOM4_MEASUREMENT_CHARACTERISTIC, timeMagicBytes);
|
||||
writeBytes(WEIGHT_MEASUREMENT_SERVICE, CUSTOM4_MEASUREMENT_CHARACTERISTIC, timeMagicBytes);
|
||||
break;
|
||||
case 4:
|
||||
sendMessage(R.string.info_step_on_scale, 0);
|
||||
|
@@ -16,14 +16,13 @@
|
||||
|
||||
package com.health.openscale.core.bluetooth;
|
||||
|
||||
import android.bluetooth.BluetoothGattService;
|
||||
import android.content.Context;
|
||||
|
||||
import com.health.openscale.R;
|
||||
import com.health.openscale.core.OpenScale;
|
||||
import com.health.openscale.core.datatypes.ScaleMeasurement;
|
||||
import com.health.openscale.core.datatypes.ScaleUser;
|
||||
import com.polidea.rxandroidble2.RxBleDeviceServices;
|
||||
import com.welie.blessed.BluetoothPeripheral;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
@@ -40,6 +39,7 @@ public class BluetoothSenssun extends BluetoothCommunication {
|
||||
private final UUID MODEL_B_NOTIFICATION_CHARACTERISTIC = UUID.fromString("0000ffb2-0000-1000-8000-00805f9b34fb");
|
||||
private final UUID MODEL_B_WRITE_CHARACTERISTIC = UUID.fromString("0000ffb2-0000-1000-8000-00805f9b34fb");
|
||||
|
||||
private UUID writeService;
|
||||
private UUID writeCharacteristic;
|
||||
|
||||
private int lastWeight, lastFat, lastHydration, lastMuscle, lastBone, lastKcal;
|
||||
@@ -57,23 +57,21 @@ public class BluetoothSenssun extends BluetoothCommunication {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBluetoothDiscovery(RxBleDeviceServices rxBleDeviceServices) {
|
||||
for (BluetoothGattService gattService : rxBleDeviceServices.getBluetoothGattServices()) {
|
||||
if (gattService.getUuid().equals(MODEL_A_MEASUREMENT_SERVICE)) {
|
||||
writeCharacteristic = MODEL_A_WRITE_CHARACTERISTIC;
|
||||
setNotificationOn(MODEL_A_NOTIFICATION_CHARACTERISTIC);
|
||||
Timber.d("Found a Model A");
|
||||
break;
|
||||
}
|
||||
if (gattService.getUuid().equals(MODEL_B_MEASUREMENT_SERVICE)) {
|
||||
writeCharacteristic = MODEL_B_WRITE_CHARACTERISTIC;
|
||||
setNotificationOn(MODEL_B_NOTIFICATION_CHARACTERISTIC);
|
||||
Timber.d("Found a Model B");
|
||||
break;
|
||||
}
|
||||
}
|
||||
protected void onBluetoothDiscovery(BluetoothPeripheral peripheral) {
|
||||
|
||||
resumeMachineState();
|
||||
if (peripheral.getService(MODEL_A_MEASUREMENT_SERVICE) != null) {
|
||||
writeService = MODEL_A_MEASUREMENT_SERVICE;
|
||||
writeCharacteristic = MODEL_A_WRITE_CHARACTERISTIC;
|
||||
setNotificationOn(MODEL_A_MEASUREMENT_SERVICE, MODEL_A_NOTIFICATION_CHARACTERISTIC);
|
||||
Timber.d("Found a Model A");
|
||||
}
|
||||
|
||||
if (peripheral.getService(MODEL_B_MEASUREMENT_SERVICE) != null) {
|
||||
writeService = MODEL_B_MEASUREMENT_SERVICE;
|
||||
writeCharacteristic = MODEL_B_WRITE_CHARACTERISTIC;
|
||||
setNotificationOn(MODEL_B_MEASUREMENT_SERVICE, MODEL_B_NOTIFICATION_CHARACTERISTIC);
|
||||
Timber.d("Found a Model B");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -83,14 +81,10 @@ public class BluetoothSenssun extends BluetoothCommunication {
|
||||
weightStabilized = false;
|
||||
stepMessageDisplayed = false;
|
||||
values = 0;
|
||||
discoverBluetoothServices();
|
||||
stopMachineState();
|
||||
break;
|
||||
case 1:
|
||||
Timber.d("Sync Date");
|
||||
synchroniseDate();
|
||||
break;
|
||||
case 2:
|
||||
case 1:
|
||||
Timber.d("Sync Time");
|
||||
synchroniseTime();
|
||||
break;
|
||||
@@ -202,7 +196,7 @@ public class BluetoothSenssun extends BluetoothCommunication {
|
||||
|
||||
addChecksum(message);
|
||||
|
||||
writeBytes(writeCharacteristic, message);
|
||||
writeBytes(writeService, writeCharacteristic, message);
|
||||
}
|
||||
|
||||
private void synchroniseTime() {
|
||||
@@ -216,7 +210,7 @@ public class BluetoothSenssun extends BluetoothCommunication {
|
||||
|
||||
addChecksum(message);
|
||||
|
||||
writeBytes(writeCharacteristic, message);
|
||||
writeBytes(writeService, writeCharacteristic, message);
|
||||
}
|
||||
|
||||
private void addChecksum(byte[] message) {
|
||||
@@ -239,6 +233,6 @@ public class BluetoothSenssun extends BluetoothCommunication {
|
||||
|
||||
addChecksum(message);
|
||||
|
||||
writeBytes(writeCharacteristic, message);
|
||||
writeBytes(writeService, writeCharacteristic, message);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,303 @@
|
||||
/* Copyright (C) 2019 olie.xdev <olie.xdev@googlemail.com>
|
||||
*
|
||||
* 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/>
|
||||
*/
|
||||
|
||||
/*
|
||||
* Based on source-code by weliem/blessed-android
|
||||
*/
|
||||
package com.health.openscale.core.bluetooth;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.health.openscale.core.OpenScale;
|
||||
import com.health.openscale.core.datatypes.ScaleMeasurement;
|
||||
import com.health.openscale.core.datatypes.ScaleUser;
|
||||
import com.welie.blessed.BluetoothBytesParser;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
import static com.welie.blessed.BluetoothBytesParser.FORMAT_UINT16;
|
||||
import static com.welie.blessed.BluetoothBytesParser.FORMAT_UINT8;
|
||||
|
||||
public class BluetoothStandardWeightProfile extends BluetoothCommunication {
|
||||
private int CURRENT_USER_CONSENT = 3289;
|
||||
|
||||
// UDS control point codes
|
||||
private static final byte UDS_CP_REGISTER_NEW_USER = 0x01;
|
||||
private static final byte UDS_CP_CONSENT = 0x02;
|
||||
private static final byte UDS_CP_DELETE_USER_DATA = 0x03;
|
||||
private static final byte UDS_CP_LIST_ALL_USERS = 0x04;
|
||||
private static final byte UDS_CP_DELETE_USERS = 0x05;
|
||||
private static final byte UDS_CP_RESPONSE = 0x20;
|
||||
|
||||
// UDS response codes
|
||||
private static final byte UDS_CP_RESP_VALUE_SUCCESS = 0x01;
|
||||
private static final byte UDS_CP_RESP_OP_CODE_NOT_SUPPORTED = 0x02;
|
||||
private static final byte UDS_CP_RESP_INVALID_PARAMETER = 0x03;
|
||||
private static final byte UDS_CP_RESP_OPERATION_FAILED = 0x04;
|
||||
private static final byte UDS_CP_RESP_USER_NOT_AUTHORIZED = 0x05;
|
||||
|
||||
public BluetoothStandardWeightProfile(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String driverName() {
|
||||
return "Bluetooth Standard Weight Profile";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onNextStep(int stepNr) {
|
||||
|
||||
switch (stepNr) {
|
||||
case 0:
|
||||
// Read manufacturer and model number from the Device Information Service
|
||||
readBytes(BluetoothGattUuid.SERVICE_DEVICE_INFORMATION, BluetoothGattUuid.CHARACTERISTIC_MANUFACTURER_NAME_STRING);
|
||||
readBytes(BluetoothGattUuid.SERVICE_DEVICE_INFORMATION, BluetoothGattUuid.CHARACTERISTIC_MODEL_NUMBER_STRING);
|
||||
break;
|
||||
case 1:
|
||||
// Write the current time
|
||||
BluetoothBytesParser parser = new BluetoothBytesParser();
|
||||
parser.setCurrentTime(Calendar.getInstance());
|
||||
writeBytes(BluetoothGattUuid.SERVICE_CURRENT_TIME, BluetoothGattUuid.CHARACTERISTIC_CURRENT_TIME, parser.getValue());
|
||||
break;
|
||||
case 2:
|
||||
// Turn on notification for Weight Service
|
||||
setNotificationOn(BluetoothGattUuid.SERVICE_WEIGHT_SCALE, BluetoothGattUuid.CHARACTERISTIC_WEIGHT_MEASUREMENT);
|
||||
break;
|
||||
case 3:
|
||||
// Turn on notification for Body Composition Service
|
||||
setNotificationOn(BluetoothGattUuid.SERVICE_BODY_COMPOSITION, BluetoothGattUuid.CHARACTERISTIC_BODY_COMPOSITION_MEASUREMENT);
|
||||
break;
|
||||
case 4:
|
||||
// Turn on notification for User Data Service
|
||||
setNotificationOn(BluetoothGattUuid.SERVICE_USER_DATA, BluetoothGattUuid.CHARACTERISTIC_CHANGE_INCREMENT);
|
||||
setNotificationOn(BluetoothGattUuid.SERVICE_USER_DATA, BluetoothGattUuid.CHARACTERISTIC_USER_CONTROL_POINT);
|
||||
break;
|
||||
case 5:
|
||||
// Turn on notifications for Battery Service
|
||||
setNotificationOn(BluetoothGattUuid.SERVICE_BATTERY_LEVEL, BluetoothGattUuid.CHARACTERISTIC_BATTERY_LEVEL);
|
||||
break;
|
||||
case 6:
|
||||
final ScaleUser selectedUser = OpenScale.getInstance().getSelectedScaleUser();
|
||||
registerUser(CURRENT_USER_CONSENT);
|
||||
setUser(selectedUser.getId(), CURRENT_USER_CONSENT);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBluetoothNotify(UUID characteristic, byte[] value) {
|
||||
BluetoothBytesParser parser = new BluetoothBytesParser(value);
|
||||
|
||||
if(characteristic.equals(BluetoothGattUuid.CHARACTERISTIC_CURRENT_TIME)) {
|
||||
Date currentTime = parser.getDateTime();
|
||||
Timber.d(String.format("Received device time: %s", currentTime));
|
||||
}
|
||||
else if(characteristic.equals(BluetoothGattUuid.CHARACTERISTIC_WEIGHT_MEASUREMENT)) {
|
||||
handleWeightMeasurement(value);
|
||||
}
|
||||
else if(characteristic.equals(BluetoothGattUuid.CHARACTERISTIC_BODY_COMPOSITION_MEASUREMENT)) {
|
||||
handleBodyCompositionMeasurement(value);
|
||||
}
|
||||
else if(characteristic.equals(BluetoothGattUuid.CHARACTERISTIC_BATTERY_LEVEL)) {
|
||||
int batteryLevel = parser.getIntValue(FORMAT_UINT8);
|
||||
Timber.d(String.format("Received battery level %d%%", batteryLevel));
|
||||
}
|
||||
else if(characteristic.equals(BluetoothGattUuid.CHARACTERISTIC_MANUFACTURER_NAME_STRING)) {
|
||||
String manufacturer = parser.getStringValue(0);
|
||||
Timber.d(String.format("Received manufacturer: %s", manufacturer));
|
||||
}
|
||||
else if(characteristic.equals(BluetoothGattUuid.CHARACTERISTIC_MODEL_NUMBER_STRING)) {
|
||||
String modelNumber = parser.getStringValue(0);
|
||||
Timber.d(String.format("Received modelnumber: %s", modelNumber));
|
||||
}
|
||||
else if(characteristic.equals(BluetoothGattUuid.CHARACTERISTIC_USER_CONTROL_POINT)) {
|
||||
if(value[0]==UDS_CP_RESPONSE) {
|
||||
switch (value[1]) {
|
||||
case UDS_CP_REGISTER_NEW_USER:
|
||||
if (value[2] == UDS_CP_RESP_VALUE_SUCCESS) {
|
||||
int userIndex = value[3];
|
||||
Timber.d(String.format("Created user %d", userIndex));
|
||||
} else {
|
||||
Timber.e("ERROR: could not register new user");
|
||||
}
|
||||
break;
|
||||
case UDS_CP_CONSENT:
|
||||
if (value[2] == UDS_CP_RESP_VALUE_SUCCESS) {
|
||||
Timber.d("Success user consent");
|
||||
} else if (value[2] == UDS_CP_RESP_USER_NOT_AUTHORIZED) {
|
||||
Timber.e("Not authorized");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Timber.e("Unhandled response");
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Timber.d(String.format("Got data: <%s>", byteInHex(value)));
|
||||
}
|
||||
}
|
||||
|
||||
private void handleWeightMeasurement(byte[] value) {
|
||||
BluetoothBytesParser parser = new BluetoothBytesParser(value);
|
||||
final int flags = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT8);
|
||||
boolean isKg = (flags & 0x01) == 0;
|
||||
final boolean timestampPresent = (flags & 0x02) > 0;
|
||||
final boolean userIDPresent = (flags & 0x04) > 0;
|
||||
final boolean bmiAndHeightPresent = (flags & 0x08) > 0;
|
||||
|
||||
ScaleMeasurement scaleMeasurement = new ScaleMeasurement();
|
||||
|
||||
// Determine the right weight multiplier
|
||||
float weightMultiplier = isKg ? 0.005f : 0.01f;
|
||||
|
||||
// Get weight
|
||||
float weightValue = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * weightMultiplier;
|
||||
scaleMeasurement.setWeight(weightValue);
|
||||
|
||||
if(timestampPresent) {
|
||||
Date timestamp = parser.getDateTime();
|
||||
scaleMeasurement.setDateTime(timestamp);
|
||||
}
|
||||
|
||||
if(userIDPresent) {
|
||||
int userID = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT8);
|
||||
Timber.d(String.format("User id: %i", userID));
|
||||
}
|
||||
|
||||
if(bmiAndHeightPresent) {
|
||||
float BMI = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * 0.1f;
|
||||
float heightInMeters = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * 0.001f;
|
||||
}
|
||||
|
||||
Timber.d(String.format("Got weight: %s", weightValue));
|
||||
addScaleMeasurement(scaleMeasurement);
|
||||
}
|
||||
|
||||
private void handleBodyCompositionMeasurement(byte[] value) {
|
||||
BluetoothBytesParser parser = new BluetoothBytesParser(value);
|
||||
final int flags = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16);
|
||||
boolean isKg = (flags & 0x0001) == 0;
|
||||
float massMultiplier = (float) (isKg ? 0.005 : 0.01);
|
||||
boolean timestampPresent = (flags & 0x0002) > 0;
|
||||
boolean userIDPresent = (flags & 0x0004) > 0;
|
||||
boolean bmrPresent = (flags & 0x0008) > 0;
|
||||
boolean musclePercentagePresent = (flags & 0x0010) > 0;
|
||||
boolean muscleMassPresent = (flags & 0x0020) > 0;
|
||||
boolean fatFreeMassPresent = (flags & 0x0040) > 0;
|
||||
boolean softLeanMassPresent = (flags & 0x0080) > 0;
|
||||
boolean bodyWaterMassPresent = (flags & 0x0100) > 0;
|
||||
boolean impedancePresent = (flags & 0x0200) > 0;
|
||||
boolean weightPresent = (flags & 0x0400) > 0;
|
||||
boolean heightPresent = (flags & 0x0800) > 0;
|
||||
boolean multiPacketMeasurement = (flags & 0x1000) > 0;
|
||||
|
||||
ScaleMeasurement scaleMeasurement = new ScaleMeasurement();
|
||||
|
||||
float bodyFatPercentage = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * 0.1f;
|
||||
scaleMeasurement.setFat(bodyFatPercentage);
|
||||
|
||||
// Read timestamp if present
|
||||
if (timestampPresent) {
|
||||
Date timestamp = parser.getDateTime();
|
||||
scaleMeasurement.setDateTime(timestamp);
|
||||
}
|
||||
|
||||
// Read userID if present
|
||||
if (userIDPresent) {
|
||||
int userID = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT8);
|
||||
Timber.d(String.format("user id: %i", userID));
|
||||
}
|
||||
|
||||
// Read bmr if present
|
||||
if (bmrPresent) {
|
||||
int bmrInJoules = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16);
|
||||
int bmrInKcal = Math.round(((bmrInJoules / 4.1868f) * 10.0f) / 10.0f);
|
||||
}
|
||||
|
||||
// Read musclePercentage if present
|
||||
if (musclePercentagePresent) {
|
||||
float musclePercentage = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * 0.1f;
|
||||
scaleMeasurement.setMuscle(musclePercentage);
|
||||
}
|
||||
|
||||
// Read muscleMass if present
|
||||
if (muscleMassPresent) {
|
||||
float muscleMass = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * massMultiplier;
|
||||
}
|
||||
|
||||
// Read fatFreeMassPresent if present
|
||||
if (fatFreeMassPresent) {
|
||||
float fatFreeMass = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * massMultiplier;
|
||||
}
|
||||
|
||||
// Read softleanMass if present
|
||||
if (softLeanMassPresent) {
|
||||
float softLeanMass = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * massMultiplier;
|
||||
}
|
||||
|
||||
// Read bodyWaterMass if present
|
||||
if (bodyWaterMassPresent) {
|
||||
float bodyWaterMass = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * massMultiplier;
|
||||
scaleMeasurement.setWater(bodyWaterMass);
|
||||
}
|
||||
|
||||
// Read impedance if present
|
||||
if (impedancePresent) {
|
||||
float impedance = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * 0.1f;
|
||||
}
|
||||
|
||||
// Read weight if present
|
||||
if (weightPresent) {
|
||||
float weightValue = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * massMultiplier;
|
||||
scaleMeasurement.setWeight(weightValue);
|
||||
}
|
||||
|
||||
// Read height if present
|
||||
if (heightPresent) {
|
||||
float heightValue = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16);
|
||||
}
|
||||
|
||||
Timber.d(String.format("Got body composition: %s", byteInHex(value)));
|
||||
addScaleMeasurement(scaleMeasurement);
|
||||
}
|
||||
|
||||
private void registerUser(int consentCode) {
|
||||
BluetoothBytesParser parser = new BluetoothBytesParser(new byte[]{0,0,0});
|
||||
parser.setIntValue(UDS_CP_REGISTER_NEW_USER, FORMAT_UINT8,0);
|
||||
parser.setIntValue(consentCode, FORMAT_UINT16,1);
|
||||
Timber.d(String.format("registerUser consentCode: %d", consentCode));
|
||||
writeBytes(BluetoothGattUuid.SERVICE_USER_DATA, BluetoothGattUuid.CHARACTERISTIC_USER_CONTROL_POINT, parser.getValue());
|
||||
}
|
||||
|
||||
private void setUser(int userIndex, int consentCode) {
|
||||
BluetoothBytesParser parser = new BluetoothBytesParser(new byte[]{0,0,0,0});
|
||||
parser.setIntValue(UDS_CP_CONSENT,FORMAT_UINT8,0);
|
||||
parser.setIntValue(userIndex, FORMAT_UINT8,1);
|
||||
parser.setIntValue(consentCode, FORMAT_UINT16,2);
|
||||
Timber.d(String.format("setUser userIndex: %d, consentCode: %d", userIndex, consentCode));
|
||||
writeBytes(BluetoothGattUuid.SERVICE_USER_DATA, BluetoothGattUuid.CHARACTERISTIC_USER_CONTROL_POINT, parser.getValue());
|
||||
}
|
||||
}
|
@@ -19,6 +19,8 @@ import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.health.openscale.R;
|
||||
import com.health.openscale.core.OpenScale;
|
||||
import com.health.openscale.core.bluetooth.lib.TrisaBodyAnalyzeLib;
|
||||
@@ -29,7 +31,6 @@ import com.health.openscale.core.utils.Converters;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
@@ -124,7 +125,7 @@ public class BluetoothTrisaBodyAnalyze extends BluetoothCommunication {
|
||||
switch (stepNr) {
|
||||
case 0:
|
||||
// Register for notifications of the measurement characteristic.
|
||||
setIndicationOn(MEASUREMENT_CHARACTERISTIC_UUID);
|
||||
setIndicationOn(WEIGHT_SCALE_SERVICE_UUID, MEASUREMENT_CHARACTERISTIC_UUID);
|
||||
break; // more commands follow
|
||||
case 1:
|
||||
// Register for notifications of the command upload characteristic.
|
||||
@@ -132,7 +133,7 @@ public class BluetoothTrisaBodyAnalyze extends BluetoothCommunication {
|
||||
// This is the last init command, which causes a switch to the main state machine
|
||||
// immediately after. This is important because we should be in the main state
|
||||
// to handle pairing correctly.
|
||||
setIndicationOn(UPLOAD_COMMAND_CHARACTERISTIC_UUID);
|
||||
setIndicationOn(WEIGHT_SCALE_SERVICE_UUID, UPLOAD_COMMAND_CHARACTERISTIC_UUID);
|
||||
break;
|
||||
case 2:
|
||||
// This state is triggered by the write in onPasswordReceived()
|
||||
@@ -306,7 +307,7 @@ public class BluetoothTrisaBodyAnalyze extends BluetoothCommunication {
|
||||
|
||||
private void writeCommandBytes(byte[] bytes) {
|
||||
Timber.d("writeCommand bytes=%s", byteInHex(bytes));
|
||||
writeBytes(DOWNLOAD_COMMAND_CHARACTERISTIC_UUID, bytes);
|
||||
writeBytes(WEIGHT_SCALE_SERVICE_UUID, DOWNLOAD_COMMAND_CHARACTERISTIC_UUID, bytes);
|
||||
}
|
||||
|
||||
private static String getDevicePasswordKey(String deviceId) {
|
||||
|
@@ -68,7 +68,7 @@ public class BluetoothYunmaiSE_Mini extends BluetoothCommunication {
|
||||
(byte)0x00, display_unit, (byte) 0x03, (byte) 0x00};
|
||||
user_add_or_query[user_add_or_query.length - 1] =
|
||||
xorChecksum(user_add_or_query, 1, user_add_or_query.length - 1);
|
||||
writeBytes(WEIGHT_CMD_CHARACTERISTIC, user_add_or_query);
|
||||
writeBytes(WEIGHT_CMD_SERVICE, WEIGHT_CMD_CHARACTERISTIC, user_add_or_query);
|
||||
break;
|
||||
case 1:
|
||||
byte[] unixTime = Converters.toInt32Be(new Date().getTime() / 1000);
|
||||
@@ -79,14 +79,14 @@ public class BluetoothYunmaiSE_Mini extends BluetoothCommunication {
|
||||
set_time[set_time.length - 1] =
|
||||
xorChecksum(set_time, 1, set_time.length - 1);
|
||||
|
||||
writeBytes(WEIGHT_CMD_CHARACTERISTIC, set_time);
|
||||
writeBytes(WEIGHT_CMD_SERVICE, WEIGHT_CMD_CHARACTERISTIC, set_time);
|
||||
break;
|
||||
case 2:
|
||||
setNotificationOn(WEIGHT_MEASUREMENT_CHARACTERISTIC);
|
||||
setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_CHARACTERISTIC);
|
||||
break;
|
||||
case 3:
|
||||
byte[] magic_bytes = new byte[]{(byte)0x0d, (byte)0x05, (byte)0x13, (byte)0x00, (byte)0x16};
|
||||
writeBytes(WEIGHT_CMD_CHARACTERISTIC, magic_bytes);
|
||||
writeBytes(WEIGHT_CMD_SERVICE, WEIGHT_CMD_CHARACTERISTIC, magic_bytes);
|
||||
sendMessage(R.string.info_step_on_scale, 0);
|
||||
break;
|
||||
default:
|
||||
|
@@ -1,23 +1,25 @@
|
||||
/* Copyright (C) 2014 olie.xdev <olie.xdev@googlemail.com>
|
||||
*
|
||||
* 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/>
|
||||
*/
|
||||
*
|
||||
* 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.gui.preferences;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Fragment;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.le.ScanResult;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
@@ -26,6 +28,7 @@ import android.graphics.PorterDuff;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceFragment;
|
||||
@@ -39,16 +42,13 @@ import com.health.openscale.core.bluetooth.BluetoothCommunication;
|
||||
import com.health.openscale.core.bluetooth.BluetoothFactory;
|
||||
import com.health.openscale.gui.utils.ColorUtil;
|
||||
import com.health.openscale.gui.utils.PermissionHelper;
|
||||
import com.polidea.rxandroidble2.RxBleClient;
|
||||
import com.polidea.rxandroidble2.RxBleDevice;
|
||||
import com.polidea.rxandroidble2.scan.ScanResult;
|
||||
import com.polidea.rxandroidble2.scan.ScanSettings;
|
||||
import com.welie.blessed.BluetoothCentral;
|
||||
import com.welie.blessed.BluetoothCentralCallback;
|
||||
import com.welie.blessed.BluetoothPeripheral;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
||||
@@ -59,11 +59,10 @@ public class BluetoothPreferences extends PreferenceFragment {
|
||||
public static final String PREFERENCE_KEY_BLUETOOTH_HW_ADDRESS = "btHwAddress";
|
||||
|
||||
private PreferenceScreen btScanner;
|
||||
private Map<String, RxBleDevice> foundDevices = new HashMap<>();
|
||||
private Map<String, BluetoothDevice> foundDevices = new HashMap<>();
|
||||
|
||||
private Handler progressHandler;
|
||||
private RxBleClient bleClient;
|
||||
private Disposable scanSubscription;
|
||||
private BluetoothCentral central;
|
||||
|
||||
private static final String formatDeviceName(String name, String address) {
|
||||
if (name.isEmpty() || address.isEmpty()) {
|
||||
@@ -72,8 +71,8 @@ public class BluetoothPreferences extends PreferenceFragment {
|
||||
return String.format("%s [%s]", name, address);
|
||||
}
|
||||
|
||||
private static final String formatDeviceName(RxBleDevice device) {
|
||||
return formatDeviceName(device.getName(), device.getMacAddress());
|
||||
private static final String formatDeviceName(BluetoothDevice device) {
|
||||
return formatDeviceName(device.getName(), device.getAddress());
|
||||
}
|
||||
|
||||
private String getCurrentDeviceName() {
|
||||
@@ -83,19 +82,19 @@ public class BluetoothPreferences extends PreferenceFragment {
|
||||
prefs.getString(PREFERENCE_KEY_BLUETOOTH_HW_ADDRESS, ""));
|
||||
}
|
||||
|
||||
private final BluetoothCentralCallback bluetoothCentralCallback = new BluetoothCentralCallback() {
|
||||
@Override
|
||||
public void onDiscoveredPeripheral(BluetoothPeripheral peripheral, ScanResult scanResult) {
|
||||
onDeviceFound(scanResult);
|
||||
}
|
||||
};
|
||||
|
||||
private void startBluetoothDiscovery() {
|
||||
foundDevices.clear();
|
||||
btScanner.removeAll();
|
||||
|
||||
scanSubscription = bleClient.scanBleDevices(
|
||||
new ScanSettings.Builder()
|
||||
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
|
||||
//.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
|
||||
.build()
|
||||
)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doFinally(this::stopBluetoothDiscovery)
|
||||
.subscribe(this::onDeviceFound, throwable -> Toast.makeText(getActivity(), throwable.getMessage(), Toast.LENGTH_LONG).show());
|
||||
central = new BluetoothCentral(getActivity().getApplicationContext(), bluetoothCentralCallback, new Handler(Looper.getMainLooper()));
|
||||
central.scanForPeripherals();
|
||||
|
||||
final Preference scanning = new Preference(getActivity());
|
||||
scanning.setEnabled(false);
|
||||
@@ -161,25 +160,25 @@ public class BluetoothPreferences extends PreferenceFragment {
|
||||
progressHandler = null;
|
||||
}
|
||||
|
||||
scanSubscription.dispose();
|
||||
central.stopScan();
|
||||
}
|
||||
|
||||
private void onDeviceFound(final ScanResult bleScanResult) {
|
||||
RxBleDevice device = bleScanResult.getBleDevice();
|
||||
BluetoothDevice device = bleScanResult.getDevice();
|
||||
|
||||
if (device.getName() == null || foundDevices.containsKey(device.getMacAddress())) {
|
||||
if (device.getName() == null || foundDevices.containsKey(device.getAddress())) {
|
||||
return;
|
||||
}
|
||||
|
||||
Preference prefBtDevice = new Preference(getActivity());
|
||||
prefBtDevice.setTitle(formatDeviceName(bleScanResult.getBleDevice()));
|
||||
prefBtDevice.setTitle(formatDeviceName(bleScanResult.getDevice()));
|
||||
|
||||
BluetoothCommunication btDevice = BluetoothFactory.createDeviceDriver(getActivity(), device.getName());
|
||||
if (btDevice != null) {
|
||||
Timber.d("Found supported device %s (driver: %s)",
|
||||
formatDeviceName(device), btDevice.driverName());
|
||||
prefBtDevice.setOnPreferenceClickListener(new onClickListenerDeviceSelect());
|
||||
prefBtDevice.setKey(device.getMacAddress());
|
||||
prefBtDevice.setKey(device.getAddress());
|
||||
prefBtDevice.setIcon(R.drawable.ic_bluetooth_device_supported);
|
||||
prefBtDevice.setSummary(btDevice.driverName());
|
||||
|
||||
@@ -205,7 +204,7 @@ public class BluetoothPreferences extends PreferenceFragment {
|
||||
}
|
||||
}
|
||||
|
||||
foundDevices.put(device.getMacAddress(), btDevice != null ? device : null);
|
||||
foundDevices.put(device.getAddress(), btDevice != null ? device : null);
|
||||
btScanner.addPreference(prefBtDevice);
|
||||
}
|
||||
|
||||
@@ -219,10 +218,6 @@ public class BluetoothPreferences extends PreferenceFragment {
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
OpenScale openScale = OpenScale.getInstance();
|
||||
|
||||
bleClient = openScale.getBleClient();
|
||||
|
||||
addPreferencesFromResource(R.xml.bluetooth_preferences);
|
||||
|
||||
btScanner = (PreferenceScreen) findPreference(PREFERENCE_KEY_BLUETOOTH_SCANNER);
|
||||
@@ -271,7 +266,7 @@ public class BluetoothPreferences extends PreferenceFragment {
|
||||
}
|
||||
}
|
||||
|
||||
private void getDebugInfo(final RxBleDevice device) {
|
||||
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 your scale...")
|
||||
@@ -299,7 +294,7 @@ public class BluetoothPreferences extends PreferenceFragment {
|
||||
|
||||
dialog.show();
|
||||
|
||||
String macAddress = device.getMacAddress();
|
||||
String macAddress = device.getAddress();
|
||||
stopBluetoothDiscovery();
|
||||
OpenScale.getInstance().connectToBluetoothDeviceDebugMode(
|
||||
macAddress, btHandler);
|
||||
@@ -308,10 +303,10 @@ public class BluetoothPreferences extends PreferenceFragment {
|
||||
private class onClickListenerDeviceSelect implements Preference.OnPreferenceClickListener {
|
||||
@Override
|
||||
public boolean onPreferenceClick(final Preference preference) {
|
||||
RxBleDevice device = foundDevices.get(preference.getKey());
|
||||
BluetoothDevice device = foundDevices.get(preference.getKey());
|
||||
|
||||
preference.getSharedPreferences().edit()
|
||||
.putString(PREFERENCE_KEY_BLUETOOTH_HW_ADDRESS, device.getMacAddress())
|
||||
.putString(PREFERENCE_KEY_BLUETOOTH_HW_ADDRESS, device.getAddress())
|
||||
.putString(PREFERENCE_KEY_BLUETOOTH_DEVICE_NAME, device.getName())
|
||||
.apply();
|
||||
|
||||
|
@@ -5,7 +5,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.3.2'
|
||||
classpath 'com.android.tools.build:gradle:3.4.0'
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
#Mon Jan 28 19:53:43 CET 2019
|
||||
#Sat May 25 16:04:40 CEST 2019
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
|
||||
|
Reference in New Issue
Block a user