BLE腕輪にAndroid端末から通知を送ってみる


Bluetooth 4.0(BLE)対応のリストバンド型活動量計を、自作の Android アプリから操作して遊んでみようという試みです。

## 手頃な値段の腕輪を探す
今回は、 Amazon で ¥2.5k ほどで売っていた、こちらをターゲットとしました。

Excelvan スマートブレスレット Bluetooth 4.0
![腕輪](https://qiita-image-store.s3.amazonaws.com/0/14913/dd6e9270-7b29-78a5-2665-0e4b4da96c4d.jpeg)
[購入先:その1](http://www.amazon.co.jp/gp/product/B00MPD6S88/ref=as_li_qf_sp_asin_il?ie=UTF8&camp=247&creative=1211&creativeASIN=B00MPD6S88&linkCode=as2&tag=matsumospage-22)
[購入先:その2](http://www.amazon.co.jp/gp/product/B00MP4BZZI/ref=as_li_tf_tl?ie=UTF8&camp=247&creative=1211&creativeASIN=B00MP4BZZI&linkCode=as2&tag=matsumospage-22)

## 腕輪について調べてみる
使えそうな Android 用のアプリケーションが2つほどありました。
[Navig8r Move](https://play.google.com/store/apps/details?id=com.chamobile.moveapp)(Google Play)
[ZERONER](https://play.google.com/store/apps/details?id=com.kunekt)(Google Play)

Move は活動量(歩数、距離、カロリー)履歴の取得のみ、ZERONER は履歴の取得に加えて、Android の端末の通知をリストバンドに送ることができます(ただし、あらかじめ決められたアプリ(Twitter, Facebook,　Skype, Whatsapp)のみで、好きなアプリの通知はできない)。

取扱説明書的なものは、こちらが使えそうです
[Viddon X5](http://www.vidonn.com/en/download/vidonn%20smart%20band%20manual.pdf)(PDFへの直接リンク)

## サービスの一覧を取得してみる
どんな UUID が使えるのか調べるため、 BluetoothGatt の discoverServices を呼んでみます。

#### サービス(Service)
以下はBLE標準のものです。

| Service Name | UUID |
|:-----------|:------------|
|Generic Access|00001800-0000-1000-8000-00805f9b34fb|
|Generic Attribute|00001801-0000-1000-8000-00805f9b34fb|
|Device Information Service|0000180a-0000-1000-8000-00805f9b34fb|
|Battery Service|0000180f-0000-1000-8000-00805f9b34fb|

以下は独自のものです。

| Service Name | UUID |
|:-----------|:------------|
|PHONE ALERT|f000ff10-0451-4000-b000-000000000000|
|MAIN|f000ff00-0451-4000-b000-000000000000|

#### キャラクタリスティック(Characteristic)
以下はBLE標準のものです。

| Characteristic Name | UUID |
|:-----------|:------------|
|Device Name|00002a00-0000-1000-8000-00805f9b34fb|
|Appearance|00002a01-0000-1000-8000-00805f9b34fb|
|Peripheral Privacy Flag|00002a02-0000-1000-8000-00805f9b34fb|
|Reconnection Address|00002a03-0000-1000-8000-00805f9b34fb|
|Peripheral Preferred Connection Parameters|00002a04-0000-1000-8000-00805f9b34fb|
|Service Changed|00002a05-0000-1000-8000-00805f9b34fb|
|System ID|00002a23-0000-1000-8000-00805f9b34fb|
|Model Number String|00002a24-0000-1000-8000-00805f9b34fb|
|Serial Number String|00002a25-0000-1000-8000-00805f9b34fb|
|Firmware Revision String|00002a26-0000-1000-8000-00805f9b34fb|
|Hardware Revision String|00002a27-0000-1000-8000-00805f9b34fb|
|Software Revision String|00002a28-0000-1000-8000-00805f9b34fb|
|Manufacturer Name String|00002a29-0000-1000-8000-00805f9b34fb|
|IEEE 11073-20601 Regulatory Certification Data List|00002a2a-0000-1000-8000-00805f9b34fb|
|Battery Level|00002a19-0000-1000-8000-00805f9b34fb|

以下は独自のものです。

| Characteristic Name | UUID |
|:-----------|:------------|
|PHONE ALERT|f000ff11-0451-4000-b000-000000000000|
|ALARM|f000ff01-0451-4000-b000-000000000000|
|USER|f000ff02-0451-4000-b000-000000000000|
|SPORT|f000ff03-0451-4000-b000-000000000000|
|LED|f000ff04-0451-4000-b000-000000000000|
|DATE|f000ff05-0451-4000-b000-000000000000|
|PAIR|f000ff06-0451-4000-b000-000000000000|
|DAILY|f000ff07-0451-4000-b000-000000000000|
|SEDENTARY|f000ff08-0451-4000-b000-000000000000|
|POWER SAVING|f000ff09-0451-4000-b000-000000000000|

#### ディスクリプタ(Descriptor)
| Descriptor Name | UUID |
|:-----------|:------------|
|-|00002902-0000-1000-8000-00805f9b34fb|
|-|00002901-0000-1000-8000-00805f9b34fb|
|-|00002902-0000-1000-8000-00805f9b34fb|
|-|00002901-0000-1000-8000-00805f9b34fb|
|-|00002901-0000-1000-8000-00805f9b34fb|
|-|00002901-0000-1000-8000-00805f9b34fb|
|-|00002901-0000-1000-8000-00805f9b34fb|
|-|00002901-0000-1000-8000-00805f9b34fb|
|-|00002901-0000-1000-8000-00805f9b34fb|

Service MAIN(f000ff00-0451-4000-b000-000000000000)
Characteristic PHONE ALERT(f000ff11-0451-4000-b000-000000000000)
が、今回お目当ての UUID となりそうです。


## 通知を送るための電文を解析してみる
前章で探し当てた UUID に、どのようなデータを writeCharacteristic すると通知が送られるのか、 ZERONER のアプリケーションで実際に通知を送ってみました。  
実際に、送られたデータをダンプしたものです。

```
02C0010100003FE0200020002000200020003FC0
02C001023FC02000200020002000200020000000
02C0010300000000000000001C003E0063004100
02C001041F0079006300430047007F0039000000
02C0010500000000000000000C003E0063004100
02C00106400040004000410063003E0000000000
02C0010700000000000000000C003E0063006100
02C0010841007F004000610063003E0000000000
02C0010900004000400040005C007E0063006100
02C0010A410041004100610063007E0000000000
02C0010B000000000000000018003E0066004300
02C0010C430043004300620076003C0000000000
02C00110
```

横96dot x 縦16dotのモノクロビットマップが送れるようです。

1〜12行目はビットマップデータが含まれています。
1行目のデータを例にすると、

| data | description |
|:-----------|:------------|
|02|start mark|
|C0|bitmap data size (16x12=192)|
|01|icon type (1=mail, 0=phone)|
|01|sequence (1, 2, 3 ... 12)|
|0000 3FE0 2000 2000 2000 2000 2000 3FC0|bitmap data (16x8 big endian。データの並び順については後述)|

ビットマップデータは、横16dot x 縦8dot x 12個に分割して、以下の順に転送します。
①③⑤⑦⑨⑪
②④⑥⑧⑩⑫

13行目はフッターです。


## 解析結果を元に通知を送ってみる
実際に Android アプリを作ってみましょう。
以下の説明は、見やすくするため、エラー処理は省略しています。

#### 接続
今回は実験なので、デバイスの検索などは行わず直接接続します。

```Java
BluetoothManager mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter mBtAdapter = mBluetoothManager.getAdapter();
BluetoothDevice mDevice = mBtAdapter.getRemoteDevice(DEVICE_ADDRESS);
BluetoothGatt　mBluetoothGatt = mDevice.connectGatt(this, false, new BluetoothGattCallback() {
    ...(省略)...
});
```

#### サービス一覧の取得
connectGatt が成功すると、 BluetoothGattCallback の onConnectionStateChange が呼ばれます。
 discoverServices を呼んで、 BluetoothGattCallback の onServicesDiscovered が呼ばれれば準備完了です。

```Java
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
    switch (newState) {
        case BluetoothProfile.STATE_CONNECTED:
            mBluetoothGatt.discoverServices();
            break;
        case BluetoothProfile.STATE_DISCONNECTED:
            break;
    }
}

@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
    if (status == BluetoothGatt.GATT_SUCCESS) {
        // 準備OK
    }
}
```

#### 通知の送信
文字列から、モノクロビットマップを作成して、腕輪に送信します。
![通知](https://qiita-image-store.s3.amazonaws.com/0/14913/7b944bed-6ec3-a9d1-13d4-74c215a33a67.jpeg)
腕輪にメッセージが表示されて、振動しました!!成功です!!

```Java
private void sendNotification(String text) {
    byte[][] r = makeBitmapPacket(text);
    for (int i = 0; i < r.length; i++) {
        BluetoothGattCharacteristic c = characteristic(BAND_SERVICE_PHONE_ALERT_UUID, BAND_CHARACTERISTIC_PHONE_ALERT_UUID);
        c.setValue(r[i]);
        mBluetoothGatt.writeCharacteristic(c);
        try {
            Thread.sleep(30);
        } catch (InterruptedException e) {
        }
    }
    BluetoothGattCharacteristic c = characteristic(BAND_SERVICE_PHONE_ALERT_UUID, BAND_CHARACTERISTIC_PHONE_ALERT_UUID);
    c.setValue(new byte[]{2, (byte) 192, 1, 16});
    mBluetoothGatt.writeCharacteristic(c);
}

private BluetoothGattCharacteristic characteristic(UUID sid, UUID cid) {
    if (mBluetoothGatt == null) return null;
    BluetoothGattService s = mBluetoothGatt.getService(sid);
    if (s == null) {
        Log.w(TAG, "Service NOT found :" + sid.toString());
        return null;
    }
    BluetoothGattCharacteristic c = s.getCharacteristic(cid);
    if (c == null) {
        Log.w(TAG, "Characteristic NOT found :" + cid.toString());
        return null;
    }
    return c;
}

private byte[][] makeBitmapPacket(String c) {
    int len = 6;
    byte size = (byte) (len * 16 * 2);

    Paint w_paint = new Paint();
    w_paint.setAntiAlias(false);
    w_paint.setColor(Color.BLACK);
    w_paint.setTextSize(14f);
    w_paint.setTextScaleX(10f / 14f);
    Paint.FontMetrics fm = w_paint.getFontMetrics();
    Bitmap bmp = Bitmap.createBitmap(16 * len, 16, Bitmap.Config.ARGB_8888);
    Canvas cv = new Canvas(bmp);
    cv.drawText(c, 0, 8 - (fm.ascent + fm.descent) / 2, w_paint);

    byte[][] r = new byte[len * 2][4 + 32];
    for (int d = 0; d < len * 2; d++) {
        for (int y = 0; y < 8; y++) {
            int d1 = 0, d2 = 0;
            for (int x = 0; x < 8; x++) {
                d1 |= bmp.getPixel((d / 2) * 16 + x, (d & 1) * 8 + y) == Color.BLACK ? 1 : 0;
                d2 |= bmp.getPixel((d / 2) * 16 + x + 8, (d & 1) * 8 + y) == Color.BLACK ? 1 : 0;
                if (x < 7) {
                    d1 <<= 1;
                    d2 <<= 1;
                }
            }
            r[d][4 + y * 2] = (byte) d1;
            r[d][4 + y * 2 + 1] = (byte) d2;
        }
        r[d][0] = 2;
        r[d][1] = size;
        r[d][2] = 1;
        r[d][3] = (byte) (d + 1);
    }
    bmp.recycle();
    return r;
}
```

#### 切断
実験が終わったら、切断処理を行います。

```Java
mBluetoothGatt.disconnect();
```

　  
今回のサンプルソースのプロジェクト一式は[ここ](https://github.com/matsumo/ble_notification_sample)からダウンロードできます。
(自分で試す際は、 MainActivity.java の DEVICE_ADDRESS を自分の腕輪の Mac アドレスに書き換えてください)

　  
他にも、

* バッテリー残量の取得
* 活動履歴(歩数、距離、カロリー)の取得
* 時間の設定、取得
* アラームの設定、取得

などが行えるので、解説していきたいと思います。