From 7591d4a8afc878c9ca8ad10a6c16be5a2129668a Mon Sep 17 00:00:00 2001 From: Sergey Trofimov Date: Sun, 18 Oct 2015 18:29:41 +0300 Subject: [PATCH 1/2] Fix typo --- .../freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java index 6ac159c16..77e90f84a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java @@ -368,7 +368,7 @@ public final class BtLEQueue { try { getCallbackToUse().onCharacteristicRead(gatt, characteristic, status); } catch (Throwable ex) { - LOG.error("onCharaceristicRead: " + ex.getMessage(), ex); + LOG.error("onCharacteristicRead: " + ex.getMessage(), ex); } } checkWaitingCharacteristic(characteristic, status); From d57c6166b9a1819170a675afb55a8f4f612e0dc3 Mon Sep 17 00:00:00 2001 From: Sergey Trofimov Date: Sun, 18 Oct 2015 21:54:22 +0300 Subject: [PATCH 2/2] Fix pairing with MI1A --- .../gadgetbridge/devices/miband/UserInfo.java | 74 ++++++++--------- .../service/devices/miband/DeviceInfo.java | 79 ++++++++++++++++--- .../service/devices/miband/MiBandSupport.java | 19 ++++- 3 files changed, 115 insertions(+), 57 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/UserInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/UserInfo.java index f508f5dd6..f14515308 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/UserInfo.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/UserInfo.java @@ -1,7 +1,10 @@ package nodomain.freeyourgadget.gadgetbridge.devices.miband; +import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.DeviceInfo; import nodomain.freeyourgadget.gadgetbridge.util.CheckSums; +import java.util.Arrays; + /** * Created by UgoRaffaele on 30/01/2015. */ @@ -56,46 +59,6 @@ public class UserInfo { this.height = height; this.weight = weight; this.type = type; - - byte[] sequence = new byte[20]; - - int uid = calculateUidFrom(alias); - String normalizedAlias = ensureTenCharacters(alias); - sequence[0] = (byte) uid; - sequence[1] = (byte) (uid >>> 8); - sequence[2] = (byte) (uid >>> 16); - sequence[3] = (byte) (uid >>> 24); - - sequence[4] = (byte) (gender & 0xff); - sequence[5] = (byte) (age & 0xff); - sequence[6] = (byte) (height & 0xff); - sequence[7] = (byte) (weight & 0xff); - sequence[8] = (byte) (type & 0xff); - - for (int u = 9; u < 19; u++) - sequence[u] = normalizedAlias.getBytes()[u - 9]; - - byte[] crcSequence = new byte[19]; - System.arraycopy(sequence, 0, crcSequence, 0, crcSequence.length); - - sequence[19] = (byte) ((CheckSums.getCRC8(crcSequence) ^ Integer.parseInt(address.substring(address.length() - 2), 16)) & 0xff); - - this.data = sequence; - } - - - private String ensureTenCharacters(String alias) { - char[] result = new char[10]; - int aliasLen = alias.length(); - int maxLen = Math.min(10, alias.length()); - int diff = 10 - maxLen; - for (int i = 0; i < maxLen; i++) { - result[i + diff] = alias.charAt(i); - } - for (int i = 0; i < diff; i++) { - result[i] = '0'; - } - return new String(result); } private int calculateUidFrom(String alias) { @@ -108,7 +71,34 @@ public class UserInfo { return uid; } - public byte[] getData() { - return this.data; + public byte[] getData(DeviceInfo mDeviceInfo) { + byte[] sequence = new byte[20]; + int uid = calculateUidFrom(alias); + + sequence[0] = (byte) uid; + sequence[1] = (byte) (uid >>> 8); + sequence[2] = (byte) (uid >>> 16); + sequence[3] = (byte) (uid >>> 24); + + sequence[4] = (byte) (gender & 0xff); + sequence[5] = (byte) (age & 0xff); + sequence[6] = (byte) (height & 0xff); + sequence[7] = (byte) (weight & 0xff); + sequence[8] = (byte) (type & 0xff); + + int aliasFrom = 9; + if (mDeviceInfo.isMili1A()) { + sequence[9] = (byte) (mDeviceInfo.feature & 255); + sequence[10] = (byte) (mDeviceInfo.appearance & 255); + aliasFrom = 11; + } + + byte[] aliasBytes = alias.substring(0, Math.min(alias.length(), 19-aliasFrom)).getBytes(); + System.arraycopy(aliasBytes, 0, sequence, aliasFrom, aliasBytes.length); + + byte[] crcSequence = Arrays.copyOf(sequence, 19); + sequence[19] = (byte) ((CheckSums.getCRC8(crcSequence) ^ Integer.parseInt(this.btAddress.substring(this.btAddress.length() - 2), 16)) & 0xff); + + return sequence; } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java index 20bd1066e..8a328d041 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java @@ -1,29 +1,84 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.miband; -import java.util.Locale; - import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.util.CheckSums; + +import java.util.Locale; public class DeviceInfo extends AbstractInfo { + public final String deviceId; + public final int profileVersion; + public final int fwVersion; + public final int hwVersion; + public final int feature; + public final int appearance; + + + private boolean isChecksumCorrect(byte[] data) { + int crc8 = CheckSums.getCRC8(new byte[]{data[0], data[1], data[2], data[3], data[4], data[5], data[6]}); + return data[7] == (crc8 ^ data[3] & 255); + } + public DeviceInfo(byte[] data) { super(data); + + if ((data.length == 16 || data.length == 20) && isChecksumCorrect(data)) { + deviceId = String.format("%02X%02X%02X%02X%02X%02X%02X%02X", data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]); + profileVersion = getInt(data, 8); + fwVersion = getInt(data, 12); + hwVersion = Integer.decode("0x" + deviceId.substring(12, 14)).intValue(); + feature = Integer.decode("0x" + deviceId.substring(8, 10)).intValue(); + appearance = Integer.decode("0x" + deviceId.substring(10, 12)).intValue(); + } else { + deviceId = "crc error"; + profileVersion = -1; + fwVersion = -1; + hwVersion = -1; + feature = -1; + appearance = -1; + } + } + + public static int getInt(byte[] data, int from, int len) { + int ret = 0; + for(int i = 0; i < len; ++i) { + ret |= (data[from + i] & 255) << i * 8; + } + return ret; + } + + private int getInt(byte[] data, int from) { + return getInt(data, from, 4); } public String getHumanFirmwareVersion() { - if (mData.length == 16) { - int last = 15; - return String.format(Locale.US, "%d.%d.%d.%d", mData[last], mData[last - 1], mData[last - 2], mData[last - 3]); - } - return GBApplication.getContext().getString(R.string._unknown_); + if (fwVersion == -1) + return GBApplication.getContext().getString(R.string._unknown_); + + return String.format(Locale.US, "%d.%d.%d.%d", + fwVersion >> 24 & 255, + fwVersion >> 16 & 255, + fwVersion >> 8 & 255, + fwVersion & 255); } public int getFirmwareVersion() { - if (mData.length == 16) { - int last = 15; - return (mData[last] << 24) | (mData[last - 1] << 16) | (mData[last - 2] << 8) | mData[last - 3]; - } - return -1; + return fwVersion; + } + + @Override + public String toString() { + return "DeviceInfo{" + + "deviceId='" + deviceId + '\'' + + ", profileVersion=" + profileVersion + + ", fwVersion=" + fwVersion + + ", hwVersion=" + hwVersion + + '}'; + } + + public boolean isMili1A() { + return (this.feature & 255) == 5 && (this.appearance & 255) == 0 || (this.feature & 255) == 0 && (this.hwVersion & 255) == 208; } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java index d42760cda..f380c564e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java @@ -9,6 +9,7 @@ import android.preference.PreferenceManager; import android.support.v4.content.LocalBroadcastManager; import android.widget.Toast; +import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WriteAction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -91,13 +92,13 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { protected TransactionBuilder initializeDevice(TransactionBuilder builder) { builder.add(new SetDeviceStateAction(getDevice(), State.INITIALIZING, getContext())); pair(builder) + .requestDeviceInfo(builder) .sendUserInfo(builder) .setWearLocation(builder) .setFitnessGoal(builder) .enableNotifications(builder, true) .setCurrentTime(builder) .requestBatteryInfo(builder) - .requestDeviceInfo(builder) .setInitialized(builder); return builder; @@ -268,8 +269,19 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { */ private MiBandSupport sendUserInfo(TransactionBuilder builder) { LOG.debug("Writing User Info!"); - BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_USER_INFO); - builder.write(characteristic, MiBandCoordinator.getAnyUserInfo(getDevice().getAddress()).getData()); + builder.add(new BtLEAction(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_USER_INFO)) { + @Override + public boolean expectsResult() { + return true; + } + + @Override + public boolean run(BluetoothGatt gatt) { + return new WriteAction(getCharacteristic(), + MiBandCoordinator.getAnyUserInfo(getDevice().getAddress()).getData(mDeviceInfo) + ).run(gatt); + } + }); return this; } @@ -749,6 +761,7 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { private void handleDeviceInfo(byte[] value, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { mDeviceInfo = new DeviceInfo(value); + LOG.warn(mDeviceInfo.toString()); versionCmd.fwVersion = mDeviceInfo.getHumanFirmwareVersion(); handleGBDeviceEvent(versionCmd); }