Improved Mi Band 2 support #323
- connecting works and is stable - firmware and hardware version is displayed - time is set
This commit is contained in:
parent
696611d392
commit
ccdb843b6e
|
@ -23,7 +23,7 @@ public class MiBand2Service {
|
||||||
public static final UUID UUID_UNKNOWN_CHARACTERISTIC7 = UUID.fromString("00000007-0000-3512-2118-0009af100700");
|
public static final UUID UUID_UNKNOWN_CHARACTERISTIC7 = UUID.fromString("00000007-0000-3512-2118-0009af100700");
|
||||||
public static final UUID UUID_UNKNOWN_CHARACTERISTIC8 = UUID.fromString("00000008-0000-3512-2118-0009af100700");
|
public static final UUID UUID_UNKNOWN_CHARACTERISTIC8 = UUID.fromString("00000008-0000-3512-2118-0009af100700");
|
||||||
// service uuid fee1
|
// service uuid fee1
|
||||||
public static final UUID UUID_UNKNOWN_CHARACTERISTIC9 = UUID.fromString("00000009-0000-3512-2118-0009af100700");
|
public static final UUID UUID_CHARACTERISTIC_AUTH = UUID.fromString("00000009-0000-3512-2118-0009af100700");
|
||||||
public static final UUID UUID_UNKNOWN_CHARACTERISTIC10 = UUID.fromString("00000010-0000-3512-2118-0009af100700");
|
public static final UUID UUID_UNKNOWN_CHARACTERISTIC10 = UUID.fromString("00000010-0000-3512-2118-0009af100700");
|
||||||
|
|
||||||
// set metric distance
|
// set metric distance
|
||||||
|
@ -233,6 +233,47 @@ public class MiBand2Service {
|
||||||
|
|
||||||
private static final Map<UUID, String> MIBAND_DEBUG;
|
private static final Map<UUID, String> MIBAND_DEBUG;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mi Band 2 authentication has three steps.
|
||||||
|
* This is step 1: sending a "secret" key to the band.
|
||||||
|
* This is byte 0, followed by {@link #AUTH_BYTE} and then the key.
|
||||||
|
* In the response, it is byte 1 in the byte[] value.
|
||||||
|
*/
|
||||||
|
public static final byte AUTH_SEND_KEY = 0x01;
|
||||||
|
/**
|
||||||
|
* Mi Band 2 authentication has three steps.
|
||||||
|
* This is step 2: requesting a random authentication key from the band.
|
||||||
|
* This is byte 0, followed by {@link #AUTH_BYTE}.
|
||||||
|
* In the response, it is byte 1 in the byte[] value.
|
||||||
|
*/
|
||||||
|
public static final byte AUTH_REQUEST_RANDOM_AUTH_NUMBER = 0x02;
|
||||||
|
/**
|
||||||
|
* Mi Band 2 authentication has three steps.
|
||||||
|
* This is step 3: sending the encrypted random authentication key to the band.
|
||||||
|
* This is byte 0, followed by {@link #AUTH_BYTE} and then the encrypted random authentication key.
|
||||||
|
* In the response, it is byte 1 in the byte[] value.
|
||||||
|
*/
|
||||||
|
public static final byte AUTH_SEND_ENCRYPTED_AUTH_NUMBER = 0x03;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Received in response to any authentication requests (byte 0 in the byte[] value.
|
||||||
|
*/
|
||||||
|
public static final byte AUTH_RESPONSE = 0x10;
|
||||||
|
/**
|
||||||
|
* Receeived in response to any authentication requests (byte 2 in the byte[] value.
|
||||||
|
* 0x01 means success.
|
||||||
|
*/
|
||||||
|
public static final byte AUTH_SUCCESS = 0x01;
|
||||||
|
/**
|
||||||
|
* Received in response to any authentication requests (byte 2 in the byte[] value.
|
||||||
|
* 0x04 means failure.
|
||||||
|
*/
|
||||||
|
public static final byte AUTH_FAIL = 0x04;
|
||||||
|
/**
|
||||||
|
* In some logs it's 0x0...
|
||||||
|
*/
|
||||||
|
public static final byte AUTH_BYTE = 0x8;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
MIBAND_DEBUG = new HashMap<>();
|
MIBAND_DEBUG = new HashMap<>();
|
||||||
MIBAND_DEBUG.put(UUID_SERVICE_MIBAND_SERVICE, "MiBand Service");
|
MIBAND_DEBUG.put(UUID_SERVICE_MIBAND_SERVICE, "MiBand Service");
|
||||||
|
|
|
@ -76,7 +76,7 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected TransactionBuilder createTransactionBuilder(String taskName) {
|
public TransactionBuilder createTransactionBuilder(String taskName) {
|
||||||
return new TransactionBuilder(taskName);
|
return new TransactionBuilder(taskName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* @see {@link #performInitialized(String)}
|
* @see {@link #performInitialized(String)}
|
||||||
*/
|
*/
|
||||||
protected void performConnected(Transaction transaction) throws IOException {
|
public void performConnected(Transaction transaction) throws IOException {
|
||||||
if (!isConnected()) {
|
if (!isConnected()) {
|
||||||
if (!connect()) {
|
if (!connect()) {
|
||||||
throw new IOException("2: Unable to connect to device: " + getDevice());
|
throw new IOException("2: Unable to connect to device: " + getDevice());
|
||||||
|
@ -119,6 +119,19 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
|
||||||
getQueue().add(transaction);
|
getQueue().add(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the actions of the given transaction as soon as possible,
|
||||||
|
* that is, before any other queued transactions, but after the actions
|
||||||
|
* of the currently executing transaction.
|
||||||
|
* @param builder
|
||||||
|
*/
|
||||||
|
public void performImmediately(TransactionBuilder builder) throws IOException {
|
||||||
|
if (!isConnected()) {
|
||||||
|
throw new IOException("Not connected to device: " + getDevice());
|
||||||
|
}
|
||||||
|
getQueue().insert(builder.getTransaction());
|
||||||
|
}
|
||||||
|
|
||||||
public BtLEQueue getQueue() {
|
public BtLEQueue getQueue() {
|
||||||
return mQueue;
|
return mQueue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import android.support.annotation.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
@ -258,6 +259,23 @@ public final class BtLEQueue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a transaction to the beginning of the queue.
|
||||||
|
* Note that actions of the *currently executing* transaction
|
||||||
|
* will still be executed before the given transaction.
|
||||||
|
*
|
||||||
|
* @param transaction
|
||||||
|
*/
|
||||||
|
public void insert(Transaction transaction) {
|
||||||
|
LOG.debug("about to insert: " + transaction);
|
||||||
|
if (!transaction.isEmpty()) {
|
||||||
|
List<Transaction> tail = new ArrayList<>(mTransactions.size() + 2);
|
||||||
|
mTransactions.drainTo(tail);
|
||||||
|
mTransactions.add(transaction);
|
||||||
|
mTransactions.addAll(tail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void clear() {
|
public void clear() {
|
||||||
mTransactions.clear();
|
mTransactions.clear();
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,9 +22,6 @@ import java.util.GregorianCalendar;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
|
||||||
|
@ -57,8 +54,8 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.AbortTransactio
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.ConditionalWriteAction;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.ConditionalWriteAction;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WriteAction;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WriteAction;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfoProfile;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations.InitOperation;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||||
|
@ -90,29 +87,19 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(MiBand2Support.class);
|
private static final Logger LOG = LoggerFactory.getLogger(MiBand2Support.class);
|
||||||
private final DeviceInfoProfile<MiBand2Support> deviceInfoProfile;
|
private final DeviceInfoProfile<MiBand2Support> deviceInfoProfile;
|
||||||
private final BatteryInfoProfile<MiBand2Support> batteryInfoProfile;
|
|
||||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
String s = intent.getAction();
|
String s = intent.getAction();
|
||||||
if (s.equals(DeviceInfoProfile.ACTION_DEVICE_INFO)) {
|
if (s.equals(DeviceInfoProfile.ACTION_DEVICE_INFO)) {
|
||||||
handleDeviceInfo((nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo) intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO));
|
handleDeviceInfo((nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo) intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO));
|
||||||
|
|
||||||
} else if (s.equals(BatteryInfoProfile.ACTION_BATTERY_INFO)) {
|
|
||||||
handleBatteryInfo((nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfo) intent.getParcelableExtra(BatteryInfoProfile.EXTRA_BATTERY_INFO));
|
|
||||||
} else if (s.equals(DeviceService.ACTION_MIBAND2_AUTH)) {
|
|
||||||
byte[] response = intent.getExtras().getByteArray(DeviceService.EXTRA_MIBAND2_AUTH_BYTE);
|
|
||||||
BluetoothGattCharacteristic temp = getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC9);
|
|
||||||
temp.setValue(response);
|
|
||||||
mBluetoothGatt.writeCharacteristic(temp);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private boolean needsAuth;
|
||||||
private volatile boolean telephoneRinging;
|
private volatile boolean telephoneRinging;
|
||||||
private volatile boolean isLocatingDevice;
|
private volatile boolean isLocatingDevice;
|
||||||
private BluetoothGatt mBluetoothGatt;
|
|
||||||
|
|
||||||
private DeviceInfo mDeviceInfo;
|
private DeviceInfo mDeviceInfo;
|
||||||
|
|
||||||
|
@ -131,14 +118,11 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||||
addSupportedService(MiBandService.UUID_SERVICE_MIBAND2_SERVICE);
|
addSupportedService(MiBandService.UUID_SERVICE_MIBAND2_SERVICE);
|
||||||
|
|
||||||
deviceInfoProfile = new DeviceInfoProfile<>(this);
|
deviceInfoProfile = new DeviceInfoProfile<>(this);
|
||||||
batteryInfoProfile = new BatteryInfoProfile<>(this);
|
|
||||||
addSupportedProfile(deviceInfoProfile);
|
addSupportedProfile(deviceInfoProfile);
|
||||||
addSupportedProfile(batteryInfoProfile);
|
|
||||||
|
|
||||||
LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext());
|
LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext());
|
||||||
IntentFilter intentFilter = new IntentFilter();
|
IntentFilter intentFilter = new IntentFilter();
|
||||||
intentFilter.addAction(DeviceInfoProfile.ACTION_DEVICE_INFO);
|
intentFilter.addAction(DeviceInfoProfile.ACTION_DEVICE_INFO);
|
||||||
intentFilter.addAction(BatteryInfoProfile.ACTION_BATTERY_INFO);
|
|
||||||
intentFilter.addAction(DeviceService.ACTION_MIBAND2_AUTH);
|
intentFilter.addAction(DeviceService.ACTION_MIBAND2_AUTH);
|
||||||
broadcastManager.registerReceiver(mReceiver, intentFilter);
|
broadcastManager.registerReceiver(mReceiver, intentFilter);
|
||||||
}
|
}
|
||||||
|
@ -152,15 +136,21 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
|
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
|
||||||
builder.add(new SetDeviceStateAction(getDevice(), State.INITIALIZING, getContext()));
|
try {
|
||||||
enableNotifications(builder, true)
|
boolean authenticate = needsAuth;
|
||||||
.setLowLatency(builder)
|
needsAuth = false;
|
||||||
.readDate(builder) // without reading the data, we get sporadic connection problems, especially directly after turning on BT
|
new InitOperation(authenticate, this, builder).perform();
|
||||||
|
} catch (IOException e) {
|
||||||
|
GB.toast(getContext(), "Initializing Mi Band 2 failed", Toast.LENGTH_SHORT, GB.ERROR, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// builder.add(new SetDeviceStateAction(getDevice(), State.INITIALIZING, getContext()));
|
||||||
|
// enableNotifications(builder, true)
|
||||||
|
// .setLowLatency(builder)
|
||||||
|
// .readDate(builder) // without reading the data, we get sporadic connection problems, especially directly after turning on BT
|
||||||
// this is apparently not needed anymore, and actually causes problems when bonding is not used/does not work
|
// this is apparently not needed anymore, and actually causes problems when bonding is not used/does not work
|
||||||
// so we simply not use the UUID_PAIR characteristic.
|
// so we simply not use the UUID_PAIR characteristic.
|
||||||
// .pair(builder)
|
// .pair(builder)
|
||||||
.testInit(builder)
|
|
||||||
.setInitialized(builder);
|
|
||||||
//.requestDeviceInfo(builder)
|
//.requestDeviceInfo(builder)
|
||||||
//.requestBatteryInfo(builder);
|
//.requestBatteryInfo(builder);
|
||||||
// .sendUserInfo(builder)
|
// .sendUserInfo(builder)
|
||||||
|
@ -176,27 +166,13 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MiBand2Support testInit(TransactionBuilder builder) {
|
|
||||||
//builder.read(getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC6)); // example read value: 0f6200e0070804072b2c20e00708040625372064
|
|
||||||
//builder.read(getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC7)); // example read value: 0019000000
|
|
||||||
setCurrentTimeWithService(builder);
|
|
||||||
// write key to miband2
|
|
||||||
//builder.write(getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC9), new byte[] { 0x01, 0x08, (byte) 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45 });
|
|
||||||
// get random auth number
|
|
||||||
builder.write(getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC9), new byte[] { 0x02 , 0x08});
|
|
||||||
//builder.read(getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC6)); // probably superfluous
|
|
||||||
//builder.write(getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC8), new byte[] { 0x20, 0x00, 0x00, 0x02 });
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// private MiBand2Support maybeAuth(TransactionBuilder builder) {
|
// private MiBand2Support maybeAuth(TransactionBuilder builder) {
|
||||||
// builder.write(getCharacteristic(MiBand2Service.UUID_UNKNOQN_CHARACTERISTIC0), new byte[] {0x20, 0x00});
|
// builder.write(getCharacteristic(MiBand2Service.UUID_UNKNOQN_CHARACTERISTIC0), new byte[] {0x20, 0x00});
|
||||||
// builder.write(getCharacteristic(MiBand2Service.UUID_UNKNOQN_CHARACTERISTIC0), new byte[] {0x03,0x00,(byte)0x8e,(byte)0xce,0x5a,0x09,(byte)0xb3,(byte)0xd8,0x55,0x57,0x10,0x2a,(byte)0xed,0x7d,0x6b,0x78,(byte)0xc5,(byte)0xd2});
|
// builder.write(getCharacteristic(MiBand2Service.UUID_UNKNOQN_CHARACTERISTIC0), new byte[] {0x03,0x00,(byte)0x8e,(byte)0xce,0x5a,0x09,(byte)0xb3,(byte)0xd8,0x55,0x57,0x10,0x2a,(byte)0xed,0x7d,0x6b,0x78,(byte)0xc5,(byte)0xd2});
|
||||||
// return this;
|
// return this;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
private MiBand2Support setCurrentTimeWithService(TransactionBuilder builder) {
|
public MiBand2Support setCurrentTimeWithService(TransactionBuilder builder) {
|
||||||
GregorianCalendar now = BLETypeConversions.createCalendar();
|
GregorianCalendar now = BLETypeConversions.createCalendar();
|
||||||
byte[] bytes = BLETypeConversions.calendarToRawBytes(now, true);
|
byte[] bytes = BLETypeConversions.calendarToRawBytes(now, true);
|
||||||
byte[] tail = new byte[] { 0, BLETypeConversions.mapTimeZone(now.getTimeZone()) }; // 0 = adjust reason bitflags? or DST offset?? , timezone
|
byte[] tail = new byte[] { 0, BLETypeConversions.mapTimeZone(now.getTimeZone()) }; // 0 = adjust reason bitflags? or DST offset?? , timezone
|
||||||
|
@ -241,17 +217,17 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||||
*
|
*
|
||||||
* @param builder
|
* @param builder
|
||||||
*/
|
*/
|
||||||
private void setInitialized(TransactionBuilder builder) {
|
public void setInitialized(TransactionBuilder builder) {
|
||||||
builder.add(new SetDeviceStateAction(getDevice(), State.INITIALIZED, getContext()));
|
builder.add(new SetDeviceStateAction(getDevice(), State.INITIALIZED, getContext()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// MB2: AVL
|
// MB2: AVL
|
||||||
// TODO: tear down the notifications on quit
|
// TODO: tear down the notifications on quit
|
||||||
private MiBand2Support enableNotifications(TransactionBuilder builder, boolean enable) {
|
public MiBand2Support enableNotifications(TransactionBuilder builder, boolean enable) {
|
||||||
builder.notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_NOTIFICATION), enable);
|
builder.notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_NOTIFICATION), enable);
|
||||||
builder.notify(getCharacteristic(GattService.UUID_SERVICE_CURRENT_TIME), enable);
|
builder.notify(getCharacteristic(GattService.UUID_SERVICE_CURRENT_TIME), enable);
|
||||||
// Notify CHARACTERISTIC9 to receive random auth code
|
// Notify CHARACTERISTIC9 to receive random auth code
|
||||||
builder.notify(getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC9), enable);
|
builder.notify(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_AUTH), enable);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,6 +252,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void pair() {
|
public void pair() {
|
||||||
|
needsAuth = true;
|
||||||
for (int i = 0; i < 5; i++) {
|
for (int i = 0; i < 5; i++) {
|
||||||
if (connect()) {
|
if (connect()) {
|
||||||
return;
|
return;
|
||||||
|
@ -365,13 +342,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MiBand2Support requestBatteryInfo(TransactionBuilder builder) {
|
public MiBand2Support requestDeviceInfo(TransactionBuilder builder) {
|
||||||
LOG.debug("Requesting Battery Info!");
|
|
||||||
batteryInfoProfile.requestBatteryInfo(builder);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MiBand2Support requestDeviceInfo(TransactionBuilder builder) {
|
|
||||||
LOG.debug("Requesting Device Info!");
|
LOG.debug("Requesting Device Info!");
|
||||||
deviceInfoProfile.requestDeviceInfo(builder);
|
deviceInfoProfile.requestDeviceInfo(builder);
|
||||||
return this;
|
return this;
|
||||||
|
@ -402,27 +373,6 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||||
return this;
|
return this;
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
/**
|
|
||||||
* Part of device initialization process. Do not call manually.
|
|
||||||
*
|
|
||||||
* @param transaction
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private MiBand2Support pair(TransactionBuilder transaction) {
|
|
||||||
LOG.info("Attempting to pair MI device...");
|
|
||||||
//BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_PAIR);
|
|
||||||
// write key to miband2
|
|
||||||
BluetoothGattCharacteristic characteristic = getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC9);
|
|
||||||
if (characteristic != null) {
|
|
||||||
transaction.write(characteristic, new byte[] { 0x01, 0x08, (byte) 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45 });
|
|
||||||
//transaction.write(characteristic, new byte[] { 0x02, 0x08 });
|
|
||||||
LOG.info("Pair write");
|
|
||||||
} else {
|
|
||||||
LOG.info("Unable to pair MI device -- characteristic not available");
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Part of device initialization process. Do not call manually.
|
* Part of device initialization process. Do not call manually.
|
||||||
*
|
*
|
||||||
|
@ -887,7 +837,6 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||||
public boolean onCharacteristicChanged(BluetoothGatt gatt,
|
public boolean onCharacteristicChanged(BluetoothGatt gatt,
|
||||||
BluetoothGattCharacteristic characteristic) {
|
BluetoothGattCharacteristic characteristic) {
|
||||||
super.onCharacteristicChanged(gatt, characteristic);
|
super.onCharacteristicChanged(gatt, characteristic);
|
||||||
mBluetoothGatt = gatt;
|
|
||||||
|
|
||||||
UUID characteristicUUID = characteristic.getUuid();
|
UUID characteristicUUID = characteristic.getUuid();
|
||||||
if (MiBandService.UUID_CHARACTERISTIC_BATTERY.equals(characteristicUUID)) {
|
if (MiBandService.UUID_CHARACTERISTIC_BATTERY.equals(characteristicUUID)) {
|
||||||
|
@ -908,18 +857,10 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||||
// } else if (MiBand2Service.UUID_UNKNOQN_CHARACTERISTIC0.equals(characteristicUUID)) {
|
// } else if (MiBand2Service.UUID_UNKNOQN_CHARACTERISTIC0.equals(characteristicUUID)) {
|
||||||
// handleUnknownCharacteristic(characteristic.getValue());
|
// handleUnknownCharacteristic(characteristic.getValue());
|
||||||
// return true;
|
// return true;
|
||||||
} else if (MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC9.equals(characteristicUUID)) {
|
} else if (MiBand2Service.UUID_CHARACTERISTIC_AUTH.equals(characteristicUUID)) {
|
||||||
|
LOG.info("AUTHENTICATION?? " + characteristicUUID);
|
||||||
logMessageContent(characteristic.getValue());
|
logMessageContent(characteristic.getValue());
|
||||||
if (characteristic.getValue()[0] == 0x10 &&
|
return true;
|
||||||
characteristic.getValue()[1] == 0x02 &&
|
|
||||||
characteristic.getValue()[2] == 0x01) {
|
|
||||||
byte[] eValue = handleAESAuth(characteristic.getValue());
|
|
||||||
byte[] responseValue = org.apache.commons.lang3.ArrayUtils.addAll(new byte[] {0x03, 0x08}, eValue);
|
|
||||||
Intent intent = new Intent(DeviceService.ACTION_MIBAND2_AUTH)
|
|
||||||
.putExtra(DeviceService.EXTRA_MIBAND2_AUTH_BYTE, responseValue);
|
|
||||||
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
|
|
||||||
}
|
|
||||||
//
|
|
||||||
} else {
|
} else {
|
||||||
LOG.info("Unhandled characteristic changed: " + characteristicUUID);
|
LOG.info("Unhandled characteristic changed: " + characteristicUUID);
|
||||||
logMessageContent(characteristic.getValue());
|
logMessageContent(characteristic.getValue());
|
||||||
|
@ -972,7 +913,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||||
} else if (MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT.equals(characteristicUUID)) {
|
} else if (MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT.equals(characteristicUUID)) {
|
||||||
handleControlPointResult(characteristic.getValue(), status);
|
handleControlPointResult(characteristic.getValue(), status);
|
||||||
return true;
|
return true;
|
||||||
} else if (MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC9.equals(characteristicUUID)) {
|
} else if (MiBand2Service.UUID_CHARACTERISTIC_AUTH.equals(characteristicUUID)) {
|
||||||
LOG.info("KEY AES SEND");
|
LOG.info("KEY AES SEND");
|
||||||
logMessageContent(characteristic.getValue());
|
logMessageContent(characteristic.getValue());
|
||||||
return true;
|
return true;
|
||||||
|
@ -1039,22 +980,6 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||||
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
|
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] handleAESAuth(byte[] value) {
|
|
||||||
byte[] mValue = Arrays.copyOfRange(value, 3, 19);
|
|
||||||
try {
|
|
||||||
Cipher ecipher = null;
|
|
||||||
byte[] sRandom = {0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45};
|
|
||||||
ecipher = Cipher.getInstance("AES/ECB/NoPadding");
|
|
||||||
SecretKeySpec newKey = new SecretKeySpec(sRandom, "AES");
|
|
||||||
ecipher.init(Cipher.ENCRYPT_MODE, newKey);
|
|
||||||
byte[] enc = ecipher.doFinal(mValue);
|
|
||||||
return enc;
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* React to unsolicited messages sent by the Mi Band to the MiBandService.UUID_CHARACTERISTIC_NOTIFICATION
|
* React to unsolicited messages sent by the Mi Band to the MiBandService.UUID_CHARACTERISTIC_NOTIFICATION
|
||||||
* characteristic,
|
* characteristic,
|
||||||
|
@ -1185,19 +1110,11 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||||
// }
|
// }
|
||||||
LOG.warn("Device info: " + info);
|
LOG.warn("Device info: " + info);
|
||||||
versionCmd.hwVersion = info.getHardwareRevision();
|
versionCmd.hwVersion = info.getHardwareRevision();
|
||||||
versionCmd.fwVersion = info.getFirmwareRevision();
|
// versionCmd.fwVersion = info.getFirmwareRevision(); // always null
|
||||||
|
versionCmd.fwVersion = info.getSoftwareRevision();
|
||||||
handleGBDeviceEvent(versionCmd);
|
handleGBDeviceEvent(versionCmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleBatteryInfo(nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfo info) {
|
|
||||||
batteryCmd.level = (short) info.getPercentCharged();
|
|
||||||
// batteryCmd.state = info.getState();
|
|
||||||
// batteryCmd.lastChargeTime = info.getLastChargeTime();
|
|
||||||
// batteryCmd.numCharges = info.getNumCharges();
|
|
||||||
handleGBDeviceEvent(batteryCmd);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void handleBatteryInfo(byte[] value, int status) {
|
private void handleBatteryInfo(byte[] value, int status) {
|
||||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||||
BatteryInfo info = new BatteryInfo(value);
|
BatteryInfo info = new BatteryInfo(value);
|
||||||
|
|
|
@ -0,0 +1,138 @@
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGatt;
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import javax.crypto.BadPaddingException;
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
|
import javax.crypto.NoSuchPaddingException;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBand2Support;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
|
public class InitOperation extends AbstractBTLEOperation<MiBand2Support> {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(InitOperation.class);
|
||||||
|
|
||||||
|
private final TransactionBuilder builder;
|
||||||
|
private final boolean needsAuth;
|
||||||
|
|
||||||
|
public InitOperation(boolean needsAuth, MiBand2Support support, TransactionBuilder builder) {
|
||||||
|
super(support);
|
||||||
|
this.needsAuth = needsAuth;
|
||||||
|
this.builder = builder;
|
||||||
|
builder.setGattCallback(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doPerform() throws IOException {
|
||||||
|
getSupport().enableNotifications(builder, true);
|
||||||
|
if (needsAuth) {
|
||||||
|
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.AUTHENTICATING, getContext()));
|
||||||
|
// write key to miband2
|
||||||
|
byte[] sendKey = org.apache.commons.lang3.ArrayUtils.addAll(new byte[]{MiBand2Service.AUTH_SEND_KEY, MiBand2Service.AUTH_BYTE}, getSecretKey());
|
||||||
|
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_AUTH), sendKey);
|
||||||
|
} else {
|
||||||
|
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
|
||||||
|
// get random auth number
|
||||||
|
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_AUTH), requestAuthNumber());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] requestAuthNumber() {
|
||||||
|
return new byte[]{MiBand2Service.AUTH_REQUEST_RANDOM_AUTH_NUMBER, MiBand2Service.AUTH_BYTE};
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] getSecretKey() {
|
||||||
|
return new byte[]{0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransactionBuilder performInitialized(String taskName) throws IOException {
|
||||||
|
throw new UnsupportedOperationException("This IS the initialization class, you cannot call this method");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCharacteristicChanged(BluetoothGatt gatt,
|
||||||
|
BluetoothGattCharacteristic characteristic) {
|
||||||
|
UUID characteristicUUID = characteristic.getUuid();
|
||||||
|
if (MiBand2Service.UUID_CHARACTERISTIC_AUTH.equals(characteristicUUID)) {
|
||||||
|
try {
|
||||||
|
byte[] value = characteristic.getValue();
|
||||||
|
getSupport().logMessageContent(value);
|
||||||
|
if (value[0] == MiBand2Service.AUTH_RESPONSE &&
|
||||||
|
value[1] == MiBand2Service.AUTH_SEND_KEY &&
|
||||||
|
value[2] == MiBand2Service.AUTH_SUCCESS) {
|
||||||
|
TransactionBuilder builder = createTransactionBuilder("Sending the secret key to the band");
|
||||||
|
builder.write(characteristic, requestAuthNumber());
|
||||||
|
getSupport().performImmediately(builder);
|
||||||
|
} else if (value[0] == MiBand2Service.AUTH_RESPONSE &&
|
||||||
|
value[1] == MiBand2Service.AUTH_REQUEST_RANDOM_AUTH_NUMBER &&
|
||||||
|
value[2] == MiBand2Service.AUTH_SUCCESS) {
|
||||||
|
// md5??
|
||||||
|
byte[] eValue = handleAESAuth(value, getSecretKey());
|
||||||
|
byte[] responseValue = org.apache.commons.lang3.ArrayUtils.addAll(
|
||||||
|
new byte[]{MiBand2Service.AUTH_SEND_ENCRYPTED_AUTH_NUMBER, MiBand2Service.AUTH_BYTE}, eValue);
|
||||||
|
|
||||||
|
TransactionBuilder builder = createTransactionBuilder("Sending the encrypted random key to the band");
|
||||||
|
builder.write(characteristic, responseValue);
|
||||||
|
getSupport().setCurrentTimeWithService(builder);
|
||||||
|
getSupport().performImmediately(builder);
|
||||||
|
} else if (value[0] == MiBand2Service.AUTH_RESPONSE &&
|
||||||
|
value[1] == MiBand2Service.AUTH_SEND_ENCRYPTED_AUTH_NUMBER &&
|
||||||
|
value[2] == MiBand2Service.AUTH_SUCCESS) {
|
||||||
|
TransactionBuilder builder = createTransactionBuilder("Sending the encrypted random key to the band");
|
||||||
|
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
|
||||||
|
getSupport().requestDeviceInfo(builder);
|
||||||
|
getSupport().setInitialized(builder);
|
||||||
|
getSupport().performImmediately(builder);
|
||||||
|
} else {
|
||||||
|
return super.onCharacteristicChanged(gatt, characteristic);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
GB.toast(getContext(), "Error authenticating Mi Band 2", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
LOG.info("Unhandled characteristic changed: " + characteristicUUID);
|
||||||
|
return super.onCharacteristicChanged(gatt, characteristic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TransactionBuilder createTransactionBuilder(String task) {
|
||||||
|
TransactionBuilder builder = getSupport().createTransactionBuilder(task);
|
||||||
|
builder.setGattCallback(this);
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] getMD5(byte[] message) throws NoSuchAlgorithmException {
|
||||||
|
MessageDigest md5 = MessageDigest.getInstance("MD5");
|
||||||
|
return md5.digest(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] handleAESAuth(byte[] value, byte[] secretKey) throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException {
|
||||||
|
byte[] mValue = Arrays.copyOfRange(value, 3, 19);
|
||||||
|
Cipher ecipher = Cipher.getInstance("AES/ECB/NoPadding");
|
||||||
|
SecretKeySpec newKey = new SecretKeySpec(secretKey, "AES");
|
||||||
|
ecipher.init(Cipher.ENCRYPT_MODE, newKey);
|
||||||
|
byte[] enc = ecipher.doFinal(mValue);
|
||||||
|
return enc;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue