Google発のBeacon用オープンフォーマットEddystoneをAndroidで触ってみた


GoogleからBeacon用のオープンフォーマットEddystoneが発表されました。

- [Google Developers Blog: Lighting the way with BLE beacons](http://googledevelopers.blogspot.jp/2015/07/lighting-way-with-ble-beacons.html)
- [Google、BLEビーコンのためのフォーマット「Eddystone」を公開、Android／iOS向けアプリに実装可能なAPIも提供：CodeZine（コードジン）](http://codezine.jp/article/detail/8847)

Android用のサンプルも用意されていたので早速触ってみました。

## Eddystoneとは

Googleが発表したBeacon用のオープン規格です。
Beaconが送信するデータの種類として下記の3種類が規定されています。

- Eddystone-UID
	- 端末識別のためのUID
- Eddystone-URL
	- URLデータ
- Eddystone-TLM
	- 温度やバッテリーの電圧など端末状態測定用データ

## お試し環境

用意されているサンプルコードは`minSdkVersion`が22となっているため送信と受信両方の動作を見るためにはLolipop以上の端末が2台必要です。

- 送信側: Nexus 6 (Android 5.1.1)
- 受信側: Nexus 9 (Android M)

## 準備

サンプルコードが含まれている[GithubのEddystoneリポジトリ](https://github.com/google/eddystone)を適当な場所にcloneします。

```bash
git clone git@github.com:google/eddystone.git
```

### 受信側

cloneしたリポジトリの`eddystone/tools/eddystone-validator/EddystoneValidator`をAndroid Studioで開いてビルド、インストールして端末側で実行します。
起動後にToastでエラーが表示されなければOKです。エラーが表示される場合は端末のBluetoothデバイスが対応していないか、デバイスの状態がおかしい可能性があります。
自分の場合は、Nexus 9でアプリを起動したところエラーが発生しましたが、端末を再起動すると正常に起動させることができました。何か別のプロセスがBluetoothデバイスを掴んでいたのかもしれません。

### 送信側

受信側と同じく、cloneしたリポジトリの`eddystone/eddystone-uid/tools/txeddystone-uid/TxEddystone-UID`をAndroid Studioで開いてアプリにインストールします。

## Eddystone-UID

サンプルのTxEddystone-UIDは名前の通りEddystone-UIDを送信します。
フォーマットは下記の通り、これはアプリ内で生成されているので興味があれば見てみるといいでしょう。

Byte offset | Field | Description
------------|-------|------------
0 | Frame Type | Value = `0x00`
1 | Ranging Data | Calibrated Tx power at 0 m
2〜11 | NID | 10-byte ID Namespace (端末を一意に識別する)
12〜17 | BID | 6-byte ID Instance (サービスを一意に識別する)
18 | RFU | Reserved for future use, must be`0x00`
19 | RFU | Reserved for future use, must be`0x00`


送信側アプリのTxスイッチをオンにすると、受信側アプリに情報が表示されることが確認できます。
一度、送信をオフにしてRNDボタンでIDをランダムに変更し再度オンにすると新しい情報が受信側に表示されます。
このとき、前回と違うIDが送られてきたためバリデーションでエラーとなっています。

![Screenshot_20150716-135838 のコピー.png](https://qiita-image-store.s3.amazonaws.com/0/53706/741959d2-2ca7-4172-e47e-b7cef107f408.png "Screenshot_20150716-135838 のコピー.png")

## Eddystone-URL

サンプルで送信できるのはEddystone-UIDだけなので、TxEddystone-UIDを改造してURLを送信してみます。
Eddystone-URLの構造は下記のようになっています。

Byte offset | Field | Description
------------|-------|------------
0 | Frame Type | Value = `0x10`
1 | TX Power | Calibrated Tx power at 0 m
2 | URL Scheme | Encoded Scheme Prefix
3+ | Encoded URL | Length 0-17

ぱっと見は簡単そうなのですが、URLは次の規則で変換してやる必要があります。

### URL Encoding

SchemeによってURL最初の1バイト目の値が決まります。

|Decimal  | Hex        | Expansion
|:------- | :--------- | :--------
|0        | 0x00       | `http://www.`
|1        | 0x01       | `https://www.`
|2        | 0x02       | `http://`
|3        | 0x03       | `https://`

また、次のようにいくつかのドメインが1byteに変換できるようになっています。

|Decimal  | Hex        | Expansion
|:------- | :--------- | :--------
|0        | 0x00       | .com/
|1        | 0x01       | .org/
|2        | 0x02       | .edu/
|3        | 0x03       | .net/
|4        | 0x04       | .info/
|5        | 0x05       | .biz/
|6        | 0x06       | .gov/
|7        | 0x07       | .com
|8        | 0x08       | .org
|9        | 0x09       | .edu
|10       | 0x0a       | .net
|11       | 0x0b       | .info
|12       | 0x0c       | .biz
|13       | 0x0d       | .gov
|14..32   | 0x0e..0x20 | Reserved for Future Use
|127..255 | 0x7F..0xFF | Reserved for Future Use

### 実装

この変換を自分で実装するのはちょっと面倒なので`eddystone/eddystone-url/tools/eddystone-url-config-validator/app/libs`の中にある`uribeacon-library-release.aar`をTxEddystone-UIDの`app`フォルダ直下に`libs`フォルダを作成してコピーします。
build.gradleにはこのaarを使うように記述します。

```groovy
repositories {
    flatDir {
        dirs 'libs'
    }
}

dependencies {
    compile 'org.uribeacon:uribeacon-library-release@aar'
    compile fileTree(dir: 'libs', include: ['*.jar'])
}
```

UID送信用のデータを作っている`buildServiceData`メソッドを参考にURLを送信するデータを作成します。

```java
    private byte[] buildUrlData() throws IOException {
        byte[] url = UriBeacon.encodeUri("http://www.google.co.jp/");
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        os.write(new byte[]{(byte) 0x10 /* URL frame type */, (byte) 0x10 /* Tx power */});
        os.write(url);
        return os.toByteArray();
    }
```

あとは`buildServiceData`を`buildUrlData`に変えるだけ。

```java
  // serviceData = buildServiceData();
  serviceData = buildUrlData();
```

この状態でビルドしてTxスイッチをオンにすると受信側ではURLが表示されます。

![Screenshot_20150716-133622.png](https://qiita-image-store.s3.amazonaws.com/0/53706/94392ce4-f158-bbe2-3c3e-aba0b24bf5d6.png "Screenshot_20150716-133622.png")

## Eddystone-TLM

最後にBeacon端末の状態測定用のEddystone-TLMです。
あまりスマホからこいつを実装して送信する用途は思いつかないのでサクッといきます。

Byte offset | Field | Description
------------|-------|------------
0 | Frame Type | Value = `0x20`
1 | Version | TLM version, value = `0x00`
2〜3 | VBATT | Battery voltage, 1 mV/bit
4〜5 | TEMP | Beacon temperature
6〜9 | ADV_CNT | Advertising PDU count
10〜13 | SEC_CNT | Time since power-on or reboot

各データの細かいフォーマットはドキュメントを確認してください。
2バイト目がUIDやURLのTx Powerではなくバージョンになっていることに注意が必要です。


### 実装

今回は、お試ししたいだけなので値は決め打ちしてしまいます。
なお、2バイト目のバージョンは`0x00`にしないと怒られるようです。

```java
    private byte[] buildTlmData() throws IOException {
        byte[] data = {(byte) 0x00, (byte) 0xf0, (byte) 0x08, (byte) 0x58,
                (byte) 0x01, (byte) 0x00, (byte) 0x30, (byte) 0x49, (byte) 0x01, (byte) 0x00, (byte) 0x30, (byte) 0x49};
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        os.write(new byte[]{(byte) 0x20 /* TLM frame type */, (byte) 0x00 /* Version */});
        os.write(data);
        return os.toByteArray();
    }
```

URLの時と同じく`buildServiceData`を呼び出している箇所を`buildTlmData`に変えて実行するとTLMを受信していることがわかります。
ただ、すこし時間が経つとバリデーションエラーとなってしまいます。
これは、時間経過でカウントアップされなくてはならない値`ADV_CNT`、`SEC_CNT`が変化していないためです。

![Screenshot_20150716-133240.png](https://qiita-image-store.s3.amazonaws.com/0/53706/d912b887-ca8d-72a5-74ba-c848b8718685.png "Screenshot_20150716-133240.png")

## まとめ

今回、GoogleからEddystoneが発表されたのでお試しで触ってみました。
フォーマット自体もそんなに複雑ではなく、自分でデータを作るのも難しくないと思いました。

発表によるとGoogle Play Serviceの7.8からは"Neaby API"が追加されているということなので、今回触った辺りはアプリ側では特に意識すること無く扱えるようになるのかな？とも思いますが簡単に仕組みを分かっておくのもありだと思います。