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_CHARACTERISTIC8 = UUID.fromString("00000008-0000-3512-2118-0009af100700");
|
||||
// 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");
|
||||
|
||||
// set metric distance
|
||||
|
@ -233,6 +233,47 @@ public class MiBand2Service {
|
|||
|
||||
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 {
|
||||
MIBAND_DEBUG = new HashMap<>();
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -110,7 +110,7 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
|
|||
* @throws IOException
|
||||
* @see {@link #performInitialized(String)}
|
||||
*/
|
||||
protected void performConnected(Transaction transaction) throws IOException {
|
||||
public void performConnected(Transaction transaction) throws IOException {
|
||||
if (!isConnected()) {
|
||||
if (!connect()) {
|
||||
throw new IOException("2: Unable to connect to device: " + getDevice());
|
||||
|
@ -119,6 +119,19 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
|
|||
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() {
|
||||
return mQueue;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import android.support.annotation.Nullable;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
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() {
|
||||
mTransactions.clear();
|
||||
}
|
||||
|
|
|
@ -22,9 +22,6 @@ import java.util.GregorianCalendar;
|
|||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
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.SetDeviceStateAction;
|
||||
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.devices.miband2.operations.InitOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
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 final DeviceInfoProfile<MiBand2Support> deviceInfoProfile;
|
||||
private final BatteryInfoProfile<MiBand2Support> batteryInfoProfile;
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String s = intent.getAction();
|
||||
if (s.equals(DeviceInfoProfile.ACTION_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 isLocatingDevice;
|
||||
private BluetoothGatt mBluetoothGatt;
|
||||
|
||||
private DeviceInfo mDeviceInfo;
|
||||
|
||||
|
@ -131,14 +118,11 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
|||
addSupportedService(MiBandService.UUID_SERVICE_MIBAND2_SERVICE);
|
||||
|
||||
deviceInfoProfile = new DeviceInfoProfile<>(this);
|
||||
batteryInfoProfile = new BatteryInfoProfile<>(this);
|
||||
addSupportedProfile(deviceInfoProfile);
|
||||
addSupportedProfile(batteryInfoProfile);
|
||||
|
||||
LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext());
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(DeviceInfoProfile.ACTION_DEVICE_INFO);
|
||||
intentFilter.addAction(BatteryInfoProfile.ACTION_BATTERY_INFO);
|
||||
intentFilter.addAction(DeviceService.ACTION_MIBAND2_AUTH);
|
||||
broadcastManager.registerReceiver(mReceiver, intentFilter);
|
||||
}
|
||||
|
@ -152,15 +136,21 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
|||
|
||||
@Override
|
||||
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
|
||||
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
|
||||
try {
|
||||
boolean authenticate = needsAuth;
|
||||
needsAuth = false;
|
||||
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
|
||||
// so we simply not use the UUID_PAIR characteristic.
|
||||
// .pair(builder)
|
||||
.testInit(builder)
|
||||
.setInitialized(builder);
|
||||
//.requestDeviceInfo(builder)
|
||||
//.requestBatteryInfo(builder);
|
||||
// .sendUserInfo(builder)
|
||||
|
@ -176,27 +166,13 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
|||
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) {
|
||||
// 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});
|
||||
// return this;
|
||||
// }
|
||||
|
||||
private MiBand2Support setCurrentTimeWithService(TransactionBuilder builder) {
|
||||
public MiBand2Support setCurrentTimeWithService(TransactionBuilder builder) {
|
||||
GregorianCalendar now = BLETypeConversions.createCalendar();
|
||||
byte[] bytes = BLETypeConversions.calendarToRawBytes(now, true);
|
||||
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
|
||||
*/
|
||||
private void setInitialized(TransactionBuilder builder) {
|
||||
public void setInitialized(TransactionBuilder builder) {
|
||||
builder.add(new SetDeviceStateAction(getDevice(), State.INITIALIZED, getContext()));
|
||||
}
|
||||
|
||||
// MB2: AVL
|
||||
// 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(GattService.UUID_SERVICE_CURRENT_TIME), enable);
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
@ -276,6 +252,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
|||
|
||||
@Override
|
||||
public void pair() {
|
||||
needsAuth = true;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (connect()) {
|
||||
return;
|
||||
|
@ -365,13 +342,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
|||
return this;
|
||||
}
|
||||
|
||||
private MiBand2Support requestBatteryInfo(TransactionBuilder builder) {
|
||||
LOG.debug("Requesting Battery Info!");
|
||||
batteryInfoProfile.requestBatteryInfo(builder);
|
||||
return this;
|
||||
}
|
||||
|
||||
private MiBand2Support requestDeviceInfo(TransactionBuilder builder) {
|
||||
public MiBand2Support requestDeviceInfo(TransactionBuilder builder) {
|
||||
LOG.debug("Requesting Device Info!");
|
||||
deviceInfoProfile.requestDeviceInfo(builder);
|
||||
return this;
|
||||
|
@ -402,27 +373,6 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
|||
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.
|
||||
*
|
||||
|
@ -887,7 +837,6 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
|||
public boolean onCharacteristicChanged(BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic) {
|
||||
super.onCharacteristicChanged(gatt, characteristic);
|
||||
mBluetoothGatt = gatt;
|
||||
|
||||
UUID characteristicUUID = characteristic.getUuid();
|
||||
if (MiBandService.UUID_CHARACTERISTIC_BATTERY.equals(characteristicUUID)) {
|
||||
|
@ -908,18 +857,10 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
|||
// } else if (MiBand2Service.UUID_UNKNOQN_CHARACTERISTIC0.equals(characteristicUUID)) {
|
||||
// handleUnknownCharacteristic(characteristic.getValue());
|
||||
// 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());
|
||||
if (characteristic.getValue()[0] == 0x10 &&
|
||||
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);
|
||||
}
|
||||
//
|
||||
return true;
|
||||
} else {
|
||||
LOG.info("Unhandled characteristic changed: " + characteristicUUID);
|
||||
logMessageContent(characteristic.getValue());
|
||||
|
@ -972,7 +913,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
|||
} else if (MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT.equals(characteristicUUID)) {
|
||||
handleControlPointResult(characteristic.getValue(), status);
|
||||
return true;
|
||||
} else if (MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC9.equals(characteristicUUID)) {
|
||||
} else if (MiBand2Service.UUID_CHARACTERISTIC_AUTH.equals(characteristicUUID)) {
|
||||
LOG.info("KEY AES SEND");
|
||||
logMessageContent(characteristic.getValue());
|
||||
return true;
|
||||
|
@ -1039,22 +980,6 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
|||
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
|
||||
* characteristic,
|
||||
|
@ -1185,19 +1110,11 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
|||
// }
|
||||
LOG.warn("Device info: " + info);
|
||||
versionCmd.hwVersion = info.getHardwareRevision();
|
||||
versionCmd.fwVersion = info.getFirmwareRevision();
|
||||
// versionCmd.fwVersion = info.getFirmwareRevision(); // always null
|
||||
versionCmd.fwVersion = info.getSoftwareRevision();
|
||||
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) {
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
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