mirror of
https://github.com/oliexdev/openScale.git
synced 2025-08-17 14:10:54 +02:00
Basic support for Beurer BF105, BF600, BF850, BF950 and Silvercrest SBF77; (#753)
* added code to register a new user and use a consent code for an existing scale * BlueToothStandardWeightProfile: move access to SharedPreferences to wrapper functions; * BlueToothStandardWeightProfile: rename CHARACTERISTIC_USER_BIRTHDAY to CHARACTERISTIC_USER_DATE_OF_BIRTH to match Bluetooth documentation; * Drop unused import: java.util.GregorianCalendar; * BluetoothStandardWeightProfile: create new user if missing either consent code or scale user index; * BluetoothStandardWeightProfile: update user's initialWeight only for user that is being currently created; * BluetoothStandardWeightProfile: store user data after InitialWeight update; * BluetoothStandardWeightProfile: fix user ID detection for weight and body composition measurements; * BluetoothStandardWeightProfile: remove unused class variables; * BluetoothStandardWeightProfile: simplify scale user selection code; * BluetoothStandardWeightProfile: add wrapper to set user activity level when creating new scale user; * BluetoothStandardWeightProfile: add wrapper to request scale measurement; * BluetoothBeurerBF105: use base class methods to access SharedPreferences; * BluetoothStandardWeightProfile: use scale user index to set user id in measurement database; * BluetoothStandardWeightProfile: separate measurement parsing and storing; * basic Beurer BF600 support; Based on BluetoothBeurerBF105 by jensMF <jens.freudenthal@zoho.com>; * BluetoothStandardWeightProfile: Update state machine: use enum with meaningful names instead of hard-coded integers; * BluetoothBeurerBF600: fix new scale user creation. Birthday in BF600 consists only from date, no time fields. * BluetoothStandardWeightProfile: Add wrappers and SM states to request vendor-specific scale user-list; * BluetoothBeurerBF600: read vendor specific user list, based on BluetoothBeurerBF105 by jensMF <jens.freudenthal@zoho.com>; * BluetoothStandardWeightProfile: fix unbinding of userScaleIndex from app userId; * Implement dialog to select existing scale user and enter scale user pin/consent code. * BluetoothStandardWeightProfile: show warning if scale battery level below 10%; * BluetoothStandardWeightProfile: When creating new scale user: request user to step on scale barefoot; * Reconnect Bluetooth scale after user enters consent code or selects scale user; * BluetoothFactory: threat Beurer BF850 and BF950 the same as BF600; * BluetoothStandardWeightProfile: fix max stepNr warning message; * BluetoothStandardWeightProfile: Refactor onBluetoothNotify(): move handling of User Control Point to separate function; * BluetoothStandardWeightProfile: make debug messages about User Control Point more explicit; * BluetoothStandardWeightProfile: make debug messages about unhandled notifications more explicit; * BluetoothStandardWeightProfile: make decoding of weight and body composition measurements more verbose; * BluetoothStandardWeightProfile: merge previous and new measurement if new measurement lacks user id (and timestamp); Bluetooth scales usually implement both "Weight Scale Feature" and "Body Composition Feature". It seems that scale first transmits weight measurement (with user index and timestamp) and later transmits body composition measurement (without user index and timestamp). If previous measurement contains user index and new measurements does not then merge them and store as one. NOTE: On scales that implement only Weight Scale feature (or only Body Composition feature) this commit will cause last measurement transmitted to be delayed until disconnect. * BluetoothStandardWeightProfile: Calculate lean body mass and bone mass; Thanks to Adam Serbinski <adam@serbinski.com> for providing formulas! * BluetoothStandardWeightProfile: remove redundant constant; * BluetoothBeurerBF600: cosmetic: fix newline; * BluetoothGattUuidBF600: show scale user initials (if set) instead of sale user index; * BluetoothSwpSBF77 experimental support for SBF77 based on BF600 and BF105; * BluetoothCommunication: Stop state machine only if setNotification operation was enqueued; * BluetoothStandardWeightProfile: avoid hang if battery service is not implemented by connected device; * BluetoothFactory: treat BF950 like SBF77; * BluetoothSwpSBF77: show scale user initials (if set) instead of sale user index; * BluetoothStandardWeightProfile: reuse common method to decode vendor specific scale user data; * BluetoothBeurerBF600 and BluetoothSwpSBF77: show debug message if setNotifyVendorSpecificUserList() passed or failed; * BluetoothSwpSBF77: Add UUID to send user initials to BF960; * BluetoothSwpSBF77: send user activity level to scale (Beurer BF950 and SBF77); * BluetoothSwpSBF77: Implement request to perform measurement (Beurer BF950 and SBF77); * BluetoothSwpSBF77: cosmetic: fix white-space; * update Blesses library version to 2.0.11 * usage of default scale user constructor * move custom scale characteristics into their scale classes * usage of string resources instead of hard coded strings * BluetoothStandardWeightProfile: Fix writeBirthday() method according to Bluetooth GAAT_Specification_Supplement_v4 chapter 3.59.2; Remove writeBirthday() overrides in classes that extends BluetoothStandardWeightProfile; * BluetoothBeurerBF105: reuse state-machine and helper functions from BluetoothStandardWeightProfile; * BluetoothBeurerBF105: use 0 instead of 1 when requesting user list; Vendor application also seems to be using 0 (according to btsnoop_hci.log from BF105); * BluetoothStandardWeightProfile: move user initials helper functions to base class; * BluetoothCommunication: fix needReConnect() function; Was broken by: update Blesses library to version to 2.0.11 (95e1d1298b
); * BluetoothBeurerBF105: move call to writeInitials() to base class; * BluetoothBeurerBF600: write user initials if specific characteristic exists on scale; Works on Beurer BF850. On Beurer BF600 there is no user initials; * BluetoothSwpSBF77: write user initials if specific characteristic exists on scale; Should work on Silvercrest SBF77 and Beurer BF950; * BluetoothStandardWeightProfile: use resource strings instead of hard coded strings * BluetoothStandardWeightProfile and BluetoothCommunication: catch IllegalArgumentException in setNotificationOn(); It seems that blessed library 2.0.11 (95e1d1298b
) returns non-null object from getCharacteristic() even if characteristic is not available and then trows exception later; * BluetoothStandardWeightProfile: fix crash when user name starts with space character; * BluetoothStandardWeightProfile: fix crash when user name contains two words with two or more spaces between them; * BluetoothSwpSBF77: prepare to merge BluetoothSwpSBF77 and BluetoothBeurerBF105; Change function order in file to reduce diff; * BluetoothSwpSBF77: cosmetic code formatting change; * BluetoothStandardWeightProfile: stop state-machine after requesting scale user list; Co-authored-by: jensMF <jens.freudenthal@zoho.com> * BluetoothStandardWeightProfile: remove doubled call; Co-authored-by: jensMF <jens.freudenthal@zoho.com> * BluetoothStandardWeightProfile: make this class abstract and make stub functions abstract also; * BluetoothStandardWeightProfile: convert VENDOR_SPECIFIC_MAX_USERS into abstract function; * Refactor: Rename BluetoothSwpSBF77 to BluetoothBeurerBF950; * BluetoothBeurerBF950: use BluetoothBeurerBF105 as base class; Affects BF950 and SBF77; * BluetoothFactory: remove commented code about BluetoothStandardWeightProfile; All mentioned scales have proper classes implemented; * BluetoothStandardWeightProfile: fix bug: app requests to select scale user on each connection if scale have maximum number of users; * update libraries to newest version and added some comments Co-authored-by: jensMF <jens.freudenthal@zoho.com> Co-authored-by: oliexdev <olie.xdev@googlemail.com>
This commit is contained in:
@@ -2,12 +2,12 @@ apply plugin: 'com.android.application'
|
||||
apply plugin: "androidx.navigation.safeargs"
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
compileSdkVersion 31
|
||||
defaultConfig {
|
||||
applicationId "com.health.openscale"
|
||||
testApplicationId "com.health.openscale.test"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
targetSdkVersion 31
|
||||
versionCode 54
|
||||
versionName "2.3.5"
|
||||
|
||||
@@ -132,11 +132,11 @@ android {
|
||||
dependencies {
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
|
||||
implementation 'com.google.android.material:material:1.5.0-alpha02'
|
||||
implementation 'com.google.android.material:material:1.5.0-alpha04'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.2.1'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
|
||||
implementation 'androidx.preference:preference:1.1.1'
|
||||
implementation 'androidx.navigation:navigation-fragment:2.3.5'
|
||||
implementation 'androidx.navigation:navigation-ui:2.3.5'
|
||||
@@ -148,7 +148,7 @@ dependencies {
|
||||
// Simple CSV
|
||||
implementation 'com.j256.simplecsv:simplecsv:2.6'
|
||||
// Blessed Android
|
||||
implementation 'com.github.weliem:blessed-android:1.30'
|
||||
implementation 'com.github.weliem:blessed-android:2.1.0'
|
||||
// CustomActivityOnCrash
|
||||
implementation 'cat.ereza:customactivityoncrash:2.3.0'
|
||||
// AppIntro
|
||||
@@ -159,7 +159,7 @@ dependencies {
|
||||
annotationProcessor 'androidx.room:room-compiler:2.3.0'
|
||||
androidTestImplementation 'androidx.room:room-testing:2.3.0'
|
||||
// Timber
|
||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||
implementation 'com.jakewharton.timber:timber:5.0.1'
|
||||
// Local unit tests
|
||||
testImplementation 'junit:junit:4.13.1'
|
||||
// Instrumented unit tests
|
||||
|
@@ -641,6 +641,22 @@ public class OpenScale {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean setBluetoothDeviceUserIndex(int appUserId, int scaleUserIndex, Handler uiHandler) {
|
||||
if (btDeviceDriver == null) {
|
||||
return false;
|
||||
}
|
||||
btDeviceDriver.selectScaleUserIndexForAppUserId(appUserId, scaleUserIndex, uiHandler);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean setBluetoothDeviceUserConsent(int appUserId, int scaleUserConsent, Handler uiHandler) {
|
||||
if (btDeviceDriver == null) {
|
||||
return false;
|
||||
}
|
||||
btDeviceDriver.setScaleUserConsent(appUserId, scaleUserConsent, uiHandler);
|
||||
return true;
|
||||
}
|
||||
|
||||
public LiveData<List<ScaleMeasurement>> getScaleMeasurementsLiveData() {
|
||||
int selectedUserId = getSelectedScaleUserId();
|
||||
|
||||
|
@@ -0,0 +1,138 @@
|
||||
/* 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 static com.welie.blessed.BluetoothBytesParser.FORMAT_UINT16;
|
||||
import static com.welie.blessed.BluetoothBytesParser.FORMAT_UINT8;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.welie.blessed.BluetoothBytesParser;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class BluetoothBeurerBF105 extends BluetoothStandardWeightProfile {
|
||||
private static final UUID SERVICE_BF105_CUSTOM = BluetoothGattUuid.fromShortCode(0xffff);
|
||||
private static final UUID SERVICE_BF105_IMG = BluetoothGattUuid.fromShortCode(0xffc0);
|
||||
|
||||
private static final UUID CHARACTERISTIC_SCALE_SETTINGS = BluetoothGattUuid.fromShortCode(0x0000);
|
||||
private static final UUID CHARACTERISTIC_USER_LIST = BluetoothGattUuid.fromShortCode(0x0001);
|
||||
private static final UUID CHARACTERISTIC_INITIALS = BluetoothGattUuid.fromShortCode(0x0002);
|
||||
private static final UUID CHARACTERISTIC_TARGET_WEIGHT = BluetoothGattUuid.fromShortCode(0x0003);
|
||||
private static final UUID CHARACTERISTIC_ACTIVITY_LEVEL = BluetoothGattUuid.fromShortCode(0x0004);
|
||||
private static final UUID CHARACTERISTIC_REFER_WEIGHT_BF = BluetoothGattUuid.fromShortCode(0x000b);
|
||||
private static final UUID CHARACTERISTIC_BT_MODULE = BluetoothGattUuid.fromShortCode(0x0005);
|
||||
private static final UUID CHARACTERISTIC_TAKE_MEASUREMENT = BluetoothGattUuid.fromShortCode(0x0006);
|
||||
private static final UUID CHARACTERISTIC_TAKE_GUEST_MEASUREMENT = BluetoothGattUuid.fromShortCode(0x0007);
|
||||
private static final UUID CHARACTERISTIC_BEURER_I = BluetoothGattUuid.fromShortCode(0x0008);
|
||||
private static final UUID CHARACTERISTIC_UPPER_LOWER_BODY = CHARACTERISTIC_BEURER_I;
|
||||
private static final UUID CHARACTERISTIC_BEURER_II = BluetoothGattUuid.fromShortCode(0x0009);
|
||||
private static final UUID CHARACTERISTIC_BEURER_III = BluetoothGattUuid.fromShortCode(0x000a);
|
||||
private static final UUID CHARACTERISTIC_IMG_IDENTIFY = BluetoothGattUuid.fromShortCode(0xffc1);
|
||||
private static final UUID CHARACTERISTIC_IMG_BLOCK = BluetoothGattUuid.fromShortCode(0xffc2);
|
||||
|
||||
|
||||
public BluetoothBeurerBF105(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String driverName() {
|
||||
return "Beurer BF105";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getVendorSpecificMaxUserCount() {
|
||||
return 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeUserDataToScale() {
|
||||
writeTargetWeight();
|
||||
super.writeUserDataToScale();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBluetoothNotify(UUID characteristic, byte[] value) {
|
||||
if (characteristic.equals(CHARACTERISTIC_USER_LIST)) {
|
||||
handleVendorSpecificUserList(value);
|
||||
}
|
||||
else {
|
||||
super.onBluetoothNotify(characteristic, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setNotifyVendorSpecificUserList() {
|
||||
if (setNotificationOn(SERVICE_BF105_CUSTOM, CHARACTERISTIC_USER_LIST)) {
|
||||
Timber.d("setNotifyVendorSpecificUserList() OK");
|
||||
}
|
||||
else {
|
||||
Timber.d("setNotifyVendorSpecificUserList() FAILED");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void requestVendorSpecificUserList() {
|
||||
BluetoothBytesParser parser = new BluetoothBytesParser();
|
||||
parser.setIntValue(0, FORMAT_UINT8);
|
||||
writeBytes(SERVICE_BF105_CUSTOM, CHARACTERISTIC_USER_LIST,
|
||||
parser.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeActivityLevel() {
|
||||
BluetoothBytesParser parser = new BluetoothBytesParser();
|
||||
int activityLevel = this.selectedUser.getActivityLevel().toInt() + 1;
|
||||
Timber.d(String.format("activityLevel: %d", activityLevel));
|
||||
parser.setIntValue(activityLevel, FORMAT_UINT8);
|
||||
writeBytes(SERVICE_BF105_CUSTOM, CHARACTERISTIC_ACTIVITY_LEVEL,
|
||||
parser.getValue());
|
||||
}
|
||||
|
||||
protected void writeTargetWeight() {
|
||||
BluetoothBytesParser parser = new BluetoothBytesParser();
|
||||
int targetWeight = (int) this.selectedUser.getGoalWeight();
|
||||
parser.setIntValue(targetWeight, FORMAT_UINT16);
|
||||
writeBytes(SERVICE_BF105_CUSTOM, CHARACTERISTIC_TARGET_WEIGHT,
|
||||
parser.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeInitials() {
|
||||
BluetoothBytesParser parser = new BluetoothBytesParser();
|
||||
String initials = getInitials(this.selectedUser.getUserName());
|
||||
Timber.d("Initials: " + initials);
|
||||
parser.setString(initials);
|
||||
writeBytes(SERVICE_BF105_CUSTOM, CHARACTERISTIC_INITIALS,
|
||||
parser.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void requestMeasurement() {
|
||||
BluetoothBytesParser parser = new BluetoothBytesParser();
|
||||
parser.setIntValue(0, FORMAT_UINT8);
|
||||
writeBytes(SERVICE_BF105_CUSTOM, CHARACTERISTIC_TAKE_MEASUREMENT,
|
||||
parser.getValue());
|
||||
}
|
||||
}
|
@@ -0,0 +1,119 @@
|
||||
/* 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 static com.welie.blessed.BluetoothBytesParser.FORMAT_UINT8;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.health.openscale.core.utils.Converters;
|
||||
import com.welie.blessed.BluetoothBytesParser;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class BluetoothBeurerBF600 extends BluetoothStandardWeightProfile {
|
||||
private static final UUID SERVICE_BEURER_CUSTOM_BF600 = BluetoothGattUuid.fromShortCode(0xfff0);
|
||||
private static final UUID CHARACTERISTIC_BEURER_BF600_SCALE_SETTING = BluetoothGattUuid.fromShortCode(0xfff1);
|
||||
private static final UUID CHARACTERISTIC_BEURER_BF600_USER_LIST = BluetoothGattUuid.fromShortCode(0xfff2);
|
||||
private static final UUID CHARACTERISTIC_BEURER_BF600_ACTIVITY_LEVEL = BluetoothGattUuid.fromShortCode(0xfff3);
|
||||
private static final UUID CHARACTERISTIC_BEURER_BF600_TAKE_MEASUREMENT = BluetoothGattUuid.fromShortCode(0xfff4);
|
||||
private static final UUID CHARACTERISTIC_BEURER_BF600_REFER_WEIGHT_BF = BluetoothGattUuid.fromShortCode(0xfff5);
|
||||
private static final UUID CHARACTERISTIC_BEURER_BF850_INITIALS = BluetoothGattUuid.fromShortCode(0xfff6);
|
||||
|
||||
private String deviceName;
|
||||
|
||||
public BluetoothBeurerBF600(Context context, String name) {
|
||||
super(context);
|
||||
deviceName = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String driverName() {
|
||||
return "Beurer " + deviceName;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getVendorSpecificMaxUserCount() {
|
||||
return 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeActivityLevel() {
|
||||
Converters.ActivityLevel al = selectedUser.getActivityLevel();
|
||||
BluetoothBytesParser parser = new BluetoothBytesParser(new byte[]{0});
|
||||
parser.setIntValue(al.toInt() + 1, FORMAT_UINT8, 0);
|
||||
Timber.d(String.format("setCurrentUserData Activity level: %d", al.toInt() + 1));
|
||||
writeBytes(SERVICE_BEURER_CUSTOM_BF600,
|
||||
CHARACTERISTIC_BEURER_BF600_ACTIVITY_LEVEL, parser.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeInitials() {
|
||||
if (haveCharacteristic(SERVICE_BEURER_CUSTOM_BF600, CHARACTERISTIC_BEURER_BF850_INITIALS)) {
|
||||
BluetoothBytesParser parser = new BluetoothBytesParser();
|
||||
String initials = getInitials(this.selectedUser.getUserName());
|
||||
Timber.d("Initials: " + initials);
|
||||
parser.setString(initials);
|
||||
writeBytes(SERVICE_BEURER_CUSTOM_BF600, CHARACTERISTIC_BEURER_BF850_INITIALS,
|
||||
parser.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void requestMeasurement() {
|
||||
BluetoothBytesParser parser = new BluetoothBytesParser(new byte[]{0});
|
||||
parser.setIntValue(0x00, FORMAT_UINT8, 0);
|
||||
Timber.d(String.format("requestMeasurement BEURER 0xFFF4 magic: 0x00"));
|
||||
writeBytes(SERVICE_BEURER_CUSTOM_BF600,
|
||||
CHARACTERISTIC_BEURER_BF600_TAKE_MEASUREMENT, parser.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setNotifyVendorSpecificUserList() {
|
||||
if (setNotificationOn(SERVICE_BEURER_CUSTOM_BF600,
|
||||
CHARACTERISTIC_BEURER_BF600_USER_LIST)) {
|
||||
Timber.d("setNotifyVendorSpecificUserList() OK");
|
||||
}
|
||||
else {
|
||||
Timber.d("setNotifyVendorSpecificUserList() FAILED");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void requestVendorSpecificUserList() {
|
||||
BluetoothBytesParser parser = new BluetoothBytesParser();
|
||||
parser.setIntValue(0x00, FORMAT_UINT8);
|
||||
writeBytes(SERVICE_BEURER_CUSTOM_BF600, CHARACTERISTIC_BEURER_BF600_USER_LIST,
|
||||
parser.getValue());
|
||||
stopMachineState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBluetoothNotify(UUID characteristic, byte[] value) {
|
||||
if (characteristic.equals(CHARACTERISTIC_BEURER_BF600_USER_LIST)) {
|
||||
handleVendorSpecificUserList(value);
|
||||
}
|
||||
else {
|
||||
super.onBluetoothNotify(characteristic, value);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
/* 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 timber.log.Timber;
|
||||
|
||||
public class BluetoothBeurerBF950 extends BluetoothBeurerBF105 {
|
||||
private String deviceName;
|
||||
|
||||
public BluetoothBeurerBF950(Context context, String name) {
|
||||
super(context);
|
||||
deviceName = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String driverName() {
|
||||
return deviceName;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getVendorSpecificMaxUserCount() {
|
||||
return 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeTargetWeight() {
|
||||
Timber.d("Target Weight not supported on " + deviceName);
|
||||
}
|
||||
}
|
@@ -16,6 +16,9 @@
|
||||
|
||||
package com.health.openscale.core.bluetooth;
|
||||
|
||||
import static android.bluetooth.BluetoothGatt.GATT_SUCCESS;
|
||||
import static android.content.Context.LOCATION_SERVICE;
|
||||
|
||||
import android.Manifest;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.bluetooth.le.ScanResult;
|
||||
@@ -29,20 +32,19 @@ import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.health.openscale.R;
|
||||
import com.health.openscale.core.datatypes.ScaleMeasurement;
|
||||
import com.welie.blessed.BluetoothCentral;
|
||||
import com.welie.blessed.BluetoothCentralCallback;
|
||||
import com.welie.blessed.BluetoothCentralManager;
|
||||
import com.welie.blessed.BluetoothCentralManagerCallback;
|
||||
import com.welie.blessed.BluetoothPeripheral;
|
||||
import com.welie.blessed.BluetoothPeripheralCallback;
|
||||
import com.welie.blessed.ConnectionState;
|
||||
import com.welie.blessed.GattStatus;
|
||||
import com.welie.blessed.HciStatus;
|
||||
import com.welie.blessed.WriteType;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
import static android.bluetooth.BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT;
|
||||
import static android.bluetooth.BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE;
|
||||
import static android.content.Context.LOCATION_SERVICE;
|
||||
import static com.welie.blessed.BluetoothPeripheral.GATT_SUCCESS;
|
||||
|
||||
public abstract class BluetoothCommunication {
|
||||
public enum BT_STATUS {
|
||||
RETRIEVE_SCALE_DATA,
|
||||
@@ -53,7 +55,9 @@ public abstract class BluetoothCommunication {
|
||||
CONNECTION_LOST,
|
||||
NO_DEVICE_FOUND,
|
||||
UNEXPECTED_ERROR,
|
||||
SCALE_MESSAGE
|
||||
SCALE_MESSAGE,
|
||||
CHOOSE_SCALE_USER,
|
||||
ENTER_SCALE_USER_CONSENT,
|
||||
}
|
||||
|
||||
private int stepNr;
|
||||
@@ -64,7 +68,7 @@ public abstract class BluetoothCommunication {
|
||||
private Handler callbackBtHandler;
|
||||
private Handler disconnectHandler;
|
||||
|
||||
private BluetoothCentral central;
|
||||
private BluetoothCentralManager central;
|
||||
private BluetoothPeripheral btPeripheral;
|
||||
|
||||
public BluetoothCommunication(Context context)
|
||||
@@ -73,7 +77,20 @@ public abstract class BluetoothCommunication {
|
||||
this.disconnectHandler = new Handler();
|
||||
this.stepNr = 0;
|
||||
this.stopped = false;
|
||||
this.central = new BluetoothCentral(context, bluetoothCentralCallback, new Handler(Looper.getMainLooper()));
|
||||
this.central = new BluetoothCentralManager(context, bluetoothCentralCallback, new Handler(Looper.getMainLooper()));
|
||||
}
|
||||
|
||||
protected boolean needReConnect() {
|
||||
if (callbackBtHandler == null) {
|
||||
return true;
|
||||
}
|
||||
if (btPeripheral != null) {
|
||||
ConnectionState state = btPeripheral.getState();
|
||||
if (state.equals(ConnectionState.CONNECTED) || state.equals(ConnectionState.CONNECTING)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,6 +136,20 @@ public abstract class BluetoothCommunication {
|
||||
}
|
||||
}
|
||||
|
||||
protected void chooseScaleUserUi(Object userList) {
|
||||
if (callbackBtHandler != null) {
|
||||
callbackBtHandler.obtainMessage(
|
||||
BT_STATUS.CHOOSE_SCALE_USER.ordinal(), userList).sendToTarget();
|
||||
}
|
||||
}
|
||||
|
||||
protected void enterScaleUserConsentUi(int appScaleUserId, int scaleUserIndex) {
|
||||
if (callbackBtHandler != null) {
|
||||
callbackBtHandler.obtainMessage(
|
||||
BT_STATUS.ENTER_SCALE_USER_CONSENT.ordinal(), appScaleUserId, scaleUserIndex).sendToTarget();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message to openScale user
|
||||
*
|
||||
@@ -162,11 +193,17 @@ public abstract class BluetoothCommunication {
|
||||
*/
|
||||
protected void onBluetoothDiscovery(BluetoothPeripheral peripheral) { }
|
||||
|
||||
/**
|
||||
* Stopped current state machine
|
||||
*/
|
||||
protected synchronized void stopMachineState() {
|
||||
Timber.d("Stop machine state");
|
||||
stopped = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* resume current state machine
|
||||
*/
|
||||
protected synchronized void resumeMachineState() {
|
||||
Timber.d("Resume machine state");
|
||||
stopped = false;
|
||||
@@ -190,11 +227,23 @@ public abstract class BluetoothCommunication {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function jump to a specific step number
|
||||
* @param nr the step number which the state machine should jump to.
|
||||
*/
|
||||
protected synchronized void jumpNextToStepNr(int nr) {
|
||||
Timber.d("Jump next to step nr " + nr);
|
||||
stepNr = nr;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function return the current step number
|
||||
* @return the current step number
|
||||
*/
|
||||
protected synchronized int getStepNr() {
|
||||
return stepNr;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function jumps to the step newStepNr only if the current step equals curStepNr,
|
||||
* i.e. if the next step (stepNr) is 1 above curStepNr
|
||||
@@ -221,6 +270,17 @@ public abstract class BluetoothCommunication {
|
||||
Timber.d("Jumped back one step to " + stepNr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if specific characteristic exists on Bluetooth device.
|
||||
*
|
||||
* @param service the Bluetooth UUID service
|
||||
* @param characteristic the Bluetooth UUID characteristic
|
||||
* @return true if characteristic exists
|
||||
*/
|
||||
protected boolean haveCharacteristic(UUID service, UUID characteristic) {
|
||||
return btPeripheral.getCharacteristic(service, characteristic) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a byte array to a Bluetooth device.
|
||||
*
|
||||
@@ -241,7 +301,7 @@ public abstract class BluetoothCommunication {
|
||||
protected void writeBytes(UUID service, UUID characteristic, byte[] bytes, boolean noResponse) {
|
||||
Timber.d("Invoke write bytes [" + byteInHex(bytes) + "] on " + BluetoothGattUuid.prettyPrint(characteristic));
|
||||
btPeripheral.writeCharacteristic(btPeripheral.getCharacteristic(service, characteristic), bytes,
|
||||
noResponse ? WRITE_TYPE_NO_RESPONSE : WRITE_TYPE_DEFAULT);
|
||||
noResponse ? WriteType.WITHOUT_RESPONSE : WriteType.WITH_RESPONSE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -274,14 +334,27 @@ public abstract class BluetoothCommunication {
|
||||
* Set notification flag on for the Bluetooth device.
|
||||
*
|
||||
* @param characteristic the Bluetooth UUID characteristic
|
||||
* @return true if the operation was enqueued, false if the characteristic doesn't support notification or indications or
|
||||
*/
|
||||
protected void setNotificationOn(UUID service, UUID characteristic) {
|
||||
protected boolean setNotificationOn(UUID service, UUID characteristic) {
|
||||
Timber.d("Invoke set notification on " + BluetoothGattUuid.prettyPrint(characteristic));
|
||||
if(btPeripheral.getService(service) != null) {
|
||||
stopMachineState();
|
||||
BluetoothGattCharacteristic currentTimeCharacteristic = btPeripheral.getCharacteristic(service, characteristic);
|
||||
btPeripheral.setNotify(currentTimeCharacteristic, true);
|
||||
if (currentTimeCharacteristic != null) {
|
||||
boolean notifySet;
|
||||
try {
|
||||
notifySet = btPeripheral.setNotify(currentTimeCharacteristic, true);
|
||||
}
|
||||
catch (IllegalArgumentException e){
|
||||
notifySet = false;
|
||||
};
|
||||
if (notifySet) {
|
||||
stopMachineState();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -303,6 +376,14 @@ public abstract class BluetoothCommunication {
|
||||
disconnectHandler.removeCallbacksAndMessages(null);
|
||||
}
|
||||
|
||||
public void selectScaleUserIndexForAppUserId(int appUserId, int scaleUserIndex, Handler uiHandler) {
|
||||
Timber.d("Set scale user index for app user id: Not implemented!");
|
||||
}
|
||||
|
||||
public void setScaleUserConsent(int appUserId, int scaleUserConsent, Handler uiHandler) {
|
||||
Timber.d("Set scale user consent for app user id: Not implemented!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a byte array to hex for debugging purpose
|
||||
*
|
||||
@@ -373,8 +454,8 @@ public abstract class BluetoothCommunication {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationStateUpdate(BluetoothPeripheral peripheral, BluetoothGattCharacteristic characteristic, int status) {
|
||||
if( status == GATT_SUCCESS) {
|
||||
public void onNotificationStateUpdate(BluetoothPeripheral peripheral, BluetoothGattCharacteristic characteristic, GattStatus status) {
|
||||
if( status.value == GATT_SUCCESS) {
|
||||
if(peripheral.isNotifying(characteristic)) {
|
||||
Timber.d(String.format("SUCCESS: Notify set for %s", characteristic.getUuid()));
|
||||
resumeMachineState();
|
||||
@@ -385,8 +466,8 @@ public abstract class BluetoothCommunication {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicWrite(BluetoothPeripheral peripheral, byte[] value, BluetoothGattCharacteristic characteristic, int status) {
|
||||
if( status == GATT_SUCCESS) {
|
||||
public void onCharacteristicWrite(BluetoothPeripheral peripheral, byte[] value, BluetoothGattCharacteristic characteristic, GattStatus status) {
|
||||
if( status.value == GATT_SUCCESS) {
|
||||
Timber.d(String.format("SUCCESS: Writing <%s> to <%s>", byteInHex(value), characteristic.getUuid().toString()));
|
||||
nextMachineStep();
|
||||
|
||||
@@ -396,14 +477,14 @@ public abstract class BluetoothCommunication {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicUpdate(final BluetoothPeripheral peripheral, byte[] value, final BluetoothGattCharacteristic characteristic, final int status) {
|
||||
public void onCharacteristicUpdate(final BluetoothPeripheral peripheral, byte[] value, final BluetoothGattCharacteristic characteristic, GattStatus status) {
|
||||
resetDisconnectTimer();
|
||||
onBluetoothNotify(characteristic.getUuid(), value);
|
||||
}
|
||||
};
|
||||
|
||||
// Callback for central
|
||||
private final BluetoothCentralCallback bluetoothCentralCallback = new BluetoothCentralCallback() {
|
||||
private final BluetoothCentralManagerCallback bluetoothCentralCallback = new BluetoothCentralManagerCallback() {
|
||||
|
||||
@Override
|
||||
public void onConnectedPeripheral(BluetoothPeripheral peripheral) {
|
||||
@@ -415,18 +496,18 @@ public abstract class BluetoothCommunication {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionFailed(BluetoothPeripheral peripheral, final int status) {
|
||||
Timber.e(String.format("connection '%s' failed with status %d", peripheral.getName(), status ));
|
||||
public void onConnectionFailed(BluetoothPeripheral peripheral, HciStatus status) {
|
||||
Timber.e(String.format("connection '%s' failed with status %d", peripheral.getName(), status.value));
|
||||
setBluetoothStatus(BT_STATUS.CONNECTION_LOST);
|
||||
|
||||
if (status == 8) {
|
||||
if (status.value == 8) {
|
||||
sendMessage(R.string.info_bluetooth_connection_error_scale_offline, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnectedPeripheral(final BluetoothPeripheral peripheral, final int status) {
|
||||
Timber.d(String.format("disconnected '%s' with status %d", peripheral.getName(), status));
|
||||
public void onDisconnectedPeripheral(final BluetoothPeripheral peripheral, HciStatus status) {
|
||||
Timber.d(String.format("disconnected '%s' with status %d", peripheral.getName(), status.value));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -482,6 +563,20 @@ public abstract class BluetoothCommunication {
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
protected boolean reConnectPreviousPeripheral(Handler uiHandler) {
|
||||
if (btPeripheral == null) {
|
||||
return false;
|
||||
}
|
||||
if (btPeripheral.getState() != ConnectionState.DISCONNECTED) {
|
||||
disconnect();
|
||||
}
|
||||
if (callbackBtHandler == null) {
|
||||
registerCallbackHandler(uiHandler);
|
||||
}
|
||||
connect(btPeripheral.getAddress());
|
||||
return true;
|
||||
}
|
||||
|
||||
private void resetDisconnectTimer() {
|
||||
disconnectHandler.removeCallbacksAndMessages(null);
|
||||
disconnectWithDelay();
|
||||
|
@@ -42,14 +42,6 @@ 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);
|
||||
}
|
||||
@@ -122,6 +114,15 @@ public class BluetoothFactory {
|
||||
if (deviceName.equals("ADV")) {
|
||||
return new BluetoothOKOK(context);
|
||||
}
|
||||
if (deviceName.equals("BF105")) {
|
||||
return new BluetoothBeurerBF105(context);
|
||||
}
|
||||
if (deviceName.equals("BF600") || deviceName.equals("BF850")) {
|
||||
return new BluetoothBeurerBF600(context, deviceName);
|
||||
}
|
||||
if (deviceName.equals("SBF77") || deviceName.equals("BF950")) {
|
||||
return new BluetoothBeurerBF950(context, deviceName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@@ -93,6 +93,7 @@ public class BluetoothGattUuid {
|
||||
public static final UUID CHARACTERISTIC_CHANGE_INCREMENT = fromShortCode(0x2a99);
|
||||
public static final UUID CHARACTERISTIC_USER_CONTROL_POINT = fromShortCode(0x2A9F);
|
||||
public static final UUID CHARACTERISTIC_USER_AGE = fromShortCode(0x2A80);
|
||||
public static final UUID CHARACTERISTIC_USER_DATE_OF_BIRTH = fromShortCode(0x2A85);
|
||||
public static final UUID CHARACTERISTIC_USER_GENDER = fromShortCode(0x2A8C);
|
||||
public static final UUID CHARACTERISTIC_USER_HEIGHT = fromShortCode(0x2A8E);
|
||||
|
||||
|
@@ -1,15 +1,15 @@
|
||||
package com.health.openscale.core.bluetooth;
|
||||
|
||||
import android.bluetooth.le.ScanFilter;
|
||||
import android.bluetooth.le.ScanResult;
|
||||
import android.content.Context;
|
||||
import android.bluetooth.le.ScanFilter;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.health.openscale.core.datatypes.ScaleMeasurement;
|
||||
import com.welie.blessed.BluetoothCentral;
|
||||
import com.welie.blessed.BluetoothCentralCallback;
|
||||
import com.welie.blessed.BluetoothCentralManager;
|
||||
import com.welie.blessed.BluetoothCentralManagerCallback;
|
||||
import com.welie.blessed.BluetoothPeripheral;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -28,8 +28,8 @@ public class BluetoothOKOK extends BluetoothCommunication {
|
||||
private static final int IDX_IMPEDANCE_LSB = 11;
|
||||
private static final int IDX_CHECKSUM = 12;
|
||||
|
||||
private BluetoothCentral central;
|
||||
private final BluetoothCentralCallback btCallback = new BluetoothCentralCallback() {
|
||||
private BluetoothCentralManager central;
|
||||
private final BluetoothCentralManagerCallback btCallback = new BluetoothCentralManagerCallback() {
|
||||
@Override
|
||||
public void onDiscoveredPeripheral(@NotNull BluetoothPeripheral peripheral, @NotNull ScanResult scanResult) {
|
||||
SparseArray<byte[]> manufacturerSpecificData = scanResult.getScanRecord().getManufacturerSpecificData();
|
||||
@@ -63,7 +63,7 @@ public class BluetoothOKOK extends BluetoothCommunication {
|
||||
public BluetoothOKOK(Context context)
|
||||
{
|
||||
super(context);
|
||||
central = new BluetoothCentral(context, btCallback, new Handler(Looper.getMainLooper()));
|
||||
central = new BluetoothCentralManager(context, btCallback, new Handler(Looper.getMainLooper()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -19,42 +19,65 @@
|
||||
*/
|
||||
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;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Pair;
|
||||
|
||||
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.health.openscale.core.utils.Converters;
|
||||
import com.welie.blessed.BluetoothBytesParser;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.Vector;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public abstract class BluetoothStandardWeightProfile extends BluetoothCommunication {
|
||||
|
||||
// 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;
|
||||
protected static final byte UDS_CP_REGISTER_NEW_USER = 0x01;
|
||||
protected static final byte UDS_CP_CONSENT = 0x02;
|
||||
protected static final byte UDS_CP_DELETE_USER_DATA = 0x03;
|
||||
protected static final byte UDS_CP_LIST_ALL_USERS = 0x04;
|
||||
protected static final byte UDS_CP_DELETE_USERS = 0x05;
|
||||
protected 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;
|
||||
protected static final byte UDS_CP_RESP_VALUE_SUCCESS = 0x01;
|
||||
protected static final byte UDS_CP_RESP_OP_CODE_NOT_SUPPORTED = 0x02;
|
||||
protected static final byte UDS_CP_RESP_INVALID_PARAMETER = 0x03;
|
||||
protected static final byte UDS_CP_RESP_OPERATION_FAILED = 0x04;
|
||||
protected static final byte UDS_CP_RESP_USER_NOT_AUTHORIZED = 0x05;
|
||||
|
||||
SharedPreferences prefs;
|
||||
protected boolean registerNewUser;
|
||||
ScaleUser selectedUser;
|
||||
ScaleMeasurement previousMeasurement;
|
||||
protected boolean haveBatteryService;
|
||||
protected Vector<ScaleUser> scaleUserList;
|
||||
|
||||
public BluetoothStandardWeightProfile(Context context) {
|
||||
super(context);
|
||||
this.prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
this.selectedUser = OpenScale.getInstance().getSelectedScaleUser();
|
||||
this.registerNewUser = false;
|
||||
previousMeasurement = null;
|
||||
haveBatteryService = false;
|
||||
scaleUserList = new Vector<ScaleUser>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -62,42 +85,121 @@ public class BluetoothStandardWeightProfile extends BluetoothCommunication {
|
||||
return "Bluetooth Standard Weight Profile";
|
||||
}
|
||||
|
||||
protected abstract int getVendorSpecificMaxUserCount();
|
||||
|
||||
private enum SM_STEPS {
|
||||
START,
|
||||
READ_DEVICE_MANUFACTURER,
|
||||
READ_DEVICE_MODEL,
|
||||
WRITE_CURRENT_TIME,
|
||||
SET_NOTIFY_WEIGHT_MEASUREMENT,
|
||||
SET_NOTIFY_BODY_COMPOSITION_MEASUREMENT,
|
||||
SET_NOTIFY_CHANGE_INCREMENT,
|
||||
SET_INDICATION_USER_CONTROL_POINT,
|
||||
SET_NOTIFY_BATTERY_LEVEL,
|
||||
READ_BATTERY_LEVEL,
|
||||
SET_NOTIFY_VENDOR_SPECIFIC_USER_LIST,
|
||||
REQUEST_VENDOR_SPECIFIC_USER_LIST,
|
||||
REGISTER_NEW_SCALE_USER,
|
||||
SELECT_SCALE_USER,
|
||||
SET_SCALE_USER_DATA,
|
||||
REQUEST_MEASUREMENT,
|
||||
MAX_STEP
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onNextStep(int stepNr) {
|
||||
|
||||
switch (stepNr) {
|
||||
case 0:
|
||||
// Read manufacturer and model number from the Device Information Service
|
||||
if (stepNr > SM_STEPS.MAX_STEP.ordinal()) {
|
||||
Timber.d( "WARNING: stepNr == " + stepNr + " outside range, must be from 0 to " + SM_STEPS.MAX_STEP.ordinal());
|
||||
stepNr = SM_STEPS.MAX_STEP.ordinal();
|
||||
}
|
||||
SM_STEPS step = SM_STEPS.values()[stepNr];
|
||||
Timber.d("stepNr: " + stepNr + " " + step);
|
||||
|
||||
switch (step) {
|
||||
case START:
|
||||
break;
|
||||
case READ_DEVICE_MANUFACTURER:
|
||||
// Read manufacturer from the Device Information Service
|
||||
readBytes(BluetoothGattUuid.SERVICE_DEVICE_INFORMATION, BluetoothGattUuid.CHARACTERISTIC_MANUFACTURER_NAME_STRING);
|
||||
break;
|
||||
case READ_DEVICE_MODEL:
|
||||
// Read model number from the Device Information Service
|
||||
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());
|
||||
case WRITE_CURRENT_TIME:
|
||||
writeCurrentTime();
|
||||
break;
|
||||
case 2:
|
||||
case SET_NOTIFY_WEIGHT_MEASUREMENT:
|
||||
// Turn on notification for Weight Service
|
||||
setNotificationOn(BluetoothGattUuid.SERVICE_WEIGHT_SCALE, BluetoothGattUuid.CHARACTERISTIC_WEIGHT_MEASUREMENT);
|
||||
break;
|
||||
case 3:
|
||||
case SET_NOTIFY_BODY_COMPOSITION_MEASUREMENT:
|
||||
// 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
|
||||
case SET_NOTIFY_CHANGE_INCREMENT:
|
||||
// Turn on notification for User Data Service Change Increment
|
||||
setNotificationOn(BluetoothGattUuid.SERVICE_USER_DATA, BluetoothGattUuid.CHARACTERISTIC_CHANGE_INCREMENT);
|
||||
setNotificationOn(BluetoothGattUuid.SERVICE_USER_DATA, BluetoothGattUuid.CHARACTERISTIC_USER_CONTROL_POINT);
|
||||
break;
|
||||
case 5:
|
||||
case SET_INDICATION_USER_CONTROL_POINT:
|
||||
// Turn on notification for User Control Point
|
||||
setIndicationOn(BluetoothGattUuid.SERVICE_USER_DATA, BluetoothGattUuid.CHARACTERISTIC_USER_CONTROL_POINT);
|
||||
break;
|
||||
case SET_NOTIFY_BATTERY_LEVEL:
|
||||
// Turn on notifications for Battery Service
|
||||
setNotificationOn(BluetoothGattUuid.SERVICE_BATTERY_LEVEL, BluetoothGattUuid.CHARACTERISTIC_BATTERY_LEVEL);
|
||||
if (setNotificationOn(BluetoothGattUuid.SERVICE_BATTERY_LEVEL, BluetoothGattUuid.CHARACTERISTIC_BATTERY_LEVEL)) {
|
||||
haveBatteryService = true;
|
||||
}
|
||||
else {
|
||||
haveBatteryService = false;
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
final ScaleUser selectedUser = OpenScale.getInstance().getSelectedScaleUser();
|
||||
registerUser(CURRENT_USER_CONSENT);
|
||||
setUser(selectedUser.getId(), CURRENT_USER_CONSENT);
|
||||
case READ_BATTERY_LEVEL:
|
||||
// read Battery Service
|
||||
if (haveBatteryService) {
|
||||
readBytes(BluetoothGattUuid.SERVICE_BATTERY_LEVEL, BluetoothGattUuid.CHARACTERISTIC_BATTERY_LEVEL);
|
||||
}
|
||||
break;
|
||||
case SET_NOTIFY_VENDOR_SPECIFIC_USER_LIST:
|
||||
setNotifyVendorSpecificUserList();
|
||||
break;
|
||||
case REQUEST_VENDOR_SPECIFIC_USER_LIST:
|
||||
scaleUserList.clear();
|
||||
requestVendorSpecificUserList();
|
||||
stopMachineState();
|
||||
break;
|
||||
case REGISTER_NEW_SCALE_USER:
|
||||
int userId = this.selectedUser.getId();
|
||||
int consentCode = getUserScaleConsent(userId);
|
||||
int userIndex = getUserScaleIndex(userId);
|
||||
if (consentCode == -1 || userIndex == -1) {
|
||||
registerNewUser = true;
|
||||
}
|
||||
if (registerNewUser) {
|
||||
Random randomFactory = new Random();
|
||||
consentCode = randomFactory.nextInt(10000);
|
||||
storeUserScaleConsentCode(userId, consentCode);
|
||||
registerUser(consentCode);
|
||||
stopMachineState();
|
||||
}
|
||||
break;
|
||||
case SELECT_SCALE_USER:
|
||||
Timber.d("Select user on scale!");
|
||||
setUser(this.selectedUser.getId());
|
||||
stopMachineState();
|
||||
break;
|
||||
case SET_SCALE_USER_DATA:
|
||||
if (registerNewUser) {
|
||||
writeUserDataToScale();
|
||||
}
|
||||
break;
|
||||
case REQUEST_MEASUREMENT:
|
||||
if (registerNewUser) {
|
||||
requestMeasurement();
|
||||
sendMessage(R.string.info_step_on_scale_for_reference, 0);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
@@ -106,6 +208,15 @@ public class BluetoothStandardWeightProfile extends BluetoothCommunication {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void writeUserDataToScale() {
|
||||
writeBirthday();
|
||||
writeGender();
|
||||
writeHeight();
|
||||
writeActivityLevel();
|
||||
writeInitials();
|
||||
setChangeIncrement();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBluetoothNotify(UUID characteristic, byte[] value) {
|
||||
BluetoothBytesParser parser = new BluetoothBytesParser(value);
|
||||
@@ -123,6 +234,9 @@ public class BluetoothStandardWeightProfile extends BluetoothCommunication {
|
||||
else if(characteristic.equals(BluetoothGattUuid.CHARACTERISTIC_BATTERY_LEVEL)) {
|
||||
int batteryLevel = parser.getIntValue(FORMAT_UINT8);
|
||||
Timber.d(String.format("Received battery level %d%%", batteryLevel));
|
||||
if (batteryLevel <= 10) {
|
||||
sendMessage(R.string.info_scale_low_battery, batteryLevel);
|
||||
}
|
||||
}
|
||||
else if(characteristic.equals(BluetoothGattUuid.CHARACTERISTIC_MANUFACTURER_NAME_STRING)) {
|
||||
String manufacturer = parser.getStringValue(0);
|
||||
@@ -133,40 +247,77 @@ public class BluetoothStandardWeightProfile extends BluetoothCommunication {
|
||||
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)));
|
||||
handleUserControlPointNotify(value);
|
||||
}
|
||||
else {
|
||||
Timber.d(String.format("Notification from unhandled characteristic: %s, value: [%s]",
|
||||
characteristic.toString(), byteInHex(value)));
|
||||
}
|
||||
}
|
||||
|
||||
private void handleWeightMeasurement(byte[] value) {
|
||||
protected void handleUserControlPointNotify(byte[] value) {
|
||||
if(value[0]==UDS_CP_RESPONSE) {
|
||||
switch (value[1]) {
|
||||
case UDS_CP_LIST_ALL_USERS:
|
||||
Timber.d("UDS_CP_LIST_ALL_USERS value [" + byteInHex(value) + "]");
|
||||
break;
|
||||
case UDS_CP_REGISTER_NEW_USER:
|
||||
if (value[2] == UDS_CP_RESP_VALUE_SUCCESS) {
|
||||
int userIndex = value[3];
|
||||
int userId = this.selectedUser.getId();
|
||||
Timber.d(String.format("UDS_CP_REGISTER_NEW_USER: Created scale user index: "
|
||||
+ "%d (app user id: %d)", userIndex, userId));
|
||||
storeUserScaleIndex(userId, userIndex);
|
||||
resumeMachineState();
|
||||
} else {
|
||||
Timber.e("UDS_CP_REGISTER_NEW_USER: ERROR: could not register new scale user, code: " + value[2]);
|
||||
}
|
||||
break;
|
||||
case UDS_CP_CONSENT:
|
||||
if (registerNewUser) {
|
||||
Timber.d("UDS_CP_CONSENT: registerNewUser==true, value[2] == " + value[2]);
|
||||
resumeMachineState();
|
||||
break;
|
||||
}
|
||||
if (value[2] == UDS_CP_RESP_VALUE_SUCCESS) {
|
||||
Timber.d("UDS_CP_CONSENT: Success user consent");
|
||||
resumeMachineState();
|
||||
} else if (value[2] == UDS_CP_RESP_USER_NOT_AUTHORIZED) {
|
||||
Timber.e("UDS_CP_CONSENT: Not authorized");
|
||||
enterScaleUserConsentUi(this.selectedUser.getId(), getUserScaleIndex(this.selectedUser.getId()));
|
||||
}
|
||||
else {
|
||||
Timber.e("UDS_CP_CONSENT: unhandled, code: " + value[2]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Timber.e("CHARACTERISTIC_USER_CONTROL_POINT: Unhandled response code "
|
||||
+ value[1] + " value [" + byteInHex(value) + "]");
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Timber.d("CHARACTERISTIC_USER_CONTROL_POINT: non-response code " + value[0]
|
||||
+ " value [" + byteInHex(value) + "]");
|
||||
}
|
||||
}
|
||||
|
||||
protected ScaleMeasurement weightMeasurementToScaleMeasurement(byte[] value) {
|
||||
String prefix = "weightMeasurementToScaleMeasurement() ";
|
||||
Timber.d(String.format(prefix + "value: [%s]", byteInHex(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;
|
||||
Timber.d(String.format(prefix + "flags: 0x%02x ", flags)
|
||||
+ "[" + (isKg ? "SI" : "Imperial")
|
||||
+ (timestampPresent ? ", timestamp" : "")
|
||||
+ (userIDPresent ? ", userID" : "")
|
||||
+ (bmiAndHeightPresent ? ", bmiAndHeight" : "")
|
||||
+ "], " + String.format("reserved flags: 0x%02x ", flags & 0xf0));
|
||||
|
||||
ScaleMeasurement scaleMeasurement = new ScaleMeasurement();
|
||||
|
||||
@@ -175,29 +326,54 @@ public class BluetoothStandardWeightProfile extends BluetoothCommunication {
|
||||
|
||||
// Get weight
|
||||
float weightValue = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * weightMultiplier;
|
||||
Timber.d(prefix+ "weight: " + weightValue);
|
||||
scaleMeasurement.setWeight(weightValue);
|
||||
|
||||
if(timestampPresent) {
|
||||
Date timestamp = parser.getDateTime();
|
||||
Timber.d(prefix + "timestamp: " + timestamp.toString());
|
||||
scaleMeasurement.setDateTime(timestamp);
|
||||
}
|
||||
|
||||
if(userIDPresent) {
|
||||
int userID = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT8);
|
||||
Timber.d(String.format("User id: %d", userID));
|
||||
int scaleUserIndex = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT8);
|
||||
int userID = getUserIdFromScaleIndex(scaleUserIndex);
|
||||
Timber.d(String.format(prefix + "scale user index: %d (app user id: %d)", scaleUserIndex, userID));
|
||||
if (userID != -1) {
|
||||
scaleMeasurement.setUserId(userID);
|
||||
}
|
||||
|
||||
if (registerNewUser) {
|
||||
Timber.d(String.format(prefix + "Setting initial weight for user %s to: %s and registerNewUser to false", userID,
|
||||
weightValue));
|
||||
if (selectedUser.getId() == userID) {
|
||||
this.selectedUser.setInitialWeight(weightValue);
|
||||
OpenScale.getInstance().updateScaleUser(selectedUser);
|
||||
}
|
||||
registerNewUser = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(bmiAndHeightPresent) {
|
||||
float BMI = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * 0.1f;
|
||||
Timber.d(prefix + "BMI: " + BMI);
|
||||
float heightInMeters = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * 0.001f;
|
||||
Timber.d(prefix + "heightInMeters: " + heightInMeters);
|
||||
}
|
||||
|
||||
Timber.d(String.format("Got weight: %s", weightValue));
|
||||
addScaleMeasurement(scaleMeasurement);
|
||||
return scaleMeasurement;
|
||||
}
|
||||
|
||||
private void handleBodyCompositionMeasurement(byte[] value) {
|
||||
protected void handleWeightMeasurement(byte[] value) {
|
||||
mergeWithPreviousScaleMeasurement(weightMeasurementToScaleMeasurement(value));
|
||||
}
|
||||
|
||||
protected ScaleMeasurement bodyCompositionMeasurementToScaleMeasurement(byte[] value) {
|
||||
String prefix = "bodyCompositionMeasurementToScaleMeasurement() ";
|
||||
Timber.d(String.format(prefix + "value: [%s]", byteInHex(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);
|
||||
@@ -213,78 +389,187 @@ public class BluetoothStandardWeightProfile extends BluetoothCommunication {
|
||||
boolean weightPresent = (flags & 0x0400) > 0;
|
||||
boolean heightPresent = (flags & 0x0800) > 0;
|
||||
boolean multiPacketMeasurement = (flags & 0x1000) > 0;
|
||||
Timber.d(String.format(prefix + "flags: 0x%02x ", flags)
|
||||
+ "[" + (isKg ? "SI" : "Imperial")
|
||||
+ (timestampPresent ? ", timestamp" : "")
|
||||
+ (userIDPresent ? ", userID" : "")
|
||||
+ (bmrPresent ? ", bmr" : "")
|
||||
+ (musclePercentagePresent ? ", musclePercentage" : "")
|
||||
+ (muscleMassPresent ? ", muscleMass" : "")
|
||||
+ (fatFreeMassPresent ? ", fatFreeMass" : "")
|
||||
+ (softLeanMassPresent ? ", softLeanMass" : "")
|
||||
+ (bodyWaterMassPresent ? ", bodyWaterMass" : "")
|
||||
+ (impedancePresent ? ", impedance" : "")
|
||||
+ (weightPresent ? ", weight" : "")
|
||||
+ (heightPresent ? ", height" : "")
|
||||
+ (multiPacketMeasurement ? ", multiPacketMeasurement" : "")
|
||||
+ "], " + String.format("reserved flags: 0x%04x ", flags & 0xe000));
|
||||
|
||||
ScaleMeasurement scaleMeasurement = new ScaleMeasurement();
|
||||
|
||||
float bodyFatPercentage = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * 0.1f;
|
||||
Timber.d(prefix + "bodyFatPercentage: " + bodyFatPercentage);
|
||||
scaleMeasurement.setFat(bodyFatPercentage);
|
||||
|
||||
// Read timestamp if present
|
||||
if (timestampPresent) {
|
||||
Date timestamp = parser.getDateTime();
|
||||
Timber.d(prefix + "timestamp: " + timestamp.toString());
|
||||
scaleMeasurement.setDateTime(timestamp);
|
||||
}
|
||||
|
||||
// Read userID if present
|
||||
if (userIDPresent) {
|
||||
int userID = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT8);
|
||||
Timber.d(String.format("user id: %d", userID));
|
||||
int scaleUserIndex = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT8);
|
||||
int userID = getUserIdFromScaleIndex(scaleUserIndex);
|
||||
Timber.d(String.format(prefix + "scale user index: %d (app user id: %d)", scaleUserIndex, userID));
|
||||
if (userID != -1) {
|
||||
scaleMeasurement.setUserId(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);
|
||||
Timber.d(prefix + "bmrInJoules: " + bmrInJoules + " bmrInKcal: " + bmrInKcal);
|
||||
}
|
||||
|
||||
// Read musclePercentage if present
|
||||
if (musclePercentagePresent) {
|
||||
float musclePercentage = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * 0.1f;
|
||||
Timber.d(prefix + "musclePercentage: " + musclePercentage);
|
||||
scaleMeasurement.setMuscle(musclePercentage);
|
||||
}
|
||||
|
||||
// Read muscleMass if present
|
||||
if (muscleMassPresent) {
|
||||
float muscleMass = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * massMultiplier;
|
||||
Timber.d(prefix + "muscleMass: " + muscleMass);
|
||||
}
|
||||
|
||||
// Read fatFreeMassPresent if present
|
||||
if (fatFreeMassPresent) {
|
||||
float fatFreeMass = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * massMultiplier;
|
||||
Timber.d(prefix + "fatFreeMass: " + fatFreeMass);
|
||||
}
|
||||
|
||||
// Read softleanMass if present
|
||||
float softLeanMass = 0.0f;
|
||||
if (softLeanMassPresent) {
|
||||
float softLeanMass = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * massMultiplier;
|
||||
softLeanMass = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * massMultiplier;
|
||||
Timber.d(prefix + "softLeanMass: " + softLeanMass);
|
||||
}
|
||||
|
||||
// Read bodyWaterMass if present
|
||||
if (bodyWaterMassPresent) {
|
||||
float bodyWaterMass = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * massMultiplier;
|
||||
Timber.d(prefix + "bodyWaterMass: " + bodyWaterMass);
|
||||
scaleMeasurement.setWater(bodyWaterMass);
|
||||
}
|
||||
|
||||
// Read impedance if present
|
||||
if (impedancePresent) {
|
||||
float impedance = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * 0.1f;
|
||||
Timber.d(prefix + "impedance: " + impedance);
|
||||
}
|
||||
|
||||
// Read weight if present
|
||||
float weightValue = 0.0f;
|
||||
if (weightPresent) {
|
||||
float weightValue = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * massMultiplier;
|
||||
weightValue = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * massMultiplier;
|
||||
Timber.d(prefix + "weightValue: " + weightValue);
|
||||
scaleMeasurement.setWeight(weightValue);
|
||||
}
|
||||
else {
|
||||
if (previousMeasurement != null) {
|
||||
weightValue = previousMeasurement.getWeight();
|
||||
if (weightValue > 0) {
|
||||
weightPresent = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// calculate lean body mass and bone mass
|
||||
if (weightPresent && softLeanMassPresent) {
|
||||
float fatMass = weightValue * bodyFatPercentage / 100.0f;
|
||||
float leanBodyMass = weightValue - fatMass;
|
||||
float boneMass = leanBodyMass - softLeanMass;
|
||||
scaleMeasurement.setLbm(leanBodyMass);
|
||||
scaleMeasurement.setBone(boneMass);
|
||||
}
|
||||
|
||||
// Read height if present
|
||||
if (heightPresent) {
|
||||
float heightValue = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16);
|
||||
Timber.d(prefix + "heightValue: " + heightValue);
|
||||
}
|
||||
|
||||
if (multiPacketMeasurement) {
|
||||
Timber.e(prefix + "multiPacketMeasurement not supported!");
|
||||
}
|
||||
|
||||
Timber.d(String.format("Got body composition: %s", byteInHex(value)));
|
||||
addScaleMeasurement(scaleMeasurement);
|
||||
return scaleMeasurement;
|
||||
}
|
||||
|
||||
private void registerUser(int consentCode) {
|
||||
protected void handleBodyCompositionMeasurement(byte[] value) {
|
||||
mergeWithPreviousScaleMeasurement(bodyCompositionMeasurementToScaleMeasurement(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Bluetooth scales usually implement both "Weight Scale Feature" and "Body Composition Feature".
|
||||
* It seems that scale first transmits weight measurement (with user index and timestamp) and
|
||||
* later transmits body composition measurement (without user index and timestamp).
|
||||
* If previous measurement contains user index and new measurements does not then merge them and
|
||||
* store as one.
|
||||
* disconnect() function must store previousMeasurement to openScale db (if present).
|
||||
*
|
||||
* @param newMeasurement the scale data that should be merged with previous measurement or
|
||||
* stored as previous measurement.
|
||||
*/
|
||||
protected void mergeWithPreviousScaleMeasurement(ScaleMeasurement newMeasurement) {
|
||||
if (previousMeasurement == null) {
|
||||
if (newMeasurement.getUserId() == -1) {
|
||||
addScaleMeasurement(newMeasurement);
|
||||
}
|
||||
else {
|
||||
previousMeasurement = newMeasurement;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ((newMeasurement.getUserId() == -1) && (previousMeasurement.getUserId() != -1)) {
|
||||
previousMeasurement.merge(newMeasurement);
|
||||
addScaleMeasurement(previousMeasurement);
|
||||
previousMeasurement = null;
|
||||
}
|
||||
else {
|
||||
addScaleMeasurement(previousMeasurement);
|
||||
if (newMeasurement.getUserId() == -1) {
|
||||
addScaleMeasurement(newMeasurement);
|
||||
previousMeasurement = null;
|
||||
}
|
||||
else {
|
||||
previousMeasurement = newMeasurement;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
if (previousMeasurement != null) {
|
||||
addScaleMeasurement(previousMeasurement);
|
||||
previousMeasurement = null;
|
||||
}
|
||||
super.disconnect();
|
||||
}
|
||||
|
||||
protected abstract void setNotifyVendorSpecificUserList();
|
||||
|
||||
protected abstract void requestVendorSpecificUserList();
|
||||
|
||||
protected 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);
|
||||
@@ -292,7 +577,7 @@ public class BluetoothStandardWeightProfile extends BluetoothCommunication {
|
||||
writeBytes(BluetoothGattUuid.SERVICE_USER_DATA, BluetoothGattUuid.CHARACTERISTIC_USER_CONTROL_POINT, parser.getValue());
|
||||
}
|
||||
|
||||
private void setUser(int userIndex, int consentCode) {
|
||||
protected 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);
|
||||
@@ -300,4 +585,269 @@ public class BluetoothStandardWeightProfile extends BluetoothCommunication {
|
||||
Timber.d(String.format("setUser userIndex: %d, consentCode: %d", userIndex, consentCode));
|
||||
writeBytes(BluetoothGattUuid.SERVICE_USER_DATA, BluetoothGattUuid.CHARACTERISTIC_USER_CONTROL_POINT, parser.getValue());
|
||||
}
|
||||
|
||||
protected synchronized void setUser(int userId) {
|
||||
int userIndex = getUserScaleIndex(userId);
|
||||
int consentCode = getUserScaleConsent(userId);
|
||||
Timber.d(String.format("setting: userId %d, userIndex: %d, consent Code: %d ", userId, userIndex, consentCode));
|
||||
setUser(userIndex, consentCode);
|
||||
}
|
||||
|
||||
protected void deleteUser(int userIndex, int consentCode) {
|
||||
setUser(userIndex, consentCode);
|
||||
deleteUser();
|
||||
}
|
||||
|
||||
protected void deleteUser() {
|
||||
BluetoothBytesParser parser = new BluetoothBytesParser(new byte[] { 0 });
|
||||
parser.setIntValue(UDS_CP_DELETE_USER_DATA, FORMAT_UINT8, 0);
|
||||
writeBytes(BluetoothGattUuid.SERVICE_USER_DATA, BluetoothGattUuid.CHARACTERISTIC_USER_CONTROL_POINT,
|
||||
parser.getValue());
|
||||
}
|
||||
|
||||
protected void writeCurrentTime() {
|
||||
BluetoothBytesParser parser = new BluetoothBytesParser();
|
||||
parser.setCurrentTime(Calendar.getInstance());
|
||||
writeBytes(BluetoothGattUuid.SERVICE_CURRENT_TIME, BluetoothGattUuid.CHARACTERISTIC_CURRENT_TIME,
|
||||
parser.getValue());
|
||||
}
|
||||
|
||||
protected void writeBirthday() {
|
||||
BluetoothBytesParser parser = new BluetoothBytesParser();
|
||||
parser.setDateTime(dateToCalender(this.selectedUser.getBirthday()));
|
||||
writeBytes(BluetoothGattUuid.SERVICE_USER_DATA, BluetoothGattUuid.CHARACTERISTIC_USER_DATE_OF_BIRTH,
|
||||
Arrays.copyOfRange(parser.getValue(), 0, 3));
|
||||
}
|
||||
|
||||
protected Calendar dateToCalender(Date date) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(date);
|
||||
return calendar;
|
||||
}
|
||||
|
||||
protected void writeGender() {
|
||||
BluetoothBytesParser parser = new BluetoothBytesParser();
|
||||
int gender = this.selectedUser.getGender().toInt();
|
||||
Timber.d(String.format("gender: %d", gender));
|
||||
parser.setIntValue(gender, FORMAT_UINT8);
|
||||
writeBytes(BluetoothGattUuid.SERVICE_USER_DATA, BluetoothGattUuid.CHARACTERISTIC_USER_GENDER,
|
||||
parser.getValue());
|
||||
}
|
||||
|
||||
protected void writeHeight() {
|
||||
BluetoothBytesParser parser = new BluetoothBytesParser();
|
||||
int height = (int) this.selectedUser.getBodyHeight();
|
||||
Timber.d(String.format("height: %d", height));
|
||||
parser.setIntValue(height, FORMAT_UINT16);
|
||||
writeBytes(BluetoothGattUuid.SERVICE_USER_DATA, BluetoothGattUuid.CHARACTERISTIC_USER_HEIGHT,
|
||||
parser.getValue());
|
||||
}
|
||||
|
||||
protected void writeActivityLevel() {
|
||||
Timber.d("Write user activity level not implemented!");
|
||||
}
|
||||
|
||||
protected void writeInitials() {
|
||||
Timber.d("Write user initials not implemented!");
|
||||
}
|
||||
|
||||
protected void setChangeIncrement() {
|
||||
BluetoothBytesParser parser = new BluetoothBytesParser();
|
||||
parser.setIntValue(1, FORMAT_UINT8);
|
||||
writeBytes(BluetoothGattUuid.SERVICE_USER_DATA, BluetoothGattUuid.CHARACTERISTIC_CHANGE_INCREMENT,
|
||||
parser.getValue());
|
||||
}
|
||||
|
||||
protected void requestMeasurement() {
|
||||
Timber.d("Take measurement command not implemented!");
|
||||
}
|
||||
|
||||
protected synchronized void storeUserScaleConsentCode(int userId, int consentCode) {
|
||||
prefs.edit().putInt("userConsentCode" + userId, consentCode).apply();
|
||||
}
|
||||
|
||||
protected synchronized int getUserScaleConsent(int userId) {
|
||||
return prefs.getInt("userConsentCode" + userId, -1);
|
||||
}
|
||||
|
||||
protected synchronized void storeUserScaleIndex(int userId, int userIndex) {
|
||||
int currentUserIndex = getUserScaleIndex(userId);
|
||||
if (currentUserIndex != -1) {
|
||||
prefs.edit().putInt("userIdFromUserScaleIndex" + currentUserIndex, -1);
|
||||
}
|
||||
prefs.edit().putInt("userScaleIndex" + userId, userIndex).apply();
|
||||
if (userIndex != -1) {
|
||||
prefs.edit().putInt("userIdFromUserScaleIndex" + userIndex, userId).apply();
|
||||
}
|
||||
}
|
||||
|
||||
protected synchronized int getUserIdFromScaleIndex(int userScaleIndex) {
|
||||
return prefs.getInt("userIdFromUserScaleIndex" + userScaleIndex, -1);
|
||||
}
|
||||
|
||||
protected synchronized int getUserScaleIndex(int userId) {
|
||||
return prefs.getInt("userScaleIndex" + userId, -1);
|
||||
}
|
||||
|
||||
protected void reconnectOrSetSmState(SM_STEPS requestedState, SM_STEPS minState, Handler uiHandler) {
|
||||
if (needReConnect()) {
|
||||
jumpNextToStepNr(SM_STEPS.START.ordinal());
|
||||
stopMachineState();
|
||||
reConnectPreviousPeripheral(uiHandler);
|
||||
return;
|
||||
}
|
||||
if (getStepNr() > minState.ordinal()) {
|
||||
jumpNextToStepNr(requestedState.ordinal());
|
||||
}
|
||||
resumeMachineState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void selectScaleUserIndexForAppUserId(int appUserId, int scaleUserIndex, Handler uiHandler) {
|
||||
Timber.d("Select scale user index from UI: user id: " + appUserId + ", scale user index: " + scaleUserIndex);
|
||||
if (scaleUserIndex == -1) {
|
||||
reconnectOrSetSmState(SM_STEPS.REGISTER_NEW_SCALE_USER, SM_STEPS.REGISTER_NEW_SCALE_USER, uiHandler);
|
||||
}
|
||||
else {
|
||||
storeUserScaleIndex(appUserId, scaleUserIndex);
|
||||
if (getUserScaleConsent(appUserId) == -1) {
|
||||
enterScaleUserConsentUi(appUserId, scaleUserIndex);
|
||||
}
|
||||
else {
|
||||
reconnectOrSetSmState(SM_STEPS.SELECT_SCALE_USER, SM_STEPS.REQUEST_VENDOR_SPECIFIC_USER_LIST, uiHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScaleUserConsent(int appUserId, int scaleUserConsent, Handler uiHandler) {
|
||||
Timber.d("set scale user consent from UI: user id: " + appUserId + ", scale user consent: " + scaleUserConsent);
|
||||
storeUserScaleConsentCode(appUserId, scaleUserConsent);
|
||||
if (scaleUserConsent == -1) {
|
||||
reconnectOrSetSmState(SM_STEPS.REQUEST_VENDOR_SPECIFIC_USER_LIST, SM_STEPS.REQUEST_VENDOR_SPECIFIC_USER_LIST, uiHandler);
|
||||
}
|
||||
else {
|
||||
reconnectOrSetSmState(SM_STEPS.SELECT_SCALE_USER, SM_STEPS.REQUEST_VENDOR_SPECIFIC_USER_LIST, uiHandler);
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleVendorSpecificUserList(byte[] value) {
|
||||
Timber.d(String.format("Got user data: <%s>", byteInHex(value)));
|
||||
BluetoothBytesParser parser = new BluetoothBytesParser(value);
|
||||
int userListStatus = parser.getIntValue(FORMAT_UINT8);
|
||||
if (userListStatus == 2) {
|
||||
Timber.d("scale have no users!");
|
||||
storeUserScaleConsentCode(selectedUser.getId(), -1);
|
||||
storeUserScaleIndex(selectedUser.getId(), -1);
|
||||
jumpNextToStepNr(SM_STEPS.REGISTER_NEW_SCALE_USER.ordinal());
|
||||
resumeMachineState();
|
||||
return;
|
||||
}
|
||||
else if (userListStatus == 1) {
|
||||
for (int i = 0; i < scaleUserList.size(); i++) {
|
||||
if (i == 0) {
|
||||
Timber.d("scale user list:");
|
||||
}
|
||||
Timber.d("\n" + (i + 1) + ". " + scaleUserList.get(i));
|
||||
}
|
||||
if ((scaleUserList.size() == 0)) {
|
||||
storeUserScaleConsentCode(selectedUser.getId(), -1);
|
||||
storeUserScaleIndex(selectedUser.getId(), -1);
|
||||
jumpNextToStepNr(SM_STEPS.REGISTER_NEW_SCALE_USER.ordinal());
|
||||
resumeMachineState();
|
||||
return;
|
||||
}
|
||||
if (getUserScaleIndex(selectedUser.getId()) == -1 || getUserScaleConsent(selectedUser.getId()) == -1) {
|
||||
chooseExistingScaleUser(scaleUserList);
|
||||
return;
|
||||
}
|
||||
resumeMachineState();
|
||||
return;
|
||||
}
|
||||
int index = parser.getIntValue(FORMAT_UINT8);
|
||||
String initials = parser.getStringValue();
|
||||
int end = 3 > initials.length() ? initials.length() : 3;
|
||||
initials = initials.substring(0, end);
|
||||
if (initials.length() == 3) {
|
||||
if (initials.charAt(0) == 0xff && initials.charAt(1) == 0xff && initials.charAt(2) == 0xff) {
|
||||
initials = "";
|
||||
}
|
||||
}
|
||||
parser.setOffset(5);
|
||||
int year = parser.getIntValue(FORMAT_UINT16);
|
||||
int month = parser.getIntValue(FORMAT_UINT8);
|
||||
int day = parser.getIntValue(FORMAT_UINT8);
|
||||
int height = parser.getIntValue(FORMAT_UINT8);
|
||||
int gender = parser.getIntValue(FORMAT_UINT8);
|
||||
int activityLevel = parser.getIntValue(FORMAT_UINT8);
|
||||
GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);
|
||||
ScaleUser scaleUser = new ScaleUser();
|
||||
scaleUser.setUserName(initials);
|
||||
scaleUser.setBirthday(calendar.getTime());
|
||||
scaleUser.setBodyHeight(height);
|
||||
scaleUser.setGender(Converters.Gender.fromInt(gender));
|
||||
scaleUser.setActivityLevel(Converters.ActivityLevel.fromInt(activityLevel - 1));
|
||||
scaleUser.setId(index);
|
||||
scaleUserList.add(scaleUser);
|
||||
if (scaleUserList.size() == getVendorSpecificMaxUserCount()) {
|
||||
if (getUserScaleIndex(selectedUser.getId()) == -1 || getUserScaleConsent(selectedUser.getId()) == -1) {
|
||||
chooseExistingScaleUser(scaleUserList);
|
||||
return;
|
||||
}
|
||||
resumeMachineState();
|
||||
}
|
||||
}
|
||||
|
||||
protected void chooseExistingScaleUser(Vector<ScaleUser> userList) {
|
||||
final DateFormat dateFormat = DateFormat.getDateInstance();
|
||||
int choicesCount = userList.size();
|
||||
if (userList.size() < getVendorSpecificMaxUserCount()) {
|
||||
choicesCount = userList.size() + 1;
|
||||
}
|
||||
CharSequence[] choiceStrings = new String[choicesCount];
|
||||
int indexArray[] = new int[choicesCount];
|
||||
int selectedItem = -1;
|
||||
for (int i = 0; i < userList.size(); ++i) {
|
||||
ScaleUser u = userList.get(i);
|
||||
String name = u.getUserName();
|
||||
choiceStrings[i] = (name.length() > 0 ? name : String.format("P%02d", u.getId()))
|
||||
+ " " + context.getString(u.getGender().isMale() ? R.string.label_male : R.string.label_female).toLowerCase()
|
||||
+ " " + context.getString(R.string.label_height).toLowerCase() + ":" + u.getBodyHeight()
|
||||
+ " " + context.getString(R.string.label_birthday).toLowerCase() + ":" + dateFormat.format(u.getBirthday())
|
||||
+ " " + context.getString(R.string.label_activity_level).toLowerCase() + ":" + (u.getActivityLevel().toInt() + 1);
|
||||
indexArray[i] = u.getId();
|
||||
}
|
||||
if (userList.size() < getVendorSpecificMaxUserCount()) {
|
||||
choiceStrings[userList.size()] = context.getString(R.string.info_create_new_user_on_scale);
|
||||
indexArray[userList.size()] = -1;
|
||||
}
|
||||
Pair<CharSequence[], int[]> choices = new Pair(choiceStrings, indexArray);
|
||||
chooseScaleUserUi(choices);
|
||||
}
|
||||
|
||||
protected String getInitials(String fullName) {
|
||||
if (fullName == null || fullName.isEmpty() || fullName.chars().allMatch(Character::isWhitespace)) {
|
||||
return getDefaultInitials();
|
||||
}
|
||||
return buildInitialsStringFrom(fullName).toUpperCase();
|
||||
}
|
||||
|
||||
private String getDefaultInitials() {
|
||||
int userId = this.selectedUser.getId();
|
||||
int userIndex = getUserScaleIndex(userId);
|
||||
return "P" + userIndex + " ";
|
||||
}
|
||||
|
||||
private String buildInitialsStringFrom(String fullName) {
|
||||
String[] name = fullName.trim().split(" +");
|
||||
String initials = "";
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (i < name.length && name[i] != "") {
|
||||
initials += name[i].charAt(0);
|
||||
} else {
|
||||
initials += " ";
|
||||
}
|
||||
}
|
||||
return initials;
|
||||
}
|
||||
}
|
||||
|
@@ -32,12 +32,18 @@ import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.Editable;
|
||||
import android.text.Html;
|
||||
import android.text.InputFilter;
|
||||
import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.util.Pair;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
@@ -651,10 +657,92 @@ public class MainActivity extends AppCompatActivity
|
||||
Timber.e("Bluetooth scale message error: " + ex);
|
||||
}
|
||||
break;
|
||||
case CHOOSE_SCALE_USER:
|
||||
chooseScaleUser(msg);
|
||||
break;
|
||||
case ENTER_SCALE_USER_CONSENT:
|
||||
enterScaleUserConsent(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void chooseScaleUser(Message msg) {
|
||||
AlertDialog.Builder mBuilder = new AlertDialog.Builder(MainActivity.this);
|
||||
Pair<CharSequence[], int[]> choices = (Pair<CharSequence[], int[]>)msg.obj;
|
||||
|
||||
mBuilder.setTitle(getResources().getString(R.string.info_select_scale_user));
|
||||
mBuilder.setSingleChoiceItems(choices.first, -1 , new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialoginterface, int i) {
|
||||
Timber.d("UI selected " + i + ": " + choices.first[i] + " P-0" + choices.second[i]);
|
||||
OpenScale.getInstance().setBluetoothDeviceUserIndex(OpenScale.getInstance().getSelectedScaleUser().getId(), choices.second[i], callbackBtHandler);
|
||||
dialoginterface.dismiss();
|
||||
}
|
||||
});
|
||||
mBuilder.setNegativeButton(getResources().getString(R.string.label_cancel), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialoginterface, int i) {
|
||||
dialoginterface.dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
AlertDialog mDialog = mBuilder.create();
|
||||
mDialog.show();
|
||||
}
|
||||
|
||||
private void enterScaleUserConsent(Message msg) {
|
||||
final int appUserId = msg.arg1;
|
||||
final int scaleUserIndex = msg.arg2;
|
||||
final int[] consentCode = {-1};
|
||||
|
||||
AlertDialog.Builder mBuilder = new AlertDialog.Builder(MainActivity.this);
|
||||
mBuilder.setTitle(getResources().getString(R.string.info_enter_consent_code_for_scale_user, Integer.toString(scaleUserIndex)));
|
||||
|
||||
final EditText input = new EditText(this);
|
||||
input.setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||
InputFilter[] filterArray = new InputFilter[1];
|
||||
filterArray[0] = new InputFilter.LengthFilter(4);
|
||||
input.setFilters(filterArray);
|
||||
mBuilder.setView(input);
|
||||
|
||||
mBuilder.setPositiveButton(getResources().getString(R.string.label_ok), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialoginterface, int i) {
|
||||
OpenScale.getInstance().setBluetoothDeviceUserConsent(appUserId, consentCode[0], callbackBtHandler);
|
||||
dialoginterface.dismiss();
|
||||
}
|
||||
});
|
||||
mBuilder.setNegativeButton(getResources().getString(R.string.label_cancel), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialoginterface, int i) {
|
||||
OpenScale.getInstance().setBluetoothDeviceUserConsent(appUserId, -1, callbackBtHandler);
|
||||
dialoginterface.dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
AlertDialog mDialog = mBuilder.create();
|
||||
mDialog.show();
|
||||
|
||||
mDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
||||
input.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
try {
|
||||
consentCode[0] = Integer.parseInt(s.toString());
|
||||
Timber.d("consent code set to " + consentCode[0] + "(" + s.toString() + ")");
|
||||
mDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true);
|
||||
} catch(NumberFormatException nfe) {
|
||||
Timber.d("Could not parse " + nfe);
|
||||
mDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setBluetoothStatusIcon(int iconResource) {
|
||||
bluetoothStatusIcon = iconResource;
|
||||
bluetoothStatus.setIcon(getResources().getDrawable(bluetoothStatusIcon));
|
||||
|
@@ -57,8 +57,8 @@ 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.welie.blessed.BluetoothCentral;
|
||||
import com.welie.blessed.BluetoothCentralCallback;
|
||||
import com.welie.blessed.BluetoothCentralManager;
|
||||
import com.welie.blessed.BluetoothCentralManagerCallback;
|
||||
import com.welie.blessed.BluetoothPeripheral;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -76,7 +76,7 @@ public class BluetoothSettingsFragment extends Fragment {
|
||||
private TextView txtSearching;
|
||||
private ProgressBar progressBar;
|
||||
private Handler progressHandler;
|
||||
private BluetoothCentral central;
|
||||
private BluetoothCentralManager central;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
@@ -123,7 +123,7 @@ public class BluetoothSettingsFragment extends Fragment {
|
||||
return formatDeviceName(device.getName(), device.getAddress());
|
||||
}
|
||||
|
||||
private final BluetoothCentralCallback bluetoothCentralCallback = new BluetoothCentralCallback() {
|
||||
private final BluetoothCentralManagerCallback bluetoothCentralCallback = new BluetoothCentralManagerCallback() {
|
||||
@Override
|
||||
public void onDiscoveredPeripheral(BluetoothPeripheral peripheral, ScanResult scanResult) {
|
||||
new Handler().post(new Runnable() {
|
||||
@@ -139,7 +139,7 @@ public class BluetoothSettingsFragment extends Fragment {
|
||||
deviceListView.removeAllViews();
|
||||
foundDevices.clear();
|
||||
|
||||
central = new BluetoothCentral(requireContext(), bluetoothCentralCallback, new Handler(Looper.getMainLooper()));
|
||||
central = new BluetoothCentralManager(requireContext(), bluetoothCentralCallback, new Handler(Looper.getMainLooper()));
|
||||
central.scanForPeripherals();
|
||||
|
||||
txtSearching.setVisibility(View.VISIBLE);
|
||||
|
@@ -164,6 +164,8 @@
|
||||
<string name="error_max_scale_users">Max. number of concurrent scale users reached</string>
|
||||
<string name="info_step_on_scale_for_reference">Please step barefoot on the scale for reference measurements</string>
|
||||
<string name="info_step_on_scale">Please step barefoot on the scale</string>
|
||||
<string name="info_select_scale_user">Select scale user</string>
|
||||
<string name="info_enter_consent_code_for_scale_user">Enter PIN/consent code for scale user %s</string>
|
||||
<string name="info_measuring">Measuring weight: %.2f</string>
|
||||
<string name="trisa_scale_not_paired">This scale has not been paired!\n\nHold the button on the bottom of the scale to switch it to pairing mode, and then reconnect to retrieve the device password.</string>
|
||||
<string name="trisa_scale_pairing_succeeded">Pairing succeeded!\n\nReconnect to retrieve measurement data.</string>
|
||||
@@ -283,4 +285,5 @@
|
||||
<string name="app_intro_next_button">Next</string>
|
||||
<string name="app_intro_back_button">Back</string>
|
||||
<string name="app_intro_done_button">Done</string>
|
||||
<string name="info_create_new_user_on_scale">Create new user on scale.</string>
|
||||
</resources>
|
Reference in New Issue
Block a user