この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こむろです。札幌もようやく春の陽気です。気温が氷点下でなくなってきました。
家の近くの様子。夜闇に沈みゆく交差点と山。
距離測定
今回は、もう一つの機能である距離観測 *1、「Ranging」を実装します。
前回までの領域観測では、ある特定のビーコンが作り出すRegionへの入場と退場を検知することができました。Rangingではビーコン一つ一つの情報やおおまかな距離を状態として取得することが出来ます。よく言われてることですが、位置は検出できませんので悪しからず。
準備
まずは、領域監視をはずした状態までソースを戻します。onBeaconServiceConnect()の中の実装を削除しリセットします。
MyActivity.java
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をワイルドカード
UUIDとmajorを指定します。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の引数指定を変化させることで、検出するビーコンをグループで分けることができます。イメージとしてはこんな感じ。
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で距離観測を行う、といった使い方が出来るかもしれません。
次回は、バックグラウンドでビーコンを検知するための実装を紹介します。
参照
- Github - AndroidBeaconLLbrary
- Javadoc - android-beacon-library
- AndroidBeaconLibrary - Samples
- Github - BlogSampleAndroidBeaconLibrary
脚注
- この訳は一部の人に首をはねられるような気がしています・・・。 ↩