Bluetooth LEで遊ぶ
BLE MIDIの仕様も承認されて、個人的にそろそろBLEで遊ぶ頃合いかなぁ、と思っていたところ、丁度良いタイミングでWeb Bluetoothの実装が遊べるレベルまで進んでいるじゃありませんか。USBを使った電子工作も楽しいんですが、mobileを考えるとやっぱりケーブルは不便。BLEはデバイスも安く入手できるらしいという情報もあったので、ざっくり味見がてら情報を集めてみました。
環境選定
デバイス事情
「Bluetooth BLE 自作」あたりで検索をかけると、さっそく有用なQiitaの記事「【目的別】おすすめBLE開発環境まとめ(2/2)」がひっかかります。ざっと見たところ980円で入手できるkoshianがお手ごろ。konashi互換でカジュアル開発環境が流用できる上に、飽きたらファーム上書きが可能。将来的に自作BLE MIDIを作る用途に流用できそうです。さらにkoshianを使ったボードkonashi2.0を購入すれば、工作不要でiOSからJavaScriptで制御できる模様。お値段たったの3,980円!
konashi2.0
konashi.jsという環境がiOS向けに用意されています。魔改造WebViewに搭載したAPIを経由してJavaScriptでプログラミングできるという代物。回路、BLEの実装情報、魔改造WebViewやSDKのソースがGitHubにて公開されているので、手軽に遊ぶには最適。サンプルとか眺めつつ、ハードの動作確認したり、なんとなくBLEがDevice > Service > Characteristicsとう階層構造を持っていて、各CharacteristicsをI/Oレジスタだと思って読み書きすりゃいーんだな、みたいな世界観を掴みます。
Open Web API
今年のChrome Dev Summitでも紹介されていましたけど、Chrome上でWeb Bluetoothの実装がボチボチ進んでいます。AndroidとChrome OSでだいたい動くって事になってるんですが、Chrome OSはPermission UIが未実装です。現行のバージョンだとデバイスへのアクセスが常にrejectされてしまいます(2015/12/2 補足:この時点ではname/namePrefixでのフィルターが未実装だったようです。サービスのUUIDを使って検索すれば動作します。詳しくは別の記事で補足しました)。AndroidはStable(46)だとまだ駄目だったのでDevの利用をお勧めします。試した範囲ではLollipopでは動作せず。Nexus 7(2013)、Nexus 6、Nexus 5ならMarshmallowで動作してます。また、実装中の機能のため、chrome://flags/#enable-web-bluetoothからフラグを有効にする必要があります。Firefoxなんかも実装を進めているので今後に期待。
ちなみにChrome OSに限定して言えばChrome Apps向けのchrome.bluetoothLowEnergyなんてAPIからもBLEが制御できますが、今回は触れません。
制御してみる
解析
ServiceとCharacteristicについては公式ページに資料が公開されています。と言っても名前とUUIDの一覧があるだけなので、具体的にどう叩けば良いかは多少の調査が必要です。
幸い全てソースが公開されているので、順に追って眺めてみます。konashi-js-sdkによればkonashi.jsで提供されているAPIは、konashi-bridge.jsの中でtriggerToNative()という関数に集約される事がわかります。将来的にはこの関数を乗っとってあげれば、konashi.jsを使ったコードがWeb Bluetooth経由で動かせそう。それはさておき、魔改造WebView側ではKonashiWebView+Bridge.mにそれぞれのAPIコールを処理する薄いラッパーがあります。ここからkonashi-ios-sdk側のAPIを呼んで、実際のデバイスと通信を行います。ios-sdk側はKonashi.m -> KNSPeripheral.m -> KNSKoshianPeripheralImpl.m (: KNSPeripheralBaseImpl.m)って感じで処理が流れていくので、基本的には最後の*Impl.mを見れば何をやってるか簡単にわかります。
具体的に見てみます。LEDを点灯させるサンプルは以下のようなコードになります。
// デバイスの検索と接続は省略
k.pinMode(k.LED2, k.OUTPUT);
k.digitalWrite(k.LED2, k.HIGH);
pinModeについて調べると、BaseImpl.mでpinModeが実装されており、PIO Settingに対して、各種PIOに対応するbitを出力=1/入力=0とした値を書き込めば良い事がわかります。
dititalWriteも同様で、PIO Outputに対して対応するbitを更新した値を書き込めば良い。それぞれ元の呼び出しはbit単位だけど、書き込みはbyte単位になるので内部的に全bit情報をキャッシュしておく必要があるんだな、なんて事が読み取れます。
Web Bluetooth
Promise使いまくりなので、知らない方はまずはPromiseを覚えてください。便利なので。あとsecure origin必須なので注意。最近増えたWeb MIDIとかもですが、権限強いAPIはsecure origin必須というのが最近の傾向です。konashi2.0準公式なjsdo.itでは残念ながらhttpsで利用できないため、JSFIDDLEあたりを使って実験するのが良いのではないでしょうか。
Web Bluetoothの仕様はこれ。ざっくり説明すると次のような手順になります。
- navigator.bluetooth.requestDevice()でBluetoothDeviceオブジェクトを取得
- BluetoothDevice#connectGATT()でBluetoothGATTRemoteServerオブジェクトを取得
- BluetoothGATTRemoteServer#getPrimaryService()でBluetoothGATTServiceオブジェクトを取得
- BluetoothGATTService#getCharacteristic()でBluetoothGATTCharacteristicオブジェクトを取得
- BluetoothGATTCharacteristic#writeValue()でデバイス制御
全部非同期な上にCharacteristicは機能ごとに存在するので、まとめて大量にとってくる事になります。Promise.all大活躍。Lチカの場合はPIO SettingとPIO Outputの2つのCharacteristicsをKonashi Serviceから取ってくる事になります。
最初のrequestDevice()はユーザーアクションの処理中に発行する必要がある点に注意してください。「デバイス検索」みたいなボタンを作って、そのclickイベントハンドラ内で処理を始めるような形にする必要があります。ちょっと面倒で使い勝手が悪いのですが、セキュリティ上の理由ですね。
トライ&エラー
原因が良くわからなかったのですが、なぜかrequestDevice()する際に、{ services: ['229bff00-03fb-40da-98a7-b0def65c2d4b'] }でフィルタするとデバイスが見つかりませんでした。デモページで試すとうまくいくんですが……なぜだろ。結局あきらめて{ namePrefix: 'konashi' }で探すようにしました。
動いた
コード
jsfiddleに実際に動くLチカのコードを公開しておきます。Marshmallow上でAndroid Chrome Devチャネルを動かし、chrome://flags/#enable-web-bluetoothを有効にしてページにアクセスしてください。konashi2.0が近くで電源が入った状態で「Find Konashi」ボタンを押す事でデバイスを見つけます。つかんだ後はLED2が点滅します。