小米手环蓝牙协议研究

上篇文章通过leScan获取rssi实现蓝牙测距, 利用这个可以做很多好玩的东西, 比如手机防丢. 那这样当手机和手环离开一定距离时, 手机和手环都发出警报.

手机的好做, 发出声音, 震动, 什么的; 但是手环想要震动, 小米没有给API啊, google了一会也没有结果

好吧, 非暴力不合作.

下载小米运动_1.4.641_1058.apk, 然后利用apk_hack :

./decompile_apk.sh 小米运动_1.4.641_1058.apk

导入到eclipse 进行debug…

eclipse崩溃? 难道这个app还做了反反编译, 反debug? 不至于吧? 各种google, 后来发现, 每次debug, eclipse右下角的内存显示都不停涨, 涨涨到512m就挂了; 好吧, 想起来eclipse的最大内存是512m, 估计是这个编译要太多内存了

修改eclipse目录下的eclipse.ini文件中的-Xmx参数到2048m; 正常运行了, 看到最大内存跳到700多m

好了, 正常debug了, 发现每次按查找手环, debug都会打印出:

05-22 20:07:31.878: D/BluetoothGatt(20195): writeCharacteristic() - uuid: 0000ff05-0000-1000-8000-00805f9b34fb
05-22 20:07:32.528: D/BluetoothGatt(20195): onCharacteristicWrite() - Device=88:0F:10:80:A2:4A UUID=0000ff05-0000-1000-8000-00805f9b34fb Status=0
05-22 20:07:32.533: D/BluetoothGatt(20195): readCharacteristic() - uuid: 0000ff0c-0000-1000-8000-00805f9b34fb
05-22 20:07:32.538: D/BluetoothGatt(20195): onNotify() - Device=88:0F:10:80:A2:4A UUID=0000ff03-0000-1000-8000-00805f9b34fb
05-22 20:07:33.773: D/BluetoothGatt(20195): onCharacteristicRead() - Device=88:0F:10:80:A2:4A UUID=0000ff0c-0000-1000-8000-00805f9b34fb Status=0

哦? BluetoothGatt, google下, 这个是低功耗蓝牙的连接方式, 简单说就是每个硬件设备会包含很多服务, 不同的服务按照UUID来区分. 也就是说, 现在这个让手环震动是通过UUID=0000ff05-0000-1000-8000-00805f9b34fb 这个服务进行的

然后顺着这个UUID, google到了下面这些:

xiaomi-mi-band-ble-protocol这个文章里已经收集了不少协议特征UUID

加上参考UgoRaffaele/xiaomi-miband-android, 基本上, UserInfo, 灯颜色, 电池信息 的数据结构都已经有了

但是google到的所有结果都只是实现读取功能, 震动什么的, 看来得自己实现了

分析了一下连接手环时的过程:


//开启notify notify
05-26 16:41:26.622: D/BluetoothGatt(31147): setCharacteristicNotification() - uuid: 0000ff03-0000-1000-8000-00805f9b34fb enable: true							
05-26 16:41:26.637: D/BluetoothGatt(31147): writeDescriptor() - uuid: 00002902-0000-1000-8000-00805f9b34fb
05-26 16:41:26.732: D/BluetoothGatt(31147): onDescriptorWrite() - Device=88:0F:10:80:A2:4A UUID=0000ff03-0000-1000-8000-00805f9b34fb


//开启 REALTIME_STEPS notify
05-26 16:41:26.752: D/BluetoothGatt(31147): setCharacteristicNotification() - uuid: 0000ff06-0000-1000-8000-00805f9b34fb enable: true							
05-26 16:41:26.762: D/BluetoothGatt(31147): writeDescriptor() - uuid: 00002902-0000-1000-8000-00805f9b34fb
05-26 16:41:26.827: D/BluetoothGatt(31147): onDescriptorWrite() - Device=88:0F:10:80:A2:4A UUID=0000ff06-0000-1000-8000-00805f9b34fb

//开启 ACTIVITY_DATA notify
05-26 16:41:26.837: D/BluetoothGatt(31147): setCharacteristicNotification() - uuid: 0000ff07-0000-1000-8000-00805f9b34fb enable: true							
05-26 16:41:26.847: D/BluetoothGatt(31147): writeDescriptor() - uuid: 00002902-0000-1000-8000-00805f9b34fb
05-26 16:41:26.922: D/BluetoothGatt(31147): onDescriptorWrite() - Device=88:0F:10:80:A2:4A UUID=0000ff07-0000-1000-8000-00805f9b34fb


//开启 电量 notify
05-26 16:41:26.932: D/BluetoothGatt(31147): setCharacteristicNotification() - uuid: 0000ff0c-0000-1000-8000-00805f9b34fb enable: true							
05-26 16:41:26.942: D/BluetoothGatt(31147): writeDescriptor() - uuid: 00002902-0000-1000-8000-00805f9b34fb
05-26 16:41:27.022: D/BluetoothGatt(31147): onDescriptorWrite() - Device=88:0F:10:80:A2:4A UUID=0000ff0c-0000-1000-8000-00805f9b34fb


//开启 SENSOR_DATA  notify
05-26 16:41:27.047: D/BluetoothGatt(31147): setCharacteristicNotification() - uuid: 0000ff0e-0000-1000-8000-00805f9b34fb enable: true							
05-26 16:41:27.057: D/BluetoothGatt(31147): writeDescriptor() - uuid: 00002902-0000-1000-8000-00805f9b34fb
05-26 16:41:27.172: D/BluetoothGatt(31147): onDescriptorWrite() - Device=88:0F:10:80:A2:4A UUID=0000ff0e-0000-1000-8000-00805f9b34fb

//读取 REALTIME_STEPS
05-26 16:41:33.167: D/BluetoothGatt(31147): readCharacteristic() - uuid: 0000ff06-0000-1000-8000-00805f9b34fb													
05-26 16:41:33.267: D/BluetoothGatt(31147): onCharacteristicRead() - Device=88:0F:10:80:A2:4A UUID=0000ff06-0000-1000-8000-00805f9b34fb Status=0


05-26 16:41:33.292: D/BluetoothGatt(31147): writeCharacteristic() - uuid: 0000ff05-0000-1000-8000-00805f9b34fb													
05-26 16:41:33.357: D/BluetoothGatt(31147): onCharacteristicWrite() - Device=88:0F:10:80:A2:4A UUID=0000ff05-0000-1000-8000-00805f9b34fb Status=0

//写UserInfo, value=[25, 113, 53, 1, 1, 32, -83, 55, 1, -24, -125, -106, -26, -94, -127, 0, 0, 0, 0, 35]
05-26 16:41:33.837: D/BluetoothGatt(31147): writeCharacteristic() - uuid: 0000ff04-0000-1000-8000-00805f9b34fb
05-26 16:41:33.947: D/BluetoothGatt(31147): onNotify() - Device=88:0F:10:80:A2:4A UUID=0000ff03-0000-1000-8000-00805f9b34fb
05-26 16:41:34.007: D/BluetoothGatt(31147): onCharacteristicWrite() - Device=88:0F:10:80:A2:4A UUID=0000ff04-0000-1000-8000-00805f9b34fb Status=0

//写入时间 value=[15, 4, 26, 17, 6, 58, -1, -1, -1, -1, -1, -1]
05-26 16:41:36.162: D/BluetoothGatt(31147): writeCharacteristic() - uuid: 0000ff0a-0000-1000-8000-00805f9b34fb													
05-26 16:41:36.247: D/BluetoothGatt(31147): onCharacteristicWrite() - Device=88:0F:10:80:A2:4A UUID=0000ff0a-0000-1000-8000-00805f9b34fb Status=0

//读取时间
05-26 16:41:36.282: D/BluetoothGatt(31147): readCharacteristic() - uuid: 0000ff0a-0000-1000-8000-00805f9b34fb													
05-26 16:41:36.382: D/BluetoothGatt(31147): onCharacteristicRead() - Device=88:0F:10:80:A2:4A UUID=0000ff0a-0000-1000-8000-00805f9b34fb Status=0

//读取电池
05-26 16:41:36.487: D/BluetoothGatt(31147): readCharacteristic() - uuid: 0000ff0c-0000-1000-8000-00805f9b34fb													
05-26 16:41:36.577: D/BluetoothGatt(31147): onCharacteristicRead() - Device=88:0F:10:80:A2:4A UUID=0000ff0c-0000-1000-8000-00805f9b34fb Status=0

//读取 device_info
05-26 16:41:36.707: D/BluetoothGatt(31147): readCharacteristic() - uuid: 0000ff01-0000-1000-8000-00805f9b34fb													
05-26 16:41:36.772: D/BluetoothGatt(31147): onCharacteristicRead() - Device=88:0F:10:80:A2:4A UUID=0000ff01-0000-1000-8000-00805f9b34fb Status=0

//读取 LE_PARAMS
05-26 16:41:37.302: D/BluetoothGatt(31147): readCharacteristic() - uuid: 0000ff09-0000-1000-8000-00805f9b34fb													
05-26 16:41:37.407: D/BluetoothGatt(31147): onCharacteristicRead() - Device=88:0F:10:80:A2:4A UUID=0000ff09-0000-1000-8000-00805f9b34fb Status=0

05-26 16:41:37.892: D/BluetoothGatt(31147): readCharacteristic() - uuid: 0000ff09-0000-1000-8000-00805f9b34fb
05-26 16:41:37.987: D/BluetoothGatt(31147): onCharacteristicRead() - Device=88:0F:10:80:A2:4A UUID=0000ff09-0000-1000-8000-00805f9b34fb Status=0


05-26 16:41:38.022: D/BluetoothGatt(31147): writeCharacteristic() - uuid: 0000ff05-0000-1000-8000-00805f9b34fb
05-26 16:41:38.187: D/BluetoothGatt(31147): onNotify() - Device=88:0F:10:80:A2:4A UUID=0000ff07-0000-1000-8000-00805f9b34fb
05-26 16:41:38.212: D/BluetoothGatt(31147): onNotify() - Device=88:0F:10:80:A2:4A UUID=0000ff07-0000-1000-8000-00805f9b34fb
05-26 16:41:38.267: D/BluetoothGatt(31147): onNotify() - Device=88:0F:10:80:A2:4A UUID=0000ff07-0000-1000-8000-00805f9b34fb

05-26 16:41:38.332: D/BluetoothGatt(31147): onCharacteristicWrite() - Device=88:0F:10:80:A2:4A UUID=0000ff05-0000-1000-8000-00805f9b34fb Status=0
05-26 16:41:38.352: D/BluetoothGatt(31147): onNotify() - Device=88:0F:10:80:A2:4A UUID=0000ff07-0000-1000-8000-00805f9b34fb
05-26 16:41:38.427: D/BluetoothGatt(31147): onNotify() - Device=88:0F:10:80:A2:4A UUID=0000ff07-0000-1000-8000-00805f9b34fb
05-26 16:41:38.462: D/BluetoothGatt(31147): onNotify() - Device=88:0F:10:80:A2:4A UUID=0000ff07-0000-1000-8000-00805f9b34fb

// value=[4, 0, 1, 15, 4, 26, 7, 0, 0, 0, 31]
05-26 16:41:38.797: D/BluetoothGatt(31147): writeCharacteristic() - uuid: 0000ff05-0000-1000-8000-00805f9b34fb
05-26 16:41:38.867: D/BluetoothGatt(31147): onCharacteristicWrite() - Device=88:0F:10:80:A2:4A UUID=0000ff05-0000-1000-8000-00805f9b34fb Status=0

// value=[4, 2, 0, 15, 4, 21, 8, 0, 29, 0, 31]
05-26 16:41:38.932: D/BluetoothGatt(31147): writeCharacteristic() - uuid: 0000ff05-0000-1000-8000-00805f9b34fb
05-26 16:41:39.012: D/BluetoothGatt(31147): onCharacteristicWrite() - Device=88:0F:10:80:A2:4A UUID=0000ff05-0000-1000-8000-00805f9b34fb Status=0

05-26 16:41:43.392: D/BluetoothGatt(31147): writeCharacteristic() - uuid: 0000ff05-0000-1000-8000-00805f9b34fb
05-26 16:41:43.507: D/BluetoothGatt(31147): onNotify() - Device=88:0F:10:80:A2:4A UUID=0000ff03-0000-1000-8000-00805f9b34fb
05-26 16:41:43.522: D/BluetoothGatt(31147): onCharacteristicWrite() - Device=88:0F:10:80:A2:4A UUID=0000ff05-0000-1000-8000-00805f9b34fb Status=0

05-26 16:41:56.712: D/BluetoothGatt(31147): readCharacteristic() - uuid: 0000ff06-0000-1000-8000-00805f9b34fb
05-26 16:41:56.862: D/BluetoothGatt(31147): onCharacteristicRead() - Device=88:0F:10:80:A2:4A UUID=0000ff06-0000-1000-8000-00805f9b34fb Status=0

05-26 16:41:56.897: D/BluetoothGatt(31147): writeCharacteristic() - uuid: 0000ff05-0000-1000-8000-00805f9b34fb
05-26 16:41:57.082: D/BluetoothGatt(31147): onCharacteristicWrite() - Device=88:0F:10:80:A2:4A UUID=0000ff05-0000-1000-8000-00805f9b34fb Status=0

结合decomplier, 几个数据结构:

UserInfo实现在com.xiaomi.hm.health.bt.profile.IMiLiProfile$UserInfo

package com.xiaomi.hm.health.bt.profile;

import android.os.Parcel;
import android.os.Parcelable;
import android.os.Parcelable.Creator;

public final class IMiLiProfile$UserInfo
  implements Parcelable
{
  public static final Parcelable.Creator<UserInfo> CREATOR = new k();
  public static final byte a = 0;
  public static final byte b = 1;
  public static final byte c = 2;
  public static final UserInfo k = new UserInfo(170420175, (byte)0, (byte)23, (byte)-88, (byte)50, "".getBytes());
  public final int d;
  public final byte e;
  public final byte f;
  public final byte g;
  public final byte h;
  public final byte[] i;
  public byte j;

  public IMiLiProfile$UserInfo(int paramInt, byte paramByte1, byte paramByte2, byte paramByte3, byte paramByte4, byte paramByte5, byte[] paramArrayOfByte)
  {
    this.d = paramInt;
    this.e = paramByte1;
    this.f = paramByte2;
    this.g = paramByte3;
    this.h = paramByte4;
    this.i = paramArrayOfByte;
    this.j = paramByte5;
  }

  public IMiLiProfile$UserInfo(int paramInt, byte paramByte1, byte paramByte2, byte paramByte3, byte paramByte4, byte[] paramArrayOfByte)
  {
    this.d = paramInt;
    this.e = paramByte1;
    this.f = paramByte2;
    this.g = paramByte3;
    this.h = paramByte4;
    this.i = paramArrayOfByte;
    this.j = 0;
  }

  public int describeContents()
  {
    return 0;
  }

  public String toString()
  {
    StringBuilder localStringBuilder1 = new StringBuilder(128);
    localStringBuilder1.append("[[[ " + getClass().getSimpleName() + " ]]]");
    localStringBuilder1.append("\n     uid: " + this.d);
    StringBuilder localStringBuilder2 = new StringBuilder().append("\n  gender: ");
    if (this.e == 0);
    for (String str = "female"; ; str = "male")
    {
      localStringBuilder1.append(str);
      localStringBuilder1.append("\n     age: " + this.f + "yrs");
      localStringBuilder1.append("\n  height: " + (0xFF & this.g) + "cm");
      localStringBuilder1.append("\n  weight: " + (0xFF & this.h) + "kg");
      localStringBuilder1.append("\n   alias: " + new String(this.i));
      localStringBuilder1.append("\n   type: " + this.j);
      return localStringBuilder1.toString();
    }
  }

  public void writeToParcel(Parcel paramParcel, int paramInt)
  {
    paramParcel.writeInt(this.d);
    paramParcel.writeByte(this.e);
    paramParcel.writeByte(this.f);
    paramParcel.writeByte(this.g);
    paramParcel.writeByte(this.h);
    paramParcel.writeString(new String(this.i));
    paramParcel.writeByte(this.j);
  }
}

电池信息在com.xiaomi.hm.health.bt.profile.d

package com.xiaomi.hm.health.bt.profile;

import java.text.DateFormat;
import java.util.Calendar;

public final class d
{
  public final int a;
  public final Calendar b;
  public int c;
  public final int d;

  public d(int paramInt1, Calendar paramCalendar, int paramInt2, int paramInt3)
  {
    this.a = paramInt1;
    this.b = paramCalendar;
    this.c = paramInt2;
    this.d = paramInt3;
  }

  public String toString()
  {
    StringBuilder localStringBuilder = new StringBuilder(128);
    localStringBuilder.append("[[[ " + getClass().getSimpleName() + " ]]]");
    localStringBuilder.append("\n       level: " + this.a + "%");
    localStringBuilder.append("\n  lastCharge: " + DateFormat.getDateTimeInstance().format(this.b.getTime()));
    localStringBuilder.append("\n     charges: " + this.c);
    localStringBuilder.append("\n      status: " + Integer.toHexString(this.d));
    return localStringBuilder.toString();
  }
}