1
0
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:
OliE
2019-06-02 15:50:00 +02:00
committed by GitHub
parent 2f07567014
commit 83a36ea2ce
26 changed files with 640 additions and 505 deletions

View File

@@ -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) {

View File

@@ -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();

View File

@@ -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) {

View File

@@ -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();

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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();

View File

@@ -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;

View File

@@ -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);

View File

@@ -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:

View File

@@ -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:

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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) {

View File

@@ -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:

View File

@@ -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();

View File

@@ -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'
}
}

View File

@@ -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