1
0
mirror of https://github.com/oliexdev/openScale.git synced 2025-08-12 19:54:29 +02:00

Bluetooth state machine 2.0 (#404)

simplified Bluetooth machine state
This commit is contained in:
OliE
2019-02-21 18:52:44 +01:00
committed by GitHub
parent 8675fe7b67
commit 3a577b9292
22 changed files with 354 additions and 692 deletions

View File

@@ -168,9 +168,8 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
}
@Override
protected boolean nextInitCmd(int stateNr) {
switch (stateNr) {
protected boolean onNextStep(int stepNr) {
switch (stepNr) {
case 0:
// Setup notification
setNotificationOn(CUSTOM_CHARACTERISTIC_WEIGHT);
@@ -212,26 +211,10 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
if (currentRemoteUser != null) {
Timber.d("Request saved measurements for %s", currentRemoteUser.name);
sendCommand(CMD_GET_SAVED_MEASUREMENTS, encodeUserId(currentRemoteUser));
// Wait for all measurements to be received
stopMachineState();
} else {
nextMachineStateStep();
}
break;
case 6:
if (currentRemoteUser != null) {
Timber.d("Deleting saved measurements for %s", currentRemoteUser.name);
sendCommand(CMD_DELETE_SAVED_MEASUREMENTS, encodeUserId(currentRemoteUser));
// Return to the previous state until all users have been processed
repeatMachineStateSteps(2);
stopMachineState();
} else {
nextMachineStateStep();
}
break;
case 7:
// Create a remote user for selected openScale user if needed
currentRemoteUser = null;
final ScaleUser selectedUser = OpenScale.getInstance().getSelectedScaleUser();
@@ -244,14 +227,20 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
if (currentRemoteUser == null) {
createRemoteUser(selectedUser);
stopMachineState();
} else {
nextMachineStateStep();
}
break;
case 8:
case 7:
sendCommand(CMD_USER_DETAILS, encodeUserId(currentRemoteUser));
stopMachineState();
break;
case 8:
if (!currentRemoteUser.isNew) {
sendCommand(CMD_DO_MEASUREMENT, encodeUserId(currentRemoteUser));
stopMachineState();
} else {
return false;
}
break;
default:
// Finish init if everything is done
return false;
@@ -260,40 +249,6 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
return true;
}
@Override
protected boolean nextBluetoothCmd(int stateNr) {
switch (stateNr) {
case 0:
if (!currentRemoteUser.isNew) {
sendCommand(CMD_DO_MEASUREMENT, encodeUserId(currentRemoteUser));
stopMachineState();
} else {
nextMachineStateStep();
}
break;
case 1:
setBtMachineState(BT_MACHINE_STATE.BT_CLEANUP_STATE);
break;
default:
return false;
}
return true;
}
@Override
protected boolean nextCleanUpCmd(int stateNr) {
switch (stateNr) {
case 0:
// Force disconnect
sendAlternativeStartCode(ID_START_NIBBLE_DISCONNECT, (byte) 0x02);
break;
default:
return false;
}
return true;
}
@Override
public void onBluetoothNotify(UUID characteristic, byte[] value) {
byte[] data = value;
@@ -303,7 +258,7 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
if (data[0] == getAlternativeStartByte(ID_START_NIBBLE_INIT)) {
Timber.d("Got init ack from scale; scale is ready");
resumeMachineState(true);
resumeMachineState();
return;
}
@@ -377,7 +332,7 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
}
// All users received
resumeMachineState(true);
resumeMachineState();
}
private void processMeasurementData(byte[] data, int offset, boolean firstPart) {
@@ -401,11 +356,18 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
int current = data[3] & 0xFF;
processMeasurementData(data, 4, current % 2 == 1);
if (current == count) {
// Resume but don't do next step until ACK has been sent
resumeMachineState(false);
}
sendAck(data);
if (current == count) {
Timber.d("Deleting saved measurements for %s", currentRemoteUser.name);
sendCommand(CMD_DELETE_SAVED_MEASUREMENTS, encodeUserId(currentRemoteUser));
if (currentRemoteUser.remoteUserId != remoteUsers.get(remoteUsers.size() - 1).remoteUserId) {
jumpNextToStepNr(5);
resumeMachineState();
}
}
}
private void processWeightMeasurement(byte[] data) {
@@ -483,7 +445,7 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
Timber.d("Set scale unit to %s (%d)", user.getScaleUnit(), requestedUnit);
sendCommand(CMD_SET_UNIT, requestedUnit);
} else {
resumeMachineState(true);
resumeMachineState();
}
break;
@@ -491,7 +453,7 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
if (data[3] == 0) {
Timber.d("Scale unit successfully set");
}
resumeMachineState(true);
resumeMachineState();
break;
case CMD_USER_LIST:
@@ -499,7 +461,7 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
int maxUserCount = data[5] & 0xFF;
Timber.d("Have %d users (max is %d)", userCount, maxUserCount);
if (userCount == 0) {
resumeMachineState(true);
resumeMachineState();
}
// Otherwise wait for CMD_USER_INFO notifications
break;
@@ -508,8 +470,9 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
int measurementCount = data[3] & 0xFF;
if (measurementCount == 0) {
// Skip delete all measurements step (since there are no measurements to delete)
repeatMachineStateSteps(1);
resumeMachineState(true);
Timber.d("No saved measurements found for user " + currentRemoteUser.name);
jumpNextToStepNr(5);
resumeMachineState();
}
// Otherwise wait for CMD_SAVED_MEASUREMENT notifications which will,
// once all measurements have been received, resume the state machine.
@@ -517,9 +480,9 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
case CMD_DELETE_SAVED_MEASUREMENTS:
if (data[3] == 0) {
Timber.d("Saved measurements successfully deleted");
Timber.d("Saved measurements successfully deleted for user " + currentRemoteUser.name);
}
resumeMachineState(true);
resumeMachineState();
break;
case CMD_USER_ADD:
@@ -533,7 +496,10 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
Timber.d("Cannot create additional scale user (error 0x%02x)", data[3]);
sendMessage(R.string.error_max_scale_users, 0);
setBtMachineState(BT_MACHINE_STATE.BT_CLEANUP_STATE);
// Force disconnect
Timber.d("Send disconnect command to scale");
jumpNextToStepNr(8);
resumeMachineState();
break;
case CMD_DO_MEASUREMENT:
@@ -556,7 +522,7 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
Timber.d("Name: %s, Birthday: %d-%02d-%02d, Height: %d, Sex: %s, activity: %d",
name, year, month, day, height, male ? "male" : "female", activity);
}
resumeMachineState(true);
resumeMachineState();
break;
default:
@@ -596,7 +562,7 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
receivedMeasurement.setMuscle(muscle);
receivedMeasurement.setBone(bone);
addScaleData(receivedMeasurement);
addScaleMeasurement(receivedMeasurement);
}
private void writeBytes(byte[] data) {

View File

@@ -38,40 +38,38 @@ import java.util.UUID;
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 timber.log.Timber;
import static android.content.Context.LOCATION_SERVICE;
public abstract class BluetoothCommunication {
public enum BT_STATUS_CODE {
BT_RETRIEVE_SCALE_DATA,
BT_INIT_PROCESS,
BT_CONNECTION_RETRYING,
BT_CONNECTION_ESTABLISHED,
BT_CONNECTION_DISCONNECT,
BT_CONNECTION_LOST,
BT_NO_DEVICE_FOUND,
BT_UNEXPECTED_ERROR,
BT_SCALE_MESSAGE
public enum BT_STATUS {
RETRIEVE_SCALE_DATA,
INIT_PROCESS,
CONNECTION_RETRYING,
CONNECTION_ESTABLISHED,
CONNECTION_DISCONNECT,
CONNECTION_LOST,
NO_DEVICE_FOUND,
UNEXPECTED_ERROR,
SCALE_MESSAGE
}
public enum BT_MACHINE_STATE {
BT_INIT_STATE,
BT_CMD_STATE,
BT_CLEANUP_STATE,
BT_STOPPED_STATE
}
private final int BT_RETRY_TIMES_ON_ERROR = 3;
private int stepNr;
private boolean stopped;
protected Context context;
private final int BT_RETRY_TIMES_ON_ERROR = 3;
private RxBleClient bleClient;
private RxBleDevice bleDevice;
private Observable<RxBleConnection> connectionObservable;
@@ -80,13 +78,6 @@ public abstract class BluetoothCommunication {
private PublishSubject<Boolean> disconnectTriggerSubject = PublishSubject.create();
private Handler callbackBtHandler;
private int cmdStepNr;
private int initStepNr;
private int cleanupStepNr;
private BT_MACHINE_STATE btMachineState;
private BT_MACHINE_STATE btStopppedMachineState;
private Handler disconnectHandler;
public BluetoothCommunication(Context context)
@@ -95,6 +86,8 @@ public abstract class BluetoothCommunication {
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) {
@@ -126,7 +119,7 @@ public abstract class BluetoothCommunication {
}
/**
* Register a callback Bluetooth handler that notify any BT_STATUS_CODE changes for GUI/CORE.
* Register a callback Bluetooth handler that notify any BT_STATUS changes for GUI/CORE.
*
* @param cbBtHandler a handler that is registered
*/
@@ -137,10 +130,10 @@ public abstract class BluetoothCommunication {
/**
* Set for the openScale GUI/CORE the Bluetooth status code.
*
* @param statusCode the status code that should be set
* @param status the status code that should be set
*/
protected void setBtStatus(BT_STATUS_CODE statusCode) {
setBtStatus(statusCode, "");
protected void setBluetoothStatus(BT_STATUS status) {
setBluetoothStatus(status, "");
}
/**
@@ -149,7 +142,7 @@ public abstract class BluetoothCommunication {
* @param statusCode the status code that should be set
* @param infoText the information text that is displayed to the status code.
*/
protected void setBtStatus(BT_STATUS_CODE statusCode, String infoText) {
protected void setBluetoothStatus(BT_STATUS statusCode, String infoText) {
if (callbackBtHandler != null) {
callbackBtHandler.obtainMessage(
statusCode.ordinal(), infoText).sendToTarget();
@@ -161,10 +154,10 @@ public abstract class BluetoothCommunication {
*
* @param scaleMeasurement the scale data that should be added to openScale
*/
protected void addScaleData(ScaleMeasurement scaleMeasurement) {
protected void addScaleMeasurement(ScaleMeasurement scaleMeasurement) {
if (callbackBtHandler != null) {
callbackBtHandler.obtainMessage(
BT_STATUS_CODE.BT_RETRIEVE_SCALE_DATA.ordinal(), scaleMeasurement).sendToTarget();
BT_STATUS.RETRIEVE_SCALE_DATA.ordinal(), scaleMeasurement).sendToTarget();
}
}
@@ -177,7 +170,7 @@ public abstract class BluetoothCommunication {
protected void sendMessage(int msg, Object value) {
if (callbackBtHandler != null) {
callbackBtHandler.obtainMessage(
BT_STATUS_CODE.BT_SCALE_MESSAGE.ordinal(), msg, 0, value).sendToTarget();
BT_STATUS.SCALE_MESSAGE.ordinal(), msg, 0, value).sendToTarget();
}
}
@@ -191,70 +184,10 @@ public abstract class BluetoothCommunication {
/**
* State machine for the initialization process of the Bluetooth device.
*
* @param stateNr the current step number
* @param stepNr the current step number
* @return false if no next step is available otherwise true
*/
abstract protected boolean nextInitCmd(int stateNr);
/**
* State machine for the normal/command process of the Bluetooth device.
*
* This state machine is automatically triggered if initialization process is finished.
*
* @param stateNr the current step number
* @return false if no next step is available otherwise true
*/
abstract protected boolean nextBluetoothCmd(int stateNr);
/**
* Step the current machine state backwards. Needs to be called before a command.
*
* @param steps Number of steps to back the machine.
*/
protected void repeatMachineStateSteps(int steps) {
switch (btMachineState) {
case BT_INIT_STATE:
initStepNr = initStepNr - steps;
break;
case BT_CMD_STATE:
cmdStepNr = cmdStepNr - steps;
break;
case BT_CLEANUP_STATE:
cleanupStepNr = cleanupStepNr - steps;
break;
}
}
/**
* Stopped the current machine state
*/
protected void stopMachineState() {
Timber.d("Machine state stopped");
btStopppedMachineState = btMachineState;
btMachineState = BT_MACHINE_STATE.BT_STOPPED_STATE;
}
/**
* Resumed the current machine state
*/
protected void resumeMachineState(boolean doNextStep) {
Timber.d("Machine state resumed");
btMachineState = btStopppedMachineState;
if (doNextStep) {
nextMachineStateStep();
}
}
/**
* State machine for the clean up process for the Bluetooth device.
*
* This state machine is *not* automatically triggered. You have to setBtMachineState(BT_MACHINE_STATE.BT_CLEANUP_STATE) to trigger this process if necessary.
*
* @param stateNr the current step number
* @return false if no next step is available otherwise true
*/
abstract protected boolean nextCleanUpCmd(int stateNr);
abstract protected boolean onNextStep(int stepNr);
/**
* Method is triggered if a Bluetooth data is read from a device.
@@ -279,17 +212,20 @@ public abstract class BluetoothCommunication {
*/
protected void onBluetoothDiscovery(RxBleDeviceServices rxBleDeviceServices) { }
/**
* Set the Bluetooth machine state to a specific state.
*
* @note after setting a new state the next step is automatically triggered.
*
* @param btMachineState the machine state that should be set.
*/
protected void setBtMachineState(BT_MACHINE_STATE btMachineState) {
this.btMachineState = btMachineState;
protected synchronized void stopMachineState() {
Timber.d("Stop machine state");
stopped = true;
}
nextMachineStateStep();
protected synchronized void resumeMachineState() {
Timber.d("Resume machine state");
stopped = false;
nextMachineStep();
}
protected synchronized void jumpNextToStepNr(int nr) {
Timber.d("Jump next to step nr " + nr);
stepNr = nr;
}
/**
@@ -297,22 +233,25 @@ public abstract class BluetoothCommunication {
* @param characteristic the Bluetooth UUID characteristic
* @param bytes the bytes that should be write
*/
protected void writeBytes(UUID characteristic, byte[] bytes) {
final Disposable disposable = connectionObservable
protected Observable<byte[]> writeBytes(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())
.retry(BT_RETRY_TIMES_ON_ERROR)
.subscribe(
.retry(BT_RETRY_TIMES_ON_ERROR);
compositeDisposable.add(observable.subscribe(
value -> {
Timber.d("Write characteristic %s: %s",
BluetoothGattUuid.prettyPrint(characteristic),
byteInHex(value));
nextMachineStateStep();
},
throwable -> onError(throwable)
);
)
);
compositeDisposable.add(disposable);
return observable;
}
/**
@@ -321,20 +260,25 @@ 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 void readBytes(UUID characteristic) {
final Disposable disposable = connectionObservable
protected Single<byte[]> readBytes(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())
.retry(BT_RETRY_TIMES_ON_ERROR)
.subscribe(bytes -> {
Timber.d("Read characteristic %s", BluetoothGattUuid.prettyPrint(characteristic));
onBluetoothRead(characteristic, bytes);
},
throwable -> onError(throwable)
);
.retry(BT_RETRY_TIMES_ON_ERROR);
compositeDisposable.add(disposable);
compositeDisposable.add(observable
.subscribe(bytes -> {
Timber.d("Read characteristic %s", BluetoothGattUuid.prettyPrint(characteristic));
onBluetoothRead(characteristic, bytes);
},
throwable -> onError(throwable)
)
);
return observable;
}
/**
@@ -342,29 +286,32 @@ public abstract class BluetoothCommunication {
*
* @param characteristic the Bluetooth UUID characteristic
*/
protected void setIndicationOn(UUID characteristic) {
final Disposable disposable = connectionObservable
protected Observable<byte[]> setIndicationOn(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));
nextMachineStateStep();
}
)
.flatMap(indicationObservable -> indicationObservable)
.subscribeOn(Schedulers.trampoline())
.observeOn(AndroidSchedulers.mainThread())
.retry(BT_RETRY_TIMES_ON_ERROR)
.subscribe(
bytes -> {
onBluetoothNotify(characteristic, bytes);
Timber.d("onCharacteristicChanged %s: %s",
BluetoothGattUuid.prettyPrint(characteristic),
byteInHex(bytes));
resetDisconnectTimer();
},
throwable -> onError(throwable)
);
.retry(BT_RETRY_TIMES_ON_ERROR);
compositeDisposable.add(disposable);
compositeDisposable.add(observable.subscribe(
bytes -> {
Timber.d("onCharacteristicChanged %s: %s",
BluetoothGattUuid.prettyPrint(characteristic),
byteInHex(bytes));
onBluetoothNotify(characteristic, bytes);
resetDisconnectTimer();
},
throwable -> onError(throwable)
)
);
return observable;
}
/**
@@ -372,46 +319,69 @@ public abstract class BluetoothCommunication {
*
* @param characteristic the Bluetooth UUID characteristic
*/
protected void setNotificationOn(UUID characteristic) {
final Disposable disposable = connectionObservable
protected Observable<byte[]> setNotificationOn(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));
nextMachineStateStep();
stopped = false;
nextMachineStep();
}
)
.flatMap(notificationObservable -> notificationObservable)
.subscribeOn(Schedulers.trampoline())
.observeOn(AndroidSchedulers.mainThread())
.retry(BT_RETRY_TIMES_ON_ERROR)
.subscribe(
bytes -> {
onBluetoothNotify(characteristic, bytes);
Timber.d("onCharacteristicChanged %s: %s",
BluetoothGattUuid.prettyPrint(characteristic),
byteInHex(bytes));
resetDisconnectTimer();
},
throwable -> onError(throwable)
);
.retry(BT_RETRY_TIMES_ON_ERROR);
compositeDisposable.add(disposable);
compositeDisposable.add(observable.subscribe(
bytes -> {
Timber.d("onCharacteristicChanged %s: %s",
BluetoothGattUuid.prettyPrint(characteristic),
byteInHex(bytes));
onBluetoothNotify(characteristic, bytes);
resetDisconnectTimer();
},
throwable -> onError(throwable)
));
return observable;
}
public void doBluetoothDiscoverServices() {
final Disposable connectionDisposable = connectionObservable
protected Observable<RxBleDeviceServices> discoverBluetoothServices() {
Timber.d("Invoke discover Bluetooth services");
final Observable<RxBleDeviceServices> observable = connectionObservable
.flatMapSingle(RxBleConnection::discoverServices)
.subscribeOn(Schedulers.trampoline())
.observeOn(AndroidSchedulers.mainThread())
.retry(BT_RETRY_TIMES_ON_ERROR)
.subscribe(
deviceServices -> {
Timber.d("Successful Bluetooth services discovered");
onBluetoothDiscovery(deviceServices);
nextMachineStateStep();
},
throwable -> onError(throwable)
);
.retry(BT_RETRY_TIMES_ON_ERROR);
compositeDisposable.add(connectionDisposable);
compositeDisposable.add(observable.subscribe(
deviceServices -> {
Timber.d("Successful Bluetooth services discovered");
onBluetoothDiscovery(deviceServices);
},
throwable -> onError(throwable)
)
);
return observable;
}
/**
* Disconnect from a Bluetooth device
*/
public void disconnect() {
Timber.d("Bluetooth disconnect");
setBluetoothStatus(BT_STATUS.CONNECTION_DISCONNECT);
if (scanSubscription != null) {
scanSubscription.dispose();
}
callbackBtHandler = null;
disconnectHandler.removeCallbacksAndMessages(null);
disconnectTriggerSubject.onNext(true);
compositeDisposable.clear();
}
/**
@@ -496,11 +466,12 @@ public abstract class BluetoothCommunication {
//.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.build()
)
.subscribeOn(Schedulers.trampoline())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(bleScanResult -> {
if (bleScanResult.getBleDevice().getMacAddress().equals(macAddress)) {
connectToDevice(macAddress);
}}, throwable -> setBtStatus(BT_STATUS_CODE.BT_NO_DEVICE_FOUND));
}}, throwable -> setBluetoothStatus(BT_STATUS.NO_DEVICE_FOUND));
}
else {
Timber.d("No coarse location permission, connecting without LE scan");
@@ -515,7 +486,6 @@ public abstract class BluetoothCommunication {
Timber.d("Stop Le san");
scanSubscription.dispose();
scanSubscription = null;
}
Handler handler = new Handler();
@@ -527,19 +497,18 @@ public abstract class BluetoothCommunication {
connectionObservable = bleDevice
.establishConnection(false)
.takeUntil(disconnectTriggerSubject)
.doOnError(throwable -> setBtStatus(BT_STATUS_CODE.BT_CONNECTION_RETRYING))
.doOnError(throwable -> setBluetoothStatus(BT_STATUS.CONNECTION_RETRYING))
.subscribeOn(Schedulers.trampoline())
.observeOn(AndroidSchedulers.mainThread())
.compose(ReplayingShare.instance());
if (isConnected()) {
disconnect();
} else {
initStepNr = -1;
cmdStepNr = -1;
cleanupStepNr = -1;
stepNr = 0;
setBtMonitoringOn();
setBtMachineState(BT_MACHINE_STATE.BT_INIT_STATE);
nextMachineStep();
resetDisconnectTimer();
}
}
@@ -552,7 +521,7 @@ public abstract class BluetoothCommunication {
connectionState -> {
switch (connectionState) {
case CONNECTED:
setBtStatus(BT_STATUS_CODE.BT_CONNECTION_ESTABLISHED);
setBluetoothStatus(BT_STATUS.CONNECTION_ESTABLISHED);
break;
case CONNECTING:
// empty
@@ -561,7 +530,7 @@ public abstract class BluetoothCommunication {
// empty
break;
case DISCONNECTED:
// setBtStatus(BT_STATUS_CODE.BT_CONNECTION_LOST);
// setBluetoothStatus(BT_STATUS.CONNECTION_LOST);
break;
}
},
@@ -571,20 +540,20 @@ public abstract class BluetoothCommunication {
compositeDisposable.add(disposableConnectionState);
}
private void onError(Throwable throwable) {
setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, throwable.getMessage());
protected void onError(Throwable throwable) {
setBluetoothStatus(BT_STATUS.UNEXPECTED_ERROR, throwable.getMessage());
}
private boolean isConnected() {
return bleDevice.getConnectionState() == RxBleConnection.RxBleConnectionState.CONNECTED;
}
public void resetDisconnectTimer() {
private void resetDisconnectTimer() {
disconnectHandler.removeCallbacksAndMessages(null);
disconnectWithDelay();
}
public void disconnectWithDelay() {
private void disconnectWithDelay() {
disconnectHandler.postDelayed(new Runnable() {
@Override
public void run() {
@@ -594,48 +563,16 @@ public abstract class BluetoothCommunication {
}, 60000); // 60s timeout
}
/**
* Disconnect from a Bluetooth device
*/
public void disconnect() {
Timber.d("Bluetooth disconnect");
setBtStatus(BT_STATUS_CODE.BT_CONNECTION_DISCONNECT);
if (scanSubscription != null) {
scanSubscription.dispose();
}
callbackBtHandler = null;
disconnectHandler.removeCallbacksAndMessages(null);
disconnectTriggerSubject.onNext(true);
compositeDisposable.clear();
}
/**
* Invoke next step for internal Bluetooth state machine.
*/
protected void nextMachineStateStep() {
switch (btMachineState) {
case BT_INIT_STATE:
initStepNr++;
Timber.d("INIT STATE: %d", initStepNr);
if (!nextInitCmd(initStepNr)) {
setBtMachineState(BT_MACHINE_STATE.BT_CMD_STATE);
}
break;
case BT_CMD_STATE:
cmdStepNr++;
Timber.d("CMD STATE: %d", cmdStepNr);
if (!nextBluetoothCmd(cmdStepNr)) {
disconnectWithDelay();
}
break;
case BT_CLEANUP_STATE:
cleanupStepNr++;
Timber.d("CLEANUP STATE: %d", cleanupStepNr);
if (!nextCleanUpCmd(cleanupStepNr)) {
Timber.d("Cleanup Bluetooth disconnect");
disconnect();
}
break;
private synchronized void nextMachineStep() {
if (!stopped) {
Timber.d("Step Nr " + stepNr);
if (onNextStep(stepNr)) {
stepNr++;
nextMachineStep();
} else {
Timber.d("Invoke delayed disconnect in 60s");
disconnectWithDelay();
}
}
}
}

View File

@@ -18,7 +18,6 @@ package com.health.openscale.core.bluetooth;
import android.content.Context;
import com.health.openscale.core.datatypes.ScaleMeasurement;
import com.polidea.rxandroidble2.RxBleClient;
import java.text.ParseException;
import java.text.SimpleDateFormat;
@@ -44,8 +43,8 @@ public class BluetoothCustomOpenScale extends BluetoothCommunication {
}
@Override
protected boolean nextInitCmd(int stateNr) {
switch (stateNr) {
protected boolean onNextStep(int stepNr) {
switch (stepNr) {
case 0:
setNotificationOn(WEIGHT_MEASUREMENT_CHARACTERISTIC
);
@@ -70,16 +69,6 @@ public class BluetoothCustomOpenScale extends BluetoothCommunication {
return true;
}
@Override
protected boolean nextBluetoothCmd(int stateNr) {
return false;
}
@Override
protected boolean nextCleanUpCmd(int stateNr) {
return false;
}
public void clearEEPROM()
{
byte[] cmd = {(byte)'9'};
@@ -106,7 +95,7 @@ public class BluetoothCustomOpenScale extends BluetoothCommunication {
btString = btString.substring(0, btString.length() - 1); // delete newline '\n' of the string
if (btString.charAt(0) != '$' && btString.charAt(2) != '$') {
setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "Parse error of bluetooth string. String has not a valid format: " + btString);
setBluetoothStatus(BT_STATUS.UNEXPECTED_ERROR, "Parse error of bluetooth string. String has not a valid format: " + btString);
}
String btMsg = btString.substring(3, btString.length()); // message string
@@ -156,18 +145,18 @@ public class BluetoothCustomOpenScale extends BluetoothCommunication {
scaleBtData.setWater(Float.parseFloat(csvField[8]));
scaleBtData.setMuscle(Float.parseFloat(csvField[9]));
addScaleData(scaleBtData);
addScaleMeasurement(scaleBtData);
} else {
setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "Error calculated checksum (" + checksum + ") and received checksum (" + btChecksum + ") is different");
setBluetoothStatus(BT_STATUS.UNEXPECTED_ERROR, "Error calculated checksum (" + checksum + ") and received checksum (" + btChecksum + ") is different");
}
} catch (ParseException e) {
setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "Error while decoding bluetooth date string (" + e.getMessage() + ")");
setBluetoothStatus(BT_STATUS.UNEXPECTED_ERROR, "Error while decoding bluetooth date string (" + e.getMessage() + ")");
} catch (NumberFormatException e) {
setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "Error while decoding a number of bluetooth string (" + e.getMessage() + ")");
setBluetoothStatus(BT_STATUS.UNEXPECTED_ERROR, "Error while decoding a number of bluetooth string (" + e.getMessage() + ")");
}
break;
default:
setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "Error unknown MCU command : " + btString);
setBluetoothStatus(BT_STATUS.UNEXPECTED_ERROR, "Error unknown MCU command : " + btString);
}
}
}

View File

@@ -164,14 +164,14 @@ public class BluetoothDebug extends BluetoothCommunication {
}
@Override
protected boolean nextInitCmd(int stateNr) {
switch (stateNr)
protected boolean onNextStep(int stepNr) {
switch (stepNr)
{
case 0:
doBluetoothDiscoverServices();
discoverBluetoothServices();
break;
case 1:
int offset = stateNr;
int offset = stepNr;
for (BluetoothGattService service : rxBleDeviceServices.getBluetoothGattServices()) {
offset = readServiceCharacteristics(service, offset);
@@ -181,7 +181,7 @@ public class BluetoothDebug extends BluetoothCommunication {
logService(service, false);
}
setBtStatus(BT_STATUS_CODE.BT_CONNECTION_LOST);
setBluetoothStatus(BT_STATUS.CONNECTION_LOST);
break;
case 2:
disconnect();
@@ -192,14 +192,4 @@ public class BluetoothDebug extends BluetoothCommunication {
return true;
}
@Override
protected boolean nextBluetoothCmd(int stateNr) {
return false;
}
@Override
protected boolean nextCleanUpCmd(int stateNr) {
return false;
}
}

View File

@@ -56,8 +56,8 @@ public class BluetoothDigooDGSO38H extends BluetoothCommunication {
@Override
protected boolean nextInitCmd(int stateNr) {
switch (stateNr) {
protected boolean onNextStep(int stepNr) {
switch (stepNr) {
case 0:
//Tell device to send us weight measurements
setNotificationOn(WEIGHT_MEASUREMENT_CHARACTERISTIC);
@@ -65,19 +65,11 @@ public class BluetoothDigooDGSO38H extends BluetoothCommunication {
case 1:
sendMessage(R.string.info_step_on_scale, 0);
break;
default:
return false;
}
return false;
}
@Override
protected boolean nextBluetoothCmd(int stateNr) {
return false;
}
@Override
protected boolean nextCleanUpCmd(int stateNr) {
return false;
return true;
}
private void parseBytes(byte[] weightBytes) {
@@ -135,7 +127,7 @@ public class BluetoothDigooDGSO38H extends BluetoothCommunication {
scaleBtData.setVisceralFat(visceralFat);
}
scaleBtData.setWeight(weight);
addScaleData(scaleBtData);
addScaleMeasurement(scaleBtData);
}
}
}

View File

@@ -44,13 +44,8 @@ public class BluetoothExcelvanCF36xBLE extends BluetoothCommunication {
}
@Override
protected boolean nextInitCmd(int stateNr) {
return false;
}
@Override
protected boolean nextBluetoothCmd(int stateNr) {
switch (stateNr) {
protected boolean onNextStep(int stepNr) {
switch (stepNr) {
case 0:
final ScaleUser selectedUser = OpenScale.getInstance().getSelectedScaleUser();
@@ -103,12 +98,7 @@ public class BluetoothExcelvanCF36xBLE extends BluetoothCommunication {
return false;
}
return false;
}
@Override
protected boolean nextCleanUpCmd(int stateNr) {
return false;
return true;
}
@Override
@@ -150,6 +140,6 @@ public class BluetoothExcelvanCF36xBLE extends BluetoothCommunication {
scaleBtData.setBone(bone);
scaleBtData.setVisceralFat(visceralFat);
addScaleData(scaleBtData);
addScaleMeasurement(scaleBtData);
}
}

View File

@@ -42,11 +42,10 @@ public class BluetoothExingtechY1 extends BluetoothCommunication {
}
@Override
protected boolean nextInitCmd(int stateNr) {
switch (stateNr) {
protected boolean onNextStep(int stepNr) {
switch (stepNr) {
case 0:
setNotificationOn(WEIGHT_MEASUREMENT_CHARACTERISTIC
);
setNotificationOn(WEIGHT_MEASUREMENT_CHARACTERISTIC);
break;
case 1:
final ScaleUser selectedUser = OpenScale.getInstance().getSelectedScaleUser();
@@ -71,16 +70,6 @@ public class BluetoothExingtechY1 extends BluetoothCommunication {
return true;
}
@Override
protected boolean nextBluetoothCmd(int stateNr) {
return false;
}
@Override
protected boolean nextCleanUpCmd(int stateNr) {
return false;
}
@Override
public void onBluetoothNotify(UUID characteristic, byte[] value) {
final byte[] data = value;
@@ -118,6 +107,6 @@ public class BluetoothExingtechY1 extends BluetoothCommunication {
scaleBtData.setVisceralFat(visc_fat);
scaleBtData.setDateTime(new Date());
addScaleData(scaleBtData);
addScaleMeasurement(scaleBtData);
}
}

View File

@@ -39,8 +39,8 @@ public class BluetoothHesley extends BluetoothCommunication {
}
@Override
protected boolean nextInitCmd(int stateNr) {
switch (stateNr) {
protected boolean onNextStep(int stepNr) {
switch (stepNr) {
case 0:
setNotificationOn(WEIGHT_MEASUREMENT_CHARACTERISTIC);
break;
@@ -58,16 +58,6 @@ public class BluetoothHesley extends BluetoothCommunication {
return true;
}
@Override
protected boolean nextBluetoothCmd(int stateNr) {
return false;
}
@Override
protected boolean nextCleanUpCmd(int stateNr) {
return false;
}
@Override
public void onBluetoothNotify(UUID characteristic, byte[] value) {
final byte[] data = value;
@@ -101,6 +91,6 @@ public class BluetoothHesley extends BluetoothCommunication {
scaleBtData.setBone(bone);
scaleBtData.setDateTime(new Date());
addScaleData(scaleBtData);
addScaleMeasurement(scaleBtData);
}
}

View File

@@ -23,14 +23,13 @@ import android.bluetooth.BluetoothSocket;
import android.content.Context;
import com.health.openscale.core.datatypes.ScaleMeasurement;
import com.polidea.rxandroidble2.RxBleClient;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;
import java.util.Date;
import java.util.Arrays;
import java.util.Date;
import java.util.UUID;
import timber.log.Timber;
@@ -57,20 +56,8 @@ public class BluetoothIhealthHS3 extends BluetoothCommunication {
}
@Override
protected boolean nextInitCmd(int stateNr) {
Timber.w("ihealthHS3 - nextInitCmd - returning false");
return false;
}
@Override
protected boolean nextBluetoothCmd(int stateNr) {
Timber.w("ihealthHS3 - nextBluetoothCmd - returning false");
return false;
}
@Override
protected boolean nextCleanUpCmd(int stateNr) {
Timber.w("ihealthHS3 - nextCleanUpCmd - returning false");
protected boolean onNextStep(int stepNr) {
Timber.w("ihealthHS3 - onNextStep - returning false");
return false;
}
@@ -79,7 +66,7 @@ public class BluetoothIhealthHS3 extends BluetoothCommunication {
BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
if (btAdapter == null) {
setBtStatus(BT_STATUS_CODE.BT_NO_DEVICE_FOUND);
setBluetoothStatus(BT_STATUS.NO_DEVICE_FOUND);
return;
}
@@ -88,7 +75,7 @@ public class BluetoothIhealthHS3 extends BluetoothCommunication {
// Get a BluetoothSocket to connect with the given BluetoothDevice
btSocket = btDevice.createRfcommSocketToServiceRecord(uuid);
} catch (IOException e) {
setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "Can't get a bluetooth socket");
setBluetoothStatus(BT_STATUS.UNEXPECTED_ERROR, "Can't get a bluetooth socket");
btDevice = null;
return;
}
@@ -103,7 +90,7 @@ public class BluetoothIhealthHS3 extends BluetoothCommunication {
btSocket.connect();
// Bluetooth connection was successful
setBtStatus(BT_STATUS_CODE.BT_CONNECTION_ESTABLISHED);
setBluetoothStatus(BT_STATUS.CONNECTION_ESTABLISHED);
btConnectThread = new BluetoothConnectedThread();
btConnectThread.start();
@@ -111,7 +98,7 @@ public class BluetoothIhealthHS3 extends BluetoothCommunication {
} catch (IOException connectException) {
// Unable to connect; close the socket and get out
disconnect();
setBtStatus(BT_STATUS_CODE.BT_NO_DEVICE_FOUND);
setBluetoothStatus(BT_STATUS.NO_DEVICE_FOUND);
}
}
};
@@ -129,7 +116,7 @@ public class BluetoothIhealthHS3 extends BluetoothCommunication {
btSocket.close();
btSocket = null;
} catch (IOException closeException) {
setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "Can't close bluetooth socket");
setBluetoothStatus(BT_STATUS.UNEXPECTED_ERROR, "Can't close bluetooth socket");
}
}
}
@@ -171,7 +158,7 @@ public class BluetoothIhealthHS3 extends BluetoothCommunication {
btInStream = btSocket.getInputStream();
btOutStream = btSocket.getOutputStream();
} catch (IOException e) {
setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "Can't get bluetooth input or output stream " + e.getMessage());
setBluetoothStatus(BT_STATUS.UNEXPECTED_ERROR, "Can't get bluetooth input or output stream " + e.getMessage());
}
}
@@ -209,7 +196,7 @@ public class BluetoothIhealthHS3 extends BluetoothCommunication {
ScaleMeasurement scaleMeasurement = parseWeightArray(weightBytes);
if (scaleMeasurement != null) {
addScaleData(scaleMeasurement);
addScaleMeasurement(scaleMeasurement);
}
}
@@ -227,7 +214,7 @@ public class BluetoothIhealthHS3 extends BluetoothCommunication {
} catch (IOException e) {
cancel();
setBtStatus(BT_STATUS_CODE.BT_CONNECTION_LOST);
setBluetoothStatus(BT_STATUS.CONNECTION_LOST);
}
}
}
@@ -266,7 +253,7 @@ public class BluetoothIhealthHS3 extends BluetoothCommunication {
try {
btOutStream.write(bytes);
} catch (IOException e) {
setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "Error while writing to bluetooth socket " + e.getMessage());
setBluetoothStatus(BT_STATUS.UNEXPECTED_ERROR, "Error while writing to bluetooth socket " + e.getMessage());
}
}

View File

@@ -73,8 +73,8 @@ public class BluetoothInlife extends BluetoothCommunication {
}
@Override
protected boolean nextInitCmd(int stateNr) {
switch (stateNr) {
protected boolean onNextStep(int stepNr) {
switch (stepNr) {
case 0:
setNotificationOn(WEIGHT_MEASUREMENT_CHARACTERISTIC);
break;
@@ -98,16 +98,6 @@ public class BluetoothInlife extends BluetoothCommunication {
return true;
}
@Override
protected boolean nextBluetoothCmd(int stateNr) {
return false;
}
@Override
protected boolean nextCleanUpCmd(int stateNr) {
return false;
}
@Override
public void onBluetoothNotify(UUID characteristic, byte[] value) {
final byte[] data = value;
@@ -229,7 +219,7 @@ public class BluetoothInlife extends BluetoothCommunication {
measurement.setLbm(lbm);
measurement.setVisceralFat(clamp(visceral, 1, 50));
addScaleData(measurement);
addScaleMeasurement(measurement);
sendCommand(0xd4);
}

View File

@@ -82,8 +82,8 @@ public class BluetoothMGB extends BluetoothCommunication {
}
@Override
protected boolean nextInitCmd(int stateNr) {
switch (stateNr) {
protected boolean onNextStep(int stepNr) {
switch (stepNr) {
case 0:
setNotificationOn(uuid_char_ctrl
);
@@ -126,19 +126,6 @@ public class BluetoothMGB extends BluetoothCommunication {
return true;
}
@Override
protected boolean nextBluetoothCmd(int stateNr) {
return false;
}
@Override
protected boolean nextCleanUpCmd(int stateNr) {
return false;
}
@Override
public void onBluetoothNotify(UUID characteristic, byte[] value) {
packet_buf = value;
@@ -202,7 +189,7 @@ public class BluetoothMGB extends BluetoothCommunication {
popInt(); // unknown =02
popInt(); // unknown =47;48;4e;4b;42
addScaleData(measurement);
addScaleMeasurement(measurement);
// Visceral fat?
// Standard weight?

View File

@@ -51,13 +51,8 @@ public class BluetoothMedisanaBS44x extends BluetoothCommunication {
}
@Override
protected boolean nextInitCmd(int stateNr) {
return false;
}
@Override
protected boolean nextBluetoothCmd(int stateNr) {
switch (stateNr) {
protected boolean onNextStep(int stepNr) {
switch (stepNr) {
case 0:
// set indication on for feature characteristic
setIndicationOn(FEATURE_MEASUREMENT_CHARACTERISTIC);
@@ -92,12 +87,6 @@ public class BluetoothMedisanaBS44x extends BluetoothCommunication {
return true;
}
@Override
protected boolean nextCleanUpCmd(int stateNr) {
return false;
}
@Override
public void onBluetoothNotify(UUID characteristic, byte[] value) {
final byte[] data = value;
@@ -109,7 +98,7 @@ public class BluetoothMedisanaBS44x extends BluetoothCommunication {
if (characteristic.equals(FEATURE_MEASUREMENT_CHARACTERISTIC)) {
parseFeatureData(data);
addScaleData(btScaleMeasurement);
addScaleMeasurement(btScaleMeasurement);
}
}

View File

@@ -35,7 +35,7 @@ import java.util.UUID;
import timber.log.Timber;
import static com.health.openscale.core.bluetooth.BluetoothCommunication.BT_STATUS_CODE.BT_UNEXPECTED_ERROR;
import static com.health.openscale.core.bluetooth.BluetoothCommunication.BT_STATUS.UNEXPECTED_ERROR;
public class BluetoothMiScale extends BluetoothCommunication {
private final UUID WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC = UUID.fromString("00002a2f-0000-3512-2118-0009af100700");
@@ -60,11 +60,21 @@ public class BluetoothMiScale extends BluetoothCommunication {
int scaleMonth = (int) data[2];
int scaleDay = (int) data[3];
if (currentYear == scaleYear && currentMonth == scaleMonth && currentDay == scaleDay) {
setBtMachineState(BT_MACHINE_STATE.BT_CMD_STATE);
} else {
if (!(currentYear == scaleYear && currentMonth == scaleMonth && currentDay == scaleDay)) {
Timber.d("Current year and scale year is different");
nextMachineStateStep();
// 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);
}
}
@@ -76,7 +86,15 @@ public class BluetoothMiScale extends BluetoothCommunication {
// Stop command from mi scale received
if (data[0] == 0x03) {
setBtMachineState(BT_MACHINE_STATE.BT_CLEANUP_STATE);
// 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();
byte[] userIdentifier = new byte[]{(byte)0x04, (byte)0xFF, (byte)0xFF, (byte) ((uniqueNumber & 0xFF00) >> 8), (byte) ((uniqueNumber & 0xFF) >> 0)};
writeBytes(WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, userIdentifier);
resumeMachineState();
}
if (data.length == 20) {
@@ -95,82 +113,37 @@ public class BluetoothMiScale extends BluetoothCommunication {
@Override
protected boolean nextInitCmd(int stateNr) {
switch (stateNr) {
protected boolean onNextStep(int stepNr) {
switch (stepNr) {
case 0:
// read device time
readBytes(BluetoothGattUuid.CHARACTERISTIC_CURRENT_TIME);
break;
case 1:
// 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);
break;
case 2:
// 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);
break;
default:
return false;
}
return true;
}
@Override
protected boolean nextBluetoothCmd(int stateNr) {
switch (stateNr) {
case 0:
case 2:
// set notification on for weight measurement history
setNotificationOn(WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC);
break;
case 1:
case 3:
// set notification on for weight measurement
setNotificationOn(BluetoothGattUuid.CHARACTERISTIC_WEIGHT_MEASUREMENT);
break;
case 2:
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);
break;
case 3:
case 5:
// invoke receiving history data
writeBytes(WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, new byte[]{0x02});
break;
default:
return false;
}
return true;
}
@Override
protected boolean nextCleanUpCmd(int stateNr) {
switch (stateNr) {
case 0:
// send stop command to mi scale
writeBytes(WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, new byte[]{0x03});
break;
case 1:
// 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);
stopMachineState();
break;
default:
return false;
@@ -225,13 +198,13 @@ public class BluetoothMiScale extends BluetoothCommunication {
scaleBtData.setWeight(Converters.toKilogram(weight, selectedUser.getScaleUnit()));
scaleBtData.setDateTime(date_time);
addScaleData(scaleBtData);
addScaleMeasurement(scaleBtData);
} else {
Timber.e("Invalid Mi scale weight year %d", year);
}
}
} catch (ParseException e) {
setBtStatus(BT_UNEXPECTED_ERROR, "Error while decoding bluetooth date string (" + e.getMessage() + ")");
setBluetoothStatus(UNEXPECTED_ERROR, "Error while decoding bluetooth date string (" + e.getMessage() + ")");
}
}

View File

@@ -31,7 +31,6 @@ import com.health.openscale.gui.views.FatMeasurementView;
import com.health.openscale.gui.views.LBMMeasurementView;
import com.health.openscale.gui.views.MeasurementViewSettings;
import com.health.openscale.gui.views.WaterMeasurementView;
import com.polidea.rxandroidble2.RxBleClient;
import java.text.ParseException;
import java.text.SimpleDateFormat;
@@ -43,7 +42,7 @@ import java.util.UUID;
import timber.log.Timber;
import static com.health.openscale.core.bluetooth.BluetoothCommunication.BT_STATUS_CODE.BT_UNEXPECTED_ERROR;
import static com.health.openscale.core.bluetooth.BluetoothCommunication.BT_STATUS.UNEXPECTED_ERROR;
public class BluetoothMiScale2 extends BluetoothCommunication {
private final UUID WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC = UUID.fromString("00002a2f-0000-3512-2118-0009af100700");
@@ -69,7 +68,18 @@ public class BluetoothMiScale2 extends BluetoothCommunication {
// Stop command from mi scale received
if (data[0] == 0x03) {
setBtMachineState(BT_MACHINE_STATE.BT_CLEANUP_STATE);
Timber.d("Scale stop byte received");
// 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();
byte[] userIdentifier = new byte[]{(byte)0x04, (byte)0xFF, (byte)0xFF, (byte) ((uniqueNumber & 0xFF00) >> 8), (byte) ((uniqueNumber & 0xFF) >> 0)};
writeBytes(WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, userIdentifier);
disconnect();
resumeMachineState();
}
if (data.length == 26) {
@@ -88,8 +98,8 @@ public class BluetoothMiScale2 extends BluetoothCommunication {
@Override
protected boolean nextInitCmd(int stateNr) {
switch (stateNr) {
protected boolean onNextStep(int stepNr) {
switch (stepNr) {
case 0:
// set scale units
final ScaleUser selectedUser = OpenScale.getInstance().getSelectedScaleUser();
@@ -108,73 +118,23 @@ 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.CHARACTERISTIC_CURRENT_TIME, dateTimeByte);
break;
case 2:
// set notification on for weight measurement history
setNotificationOn(
WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC
);
setNotificationOn(WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC);
break;
default:
return false;
}
return true;
}
@Override
protected boolean nextBluetoothCmd(int stateNr) {
switch (stateNr) {
case 0:
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(WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, userIdentifier);
break;
case 1:
// set notification on for weight measurement history
setNotificationOn(
WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC
);
break;
case 2:
case 4:
// invoke receiving history data
writeBytes(
WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, new byte[]{0x02});
break;
default:
return false;
}
return true;
}
@Override
protected boolean nextCleanUpCmd(int stateNr) {
switch (stateNr) {
case 0:
// send stop command to mi scale
writeBytes(
WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, new byte[]{0x03});
break;
case 1:
// 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);
break;
case 2:
// set notification on for body composition measurement
setNotificationOn(
BluetoothGattUuid.CHARACTERISTIC_BODY_COMPOSITION_MEASUREMENT
);
writeBytes(WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, new byte[]{0x02});
stopMachineState();
break;
default:
return false;
@@ -239,13 +199,13 @@ public class BluetoothMiScale2 extends BluetoothCommunication {
EstimatedLBMMetric.FORMULA.valueOf(settings.getEstimationFormula()));
scaleBtData.setLbm(lbmMetric.getLBM(selectedUser, scaleBtData));
addScaleData(scaleBtData);
addScaleMeasurement(scaleBtData);
} else {
Timber.e("Invalid Mi scale weight year %d", year);
}
}
} catch (ParseException e) {
setBtStatus(BT_UNEXPECTED_ERROR, "Error while decoding bluetooth date string (" + e.getMessage() + ")");
setBluetoothStatus(UNEXPECTED_ERROR, "Error while decoding bluetooth date string (" + e.getMessage() + ")");
}
}

View File

@@ -47,12 +47,10 @@ public class BluetoothOneByone extends BluetoothCommunication {
}
@Override
protected boolean nextInitCmd(int stateNr) {
switch (stateNr) {
protected boolean onNextStep(int stepNr) {
switch (stepNr) {
case 0:
setNotificationOn(
WEIGHT_MEASUREMENT_CHARACTERISTIC_BODY_COMPOSITION
);
setNotificationOn(WEIGHT_MEASUREMENT_CHARACTERISTIC_BODY_COMPOSITION);
break;
case 1:
ScaleUser currentUser = OpenScale.getInstance().getSelectedScaleUser();
@@ -83,16 +81,6 @@ public class BluetoothOneByone extends BluetoothCommunication {
return true;
}
@Override
protected boolean nextBluetoothCmd(int stateNr) {
return false;
}
@Override
protected boolean nextCleanUpCmd(int stateNr) {
return false;
}
@Override
public void onBluetoothNotify(UUID characteristic, byte[] value) {
final byte[] data = value;
@@ -155,6 +143,6 @@ public class BluetoothOneByone extends BluetoothCommunication {
Timber.d("scale measurement [%s]", scaleBtData);
addScaleData(scaleBtData);
addScaleMeasurement(scaleBtData);
}
}

View File

@@ -84,13 +84,8 @@ public class BluetoothQNScale extends BluetoothCommunication {
}
@Override
protected boolean nextInitCmd(int stateNr) {
return false;
}
@Override
protected boolean nextBluetoothCmd(int stateNr) {
switch (stateNr) {
protected boolean onNextStep(int stepNr) {
switch (stepNr) {
case 0:
// set notification on for custom characteristic 1 (weight, time, and others)
setNotificationOn(CUSTOM1_MEASUREMENT_CHARACTERISTIC);
@@ -101,7 +96,7 @@ public class BluetoothQNScale extends BluetoothCommunication {
break;
case 2:
// write magicnumber 0x130915011000000042 to 0xffe3
byte[] ffe3magicBytes = new byte[] {(byte)0x13, (byte)0x09, (byte)0x15, (byte)0x01, (byte)0x10, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x42};
byte[] ffe3magicBytes = new byte[]{(byte) 0x13, (byte) 0x09, (byte) 0x15, (byte) 0x01, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x42};
writeBytes(CUSTOM3_MEASUREMENT_CHARACTERISTIC, ffe3magicBytes);
break;
case 3:
@@ -110,34 +105,23 @@ public class BluetoothQNScale extends BluetoothCommunication {
timestamp -= SCALE_UNIX_TIMESTAMP_OFFSET;
byte[] date = new byte[4];
Converters.toInt32Le(date, 0, timestamp);
byte[] timeMagicBytes = new byte[] {(byte)0x02, date[0], date[1], date[2], date[3]};
byte[] timeMagicBytes = new byte[]{(byte) 0x02, date[0], date[1], date[2], date[3]};
writeBytes(CUSTOM4_MEASUREMENT_CHARACTERISTIC, timeMagicBytes);
break;
case 4:
sendMessage(R.string.info_step_on_scale, 0);
break;
default:
return false;
}
return true;
}
@Override
protected boolean nextCleanUpCmd(int stateNr) {
switch (stateNr) {
case 0:
/*case 5:
// send stop command to scale (0x1f05151049)
writeBytes(CUSTOM3_MEASUREMENT_CHARACTERISTIC, new byte[]{(byte)0x1f, (byte)0x05, (byte)0x15, (byte)0x10, (byte)0x49});
break;
break;*/
default:
return false;
}
return true;
}
@Override
public void onBluetoothNotify(UUID characteristic, byte[] value) {
final byte[] data = value;
@@ -198,7 +182,7 @@ public class BluetoothQNScale extends BluetoothCommunication {
btScaleMeasurement.setMuscle(qnscalelib.getMuscle(weightKg, impedance));
btScaleMeasurement.setBone(qnscalelib.getBone(weightKg, impedance));
btScaleMeasurement.setWeight(weightKg);
addScaleData(btScaleMeasurement);
addScaleMeasurement(btScaleMeasurement);
}
}
}

View File

@@ -68,8 +68,8 @@ public class BluetoothSenssun extends BluetoothCommunication {
}
@Override
protected boolean nextInitCmd(int stateNr) {
switch (stateNr) {
protected boolean onNextStep(int stepNr) {
switch (stepNr) {
case 0:
setNotificationOn(WEIGHT_MEASUREMENT_CHARACTERISTIC);
break;
@@ -85,16 +85,6 @@ public class BluetoothSenssun extends BluetoothCommunication {
return true;
}
@Override
protected boolean nextBluetoothCmd(int stateNr) {
return false;
}
@Override
protected boolean nextCleanUpCmd(int stateNr) {
return false;
}
@Override
public void onBluetoothNotify(UUID characteristic, byte[] value) {
final byte[] data = value;
@@ -107,7 +97,7 @@ public class BluetoothSenssun extends BluetoothCommunication {
}
if (isBitSet(WeightFatMus,2) ) {
addScaleData(measurement);
addScaleMeasurement(measurement);
}
}

View File

@@ -92,7 +92,7 @@ public class BluetoothTrisaBodyAnalyze extends BluetoothCommunication {
* set-broadcast-id command, and should disconnect after the write succeeds.
*
* @see #onPasswordReceived
* @see #nextBluetoothCmd
* @see #onNextStep
*/
private boolean pairing = false;
@@ -119,13 +119,13 @@ public class BluetoothTrisaBodyAnalyze extends BluetoothCommunication {
}
@Override
protected boolean nextInitCmd(int stateNr) {
Timber.i("nextInitCmd(%d)", stateNr);
switch (stateNr) {
protected boolean onNextStep(int stepNr) {
Timber.i("onNextStep(%d)", stepNr);
switch (stepNr) {
case 0:
// Register for notifications of the measurement characteristic.
setIndicationOn(MEASUREMENT_CHARACTERISTIC_UUID);
return true; // more commands follow
break; // more commands follow
case 1:
// Register for notifications of the command upload characteristic.
//
@@ -133,40 +133,22 @@ public class BluetoothTrisaBodyAnalyze extends BluetoothCommunication {
// immediately after. This is important because we should be in the main state
// to handle pairing correctly.
setIndicationOn(UPLOAD_COMMAND_CHARACTERISTIC_UUID);
// falls through
default:
return false; // no more commands
}
}
@Override
protected boolean nextBluetoothCmd(int stateNr) {
Timber.i("nextBluetoothCmd(%d)", stateNr);
switch (stateNr) {
case 0:
default:
return false; // no more commands
case 1:
break;
case 2:
// This state is triggered by the write in onPasswordReceived()
if (pairing) {
pairing = false;
disconnect();
}
return false; // no more commands;
}
}
@Override
protected boolean nextCleanUpCmd(int stateNr) {
Timber.i("nextCleanUpCmd(%d)", stateNr);
switch (stateNr) {
case 0:
break;
case 3:
writeCommand(DOWNLOAD_INFORMATION_ENABLE_DISCONNECT_COMMAND);
// falls through
break;
default:
return false; // no more commands
}
return true;
}
@Override
@@ -247,7 +229,7 @@ public class BluetoothTrisaBodyAnalyze extends BluetoothCommunication {
return;
}
addScaleData(measurement);
addScaleMeasurement(measurement);
}
public ScaleMeasurement parseScaleMeasurementData(byte[] data, ScaleUser user) {

View File

@@ -52,8 +52,8 @@ public class BluetoothYunmaiSE_Mini extends BluetoothCommunication {
}
@Override
protected boolean nextInitCmd(int stateNr) {
switch (stateNr) {
protected boolean onNextStep(int stepNr) {
switch (stepNr) {
case 0:
byte[] userId = Converters.toInt16Be(getUniqueNumber());
@@ -82,8 +82,7 @@ public class BluetoothYunmaiSE_Mini extends BluetoothCommunication {
writeBytes(WEIGHT_CMD_CHARACTERISTIC, set_time);
break;
case 2:
setNotificationOn(WEIGHT_MEASUREMENT_CHARACTERISTIC
);
setNotificationOn(WEIGHT_MEASUREMENT_CHARACTERISTIC);
break;
case 3:
byte[] magic_bytes = new byte[]{(byte)0x0d, (byte)0x05, (byte)0x13, (byte)0x00, (byte)0x16};
@@ -97,16 +96,6 @@ public class BluetoothYunmaiSE_Mini extends BluetoothCommunication {
return true;
}
@Override
protected boolean nextBluetoothCmd(int stateNr) {
return false;
}
@Override
protected boolean nextCleanUpCmd(int stateNr) {
return false;
}
@Override
public void onBluetoothNotify(UUID characteristic, byte[] value) {
final byte[] data = value;
@@ -154,7 +143,7 @@ public class BluetoothYunmaiSE_Mini extends BluetoothCommunication {
Timber.d("scale measurement [%s]", scaleBtData);
}
addScaleData(scaleBtData);
addScaleMeasurement(scaleBtData);
}
private int getUniqueNumber() {

View File

@@ -154,8 +154,8 @@ public class MainActivity extends BaseAppCompatActivity
if(prefs.edit().putInt("launchCount", ++launchCount).commit()){
valueOfCountModified = true;
// ask the user once for feedback on the 30th app launch
if(launchCount == 30){
// ask the user once for feedback on the 15th app launch
if(launchCount == 15){
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(R.string.label_feedback_message_enjoying)
@@ -476,10 +476,10 @@ public class MainActivity extends BaseAppCompatActivity
@Override
public void handleMessage(Message msg) {
BluetoothCommunication.BT_STATUS_CODE btStatusCode = BluetoothCommunication.BT_STATUS_CODE.values()[msg.what];
BluetoothCommunication.BT_STATUS btStatus = BluetoothCommunication.BT_STATUS.values()[msg.what];
switch (btStatusCode) {
case BT_RETRIEVE_SCALE_DATA:
switch (btStatus) {
case RETRIEVE_SCALE_DATA:
setBluetoothStatusIcon(R.drawable.ic_bluetooth_connection_success);
ScaleMeasurement scaleBtData = (ScaleMeasurement) msg.obj;
@@ -496,42 +496,42 @@ public class MainActivity extends BaseAppCompatActivity
openScale.addScaleData(scaleBtData, true);
break;
case BT_INIT_PROCESS:
case INIT_PROCESS:
setBluetoothStatusIcon(R.drawable.ic_bluetooth_connection_success);
Toast.makeText(getApplicationContext(), getResources().getString(R.string.info_bluetooth_init), Toast.LENGTH_SHORT).show();
Timber.d("Bluetooth initializing");
break;
case BT_CONNECTION_LOST:
case CONNECTION_LOST:
setBluetoothStatusIcon(R.drawable.ic_bluetooth_connection_lost);
Toast.makeText(getApplicationContext(), getResources().getString(R.string.info_bluetooth_connection_lost), Toast.LENGTH_SHORT).show();
Timber.d("Bluetooth connection lost");
break;
case BT_NO_DEVICE_FOUND:
case NO_DEVICE_FOUND:
setBluetoothStatusIcon(R.drawable.ic_bluetooth_connection_lost);
Toast.makeText(getApplicationContext(), getResources().getString(R.string.info_bluetooth_no_device), Toast.LENGTH_SHORT).show();
Timber.e("No Bluetooth device found");
break;
case BT_CONNECTION_RETRYING:
case CONNECTION_RETRYING:
setBluetoothStatusIcon(R.drawable.ic_bluetooth_searching);
Toast.makeText(getApplicationContext(), getResources().getString(R.string.info_bluetooth_no_device_retrying), Toast.LENGTH_SHORT).show();
Timber.e("No Bluetooth device found retrying");
break;
case BT_CONNECTION_ESTABLISHED:
case CONNECTION_ESTABLISHED:
setBluetoothStatusIcon(R.drawable.ic_bluetooth_connection_success);
Toast.makeText(getApplicationContext(), getResources().getString(R.string.info_bluetooth_connection_successful), Toast.LENGTH_SHORT).show();
Timber.d("Bluetooth connection successful established");
break;
case BT_CONNECTION_DISCONNECT:
case CONNECTION_DISCONNECT:
setBluetoothStatusIcon(R.drawable.ic_bluetooth_connection_lost);
Toast.makeText(getApplicationContext(), getResources().getString(R.string.info_bluetooth_connection_disconnected), Toast.LENGTH_SHORT).show();
Timber.d("Bluetooth connection successful disconnected");
break;
case BT_UNEXPECTED_ERROR:
case UNEXPECTED_ERROR:
setBluetoothStatusIcon(R.drawable.ic_bluetooth_connection_lost);
Toast.makeText(getApplicationContext(), getResources().getString(R.string.info_bluetooth_connection_error) + ": " + msg.obj, Toast.LENGTH_SHORT).show();
Timber.e("Bluetooth unexpected error: %s", msg.obj);
break;
case BT_SCALE_MESSAGE:
case SCALE_MESSAGE:
String toastMessage = String.format(getResources().getString(msg.arg1), msg.obj);
Toast.makeText(getApplicationContext(), toastMessage, Toast.LENGTH_LONG).show();
break;

View File

@@ -31,7 +31,6 @@ import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
import android.widget.BaseAdapter;
import android.widget.EditText;
import android.widget.Toast;
import com.health.openscale.R;
@@ -289,8 +288,8 @@ public class BluetoothPreferences extends PreferenceFragment {
Handler btHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (BluetoothCommunication.BT_STATUS_CODE.values()[msg.what]) {
case BT_CONNECTION_LOST:
switch (BluetoothCommunication.BT_STATUS.values()[msg.what]) {
case CONNECTION_LOST:
OpenScale.getInstance().disconnectFromBluetoothDevice();
dialog.dismiss();
break;

View File

@@ -230,8 +230,9 @@ public class ChartMeasurementView extends LineChart {
}
private void setMeasurementList(List<ScaleMeasurement> measurementList) {
scaleMeasurementList = measurementList;
if (!measurementList.isEmpty()) {
scaleMeasurementList = measurementList;
lastMeasurement = measurementList.get(0);
Collections.reverse(measurementList);
firstMeasurement = measurementList.get(0);
@@ -409,12 +410,12 @@ public class ChartMeasurementView extends LineChart {
}
private void refresh() {
clear();
if (scaleMeasurementList.isEmpty()) {
return;
}
clear();
List<ILineDataSet> lineDataSets = new ArrayList<>();
ScaleMeasurement[] avgMeasurementList = averageScaleMeasurementList(scaleMeasurementList);