#1. 要約
Apple CoreBluetooth フレームワークを使い、PC 内蔵の BLE モジュールと iBeacon 端末間の距離測定(電波強度で代用)を行うアプリケーションの作成方法。
「BLE て何?」
「BLE ってどういうふうに通信してるの?」
「iPhone はまあいいけど、そもそも Mac で BLE できるの?」
... という理解レベルからアプリ作成に着手した筆者が、ネット上のリソースを駆使したら、なんとか動くアプリができたので、経緯とリソースをレポート形式でまとめました。
#2. 背景
とある イベント の出し物で、小型・無線を利用した簡易なジェスチャー解析アプリケーションを使ったデモをやることになり、BLE を利用したシステムを組んでみることにした。
この手のジェスチャー認識のための製品/フレームワークでは Leap Motion や Kinect などがよく知られている。人気もあるので開発のためのオンラインリソースも充実しており、また開発元が高機能な SDK や API を用意している。
しかしながら、これらの SDK, API は、デバイスへの依存性が高く、また重厚で、自作のシステムに組み込むにはかえって都合が悪いように思えた。
よりローレベルな規格である BLE では、標準で端末間の距離(電波強度)測定機能を備えており、シンプルにフィジカルコントローラを作成する、という目的に合致していると思われた。
※ 要するに「安価でコンパクトなデバイス構成で、勉強するなら標準的で応用が効く規格を自分のアプリに無線距離計測を取り込みたくて BLE 始めました」という感じ
#3. 完成像
- PC を BLE セントラル端末とする。
- 片手に収まるサイズの BLE 基板を、ペリフェラル端末とする。
- セントラルは定期的(100 msecオーダー)に、ペリフェラルとの距離を電波強度(db)の形で取得する。
- プロトタイピングのため、プログラムの作り込みはしない。
#4. 手順1 - 開発の準備
##4.1 用意したもの
- ・PC
- BLE アンテナを内蔵したPC。MacBookPro - 2012 Mid
- ・iBeacon 端末
- BLE アンテナを搭載した基板。 Laird Technology 社 BL600
BLE アプリケーションの開発は iOS や Windows でもサポートされているが、CoreBluetooth を使うことで、単に手元の機器の状況から金銭的、時間的に少ないコストで作成できると思われた。
BL600 は日本国内で無線装置を使うための基準である技適を満たしており、4000円くらいで入手できる。
実際のところは BLE そのものの規格で作成するのではなく、Apple社が BLE 上に敷いた iBeacon という枠組みの上でアプリケーションを作成する。
幸い、BL600 は BLE サービスの構築基板ではあるが、出荷時状態に iBeacon 端末として動作するためのデモアプリケーションが乗っている。
このほか、検証段階では iOS端末(ver7.1)上の既製品 iBeacon アプリを利用した。
##4.2 実施事項
###4.2.1 【準備】OS X 端末のシステムアップデート
もともと 10.8 で利用していた端末だが、Apple の最新サンプルを利用できたほうがよいと思い、10.9 にアップデートした。これにともない Xcode は 5.x 系を利用するように構成した。コマンドラインツールも最新化。
###4.2.2【準備】OS X 端末の BLE 動作確認
着手時点では、そもそも MBP 上で BLE が動作するのかどうか確証を得ていない状態だったので、Xcode を起動してがっつりソースを書く前に、既製品による要素テストを行った。
※ 別にやらなくてもアプリは作れるけど、ひととおりおさらいしておくと、BLE がどんな物か雰囲気がよくわかるし、次の段階でトラブったときもトラブルのアタリがつけやすくなると思う。
※ 以降では、肝心の OS X と BL600 間の通信は特に記載してないが、bleacon & BL600 の組み合わせでテストしてコンソールに出力が流れれば、次段階に進んで問題ない。
※ BLE は単なる Bluetooth と異なり、ここ 2-3年で普及しだした規格(Bluetooth 4.0 / Bluetooth Low Energy)であるので、Macのメニューバーに Bluetooth マークあるのを見たよ、というだけでは動くかどうかわからない。
実際のところ 過去の Mac の技術仕様を確認すると、2011 Late MBP は Bluetooth 2.1 なので対象外。BLE が搭載されだしたのが 2012 Mid といった模様。
####4.2.2.1 OS X 端末をペリフェラルにする
MBP をペリフェラルにしてアドバタイジングデータを送出させるテスト。以下のアプリケーションを利用する。
iOS 端末があれば、LightBlue, Locate といったアプリをセントラルにして MBP を簡単に捕捉できる。
・[LightBlue - Bluetooth Low Energy(Punch Through)] (https://itunes.apple.com/jp/app/lightblue-bluetooth-low-energy/id557428110?mt=8)
####4.2.2.2 OS X 端末をセントラルにする
逆に、セントラルにして iOS 側を iBeacon にしてみる。
bleacon はセントラルとして動作させるため、startScanning()を使う。
ターミナル上でペリフェラルからのアドバタイジングデータを受信している様子が確認できる。
node.js が導入してあれば、同梱されているスクリプトを改変なしで実行するだけなのでお手軽。
対にするペリフェラルは、iOS 上で iBeaconEmitter.
###4.2.3 【準備】BL600の動作確認テスト
・モード切替スイッチをRUN
・電池を入れる
・電源ONに入れる
2,3秒くらいでセントラルのアプリ(LightBlue、Locate)で検知できる。
#5. 手順2 - CoreBluetooth アプリケーションの作成
以上で必要な機器のテストが済んだので、次に、当初の目的をなすアプリケーションを作成してみる。
例によって ADC でサンプルプログラムが配布されているので、これを元に必要な拡張をする、というアプローチで進める。
##5.1 サンプルプログラム
CoreBluetoothフレームワークを用いて BLE ペリフェラルと接続し、特性を読み書きするというもの。
使い方
- 起動すると、体温計の図が表示される
- Start ボタンを押すと、ペリフェラル端末を検索(scan ※1)開始する。周囲に複数ペリフェラル端末があれば、その分だけリストに一覧表示する
- 任意の一端末を選択してリストを閉じると、ペリフェラルと接続確立要求(※2)する。特に問題なければ即座に接続が確立される
- 以下略 (サンプルの用途に合致する iBeacon端末を接続させると、それっぽいサービスと特性が読めるようだが、試せていないので...)
※1 scanForPeripheralsWithServices:options:
※2 connectPeripheral:options:
##5.2 電波強度の取得
電波強度 (RSSI) はペリフェラルからのアドバタイジング中に含まれているため、サンプルアプリケーションの少々の改変(アドバタイジング検出しても接続確立手続に移行せずに延々とループさせる)で目的は達成できるかと思われた。
実際にこの方式でやると、アドバタイジングのデータが数秒に一回しかやってこない。アドバタイジングの送出間隔はペリフェラル側のプログラム改変で変更できるものの、どうやら BLE 仕様的に1秒未満間隔での送出は想定していないらしい。(頻繁にアドバタイジングをするとその分電池の消耗も激しくなる)
ということで、アドバタイジングからRSSIを取得するのではなく、接続確立させた上で取得する方針をとることにした。
セントラル-ペリフェラルで接続を確立させることの利点は、セントラルがデータを欲しいタイミングでリクエストできることだ。
セントラルが 500msec 毎にペリフェラルの情報を取得するよう、プログラムを変更した。
接続確立後にRSSIを取得するには、CBPeripheralインスタンスの readRSSI セレクタを呼べばよい。
// Invoked when a connection is successfully created with a peripheral.
// ペリフェラルと接続が確立できた時に呼ばれる
- (void)centralManager:(CBCentralManager *)central
didConnectPeripheral:(CBPeripheral *)peripheral
{
// ペリフェラルとやり取りするために、ペリフェラルのデリゲートを設定
[peripheral setDelegate:self];
// [追加]信号強度の取得
[peripheral readRSSI];
}
この結果が peripheralDidUpdateRSSI:error として呼ばれる
// Invoked when you retrieve the value of the peripheral’s current RSSI while it is connected to the central manager.
// ペリフェラルからRSSIの値を読み出した時に呼ばれる
- (void)peripheralDidUpdateRSSI:(CBPeripheral *)peripheral
error:(NSError *)error;
{
NSString* gotRSSI = [peripheral.RSSI stringValue];
int rssi = [gotRSSI integerValue];
}
##5.3 空のプロジェクトで作成する
サンプルベースの改変でそれっぽく動くことが確認できたので、次に、空のプロジェクトから作成してみる。
体温計のビューは不要だし、特定のサービスに合わせて書かれたコードも不要なので、このあたりを除去したバージョンを作る。
色々やり方があると思うが、以下で実施した。
・New Project -> CocoaApplication
・AppDelegate.h, AppDelegate.m に必要なコードを書く
・フレームワーク CoreBluetooth を追加
・メモリ管理の設定変更: "USE Automatic Reference Counting" を YES
以上で作成しても、Cocoa のデフォルトウィンドウが残る。そもそも不要なのだが、不勉強で削除の仕方がわからない。特に害はないのでひとまずそのままにしておいた。
#6. 成果と未解決課題
6.1 成果
- CoreBluetooth アプリケーションを作成して、BLE ペリフェラルから、500msec 毎に RSSI 値を取得することができた。
(TODO: gist か github へのリンク)
6.2 未解決課題
- しかしながら、しばらく接続していると、
peripheralDidUpdateRSSI
が呼び出しされる間隔が0.5秒から 突然10秒、 20秒となり、またしばらくすると、2,3回500msec間隔で呼ばれたりと、コンスタントな RSSI の読み出しが行われなくなる。 - 原因不明で今も解決していない。ひょっとすると、BLE で定められた省電力のための通信の仕様なのかもしれない。
#7. よくある問題の解消方法
###7.1 セントラルアプリがペリフェラルとの接続中に、突然接続終了する
- error は nil.
- 接続終了するタイミングは実行のつどまちまち。読み出しの間隔を小さくすると接続終了することが多い
- 無線LANと干渉してるみたい。
- MBP の BT モジュールは、Wifi モジュールと同一らしく(下記リンク)、BLE 使用時はつねに無線 LAN を切っておくことで事象を回避できた。
#8. まとめ
- 1000msec未満の間隔でペリフェラルの電波強度読み出しをするには、アドバタイジングではなく接続確立して readRSSI を呼ぶようにする。
- 接続後しばらくすると、一定間隔が保てなくなる状況。セントラルとペリフェラルの通信間隔をコンスタントに保つための問題の切り分けと対処が課題。
本試行をするにあたり、otoasobi の yokobori さんにBLEのイロハを教えていただきました。ありがとうございます。本稿は勢いで書いていることもあり、誤りなどもあるかもしれません。その場合の責は筆者にあります。