From 36eb1a8b40fd4c219a3d4ae9d3dad8f5baa69af1 Mon Sep 17 00:00:00 2001 From: Santtu Lakkala Date: Sun, 10 Oct 2021 12:08:16 +0300 Subject: [PATCH] Add support for OKOK 1.1 scales (weight only) (#738) --- .../core/bluetooth/BluetoothFactory.java | 2 +- .../core/bluetooth/BluetoothOKOK.java | 130 +++++++++++++----- 2 files changed, 99 insertions(+), 33 deletions(-) diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothFactory.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothFactory.java index 0802df38..e971c724 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothFactory.java +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothFactory.java @@ -111,7 +111,7 @@ public class BluetoothFactory { if (deviceName.equals("Hoffen BS-8107")) { return new BluetoothHoffenBBS8107(context); } - if (deviceName.equals("ADV")) { + if (deviceName.equals("ADV") || deviceName.equals("Chipsea-BLE")) { return new BluetoothOKOK(context); } if (deviceName.equals("BF105")) { diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothOKOK.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothOKOK.java index 5cff0e4c..300695fa 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothOKOK.java +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothOKOK.java @@ -20,43 +20,102 @@ import java.util.List; import timber.log.Timber; public class BluetoothOKOK extends BluetoothCommunication { - private static final int MANUFACTURER_DATA_ID = 0x20ca; // 16-bit little endian "header" 0xca 0x20 - private static final int IDX_FINAL = 6; - private static final int IDX_WEIGHT_MSB = 8; - private static final int IDX_WEIGHT_LSB = 9; - private static final int IDX_IMPEDANCE_MSB = 10; - private static final int IDX_IMPEDANCE_LSB = 11; - private static final int IDX_CHECKSUM = 12; + private static final int MANUFACTURER_DATA_ID_V20 = 0x20ca; // 16-bit little endian "header" 0xca 0x20 + private static final int MANUFACTURER_DATA_ID_V11 = 0x11ca; // 16-bit little endian "header" 0xca 0x11 + private static final int IDX_V20_FINAL = 6; + private static final int IDX_V20_WEIGHT_MSB = 8; + private static final int IDX_V20_WEIGHT_LSB = 9; + private static final int IDX_V20_IMPEDANCE_MSB = 10; + private static final int IDX_V20_IMPEDANCE_LSB = 11; + private static final int IDX_V20_CHECKSUM = 12; + + private static final int IDX_V11_WEIGHT_MSB = 3; + private static final int IDX_V11_WEIGHT_LSB = 4; + private static final int IDX_V11_BODY_PROPERTIES = 9; + private static final int IDX_V11_CHECKSUM = 16; private BluetoothCentralManager central; private final BluetoothCentralManagerCallback btCallback = new BluetoothCentralManagerCallback() { @Override public void onDiscoveredPeripheral(@NotNull BluetoothPeripheral peripheral, @NotNull ScanResult scanResult) { - SparseArray manufacturerSpecificData = scanResult.getScanRecord().getManufacturerSpecificData(); - byte[] data = scanResult.getScanRecord().getManufacturerSpecificData(MANUFACTURER_DATA_ID); - float divider = 10.0f; - byte checksum = 0x20; // Version field is part of the checksum, but not in array - if (data == null || data.length != 19) - return; - if ((data[IDX_FINAL] & 1) == 0) - return; - for (int i = 0; i < IDX_CHECKSUM; i++) - checksum ^= data[i]; - if (data[IDX_CHECKSUM] != checksum) { - Timber.d("Checksum error, got %x, expected %x", data[12] & 0xff, checksum & 0xff); - return; + SparseArray manufacturerSpecificData = scanResult.getScanRecord().getManufacturerSpecificData(); + if (manufacturerSpecificData.indexOfKey(MANUFACTURER_DATA_ID_V20) > -1) { + byte[] data = manufacturerSpecificData.get(MANUFACTURER_DATA_ID_V20); + float divider = 10.0f; + byte checksum = 0x20; // Version field is part of the checksum, but not in array + if (data == null || data.length != 19) + return; + if ((data[IDX_V20_FINAL] & 1) == 0) + return; + for (int i = 0; i < IDX_V20_CHECKSUM; i++) + checksum ^= data[i]; + if (data[IDX_V20_CHECKSUM] != checksum) { + Timber.d("Checksum error, got %x, expected %x", data[IDX_V20_CHECKSUM] & 0xff, checksum & 0xff); + return; + } + if ((data[IDX_V20_FINAL] & 4) == 4) + divider = 100.0f; + int weight = data[IDX_V20_WEIGHT_MSB] & 0xff; + weight = weight << 8 | (data[IDX_V20_WEIGHT_LSB] & 0xff); + int impedance = data[IDX_V20_IMPEDANCE_MSB] & 0xff; + impedance = impedance << 8 | (data[IDX_V20_IMPEDANCE_LSB] & 0xff); + Timber.d("Got weight: %f and impedance %f", weight / divider, impedance / 10f); + ScaleMeasurement entry = new ScaleMeasurement(); + entry.setWeight(weight / divider); + addScaleMeasurement(entry); + disconnect(); + } else if (manufacturerSpecificData.indexOfKey(MANUFACTURER_DATA_ID_V11) > -1) { + byte[] data = manufacturerSpecificData.get(MANUFACTURER_DATA_ID_V11); + float divider = 10.0f; + float extraWeight = 0; + byte checksum = 0xca ^ 0x11; // Version and magic fields are part of the checksum, but not in array + if (data == null || data.length != IDX_V11_CHECKSUM + 6 + 1) + return; + for (int i = 0; i < IDX_V11_CHECKSUM; i++) + checksum ^= data[i]; + if (data[IDX_V11_CHECKSUM] != checksum) { + Timber.d("Checksum error, got %x, expected %x", data[IDX_V11_CHECKSUM] & 0xff, checksum & 0xff); + return; + } + + int weight = data[IDX_V11_WEIGHT_MSB] & 0xff; + weight = weight << 8 | (data[IDX_V11_WEIGHT_LSB] & 0xff); + + switch ((data[IDX_V11_BODY_PROPERTIES] >> 1) & 3) { + default: + Timber.w("Invalid weight scale received, assuming 1 decimal"); + /* fall-through */ + case 0: + divider = 10.0f; + break; + case 1: + divider = 1.0f; + break; + case 2: + divider = 100.0f; + break; + } + + switch ((data[IDX_V11_BODY_PROPERTIES] >> 3) & 3) { + case 0: // kg + break; + case 1: // Jin + divider *= 2; + break; + case 3: // st & lb + extraWeight = (weight >> 8) * 6.350293f; + weight &= 0xff; + /* fall-through */ + case 2: // lb + divider *= 2.204623; + break; + } + Timber.d("Got weight: %f", weight / divider); + ScaleMeasurement entry = new ScaleMeasurement(); + entry.setWeight(extraWeight + weight / divider); + addScaleMeasurement(entry); + disconnect(); } - if ((data[IDX_FINAL] & 4) == 4) - divider = 100.0f; - int weight = data[IDX_WEIGHT_MSB] & 0xff; - weight = weight << 8 | (data[IDX_WEIGHT_LSB] & 0xff); - int impedance = data[IDX_IMPEDANCE_MSB] & 0xff; - impedance = impedance << 8 | (data[IDX_IMPEDANCE_LSB] & 0xff); - Timber.d("Got weight: %f and impedance %f", weight / divider, impedance / 10f); - ScaleMeasurement entry = new ScaleMeasurement(); - entry.setWeight(weight / divider); - addScaleMeasurement(entry); - disconnect(); } }; @@ -75,11 +134,18 @@ public class BluetoothOKOK extends BluetoothCommunication { public void connect(String macAddress) { Timber.d("Mac address: %s", macAddress); List filters = new LinkedList(); + ScanFilter.Builder b = new ScanFilter.Builder(); b.setDeviceAddress(macAddress); + b.setDeviceName("ADV"); - b.setManufacturerData(MANUFACTURER_DATA_ID, null, null); + b.setManufacturerData(MANUFACTURER_DATA_ID_V20, null, null); filters.add(b.build()); + + b.setDeviceName("Chipsea-BLE"); + b.setManufacturerData(MANUFACTURER_DATA_ID_V11, null, null); + filters.add(b.build()); + central.scanForPeripheralsUsingFilters(filters); }