[Android][iBeacon] Android Beacon Library パラっと解説 その3 [BeaconParser]

2015.03.16

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは。こむろです。前回の続きです。簡単な領域監視の実装まで完了していましたが、うまく動きませんでした。それを解決していきます。

BeaconParser

うまくビーコンを検知しない理由は、iBeacon用にParserが設定されていないためです。
ビーコンの情報は単なるデータの列であるため、予め定義された形式があります。元々Android Beacon LibraryはAltBeacon用に開発されているライブラリです。そのため、デフォルトのParserの設定は、iBeaconを対象としていません。これをiBeacon用のものに書き換えてやる必要があります。

そのキーワードになるのが BeaconParser クラスです。

A BeaconParser may be used to tell the library how to decode a beacon's fields from a Bluetooth LE advertisement by specifying what byte offsets match what fields, and what byte sequence signifies the beacon.

BLEで取得されたデータを見てみると、何もしなければ単なるバイト列です。計測にはyoutenさんのiBeaconDetectorを利用させていただきました。

Screenshot_2015-03-16-08-51-55-640x285

この値を意味のある形に分解する必要があるのですが、その定義を設定できるのがBeaconParserクラスです。

iBeaconを認識させるためには

BeaconParserのAPI Referenceを見てみましょう。BeaconParser#setBeaconLayout()が該当するメソッドになります。バイト列を分解してBeaconを認識させるためのレイアウト。ここではBeaconLayoutと呼ばれています。

  • m: ビーコンタイプを指定します。ここでiBeaconなどビーコンの種類を特定します。1つ必ず指定する必要があります。
  • i: identifierを指定します。UUID, major, minorの指定はここになります。1つ以上指定、複数指定することができます。
  • p: パワーキャリブレーションの値になるようです。1つ必ず指定する必要があります。
  • d: データフィールド。オプションで複数して可能です。指定しなくてもOKの模様

BeaconLayoutの作成

iBeaconを識別するために、先ほどの定義に従って記述してみます。以下のような記述になります。

// iBeaconのデータを認識するためのParserフォーマット
public static final String IBEACON_FORMAT = "m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24";

BeaconLayoutではm:2-3=0215, と記述することで、iBeaconの信号を受信することが出来ます。参考にさせていただいたページはこちら

ちなみにこのLayoutですが、最終的にStackOverflowで答えを見つけました。
こちらこちらを参考に散々悩んだ挙句、公式のReferenceをよく見てみると"If you want to see examples of how other folks have set up BeaconParsers for different kinds of beacons, try doing a Google search for "getBeaconParsers" (include the quotes in the search.)"と書いてあるのに気づきました。公式Referenceで「ggrks」を見ることになるとは・・・。

その後に続きi:4-19,i:20-21,i:22-23,。こちらは、それぞれUUID, major, minorの値の定義になります。

最後のp:24-24。こちらはパワーキャリブレーションの値です。

BeaconParserにLayoutを設定

iBeaconを認識するBeaconLayoutが出来たので、設定します。

MyActivity.java

private BeaconManager beaconManager;

// iBeaconのデータを認識するためのParserフォーマット
public static final String IBEACON_FORMAT = "m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24";

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_my);
    if (savedInstanceState == null) {
        getFragmentManager().beginTransaction()
                .add(R.id.container, new PlaceholderFragment())
                .commit();
    }

    // staticメソッドで取得
    beaconManager = BeaconManager.getInstanceForApplication(this);

    // BeaconParseを設定
    beaconManager.getBeaconParsers()
            .add(new BeaconParser().setBeaconLayout(IBEACON_FORMAT));
}

これでようやくiBeaconを認識する準備が整いました。実行してみましょう。

03-16 20:13:17.385  16591-17177/beacon.android.classmethod.jp.altbeaconlibrarysample D/MyActivity﹕ DetermineState: 1
03-16 20:13:17.385  16591-17177/beacon.android.classmethod.jp.altbeaconlibrarysample D/Beacon﹕ ENTER Region.
03-16 20:13:17.405  16591-16607/beacon.android.classmethod.jp.altbeaconlibrarysample D/BluetoothLeScanner﹕ onScanResult() - ScanResult{mDevice=00:1C:4D:43:F1:30, mScanRecord=ScanRecord [mAdvertiseFlags=6, mServiceUuids=[00000000-24f6-4000-a000-030533643b2e, 0000fef8-0000-1000-8000-00805f9b34fb], mManufacturerSpecificData={}, mServiceData={}, mTxPowerLevel=2, mDeviceName= ], mRssi=-54, mTimestampNanos=1851382343892238}
03-16 20:13:17.421  16591-16608/beacon.android.classmethod.jp.altbeaconlibrarysample D/BluetoothLeScanner﹕ onScanResult() - ScanResult{mDevice=C0:1C:4D:43:F1:30, mScanRecord=ScanRecord [mAdvertiseFlags=6, mServiceUuids=null, mManufacturerSpecificData={76=[2, 21, 0, 0, 0, 0, 106, -50, 16, 1, -80, 0, 0, 28, 77, -39, 41, 88, 0, 4, 0, 17, -63]}, mServiceData={}, mTxPowerLevel=-2147483648, mDeviceName=null], mRssi=-40, mTimestampNanos=1851382359867706}
03-16 20:13:17.436  16591-16853/beacon.android.classmethod.jp.altbeaconlibrarysample D/BluetoothLeScanner﹕ onClientRegistered() - status=0 clientIf=5

DetermineStateENTER Regionが出力されていますね。iBeaconをきちんと認識できたようです。

これでiBeaconを認識して領域監視ができるようになりました。今回のブランチは「beacon_003」になります。ソースはこちらになります。次回は、RangingMonitor(距離観測?)を実装してみます。

やってはいけないBeaconLayout設定

注意しなければならないのが、このbeaconManager.getBeaconParsers().add()メソッド。Listにaddしているだけに見えるため、どこでも呼び出しても問題ないように見えますが、これは一度だけ設定するようにしてください。別の箇所で後付でLayoutを変更しようと同じようにaddしようとすると例外が発生します。

MyActivity.java

@Override
public void onBeaconServiceConnect() {
    beaconManager.getBeaconParsers()
            .add(new BeaconParser().setBeaconLayout(IBEACON_FORMAT));

    beaconManager.setMonitorNotifier(new MonitorNotifier() {
        @Override
        public void didEnterRegion(Region region) {
            // 領域への入場を検知
            Log.d("Beacon", "ENTER Region.");
        }

        @Override
        public void didExitRegion(Region region) {
            // 領域からの退場を検知
            Log.d("Beacon", "EXIT Region. ");
        }

        @Override
        public void didDetermineStateForRegion(int i, Region region) {
            // 領域への入退場のステータス変化を検知
            Log.d("MyActivity", "DetermineState: " + i);
        }
    });

    try {
        // ビーコン情報の監視を開始
        beaconManager.startMonitoringBeaconsInRegion(new Region("unique-id-001", null, null, null));
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

少々手荒ですが、onCreate()でもBeaconLayoutを設定しているにも関わらず、Serviceに接続した際にもBeaconLayoutを再度設定させます。これを実行すると以下の例外がスローされます。

03-16 20:14:12.029  17472-17472/beacon.android.classmethod.jp.altbeaconlibrarysample D/AndroidRuntime﹕ Shutting down VM
03-16 20:14:12.033  17472-17472/beacon.android.classmethod.jp.altbeaconlibrarysample E/AndroidRuntime﹕ FATAL EXCEPTION: main
    Process: beacon.android.classmethod.jp.altbeaconlibrarysample, PID: 17472
    java.lang.UnsupportedOperationException
            at java.util.Collections$UnmodifiableCollection.add(Collections.java:928)
            at beacon.android.classmethod.jp.altbeaconlibrarysample.MyActivity.onBeaconServiceConnect(MyActivity.java:82)
            at org.altbeacon.beacon.BeaconManager$1.onServiceConnected(BeaconManager.java:585)
            at android.app.LoadedApk$ServiceDispatcher.doConnected(LoadedApk.java:1203)
            at android.app.LoadedApk$ServiceDispatcher$RunConnection.run(LoadedApk.java:1220)
            at android.os.Handler.handleCallback(Handler.java:739)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:135)
            at android.app.ActivityThread.main(ActivityThread.java:5221)
            at java.lang.reflect.Method.invoke(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:372)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)

このため、BeaconLayoutの設定箇所は一度だけにしたほうが良いようです。ソースはこちらになります。

参照