[Android][iBeacon] Android Beacon Library パラっと解説 その4 [距離観測]

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

こむろです。札幌もようやく春の陽気です。気温が氷点下でなくなってきました。

IMG_20150308_173407
家の近くの様子。夜闇に沈みゆく交差点と山。

距離測定

今回は、もう一つの機能である距離観測 *1「Ranging」を実装します。
前回までの領域観測では、ある特定のビーコンが作り出すRegionへの入場と退場を検知することができました。Rangingではビーコン一つ一つの情報やおおまかな距離を状態として取得することが出来ます。よく言われてることですが、位置は検出できませんので悪しからず。

準備

まずは、領域監視をはずした状態までソースを戻します。onBeaconServiceConnect()の中の実装を削除しリセットします。

public class MyActivity extends Activity implements BeaconConsumer {

    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));
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.my, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onBeaconServiceConnect() {
        // TODO: Rangingの情報を受けるListenerを設定する
    }

    /**
     * A placeholder fragment containing a simple view.
     */
    public static class PlaceholderFragment extends Fragment {

        public PlaceholderFragment() {
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            View rootView = inflater.inflate(R.layout.fragment_my, container, false);
            return rootView;
        }
    }
}

これで準備は完了です。さっそく実装しましょう。

イベントを受け取るインターフェースを実装

Rangingのイベントを取得するにはRangeNotifierインターフェースを実装し、BeaconManagerに設定する必要があります。

beaconManager.setRangeNotifier(new RangeNotifier() {
    @Override
    public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
        // TODO: 検出されたビーコン
    }
});

一度のスキャンで複数のビーコンを検知する可能性があるため、Collectionとして複数のBeacon情報が返却されるよう定義されています。
Region はどのグループに属するビーコンを監視の対象にしているものかを記述した値になります。

Regionについて

こちらは領域監視のところでも出てきました。監視対象とするビーコンのフィルタ定義のようなものと考えて良いと思います。どのような指定をするとどういった動きを見せるのでしょうか?少しだけ見てみましょう。

例として以下のようなビーコンが周囲に存在する環境を想定します。データはダミーです
(が、もしかすると偶然存在するデータかもしれませんが)

    

        

        

            

            

            

        

    

    

    

            

            

            

            

        

        

        

            

            

            

        

        

            

            

            

            

        

        

            

            

            

            

        

    

Name UUID major minor
ビーコンα 6851F516-CF11-45D1-826C-9DA9CFF744C1 122 23
ビーコンβ 8B5FC5DA-6CFD-4CD0-9F8B-D3B07A306A38 1 5
ビーコンγ 8B5FC5DA-6CFD-4CD0-9F8B-D3B07A306A38 32 64
ビーコンδ 8B5FC5DA-6CFD-4CD0-9F8B-D3B07A306A38 32 30

さて、これを前提に話を進めていきましょう。

UUID, major, minorすべてをワイルドカードで指定

このようなインスタンスを生成すると、UUID, major, minorすべてワイルドカードでの指定となります。

new Region("unique-id", null, null, null);

検出されるビーコンは以下。UUID, major, minorすべてが異なっていても、iBeaconの形式であれば検出されます。

  • 「ビーコンα」
  • 「ビーコンβ」
  • 「ビーコンγ」
  • 「ビーコンδ」

UUIDのみ指定。major, minorをワイルドカード指定

次にUUIDのみを指定した場合。この場合は、major, minorがワイルドカードでの指定となります。

new Region("unique-id", Identifier.parse("8B5FC5DA-6CFD-4CD0-9F8B-D3B07A306A38"), null, null);

UUIDを8B5FC5DA-6CFD-4CD0-9F8B-D3B07A306A38で指定します。検出されるビーコンは、以下のように。

  • 「ビーコンβ」
  • 「ビーコンγ」
  • 「ビーコンδ」

ビーコンαのみ、UUIDが異なるため除外されました。

UUID, majorを指定。minorをワイルドカード

UUIDmajorを指定します。minorのみワイルドカードとして扱われます。

new Region("unique-id", Identifier.parse("8B5FC5DA-6CFD-4CD0-9F8B-D3B07A306A38"), Identifier.parseInt(32), null);

検出するビーコンは以下の通り。

  • 「ビーコンγ」
  • 「ビーコンδ」

ビーコンβはUUIDは同じでもmajorが異なるため除外されています。

UUID, major, minorを指定

すべての値を明示的に指定します。こうなるとビーコンは一意に特定できるはずなので(ID体系に不整合がなければ)、一つのビーコンのみを検出することができます。

new Region("unique-id", Identifier.parse("8B5FC5DA-6CFD-4CD0-9F8B-D3B07A306A38"), Identifier.parseInt(32), Identifier.parseInt(30));

検出するビーコンはただひとつ。

  • 「ビーコンδ」

このようにRegionの引数指定を変化させることで、検出するビーコンをグループで分けることができます。イメージとしてはこんな感じ。

Region

Rangingを開始する

さて、脇道に逸れてしまったので本筋に戻ります。Rangingのイベントを受け取るListenerを設定しましたので、Rangingを開始させましょう。今回も前回と同様、iBeaconであれば何でも検知できるように、UUID, major, minorすべて指定しないRegionを生成して指定します。

try {
    beaconManager.startRangingBeaconsInRegion(new Region("unique-ranging-region-id", null, null, null));
} catch (RemoteException e) {
    e.printStackTrace();
}

このコードで距離測定が開始されます。
Rangingのイベントを受け取るListenerと合わせたコード全体は以下の様な形にしました。

@Override
public void onBeaconServiceConnect() {
    beaconManager.setRangeNotifier(new RangeNotifier() {
        @Override
        public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
            // 検出したビーコンの情報を全部Logに書き出す
            for(Beacon beacon : beacons) {
                Log.d("MyActivity", "UUID:" + beacon.getId1() + ", major:" + beacon.getId2() + ", minor:" + beacon.getId3() + ", Distance:" + beacon.getDistance());
            }
        }
    });
    
    try {
        // 距離観測の開始
        beaconManager.startRangingBeaconsInRegion(new Region("unique-ranging-region-id", null, null, null));
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

これで準備が出来ました。あとはBeaconManagerのサービスを起動しましょう。

@Override
protected void onResume() {
    super.onResume();
    beaconManager.bind(this);
}

@Override
protected void onPause() {
    beaconManager.unbind(this);
    super.onPause();
}

実行結果

実行すると、前回までの領域監視とは異なりビーコンが見つかり次第次々と情報が出力されていきます。そのため、logcatの画面がザクザク流れていくかと思います。自分の XPERIA Z2 で実行してみた結果が以下になります。

03-20 17:24:11.678  27725-27725/beacon.android.classmethod.jp.altbeaconlibrarysample D/BluetoothAdapter﹕ stopLeScan()
03-20 17:24:11.688  27725-27725/beacon.android.classmethod.jp.altbeaconlibrarysample D/BluetoothAdapter﹕ startLeScan(): null
03-20 17:24:11.688  27725-27739/beacon.android.classmethod.jp.altbeaconlibrarysample D/BluetoothAdapter﹕ onClientRegistered() - status=0 clientIf=5
03-20 17:24:11.698  27725-27922/beacon.android.classmethod.jp.altbeaconlibrarysample D/MyActivity﹕ UUID:8B5FC5DA-6CFD-4CD0-9F8B-D3B07A306A38, major:32, minor:30, Distance:0.08340623762608852
03-20 17:24:12.798  27725-27725/beacon.android.classmethod.jp.altbeaconlibrarysample D/BluetoothAdapter﹕ stopLeScan()
03-20 17:24:12.808  27725-27725/beacon.android.classmethod.jp.altbeaconlibrarysample D/BluetoothAdapter﹕ startLeScan(): null
03-20 17:24:12.808  27725-27740/beacon.android.classmethod.jp.altbeaconlibrarysample D/BluetoothAdapter﹕ onClientRegistered() - status=0 clientIf=5
03-20 17:24:12.818  27725-27951/beacon.android.classmethod.jp.altbeaconlibrarysample D/MyActivity﹕ UUID:8B5FC5DA-6CFD-4CD0-9F8B-D3B07A306A38, major:32, minor:30, Distance:0.08624645983330642

UUID, major, minorがとれているのはもちろんのこと、Distance が取得できているのが確認できます。これで目的の距離情報を取得することが出来ました。今回のソースコードはこちら。ブランチ名は「beacon_004」になります。

Proximityとdistanceは全く別物なのでご注意を。そしてこのDistanceという値。思った以上に変化が緩やかです。
恐らくノイズ除去のための幾何平均などの処理が入り込んでるものと推測できます。多分この辺りの計算ロジックが影響しているものと考えられます。

ビーコンの詳細情報が取得したい場合は、こちらを利用する必要があります。
領域監視で特定のグループ(UUID, majorを指定など)の領域に入ったら、そのUUID, majorで距離観測を行う、といった使い方が出来るかもしれません。

次回は、バックグラウンドでビーコンを検知するための実装を紹介します。

参照

脚注

  1. この訳は一部の人に首をはねられるような気がしています・・・。