[Android][iBeacon] Android Beacon Library パラっと解説 その3 [BeaconParser]
こんにちは。こむろです。前回の続きです。簡単な領域監視の実装まで完了していましたが、うまく動きませんでした。それを解決していきます。
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を利用させていただきました。
この値を意味のある形に分解する必要があるのですが、その定義を設定できるのが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が出来たので、設定します。
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
DetermineStateとENTER Regionが出力されていますね。iBeaconをきちんと認識できたようです。
これでiBeaconを認識して領域監視ができるようになりました。今回のブランチは「beacon_003」になります。ソースはこちらになります。次回は、RangingMonitor(距離観測?)を実装してみます。
やってはいけないBeaconLayout設定
注意しなければならないのが、このbeaconManager.getBeaconParsers().add()メソッド。Listにaddしているだけに見えるため、どこでも呼び出しても問題ないように見えますが、これは一度だけ設定するようにしてください。別の箇所で後付でLayoutを変更しようと同じようにaddしようとすると例外が発生します。
@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の設定箇所は一度だけにしたほうが良いようです。ソースはこちらになります。