iBeaconを検知してあれこれできるアプリをXamarinで開発したい
iBeaconは、店頭でのクーポン配布やスタンプラリー等々、色々な用途で使われたりしています。
その他、使い方次第ではフィールドゲーム用アプリのギミック等々にも使えます。
そんなiBeaconを検知して任意の処理を行うアプリを色々なプラットフォーム向けに作ろうとすると、普通は以下のように作ることになります。
- 純粋なネイティブの開発環境(XcodeやAndroid Studio)で、プラットフォームごとに個別のアプリを作る。
- Xamarin等でクロスプラットフォーム開発をするにしても、iBeaconの検知部分はプラットフォームごとに作る。
- 特にiOS以外で開発するときは、「受信したBluetoothのパケットの何バイト目がどういう意味で、云々」を調べたりする必要がある。
せっかくアイデア次第で色々できるiBeacon。どうせなら、Xamarinで色々なプラットフォーム(iOS・Android・UWP)のアプリをいっぺんに超手軽に開発したいですよね。
iBeaconの検知を超簡単に実装できるパッケージを作ってみた
というわけで、iBeaconを超簡単に検知したり、検知時に任意の処理をトリガーできるXamarin向けのNuGetパッケージを作って公開してみました。
パッケージ名はBeahat(iBeacon handling as a triggerの略)です。
パッケージ
ソース
対応プラットフォーム
iOS | Android | UWP |
---|---|---|
8.0以上 | 4.4以上 | 制限なし |
ライセンス・免責
パッケージ・ソース共にCC0(権利放棄)です。改変・再配布等々自由に使っていただいて大丈夫です。
開発のたびに面倒な思いをさせられるOSSの記述等も一切不要です。
ですが、利用は自己責任でお願いします。
実は本パッケージ、de:code 2017 のイベントアプリで使っていただいたりしています。
iBeacon検知用のXamarin向けNuGetパッケージは、本記事投稿時点でまだ他の誰も作っていません(需要がないだけ!?)。
Beahatの使い方
以下、Xamarin.Formsで使うことを前提に説明します。
1. パッケージのインストール
NuGetパッケージマネージャーで、PCLや各プラットフォームのプロジェクトにBeahatをインストールします。
2. パーミッション関連の下準備
各プラットフォームのプロジェクトで、以下のようにパーミッション関連の設定を追加します。
iOS
Info.plistに、以下のどちらかのkeyを追加してください。
・ NSLocationWhenInUseUsageDescription (Location When In Use Usage Description)
・ NSLocationAlwaysUsageDescriptionProperty (Location Always Usage Description Property)
また、Bluetoothのオン/オフを判定する処理を使用するためには、以下のkeyを追加してください。
・ NSBluetoothPeripheralUsageDescription (Bluetooth Peripheral Usage Description)Android
AndroidManifest.xmlで、以下の2つのアクセス許可を追加してください。
・ BLUETOOTH
・ BLUETOOTH_ADMINUWP
Package.appxmanifestの“機能”タブで、以下の項目のチェックをオンにしてください。
・ Bluetooth
3. Beahatのインスタンスの取得
共通プロジェクトの中のBeahatを使うViewModelやコードビハインドで、以下のようにBeahatのインスタンスを取得します。
// 以下の2つをusingに追加
using Plugin.Beahat;
using Plugin.Beahat.Abstractions;
// Beahatのインスタンスを保持するフィールドを用意
private readonly IBeahat _beahat;
// コンストラクタ
public YourClass()
{
// Beahatのインスタンスを取得
_beahat = Beahat.Current;
}
インスタンスの取得は必ずしもコンストラクタでやらなくても大丈夫です。
4. 検知したいiBeaconの登録
任意のメソッドの中で以下のように実装することで、iBeaconを登録します。
// 検知したいiBeaconのUUID、Major、Minor
var uuid = new Guid("01234567-0123-0123-ABCD-0123456789AB");
ushort major = 1;
ushort minor = 2;
// 上記のUUID、Major、Minorを持つiBeaconをBeahatに登録する。
_beahat.AddObservableBeacon(uuid, major, minor);
// 以下のように、複数のiBeaconを検知対象として次々登録することもできる。
_beahat.AddObservableBeacon(_uuid2, _major2, _minor2);
_beahat.AddObservableBeacon(_uuid3, _major3, _minor3);
_beahat.AddObservableBeacon(_uuid4, _major4, _minor4);
//「UUIDのみの指定」や「UUIDとMajorのみの指定」も可能。
_beahat.AddObservableBeacon(uuid); // UUIDが一致するすべてのiBeaconを探す。
_beahat.AddObservableBeacon(uuid, major); // UUIDとMajorが一致するすべてのiBeaconを探す。
5. iBeaconのスキャンを開始
任意のメソッドの中で以下の処理を呼ぶことで、iBeaconのスキャンを開始します。
// スキャンを開始
_beahat.StartScan();
スキャン中に検知対象のiBeaconを追加する(AddObservableBeaconを実行する)ことも可能です。
追加されたiBeaconも、そのとき実行中のスキャンにおける検知対象となります。
6. スキャン結果の確認
スキャンで見つかったiBeaconのリストを、Beahatから以下のように取得することができます。
// スキャンで検出されたiBeaconの一覧その1。 検出された各iBeaconについて、最も近接したときの情報が格納される。
List<iBeacon> detectedBeacons1 = _beahat.BeaconListFromClosestApproachedEvent;
// スキャンで検出されたiBeaconの一覧その2。 検出された各iBeaconについて、最後に検知したときの情報が格納される。
List<iBeacon> detectedBeacons2 = _beahat.BeaconListFromLastApproachedEvent;
/*
iBeacon型のインスタンスには以下の情報が格納される。
Guid Uuid : UUID。
ushort Major : Major値。
ushort Minor : Minor値。
short Rssi : 検出電波強度(RSSI)。
short TxPower : 出力電波強度(TxPower)。※iOS版では検出不可
double EstimatedDistanceMeter : 推定距離(メートル単位)。
*/
スキャンを止めなくても、上記のリストはいつでも取得可能です。
7. スキャンの停止
以下のメソッドで停止します。
// スキャンを停止
_beahat.StopScan();
8. iBeacon検知時のコールバック処理の登録と利用
「iBeaconの検知をトリガーにして何かの処理を実行したい」という場面はよくあると思います。
そんな処理も簡単に実装できます。
public void startScan()
{
// 検知対象のiBeaconと、検知時のコールバック処理を設定する。
// ThresholdRssi : この電波強度を超えたときにだけコールバック処理が実行される。
// TimeIntervalMilliSec : 一度コールバックが実行されてから、次に同じコールバックが実行可能になるまでの停止時間。
// myBeaconDetectCallback : 実行させたいコールバック処理。
_beahat.AddObservableBeaconWithCallback(_anyUuid, _anyMajor, _anyMinor,
ThresholdRssi,
TimeIntervalMilliSec,
myBeaconDetectCallback);
_beahat.StartScan();
}
// iBeacon検知時に自動実行したい任意の処理
private void myBeaconDetectCallback()
{
// iBeaconを見つけたことをダイアログでユーザーに知らせる。
_pageDialogService.DisplayAlertAsync("Beahat",
"iBeaconを発見!",
"OK");
}
上の例では1つのコールバック処理を登録していますが、以下のようなことも可能です。
- 1つのiBeaconに対して、複数種のコールバック処理を、異なるトリガー条件(異なる電波強度や停止時間)で登録する。
- UUIDだけを指定して、そのUUIDに合致するiBeaconを検知したらとにかくコールバック処理を実行する。
コールバックと言いつつも、SenderもEventArgsもありません。疑似コールバックです。。。
その他ユーティリティ的な処理
端末のBluetoothサポート有無をチェックする処理
以下の処理(SupportsBluetooth)で、「端末がBluetoothをサポートしているかどうか」を取得できます。
if (_beahat.SupportsBluetooth())
{
// このデバイスはBluetoothをサポートしています。
}
else
{
// このデバイスはBluetoothをサポートしていません。
// シミュレーターでデバッグするとこっちに入ります。
}
UWPで対応する処理が見つからなかったため、UWPではこれを実行しても現状何も起こりません。
教えていただけるとめちゃくちゃ有難いです。。。
端末のBluetoothオン/オフをチェックする処理
以下の処理(IsReadyToUseBluetooth)で、「端末のBluetooth機能がオンかどうか」を取得できます。
if (_beahat.IsReadyToUseBluetooth())
{
// Bluetooth機能がオンになっています。
}
else
{
// Bluetooth機能がオフになっています。
// WindowsPhoneって、たまに気付いたときに勝手にオフになってたりするんだよなぁ。。。
}
[iOS用]位置情報が利用可能かどうかをチェックする処理
iOSだけは、大元の本家APIがiBeaconを「位置情報」として扱っています(CoreLocation)。
そのため、端末の位置情報がオフにされていたり、アプリに位置情報利用を許可していないとiBeaconの検知ができません。
以下の処理で、「iBeacon向けに位置情報が利用可能となっているかどうか」をチェックします。
if (_beahat.CanUseLocationForDetectBeacons())
{
// 位置情報の利用が許可されているので、BluetoothさえオンになっていればiBeaconの検知ができます。
}
else
{
// 端末の位置情報がオフだったり、アプリに対する位置情報利用が不許可だったりします。
}
尚、AndroidとUWPはiBeacon検知のために位置情報の許可は要らないので、AndroidとUWPでこの処理を実行すると常にtrueが返って来ます。
飽くまでもiBeacon検知にフォーカスした処理となっています。
Bluetoothをオンにするための標準ダイアログや標準インテントを表示する処理
Bluetoothがオフにされていたなら、オンにすることを促すための処理が欲しくなります。
それを実現するための処理です。
_beahat.RequestToTurnOnBluetooth();
UWPで対応する処理が見つからなかったため、UWPではこれを実行しても現状何も起こりません。
[iOS用]位置情報をオンにするための標準ダイアログを表示する処理
iOSだけは先述の通り位置情報の利用許可が必要です。
それを促す標準ダイアログを表示する処理です。
_beahat.RequestToAllowUsingLocationForDetectBeacons();
こちらもiBeacon検知にフォーカスした処理となっているため、AndroidとUWPでは何も起こらないようになっています。
また、内側ではiOS標準のAPIを使っているため、位置情報利用を一度不許可とされた場合は、それ以降この処理を実行してもダイアログは表示されなくなります。
ケース別の実装例
【ケース1】 スタンプラリーに使いたい
ユーザーが「スタンプを探す」の処理を実行したとき、最も近くにあるスタンプラリー用のiBeaconを判定し、それに対応するスタンプ獲得処理を実行する例です。
private void prepareStamps()
{
// 各拠点に設置するスタンプ用iBeaconを登録する。
_beahat.AddObservableBeacon(stampsUuid1, stampsMajor1, stampsMinor1);
_beahat.AddObservableBeacon(stampsUuid2, stampsMajor2, stampsMinor2);
_beahat.AddObservableBeacon(stampsUuid3, stampsMajor3, stampsMinor3);
// ...以下略
}
public void FindStamp()
{
// 検知したiBeacon一覧の情報を初期化する。
_beahat.InitializeDetectedBeaconList();
// スタンプ用iBeaconを2秒間探す。
showIndicator(); // インジケーターを表示する処理(中身省略)
_beahat.StartScan();
Task.Delay(2000).Wait();
_beahat.StopScan();
hideIndicator(); // インジケーターを隠す処理(中身省略)
// 検知できたiBeaconの内、最も推定距離が近かったものを「検知したスタンプ」とみなす。
var beaconList = _beahat.BeaconListFromClosestApproachedEvent;
iBeacon closestBeacon = beaconList.First();
// ...以下略(見つけた最近接iBeaconに対する後続処理)
}
【ケース2】「特定のエリアにいる間ダメージを受け続ける」的な使い方をしたい
フィールドゲームのギミックとして使うことを想定した例です。
疑似的な"毒ガス"エリアみたいなイメージです。
private void setDamageAreaEvent()
{
// ダメージ領域用のiBeaconの近くにいるとき、およそ毎秒1回ペースでダメージ処理を行うようにする。
_beahat.AddObservableBeaconWithCallback(_damageAreaUuid, _damageAreaMajor, _damageAreaMinor,
_thresholdRssi,
1000,
receiveDamage);
}
private void startScan()
{
_beahat.StartScan();
}
private void receiveDamage()
{
// ライフから一定量の数値を引く処理等々
}
今後の展望
iBeaconをXamarinで簡単に扱えるようにするために作ったパッケージではありますが、まだまだ色々足りないところがあります。
以下のような処理の追加が今後の課題かな~と思っています。
- アプリがバックグランド状態でのiBeaconの検知
- 領域へのINやOUTをトリガーにしたコールバック処理を簡単に実装できる仕組み
- 1回ぽっきりのみ実行されるコールバック処理を簡単に実装できる仕組み
etc...
余談
Beahatのソース(特にAndroidの部分)をご覧になられた方がいたら、「なんて冗長な実装をしているんだ!」と思われるかもしれません。
特に、Android4.4以下向けの処理とAndroid5.0以上向けの処理は共通化して纏められる部分が多いように見えると思います。
ですが、共通化できる部分を共通処理として外出しすると、Bluetooth検知時のAndroid側内部のコールバックがスキャン開始直後の最初の1回しか呼ばれなくなったり等々、謎の現象にぶち当たります。Androidだけが超絶デリケートで度々頭を抱えさせられます。
この現象に関しては、実装の仕方が悪いのか、はたまたXamarinのバグかAndroidのバグか、原因は分かっていません。。。