iBeaconのスキャナーを作ってみた

2016.05.04

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

1 はじめに

今回は、近くに存在するiBeacon装置を列挙するプログラムを書いてみました。

iBeaconの装置は、BLE(Bluetooth Low Energy)のアドバタイズモードで動作するにベリフェラルであると言えます。

通常のペリフェラル機器は、自分の存在をアドバタイズしてセントラルから見つけてもらい、その後、接続してデータのやり取りをしますが、iBeaconでは、このアドバタイズ機能だけに特化して低コスト(低消費電力など)を追求しています。

ここで関連する用語だけを簡単にまとめると次のようになります。


 * ペリフェラル (BLE機器 周辺に定期的に自分の存在をアドバタイズする)
 * セントラル (BLE機器を利用する側)
 * アドバタイズモード (定期的に情報を送信モード)
 * スキャンモード (アドバタイズをリッスンするモード)

2 iBeaconのアドバタイズデータ

iBeaconは、BLEのサブセットであると言うことで、そのパケットにも特徴があります。

下記は、iBeaconのアドバタイズパケットです。XX以外の部分は、固定です。

内容
1A AD Structureの長さ
FF ADのタイプ
4C 00 企業識別子 0x004C(Apple)
02 iBeacon 識別子
15 iBeacon 識別子
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX UUID
XXXX major
XXXX minor
XX Power

「iOS×BLE Core Bluetoothプログラミング P360」より引用させて頂きました。

つまり、セントラルとしてアドバタイズをスキャンして、上記の特徴に当てはまるものを取得すればiBeaconを列挙できたことになります。

3 iOSでの制限

iOSでは、CoreLocation.frameworkを利用することで、iBeaconを簡単に扱うことができます。

[iOS 7] 新たな領域観測サービス iBeacon を使ってみる
[iOS8] iOS8でiBeacon実装の落とし穴

上記、フレームワークで提供されるCLLocationManagerでは、ビーコンのUUIDごとに纏めてリージョンとして扱い、それを監視することで、領域に入ったかどうかを検出できます。 しかし、UUIDを指定するという事は、UUIDの分からないビーコンは列挙できないということになります。

そこで、先に検討したとおり、BLEのスキャンモードで実装します。

BLEを扱うためには、CoreBluetooth.frameworkを利用します。

001

最初に、自身をデリゲートに指定してCBCentralManagerを生成します。 そして、セントラルとして動作できることが確認できたらscanForPeripheralsWithServicesを使用してスキャンを開始します。serviceUUIDsoptionsには、スキャン対象を指定できますが、ここをnilとすることで、全てを対象にしています。

下記が、実装したコードです。列挙ができたらログに出力しています。

ViewController.h

#import <UIKit/UIKit.h>
#import <CoreBluetooth/CoreBluetooth.h>

@interface ViewController : UIViewController<CBCentralManagerDelegate>
@property (nonatomic) CBCentralManager *centralManager;
@end

ViewController.m

- (void)viewDidLoad {
    [super viewDidLoad];
    // 自身をdelegateに設定してCBCentralManagerを生成する
    _centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
}

- (void) centralManagerDidUpdateState:(CBCentralManager *)central
{
    // セントラルとして動作できることを確認
    if(central.state == CBCentralManagerStatePoweredOn) {
        // 対象を指定しない(nil設定)でスキャンを開始
        [_centralManager scanForPeripheralsWithServices:nil options:nil];
    }
}

- (void)centralManager:(CBCentralManager *)central
 didDiscoverPeripheral:(CBPeripheral *)peripheral
     advertisementData:(NSDictionary *)advertisementData
                  RSSI:(NSNumber *)RSSI
{
    // 検出内容をログ出力
    NSLog(@"%@", RSSI);
    NSLog(@"%@", advertisementData);

}

しかし、出力されたログは、次のようなものでした。信号強度(dB)と接続可能ということだけしか分かりません。

2016-05-04 06:00:47.939 iBeaconScanner[2218:1311980] -40
2016-05-04 06:00:47.942 iBeaconScanner[2218:1311980] {
    kCBAdvDataIsConnectable = 1;
}

試しに、全く同じコードをOS X で書くと次のようなログになります。

2016-05-04 06:00:52.078 iBeaconScanner[44396:3524488] -48
2016-05-04 06:00:52.078 iBeaconScanner[44396:3524488] {
    kCBAdvDataAppleMfgData =     {
        kCBScanOptionAppleFilterPuckType = 2;
    };
    kCBAdvDataIsConnectable = 1;
    kCBAdvDataManufacturerData = <4c000215 48534442 4c454144 80c01800 ffffffff 00640001 c8>;
}

今度は、しっかりとアドバタイズデータが取得できています。 iOSでは、iBeaconをBLE機器として扱うプログラムが書けないように制限が設けられているようです。

できれば、iOSアプリとして書きたかったのですが、制限があるのでは、どうしようも無いと言うことで、OS Xで実装します。

4 OS Xでの実装

iOSのCoreLocation.frameworkを利用すると、startRangingBeaconsInRegionでiBeacon装置との距離を特定することができます。この時、CLBeaconというクラスで情報を受け取りますが、このクラスには、UUIDmajorminorの他に、距離に関する情報として、proximity(接近の度合)、accuracy(距離)、rssi(信号強度)が受け取れます。

005

スキャンモードで受け取ったデータには、信号強度しか有りませんが、代わりにPower(出力)があります。CoreLocation.frameworkでは、この2つの値から、proximity(接近の度合)とaccuracy(距離)を算出しているのでしょう。

残念ながら、OS X では、CoreLocation.frameworkが利用できませんので、今回は、上記の計算を実装しました。

距離の計算については、Stackoverflowの次の記事を
Stackoverflow - Understanding ibeacon distancing

そして、接近の度合については、下記の記事を参照させて頂きました。
RSSI と TxPower からビーコンとの距離および近接度(Proximity)を推定する

iBeacon.m

self.distance = -1.0;
if(rssi != 0){
    double ratio = rssi*1.0/power;
    if (ratio < 1.0) {
        self.distance = pow(ratio,10.0);
    }else {
        self.distance =  (0.89976) * pow(ratio,7.7095) + 0.111;
    }
}

self.proximity = @"Unknown";
if (_distance >= 2.0){ // 2m以上で Far
    self.proximity = @"Far";
}else if(_distance >= 0.2){ //20cm以上で Near
    self.proximity = @"Near";
}else if (_distance >= 0){ // 20cm未満で Immediate
    self.proximity = @"Immediate";
}

下記は、作成したプログラムを実行している様子です。周辺の全てのiBeaconを列挙できます。

002

コードは、下記に置きました。

github [GitHub] https://github.com/furuya02/iBeaconScanner

5 おまけ(Raspberry PiをiBeacon発信機にする)

今回、色々なiBeaconを並べるために、ビーコンの発信機をRaspberry Piでも作成しました。 本題とは直接関係ありませんが、ライブラリをインストールするだけで簡単に利用できたので、覚書を兼ねて紹介させて下さい。

003

(1) bleacon(ライブラリ)のインストール

libbluetooth-devというライブラリをインストールします。

$ sudo apt-get install libbluetooth-dev
$ npm install bleacon

(2) Node.js

次のようなファイル(ここでは、名前をibeacon.jsとしました)を作成して

Bleacon = require('bleacon');
var uuid = 'F65BE0B1-D712-4647-A665-8EC2B4337410';
var major = 0;
var minor = 1;
var measuredPower = -50;

Bleacon.startAdvertising(uuid, major, minor, measuredPower);

nodejsで実行します。

$nodejs ibeacon.js

(3) Bluetooth Explorer

次の図は、実行中の状況をBluetooth Explorerで確認している様子です。

004

6 最後に

今回、iBeaconを少し掘り下げてBLEから眺めることで、いろいろ勉強になりました。また、iOSにおける制限や、CoreLocation.frameworkだからこそ実装できる機能についても整理ができました。

iBeaconの実装は、リージョンから出た時のタイムラグや、電波の状況による不安定さから、色々ハマリどころがあり、習得が難しいと感じましたが、BLEのサブセットとして捉え直すことで、うまく理解ができたように感じました。

7 参考資料


[iOS 7] 新たな領域観測サービス iBeacon を使ってみる
[iOS8] iOS8でiBeacon実装の落とし穴
Stackoverflow - Understanding ibeacon distancing
RSSI と TxPower からビーコンとの距離および近接度(Proximity)を推定する
iOS×BLE Core Bluetoothプログラミング
Apple製開発ツール「Bluetooth Explorer」でBLEデバイスのGATT仕様を確認する
Node.jsとiPhoneでiBeaconの送受信テスト