はじめに

 ここ最近 iBeacon が大きな注目を集めているようですが、これは iOS 7 特有の機能です。
ただし、これは Bluetooth LE 上で実装されている技術です。ならば、Bluetooth LE に対応したAPI が追加された Android 4.3 以降であれば、iBeacon 互換のプログラムを作ることが出来るかもしれないと考えました。

 Android 4.3 以降で使用することの出来る BLE 機能でサポートされているのは受信のみになります。そのため今回の記事では、iBeacon 用の発信機からの信号を、Android アプリで受け取る実験をしてみることにしましょう。


Android で Beacon の信号を取得する

 早速、Android で周囲にある Beacon 端末が発している信号を取得する方法を見て行きましょう。Beacon 側からは 発信機が出力しているデータ(任意バイナリ) と、電波強度を取得することが出来ます。この発信機が出力しているデータを解析することで様々な情報を取得することができるようになっています。

 また、距離と電波強度の関係性を示す計算式が有るので、電波強度からおおよその距離を逆算することができます。おおよその距離というのが重要で、結局のところ電波強度との関係式になるため、間に障害物が入ると正常な距離を取得することができなくなってしまいます。

 それでは実際にコードを書きながらどのように Android で Beacon 信号を取得するのか説明していきたいと思います。

初期設定

 まずは、Android で Bluetooth LE を使用する準備を行います。

 AndroidManifest ファイルに Bluetooth を使用するためのパーミッションを設定します。以下のコードを Manifest ファイルに付け加えてください。

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

<uses-feature android:name="android.hardware.bruetooth_le" android:required="true"/>

<uses-permission> と書かれている 2行のコードが、Android で Bluetooth を使用するためのパーミッションになります。

 そして一番下の uses-feature では、このアプリが android.hardware.bruetooth_le のない端末にはインストール出来ないということを示しています。もしandroid.hardware.bruetooth_le がない端末でもインストールできるようにしたい場合には、 android:required = "false"にしてください。

周囲の Beacon をスキャンさせる

 Bluetooth LE を Android で使用するには、BluetoothAdapter を使用する必要があります。

final BluetoothManager bluetoothManager = (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter mBluetoothAdapter = bluetoothManager.getAdapter();
mBluetoothAdapter.startLeScan(mLeScanCallback);

 今回はまず、BluetoothManagergetSystemServiceで取得します。次にその Manager のgetAdapter()メソッドを使用して BluetoothAdapter を取得します。

 Adapter を取り出すことが出来たなら、そのクラスのメソッドである startLeScan を呼び出します。この時に引数として渡している mLeScanCallbackBluetoothAdapter.LeScanCallbackのインスタンスで、結果が渡されるコールバックになります。そのため、このコールバックの中身を実装しておくことで、結果に対する処理を行う事が出来ます。このコールバックの詳細に付いては次の項で説明します。    しかし、BluetoothManager と BluetoothAdapter の startLeScan() メソッドは、Android 4.3 以降でのみ使用することが出来るクラスやメソッドになるので、 minSDKVersion を 18 にしておくか、コード内で分岐するなどの処理を行う必要があるので注意しましょう。

Beacon の検出をハンドリングする

 先ほどまでの実装で、Beacon が発見された時に、callback メソッドに結果が返ってくるようになりました。なので、後はコールバックの中身を実装し、渡される結果について処理を行えるようにしましょう。

コールバックは以下のように定義します。

private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {

        //ここに結果に対して行う処理を記述する

    }
}

 ビーコンが発見される度に、この onLeScan が呼ばれるようになります。

 このメソッドに返ってくる scanRecord は、そのままではただの byte 列になります。

 以下に iBeacon のデータフォーマットに則って UUID や、major 、minor などの値を取得する一例を示します。

    if(scanRecord.length > 30)
    {
        //iBeacon の場合 6 byte 目から、 9 byte 目はこの値に固定されている。
        if((scanRecord[5] == (byte)0x4c) && (scanRecord[6] == (byte)0x00) &&
        (scanRecord[7] == (byte)0x02) && (scanRecord[8] == (byte)0x15))
        {
            String uuid = IntToHex2(scanRecord[9] & 0xff) 
                        + IntToHex2(scanRecord[10] & 0xff)
                        + IntToHex2(scanRecord[11] & 0xff)
                        + IntToHex2(scanRecord[12] & 0xff)
                        + "-"
                        + IntToHex2(scanRecord[13] & 0xff)
                        + IntToHex2(scanRecord[14] & 0xff)
                        + "-"
                        + IntToHex2(scanRecord[15] & 0xff)
                        + IntToHex2(scanRecord[16] & 0xff)
                        + "-"
                        + IntToHex2(scanRecord[17] & 0xff)
                        + IntToHex2(scanRecord[18] & 0xff)
                        + "-"
                        + IntToHex2(scanRecord[19] & 0xff)
                        + IntToHex2(scanRecord[20] & 0xff)
                        + IntToHex2(scanRecord[21] & 0xff)
                        + IntToHex2(scanRecord[22] & 0xff)
                        + IntToHex2(scanRecord[23] & 0xff)
                        + IntToHex2(scanRecord[24] & 0xff);

            String major = IntToHex2(scanRecord[25] & 0xff) + IntToHex2(scanRecord[26] & 0xff);
            String minor = IntToHex2(scanRecord[27] & 0xff) + IntToHex2(scanRecord[28] & 0xff);
        }
    } 

//intデータを 2桁16進数に変換するメソッド
public String IntToHex2(int i) {
    char hex_2[] = {Character.forDigit((i>>4) & 0x0f,16),Character.forDigit(i&0x0f, 16)};
    String hex_2_str = new String(hex_2);
    return hex_2_str.toUpperCase();
}

簡単に scanRecord の中身をまとめると、以下の表のようになります。

Byte 数 説明
1 1 ブロック目のバイト数
2,3 flag
4 2 ブロック目のバイト数
5 メーカー固有の AD type データ
6,7 会社コード(0x004C が Apple の会社コード)
8 データのタイプ(0x02 が iBeacon)
9 連なる iBeacon データのバイト数
10~25 UUID
26,27 major
28,29 minor
30 校正された電波強度(距離を求めるときの基準値、2 の補数)

 6~9 byte 目までの値は、iBeacon であれば全て同じため、この値を参照することで iBeacon なのか判定することが出来ます。


aBeacon を使用する上で

 aBeacon を実際に使用するにあたり、考えなくてはいけないことを以下にまとめていきたいと思います。

フィルタリング

 onLeScan は、Beacon を検出するたびに呼ばれます。そのため、監視対象の Beacon 領域内に居続ける間は、ずっと onLeScan が呼ばれ続ける事になります。端末によっては、自動的に判断しフィルタリングしてくれるそうなのですが、そうじゃない場合には対策を立てないと、何度も何度も通知が来てしまいます。

 それを回避するには、ArrayList などに検出した Beacon の情報を追加していき、onLeScan が呼ばれるごとにList 内を探査・比較し新しく発見した端末なのかを判断するという方法が考えられます。

 ただし、この方法は接続している Beacon の台数が増えてしまうと、List の量が多くなり探査・比較に時間がかかってしまう可能性があるため注意しましょう。

Enter と Exit

 iBeacon では、Beacon 領域への Enter と Exit を簡単に検出することが出来ました。よって、Android でも Enter と Exit を検出することが出来るようになれば、一段と iBeacon へ近づくことができます。

 Enter の方は、onLeScan に初めて反応があったタイミングを取得すればいいので、比較的簡単に検出することが出来ます。

 一方、Exit の方は API 的にサポートされているわけではありません。この退出の検知の方法ですが、領域内にいなくなったことを持ってして即座に Exit とみなしてしまうと、検知範囲ギリギリにいた場合に、 Enter と Extit を繰り返してしまう可能性があります。これを回避するために、iBeacon では 「一定時間、検知されない状況が継続してはじめて、Exit とみなす」という工夫をしているようです。そのため、Android でも同様の処理を加える必要があります。

 方法としては、2秒ごとにタイマーで回しながら、Beacon の領域内にいるという通知を監視します。その通知が来ない時が 10 回あったら、それは Beacon の領域外に出たと検出する。などとすると良いのではないでしょうか。この方法だと、約 20 秒のディレイをかけてから Exit の通知を送ることが出来るため、iBeacon 風に検知できます。

バックグラウンド動作

 Android は iOS と比べ、比較的簡単にバックグラウンドの処理を行うことが出来ます。それには色々有りますが、Service 等を使うのが一般的なのではないでしょうか。

 また、Android には BroadcastReceiver という仕組みが有るため、アプリがバックグラウンドにすらいない状態でもイベントを取得することが出来ます。そのため、ある Beacon の範囲内に入ったタイミングでアプリを立ち上げるといったことができます。

最大の壁

 aBeacon 最大の壁といえば OS のバージョンです。 Bluetooth LE に対応しているのが Android 4.3 からになっているため、現状日本ではなかなかこの機能を活躍させる場がありません。

 しかし、Android 4.4 からは、要求するハードのスペックが低くなっています。これにより、現状 低スペックで Android 4 系を入れられない端末にも、Android 4.4 なら載せられる可能性があります。そのため、この問題は時間が解決してくれる可能性があります。


終わりに

 今回の特集はいかがだったでしょうか。

 ここ最近の iBeacon の流行りに乗じて、Android でも同じことが出来たらいいんじゃないかと考えてる人にとっての手助けになれば幸いです。

 次の特集もお楽しみに!!