Web Bluetooth APIと自作BLEデバイスで戸締り確認をしてみる。
この記事について
この記事ではWeb Bluetooth API戸締り確認アプリを紹介します。デバイス側はRN4020を使って実装しました。Web Bluetooth APIとRN4020を使ってBLEのアプリを作ってみたかったので、製作しました。実用性はありませんが、それぞれの使い方を覚えるのには大変良さそうな題材です。
戸締り確認アプリ
戸締り確認アプリは大まかに以下の仕様になっています。
- BLE経由で鍵の開閉を確認することができる。
- 鍵の状態が変わったらBLE経由で通知が来る。
- 鍵が開場した場合にブザーが鳴る。
デバイスの紹介
今回RN4020で作るBLEデバイスにはホール素子とブザーが付いています。ホール素子は磁石が近づくと信号を出力するセンサです。自転車の速度計などに利用されています。また、ブザーについてはRN4020のデジタル出力で直接駆動できないため、トランジスタで増幅しています。抵抗値などはかなり適当です。
実体配線図っぽいのは以下の通りです。
RN4020のサービスとキャラクタリスティックは以下のように設定しました。一つのサービスに開閉のデータを送るキャラクタリスティックとブザーを制御するためのキャラクタリスティックの2つを設定しています。また開閉のキャラクタリスティックについてはReadとNotifyの2つを設定しています。後ほどスクリプトでRN4020のIOポートとつなげるため、データ長は1バイトにしています。(単純な動作なので)
- サービス
- UUID 123456789012345678901234567890FF
- 鍵開閉のキャラクタリスティック
- UUID 12345678901234567890123456789011
- Mode Read,Notify
- Length 1byte(0x02の場合ロック,0x00の場合アンロック)
- ブザーのキャラクタリスティック
- UUID 12345678901234567890123456789022
- Mode Write(応答なし)
- Length 1byte(0x04の場合に発音,0x00の場合に消音)
実際のコマンドは以下のように設定します。
PS,123456789012345678901234567890FF PC12345678901234567890123456789011,12,01 PC12345678901234567890123456789022,04,01
@PW_ON |O,04,00 |O,04,%000E A @CONN SM,1,000F4240 @TMR1 $VAR1 = |I,02 SHW,000B,$VAR1 SM,1,000F4240 @DISCON A
スクリプトやその他設定の方法については以下を参照してください。
Webブライザ側のアプリケーションについて
Webブラウザ側のアプリケーションについては以下のUIを作りました。AugularJSとを利用して作成しています。
connectを押すとBLEの接続要求が実行されます。またreadやSubscribe/Unsubscribeでデバイス側のホール素子の値を取得します。
ソースコード全体については以下のリポジトリに置いてあります。
アプリの実行
アプリの実行時の動作を動画にしてみました。ブザーの音がするので音量は控えめにしてください。
デバイスへの接続ご、サブスクライブ(デバイスからのNotifyをスタート)を行い、画面上のLock Unlock表示が変わります。また、Alertをセットすることで、Unlock時にブザーが鳴るようになります。
まとめ
Web Bluetooth API とRN4020を使うことで簡単にBLEを使ったアプリケーションを作ることができました。今回は半日程度で両方実装できたので非常にお手軽だったと思います。BLEで何かしようと思った場合、スマフォなどのアプリは作れるけど、そもそも取り扱えるBLEデバイスの種類がないので面白いことができないといった場合にはRN4020でサクッとデバイスを作ってしまうのもいいかもしれません。また、自作のBLEデバイスを作ってみたけれど、コントロールするスマフォアプリを作れない、市販デバイスを気軽にハックしたいという場合はWeb Bluetooth APIを利用するとサクッと見栄えのいいアプリが作れると思います。コンセプトを試したいときなどアイディアの試作に利用できそうです。
今回の作例を使ったワークショップを2017年10月20日に開催予定です。どなたでもご参加いただけます。
BLEモジュールRN4020をスクリプトから制御する。
この記事について
この記事では前回に引き続き、RN4020を制御する方法について記載しています。前回まではシリアル通信から制御をおこなっていました。しかし、今回はRN4020のスクリプト機能を使って、スタンドアローンで制御する方法を紹介します。RN4020については以下の記事を参照してください。
下記写真のようにスマフォのアプリからBLE経由でLEDの制御が可能です。作業時間10分程度でここまでのことが実現できます。
RN4020のスクリプト制御
RN4020ではシリアル制御のコマンドをRN4020上に記憶させることで外部からの制御なしにスタンドアローンで動作させることが可能となります。スクリプトの制御では予め設定されたイベント(電源ONやBLEの接続イベント、タイマーイベントなど)ごとに処理を記載してBLEデバイスとしての振る舞いを設定します。また、I/Oポートの値をキャラクタリスティックに紐づけることができます。
RN4020の初期設定
まずはじめにRN4020をスクリプト制御する前BLEの動作モードやプライベートサービスの設定を行います。設定については前回の記事で紹介していますので参考にしてください。今回はキャラクタリスティックを1つ設定し、書き込みモードとしました。
+ - Echo On SF,1 -AOK SS,00000001 -AOK SR,00000000 -AOK PZ -AOK PS,123456789012345678901234567890FF -AOK PC,12345678901234567890123456789011,04,01 -AOK R,1 -Reboot -CMD
スクリプトの書き込み準備
スクリプトの書き込みを行うためには以下のコマンドを入力します。
WC //スクリプトの初期化を行う。 -AOK WW // スクリプト書き込みモードにする。 -AOK
これで、シリアルコンソールからスクリプトを書き込む準備ができました。
スクリプトの作成
今回は上記で設定したキャラクタリスティックに値を書き込むとLEDが光るようなサービスを指定します。以下のスクリプトを1行ずつ入力してください。
@PW_ON//(1) |0,01,%000A//(2) A @CONN//(3) SM,1,00500000//(4) @TMR1//(5) SM,1,00500000//(6) @DISCON//(7) A
上記スクリプトの入力が完了したら<ESC>
キーを押してください。スクリプト入力モードから抜け出せます。
スクリプトの動作について説明します。
- (1) 電源が入った場合に呼ばれるイベントで初期化の処理を記載します。ここではAコマンドでアドバタイズを実行しています。
- (2) PIO1に000A のハンドラを持ったキャラクタリスティックを関連づけています。これによりキャラクタリスティックに書き込みに従ってPIO1を制御できます。
- (3) セントラルから接続された場合に呼ばれるイベントです。
- (4) SMコマンドでタイマーをセットしています。タイマー1を5秒ごに実行するように設定しています。
- (5) タイマー1のイベントが発生した場合に呼び出されます。
- (6) 再びタイマーをセットし、タイマー1イベントが繰り返し呼ばれるようにします。今回はタイマーループはからの処理ですので飛ばしても構いません。
- (7) 切断後再度接続ができるようにアドバタイズを再開する。
上ではキャラクタリスティックに値を書き込むことでデバイスの制御をしていますが、もちろん、I/Oの値をキャラクタリスティックにわたしデータを外部に送信することも可能になっています。
例えば@TMR1イベント中で以下のように記載することでAIO0の値をキャラクタリスティックに書き込めます。 これはノティフィケーションの通知のような場合にできる記載方法です。
$VAR1 = @I,0 SHW,000B,$VAR1
また初期化時に以下のように記載すると000Eのハンドラを持つキャラクタリスティックをReadした場合にAIO2の値を読み出すことができます。
%000E = @I,2
- スクリプトで利用できるイベントは以下のようになっています。
イベント | イベントラベル |
---|---|
電源 ON | @PW_ON |
Timer1 タイムアウト | @TMR1 |
Timer2 タイムアウト | @TMR2 |
Timer3 タイムアウト | @TMR3 |
接続完了 | @CONN |
切断完了 | @DISCON |
PIO4( ピン 13) の入力が Low へ変化 | @PIOL |
PIO4( ピン 13) の入力が High へ変化 | @PIOH |
高優先度アラート | @ALERTH |
低優先度アラート | @ALERTL |
アラート OFF | @ALERTO |
スクリプトの実行
スクリプトを実行する場合は以下のようにRN4020の動作モードをスクリプトモードにしてから再起動します。
SR,01000000 //ペリフェラルかつスクリプト実行モードにする。 R,1
スクリプトの消去について
スクリプトモードにして再起動すると他の操作を受け付けません。再起動コマンドと初期化コマンドは受け付けるようになっているためもしスクリプトを消去したい場合は以下のコマンドを実行して初期化してください。
SF,1 R,1
2017/09/28追記:深夜の勢いで上のように書きましたが普通に上で紹介してるWC
コマンド使えば消去できると思います。
実行の様子
実際に上記の動作の様子を動画にしました。撮影が下手なので分かりづらいですが、スマートフォン側からRN4020に接続し、設定したキャラクタリスティックの値を変更することでLEDが点灯または消灯する様子がわかると思います。00を書き込んだタイミングでLEDが点灯し、それ以外の値を書き込んだタイミングでLEDが消灯します。
まとめ
前回まではシリアルから制御していたため、必ずホストとなるコンピュータが必要になりました。しかしスクリプトを利用することでRN4020単体でもデバイスの動作を実現できることがわかりました。制御文などが十分ではないため動作としてはあまりリッチな動きができそうにありませんが、非常に単純な機能であれば使いどころはありそうです。
BLEモジュールRN4020のIOポートをシリアルから制御する。
この記事について
この記事ではBLEモジュールRN4020のIOポートを制御する方法について紹介します。RN4020については前回の記事をみてください。
RN4020のI/Oポート
RN4020のI/OポートはアナログI/O(AIO)とデジタルI/O(PIO)の2種類のポートがあります。それぞれシリアルインタフェースから制御するためのコマンドが用意されており、RN4020にスイッチやLED、アナログ出力のセンサを取り付けて値を読み出すことが可能です。
アナログI/Oについて
RN4020のアナログI/0はAIO0からAIO2までの3ポートあります。入出力電圧のレンジは0Vから1.3Vと低電圧となっています。1mV単位の指定ができ値のレンジは(0x0000〜0x0514)となります。
デジタルI/Oについて
デジタルI/OはPIO1,PIO2,PIO3,PIO7の4ポートあります。欠番になっているピンも存在しているのですが、基本的にシリアルのフロー制御など他の用途に使われているため使用しません。もちろんI/Oとしても利用することは可能だと思いますが、ユーザガイド上に方法が記載されていません。
ピンアサイン
モジュール自体のピンアサインはユーザガイドをみてください。ここでは秋月電子通商のブレークアウトボードでのピンアサインを紹介します。以下図の通りのピンアサインになっています。紹介したPIO7についてはこちらでは出ていません。従ってデジタルI/OはPIO1からPIO3までの3ピンのみ利用可能です。またPIO1はボード上のLED制御ピンと兼用になっています。
シリアルからのI/O制御
I/Oの制御方法はアナログとシリアルでそれぞれ変わります。それぞれの制御コマンドについて説明していきます。
アナログI/Oの制御
- アウトプットコマンド
アナログアウトプットのコマンドは@Oです。以下のように使います。
@O,2,0014
最初の引数がポート番号を指し、今回はAIO2を指定しています。さらに0014は出力値を16進数で表しています。20mVがAIO2に出力されるはずです。他のピンについても同様に指定できます。
- インプットコマンド
アナログインプットは@Iです。以下のように使います。
@I,2 - 0000
最初の引数はアウトプットと同じくポートを表しています。結果としてAIO2のポートの電圧値(ここでは0)が結果として帰ってきます。
デジタルI/Oの制御
- デジタルアウトプットコマンド
デジタルアウトプットのコマンドは|Oです。以下のように使います。
|O,07,05
最初の引数はピン番号の制御ですが、16進数で指定しています。ここではPIO1,PIO2,PIO3を指定しています。2つ目の引数は出力指定です。こちらも16進数指定でPIO1,PIO3をHighに指定し、PIO2をLowに指定しています。ブレークアウトボードの場合、ボード上のLEDが消灯するはずです。(Highで消灯、Lowで点灯)各PINとレジスタの関係は以下の表のようになっています。複数指定する場合は足し算してください。
ピン番号 | レジスタ値 |
---|---|
PIO1 | 0x01 |
PIO2 | 0x02 |
PIO3 | 0x04 |
PIO7 | 0x08 |
- デジタルインプット
デジタルインプットは同様に上記のレジスタの表に従って以下のように指定します。
|I,01, -01
これはPIO0の値を読み取り結果が同じくレジスタの形式で帰ってきます。(-はRN4020からの応答を表す)PIO1の値が1になっています。(LEDからの入力電圧があるため)
まとめ
以上のようにRN4020はシリアルからI/Oを制御することが可能です。しかし単純なI/O制御だけならばシリアル接続側のマイコンで行っても良いと思います。RN4020のスクリプト機能を使うとアナログI/O,デジタルI/Oともに変数に代入したり、キャラクタリスティックのハンドラにバインドすることができます。こちらについてもまとめ次第記事にしたいと思います。
1000円から始めるBLEデバイス開発、RN4020をPCから制御して使ってみる。
この記事について
この記事では1000円代で購入出来るBLEモジュールRN4020の使い方を検証してみました。まずはシリアル通信で制御しペリフェラルとして動作させます。さらにセントラルと値をやり取りする方法についても紹介しています。
RN4020とは
RN4020はMicrochip社が製造しているBLEモジュールです。価格は1000円程度で、秋月電子通商などで購入できます。
また、使いやすくしたブレークアウトボード版も販売されています。(製造も秋月電子通商)今回はこちらのブレークアウトボードを利用しています。
RN4020はUARTインタフェースが搭載されており、シリアル通信からコマンドを入力することで、簡単にBLEデバイス(ペリフェラル動作)を実現することができます。Arduinoなどのマイコンボードなどに接続しBLEデバイスを作成することができます。また、セントラルとしても動作させることができるため、汎用的なBLEの通信モジュールとして利用することが可能です。さらにUARTインタフェースだけでなく、アナログI/O, デジタルI/Oを兼ね備えており、LEDやセンサを接続して利用することも可能です。 RN4020は専用のスクリプトを書き込むことで、BLEとI/Oを制御し、RN4020のみでBLEデバイスを構成することができます。詳細な仕様については以下のMicrosoftのデータシートを参考にしてください。また、シリアルコマンドはスクリプトのリファレンスはユーザガイドに記載されています。
- データシート
http://ww1.microchip.com/downloads/jp/DeviceDoc/50002279A_JP.pdf
- ユーザガイド
http://akizukidenshi.com/download/ds/microchip/70005191A_JP.pdf
RN4020をシリアル通信で制御する
まずはじめにRN4020をUARTで制御します。今回はUSBシリアル変換を使い、PC上から制御します。マイコンなどを接続する場合も基本的に同じコマンドとなります。
接続回路
RN4020ですが、前述のブレークアウトボードを利用する場合、シルクパターンに従って、GND, +5V, RX, TXを接続するのみです。ブレークアウトボードの場合電源が+5Vである点に注意してください。今回はUSBシリアル変換は秋月電子通商から発売されている以下のモジュールを利用しました。
実は上記ブレークアウトボードとこのUSBシリアル変換機のピンアサインが向かい合わせでぴったりとはまるようになっています。写真のように向かい合わせでブレットボード上に刺すとGND、電源、がお互い接続され、RXとTXはクロスに接続されるようになっています。
ですが、実際の配線は製造ロットによって変わる場合もあるので、予め確認して接続するようにしてください。接続はそれぞれのモジュールが以下の表のように対応していれば大丈夫です。お互いのRXとTXをクロスに接続することろがポイントです。
USBシリアル変換 | RN4020 |
---|---|
GND | GND |
+5V | +5V |
RX | TX |
TX | RX |
PCでの接続
回路の接続ができたら、USBシリアル変換機とPCをUSBケーブルで接続します。WindowsであればTeraTermなどのターミナルソフトから以下のパラメータでシリアル接続します。
項目 | 値 |
---|---|
ボーレート | 115200 |
データビット | 8bit |
パリティ | なし |
ストップビット | 1bit |
フロー制御 | なし |
改行文字 | CR |
OSXの場合は以下のコマンドで接続します。
$screen /dev/tty.usbserial-A1032M5W 115200
/dev/tty.usbserial-A1032M5W
は接続するUSBシリアル変換機ごとに変わるので予め/dev/フォルダ以下を見てそれっぽいものを確認します。
ペリフェラルとしてBLEのHeartRateサービスをアドバタイズしてみる。
ターミナルの接続が完了したら以下のようにコマンドを1行ずつ実行します。RN4020がHeartRateサービスを持ったペリフェラルとしてアドバタイズを開始するはずです。 先頭に「-」が付いている行はRN4020からの応答行です。実際には「-」は表示されていません。
+//(1) - Echo On SF,1//(2) - AOK SS,A0000000//(3) - AOK SR,00000000//(4) - AOK R,1//(5) - Reboot - CMD A//(6)
- (1) EchoをOnにしてターミナルに入力した値がエコーバックで表示されるようにする。
- (2) デバイスの初期化を行う(デバイスの基本情報はそのまま)
- (3) HeartRateとDeviceInformationのサービスを設定
- (4) デバイスモードをペリフェラルに設定
- (5) ハードウェアを再起動して設定を再読み込み
- (6) アドバタイズ開始
実際にBLEデバイスをLightblueなどのBLEデバイスのユーティリティアプリで見るとRN4020が見えるようになっています。 引き続き、RN4020がどういう状態になっているのか詳細を見てみましょう。まずはシリアルコンソールからLSコマンドを入力して、現在設定されているサービスとキャラクタリスクを確認してみます。そうすると以下のように返答が帰ってきます。
LS 180A 2A25,000B,V 2A27,000D,V 2A26,000F,V 2A28,0011,V 2A29,0013,V 2A24,0015,V 180D 2A37,0018,V 2A37,0019,C 2A38,001B,V 2A39,001D,V END
先頭の180Aは承認サービスとして予め定義されているDeviceInformation (180A)です。そこから一段下がってDeviceInformationのキャラクタリスティックが表示されています。また180DはHeartRateサービスとなっています。このようにSS
コマンドを利用することで、承認サービスを簡単に設定することができます。
続いてDコマンドを入力し、デバイスの詳細情報を出力します。
D - BTA=001EC04633AB - Name=RN33AB - Connected=no - Bonded=no - Server Service=A0000000 - Features=00000000 - TxPower=4
これらの情報も別のコマンドから書き換えることができ、デバイス名やBLEとしての役割の変更(セントラル、ペリフェラルなど)を変更することが可能です。詳細はユーザマニュアルを確認してください。
キャラクタリスティクへの値の書き込み
キャラクタリスティックに値を書き込んで、接続しているセントラルにデータを送信します。今回はセントラル側としてOSX上でLightblueを使います。まず今の状態のまま、LightblueからRN4020にアクセスすると、先ほどLSコマンドで見た通りのサービスとキャラクタリスティックを確認できます。キャラクタリスティックの値はまた表示されていません。
以下のコマンドでキャラクタリスティックに値を書き込みます。2A37は心拍数の値を返すコマンドのようです。予め、Lightblue上でサブスクライブしておきます。
SUW,2A37,50
上記のコマンドはキャラクタリスティック2A37に対して0x50(80)を設定する値となります。 またはハンドラに対する書き込みだと以下のようになり結果は同じになります。
SHW,0018,50
Lightblueを見ると値が書き込まれているのがわかります。このようにキャラクタリスティックに値を書き込むとサブスクライブしているセントラルに値を通知し、またReadが許可されている場合はReadしたセントラルに値を送信します。
キャラクタリスティクからの値の読み取り
続いて、セントラルから書き込まれた値を読み出してみます。同じくHeartRateサービスの2A39が書き込み可能なキャラクタリスティックになっています。lightblueから値を書き込むと以下のようにコンソールに表示されます。
-WV,001D,50.
これは001Dのハンドラ(UUIDじゃないことに注意)に0x50が書き込まれたという表示です。
ユーザ定義のBLEサービスとキャラクタリスティックを設定する。
RN4020はDeviceInforamtionやHeartRateサービスのように承認済みサービスだけでなく、ユーザが独自に定義したサービスとキャラクタリスティックを設定することができます。以下のコマンドを実行することでユーザ定義のサービスとキャラクタリスティックを設定できます。
+ - Echo On SF,1 - AOK SS,00000001 - AOK PZ //(1) - AOK PS,11223344556677889900AABBCCDDEEFF //(2) - AOK PC,010203040506070809000A0B0C0D0E0F,02,05//(3) - AOK R,1 - Reboot - CMD
- (1) ユーザ定義のサービスとキャラクタリスティックを削除
- (2) 指定のUUIDでサービスを設定
- (3) 指定のUUIDでキャラクタリスィックをRead Only, 5byte長として設定する。
PC
コマンドでキャラクタリスティックの設定をしますが、キャラクタリスティックに許可する操作(Read, Write, Notifyなど)を2つ目の引数で設定することができます。ユーザマニュアルを読むと細かく設定できるようです。よく使いそうな値は以下の表の通りかと思います。
設定名 | 値 |
---|---|
Notify | 0x10 |
Write(応答あり) | 0x08 |
Write(応答なし) | 0x04 |
Read | 0x02 |
例えばReadとNotifyを設定する場合は上記の例だと以下のようになります。
PC,010203040506070809000A0B0C0D0E0F,12,05
キャラクタリスティックの値の読み書きについては先ほどと同じ方法で行えます。
まとめ
RN4020を利用することで非常に簡単にBLEのペリフェラルデバイスを作成できます。Arduinoなどを使って簡単な工作ができればRN4020を接続するだけでBLE機器として利用できそうです。今回はシリアル通信からの制御を中心に紹介しました。RN4020をマイコン代わりに使い、センサやLEDを直接接続して動かすこともできます。また簡単なスクリプトを利用し、他のマイコンなどと接続せずに単独のデバイスとして動かすこともできます。これらの利用方法についてもまとめ次第紹介したいと思います。
WebブラウザからBLE接続 WEB Bluetooth APIでNotificationを受け取る方法
この記事について
この記事ではWebBluetooth API を使い、BLEデバイスからNotificationを受け取る方法を説明します。 対象とするデバイスは2JCIE-BL01を利用します。デバイスから一定間隔で環境データを取得します。
WEB Bluetooth API
WEB Bluetooth APIはWEBブラウザ上でBLEデバイスと通信を実現するJavaScriptのAPIです。実装は各ブラウザごとに 違いますが2017年9月現在、各ブラウザともそんなに違いはないそうです。iOSについては今の所非対応のようですが、OSXでは問題なく使えそうです。
WEB Bluetooth APIの実装
WEB Bluetooth API を使いデバイスからNotificationを受ける実装は非常に簡単です。以下のコードで実現できます。onStartNotifyをブラウザのUIから呼び出すことで、実行することが可能です。今回ブラウザはChromeを利用しました。
var SensorServiceUUID = "0c4c3000-7700-46f4-aa96-d5e974e32a54" // WxBeacon2のSensorService UUID var LatestDataUUID = "0c4c3001-7700-46f4-aa96-d5e974e32a54"// 最新データ取得のためのキャラクタリスティック UUID function onStartNotify() { navigator.bluetooth.requestDevice( { acceptAllDevices:true,optionalServices:[SensorServiceUUID] } // (1) ) .then(device => device.gatt.connect())//(2) .then(server => server.getPrimaryService(SensorServiceUUID)) .then(service => service.getCharacteristic(LatestDataUUID)) .then(characteristic =>{ characteristic.addEventListener('characteristicvaluechanged', onRecvSensorData); //(3) characteristic.startNotifications();//(4) }) }
- コード解説
(1) requestDeviceでBLEデバイスの検索を開始します。引数には探索するデバイスのフィルタを指定できます。今回はデバイスの指定は行わず、すべてのデバイスを受信します。また、デフォルトでは任意のサービスに接続できないため、予め接続できるサービスのUUIDをoptionalServices
で指定しておきます。
このメソッドは実行されると以下のようなダイアログがWeb ブラウザ上に表示されます。接続したいデバイスを選択し、ペア設定を押します。
(2) (1)で指定した デバイスに対して接続処理を行います。接続後、サービスの検索、キャラクタリスティックの検索と処理が 続いていきます。
(3) キャラクタリスティックを取得後、データがNotifyされた際に呼ばれるイベントリスナcharacteristicvaluechanged
にコールバックを指定します。BLEデバイスからデータが通知されたらonRecvSensorDataメソッドが呼ばれるようになります。
(4) startNotifications()
メソッドでデバイスからの通知が開始されます。
WxBeacon2のデータの処理を行うonRecvSensorDataは以下のようになっています。
function onRecvSensorData(event) {//(1) let characteristic = event.target; //(2) let value = characteristic.value; //(3) let temp = decodeValue(value.getUint8(2), value.getUint8(1), 0.01); console.log(temp); let humid = decodeValue(value.getUint8(4),value.getUint8(3), 0.01); console.log(humid); let lumix = decodeValue(value.getUint8(6), value.getUint8(5)); console.log(lumix); let uv = decodeValue(value.getUint8(8), value.getUint8(7), 0.01); console.log(uv); let atom = decodeValue(value.getUint8(10), value.getUint8(9), 0.1); console.log(atom); let noise = decodeValue(value.getUint8(12), value.getUint8(11), 0.01); console.log(noise); let disco = decodeValue(value.getUint8(14), value.getUint8(13), 0.01); console.log(disco); let heat = decodeValue(value.getUint8(16), value.getUint8(15), 0.01); console.log(heat); let battery = decodeValue(value.getUint8(18), value.getUint8(17), 0.001); console.log(battery); } function decodeValue(msb, lsb, gain){ return ((msb << 8) + lsb) * gain }
- コード解説
(1) コールバックはデバイスからの通知結果を受け取るeventと言う引数を1つとります。 (2) eventからキャラクタリスティックのオブジェクトを取得します。 (3) キャラクタリスティックのvalueの値を取得します。
最終的に取得されるキャラクタリスティックの値はDataViewオブジェクトになっています。getUint8メソッドを利用することで1バイトづつデータを取得することができます。 WxBeacon2の設定を変更していなければ5分間隔でデータが出力されます。今回はコンソールログに出力しているため、開発者ツールなどから確認します。
まとめ
WEB Bluetooth APIは非常に簡単にデバイスへの接続とキャラクタリスティックに対するデータの読み書きができます。今回はNotificationの受け取りのみですが、もちろん通常のreadやwriteも可能です。プラットフォームに依存なく簡単にBLEデバイスにアクセスできます。また、WEBブラウザから実行できるという性格上、検索できるデバイスやデバイスのサービスをホワイトリスト形式で縛る、実行にはかならずユーザアクションが必要など、セキュリティ面にも気を使った仕様になっていると思いました。実際にWEBアプリケーションと組み合わせた使い方をする場合はセキュリティ含めてアプリケーションの実装上工夫が必要そうな気はしますが。 また今回Beaconの情報を取得できないか挑戦しました。WEB Bluetooth API 自体の仕様上、可能なようですが、必要な実装はChrome含めて未対応のようです。この辺りについては今後に期待でしょうか。 WEB USB API 含めてWEB ブラウザから直接ハードウェアにアクセスできるのは未来を感じます。
JavaでもBLEでIoT〜RaspberryPi Zero WからBLEデバイスにアクセスしてみる。
この記事について
この記事ではRaspberry Pi Zero W上でJavaからBLEデバイスにアクセスする方法を記載しています。Raspberry PiからBLEインタフェースを操作する方法はNode.jsやPythonを使うやり方が多く見られます。これらのライブラリはLinuxのBlutoothスタックであるBluezのライブラリリンクして実行するか、D-BUSインタフェース経由でBuezを呼び出すことで実現されています。そのため、Bluezのインタフェースにアクセスできれば、プログラミング言語に依存せずにBLEにアクセスすることができます。 しかし、いずれの方法も一から実装すると非常に時間がかかるためすでにあるライブラリを利用する方法が良いでしょう。今回はJavaからBluezを呼び出すライブラリを調査し、利用方法をまとめましたので紹介します。サンプルコードは以下のリポジトリに置いています。Raspberry PiやRaspbianに限らずLinux上であれば動かせると思います。
JavaのBLEライブラリ
今回調べた中で、JavaからBluezにアクセスしライブラリを呼び出すためのライブラリは以下の2つがありました。どちらもBluezをD-BUSインタフェース経由で動かしており、ライセンスはMITライセンスとなっています。この2つのライブラリの違いですが、tinybはintel-iot-devkitプロジェクトの中にあるようです。そのため、比較的作りがしっかりしていそうです。その反面、ライブラリを使うまでのコストが高く、ローカルで関連ライブラリをビルドしなければなりません。一方bluez-dbusの方は個人開発のライブラリのようです。tinybにインスパイアされたプロジェクトということですが、Mavenのセントラルリポジトリに登録されています。Maven プロジェクトからダウンロードして利用できるのが非常に使いやすく感じました。また必要なライブラリもapt-get でインストールできるため、今回はこのbluez-dbusを利用してみます。
ライブラリ名 | 備考 |
---|---|
tiny | C++ライブラリも同梱 |
bluez-dbus | Maven central リポジトリに登録 |
Raspberry Pi Zero Wの環境構築
OSはRaspbian STRECHを利用しました。このバージョンからデフォルトインストールのBluezが5.43となります。bluez-dbusはBluez 5.43対応のため、Raspbian JESSEIなどでうまくいかない場合はBluezのバージョンを揃えることをお勧めします。
$uname -a Linux raspberrypi 4.9.41+ #1023 Tue Aug 8 15:47:12 BST 2017 armv6l GNU/Linux
始める前にapt-getのアップデートを実施しておきます。
$sudo apt-get update & apt-get upgrade
libunixsocket-javaのインストール
bluez-dbusが唯一依存しているネイティブライブラリです。d-busのインタフェースライブラリからリンクされています。
$sudo apt-get install libunixsocket-java
Javaのインストール
JavaはOracle Javaをインストールしました。Open JDKでも問題はないと思います。
$ sudo apt-get install openjdk-8-jdk
Mavenのインストール
Mavenも以下のように設定します。
$ wget http://ftp.jaist.ac.jp/pub/apache/maven/maven-3/3.5.0/binaries/apache-maven-3.5.0-bin.tar.gz $ tar xzvf apache-maven-3.5.0-bin.tar.gz $ sudo mv apache-maven-3.5.0 /opt/
環境変数の設定
環境変数にJAVA_HOMEとPATHを設定します。~/.bashrcの末尾に以下を記載します。
$ export JAVA_HOME=$(readlink -f /usr/bin/javac | sed "s:/bin/javac::") $ export PATH=/opt/apache-maven-3.5.0/bin:$JAVA_HOME/bin:$PATH
サンプルコードのビルド
まずはじめにサンプルコードの実行方法を説明します。https://github.com/masato-ka/bluez-dbus-sample.gitをクローンしてください。 クローン後、チェックアウトされたフォルダに入りmavenを使いビルドします。
$git clone https://github.com/masato-ka/bluez-dbus-sample.git $cd bluez-dbus-sample $mvn package
ビルド完了後、libunixsocket-javaのライブラリをコピーします。これはbluez-javaライブラリの仕様によるもので、このライブラリは Javaの実行パスからlibフォルダを探して、libunix-javaを読み込みます。もし存在しなければクラスパスからライブラリを読み込むのですが依存jarの一つであるmatthew.jarにlibunix-java.soが含まれていて読み込みます。しかしこのsoはRaspberry Pi Zero wでは動かないので、あらかじめ実行パスにライブラリをコピーしておきます。
$cd bluez-dbus-sample $mkdir lib $cp /usr/lib/jni/libunix-java.so ./lib/
実はこのライブラリの読み込みにかなりはまりました。
これで実行の準備が整いました。実行にはsudoをつけて実行します。Raspbian STRECH + Bluez 5.43ではD-BUSにBluezのD-BUSインタフェースに接続するのにbluetoothユーザグループかルート権限が必要になります。 以下のフォーラムのようにpiユーザにユーザグループを追加すればsudoは必要なくなるはずです。(私はやってみましたが結局sudoが必要でした。。。)
$cd ~/bluez-dbus-sample $sudo java -jar target/bluz-sample-0.0.1-SNAPSHOT-jar-with-dependencies.jar SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details. -----------------HCI Interface--------------------- hci0 -----------------Scan BLE Device--------------------- -----------------Result BLE Device---------------- EnvSensor-BL01 ID115 Choose device:EnvSensor-BL01 //ここで検索したいデバイス名を入力する。 -----------------Characteristics---------------- 00002a05-0000-1000-8000-00805f9b34fb 00002a29-0000-1000-8000-00805f9b34fb 00002a24-0000-1000-8000-00805f9b34fb 00002a25-0000-1000-8000-00805f9b34fb 00002a27-0000-1000-8000-00805f9b34fb 00002a26-0000-1000-8000-00805f9b34fb 0c4c3001-7700-46f4-aa96-d5e974e32a54 0c4c3002-7700-46f4-aa96-d5e974e32a54 0c4c3003-7700-46f4-aa96-d5e974e32a54 0c4c3004-7700-46f4-aa96-d5e974e32a54 0c4c3005-7700-46f4-aa96-d5e974e32a54 0c4c3006-7700-46f4-aa96-d5e974e32a54 0c4c3011-7700-46f4-aa96-d5e974e32a54 0c4c3012-7700-46f4-aa96-d5e974e32a54 0c4c3013-7700-46f4-aa96-d5e974e32a54 0c4c3014-7700-46f4-aa96-d5e974e32a54 0c4c3015-7700-46f4-aa96-d5e974e32a54 0c4c3016-7700-46f4-aa96-d5e974e32a54 0c4c3017-7700-46f4-aa96-d5e974e32a54 0c4c3018-7700-46f4-aa96-d5e974e32a54 0c4c3019-7700-46f4-aa96-d5e974e32a54 0c4c301a-7700-46f4-aa96-d5e974e32a54 0c4c3031-7700-46f4-aa96-d5e974e32a54 0c4c3032-7700-46f4-aa96-d5e974e32a54 0c4c3033-7700-46f4-aa96-d5e974e32a54 0c4c3034-7700-46f4-aa96-d5e974e32a54 0c4c3041-7700-46f4-aa96-d5e974e32a54 0c4c3042-7700-46f4-aa96-d5e974e32a54
実行すると上記のようにHCIインタフェース名、検出されたデバイス名、キャラクタリスティックのUUIDが出力されます。今回は前回の記事でも紹介したOMRONの2JCIE-BL01を対象としました。次にサンプルソースの説明を記載します。
サンプルコードの解説
サンプルコードの全文はGitHubで確認してください。やっつけで書いたので例外処理などあれな部分も含まれています。 サンプルコードの処理は全部で大きく以下の3つの部分に分かれています。
pom.xmlへblues-dbusを依存関係として追加します。
<dependency> <groupId>com.github.hypfvieh</groupId> <artifactId>bluez-dbus</artifactId> <version>0.0.2</version> </dependency>
1 DeviceManagerオブジェクトの取得
try { DeviceManager.createInstance(false); } catch (DBusException e1) { System.out.println("failed create instance caused by D-BUS."); } DeviceManager deviceManager = DeviceManager.getInstance();
- (1) DeviceManagerのインスタンスを作成しています。DeviceManager::createInstance(Boolean)で
true
を設定するとD-BUSのログインセッションを利用し、false
を設定するとシステム管理用セッションを利用します。(ライブラリのコメントから解釈しています。)
2 BLEデバイスのスキャン
List<BluetoothAdapter> result = deviceManager.getAdapters(); BluetoothAdapter bluetoothAdaptor = result.get(0); //(1) System.out.println("-----------------HCI Interface---------------------"); result.stream().map(e->e.getDeviceName()).forEach(System.out::println); System.out.println("-----------------Scan BLE Device---------------------"); bluetoothAdaptor.startDiscovery();//(2) Thread.sleep(5000); bluetoothAdaptor.stopDiscovery();//(3)
- (1) HCIデバイスのオブジェクトを取得します。今回は最初に見つかったHCIインタフェースを利用します。複数インタフェースがある場合(BLEドングルを刺しているなど)は選ぶ必要があります。
- (2) デバイスの検索を開始します。
- (3) 5秒待った後デバイスの検索を停止します。
3 BLEデバイスへの接続とキャラクタリスティックの取得
List<BluetoothDevice> devicies = deviceManager.getDevices(); //(1) devicies.stream().map(e->e.getName()).forEach(System.out::println); BufferedReader buffredReader = new BufferedReader(new InputStreamReader(System.in)); System.out.print("Choose device:"); String deviceName = buffredReader.readLine(); System.out.println("-----------------Characteristics----------------"); for(BluetoothDevice bluetoothDevice : devicies){ if(bluetoothDevice.getName().equals(deviceName)){ try{ bluetoothDevice.connect();//(2) bluetoothDevice.refreshGattServices(); List<BluetoothGattService> servicies = bluetoothDevice.getGattServices();//(3) for(BluetoothGattService service : servicies){ List<BluetoothGattCharacteristic> characteristics = service.getGattCharacteristics();//(4) characteristics.stream().map(e->e.getUuid()).forEach(System.out::println); } }catch(DBusExecutionException e){ System.out.println("FailedConnection"); e.printStackTrace(); } } }
- (1) 検索したデバイスを取得します。
- (2) デバイスへの接続を行います。
- (3) 接続が完了したデバイスからサービスを取得します。
- (4) すべてのサービスからキャラクタリスティックを出力しています。
まとめ
BLEを扱えるJavaのライブラリを探していましたがMavenから追加できてなんとなく動くものが見つかりました。まだまだ発展の余地ありなライブラリですが、ちょっと遊ぶのには良さそうです。またLinux環境のみで開発するのは辛いのでOSXでも動くといいのですが。BLEについては基本的にプラットフォームに依存するためOSを超えての移植性は難しそうです。LinuxであればBluezになりますがOSXではCoreBluetoothをラップする必要があります。WIndowsは。。。Node.jsのnobleであれば全てのプラットフォームに対応できるようです。
PyObjCとからCoreBluetoothを呼び出しWx2Beaconのデータを取得する。
この記事で紹介すること
この記事ではMacOSXのBluetoothライブラリである、CoreBluethoothをPythonから呼び出し、WxBeacon2(OMRON 2JCIE-BL01)から環境データを取得する方法について解説しています。 CoreBlutoothはObjectiveCのライブラリのため、PythonとObjectiveCのブリッジライブラリのPyObjCを利用しています。 以下の順番で説明します。
- WxBeacon2の概要+BLEの基礎
- PyObjeCのインストール
- WxBeacon2の読み出しプログラム
WxBeacon2
前回の記事PythonからBLEを制御するライブラリの調査でも記載しましたが、WxBeacon2はウェザーニュースから販売されているBLEインタフェースの環境センサです。OMRONの2JCIE-BL01とほぼ同等の製品になります。外観は以下のようになっており、WxBeacon2にはウェザーニュースさんのロゴがプリントされているのが特徴です。
センサとして取得できる項目は以下の通りです、
センサ名 | 単位 |
---|---|
温度センサ | ℃ |
湿度センサ | % |
気圧センサ | hPa |
照度センサ | lum |
紫外線センサ | ? |
騒音センサ(マイク) | dB |
またこれ以外の値としてバッテリーの値が取得できます。 WxBeacon2はBLEにおいて、ペリフェラルとして動作します。またはBeaconのブロードキャスターとしても動作します。今回はWxBeacon2をBLEペリフェラルとして接続します。 話は脱線しますが、BLEの通信について少し記載しておきます。
BLEの通信について
BLEの通信ではセントラルとペリフェラルという2つの役割があります。BLEを使うユースケースの例としてスマートフォンと活動量計を考えます。たいていの場合、スマートフォンがセントラル、活動量計がペリフェラルとなります。
セントラル
親機のようなイメージ。ペリフェラルを発見後接続要求を送り、データをやりとりを行う。また、ペリフェラルへNotificationの依頼を送れる。
ペリフェラル
アドバタイズと呼ばれるデータを送信しラントラルに発見させます。 ペリフェラルの機能は大分類のサービスと小分類のキャラクタリスティックで定義されています。さらに個々のキャラクタリスティックのデータに対してRead, Write, Notifyのどの操作が可能か定義されてます。キャラクタリスティックはデータが格納されているアドレスと考えるとすんなりわかります。
セントラルとペリフェラルの通信の流れ
通信は以下のような流れになります。
- ペリフェラルがアドバタイズと呼ばれるデータをブロードキャストする。
- ペリフェラルのアドバタイズを受信したセントラルが接続を要求する。
- ペリフェラルに接続したセントラルがペリフェラルのサービスとキャラクタリスティックを検索
- 目的のキャラクタリスティックにRead Write Notifyなどの処理を行う。
PyObjCのインストール
PyObjCはPythonからOSXのObjectiveCのAPIへアクセスするためのブリッジです。
インストール方法
pipからインストールできます。私の環境OSX 10.11.4 El Capitanではlibffiを入れ、パスを通す必要がありました。
$brew install pkg-config libffi $export PKG_CONFIG_PATH=/usr/local/Cellar/libffi/3.0.13/lib/pkgconfig/ $pip install pyobjc
ソースコード
PyObjCを利用してCoreBluetoothを呼び出し、WxBeacon2からデータを取得するまでのコードを解説します。以下にソースコードを置いています。
最初に必要なモジュールをインポートします。
#!/usr/bin/env python # encoding: utf-8 import struct from Foundation import * from PyObjCTools import AppHelper
from Foundation import *
にてCoreBluetoothを含むObjectiveCのライブラリを読み込みます。from PyObjeCTools import AppHelper
でアプリケーションを実行する
ヘルパー関数を読み込みます。
次にアプリケーションのメイン関数部分です。
if "__main__" == __name__: central_manager = CBCentralManager.alloc()#(1) central_manager.initWithDelegate_queue_options_(BleClass(), None, None)#(2) AppHelper.runConsoleEventLoop()#(3)
- (1) CBCentralManagerのインスタンスを取得
- (2) (1)で取得したインスタンスを初期化する。その際、コールバックを設定しているBleClassを設定
- (3) アプリケーションを実行すうスレッドを起動する。
wx2_service = CBUUID.UUIDWithString_(u'0C4C3000-7700-46F4-AA96-D5E974E32A54') wx2_characteristic_data = CBUUID.UUIDWithString_(u'0C4C3001-7700-46F4-AA96-D5E974E32A54') class BleClass(object): def centralManagerDidUpdateState_(self, manager): self.manager = manager manager.scanForPeripheralsWithServices_options_(None,None) #(1) def centralManager_didDiscoverPeripheral_advertisementData_RSSI_(self, manager, peripheral, data, rss): #(2) self.peripheral = peripheral if '8A783AEE-4277-4C3F-8382-ABFA4F6DB8B6' in repr(peripheral.UUID): print 'DeviceName' + peripheral.name() manager.connectPeripheral_options_(peripheral, None) manager.stopScan() def centralManager_didConnectPeripheral_(self, manager, peripheral): #(3) print repr(peripheral.UUID()) peripheral.setDelegate_(self) self.peripheral.discoverServices_([wx2_service]) def peripheral_didDiscoverServices_(self, peripheral, services): #(4) self.service = self.peripheral.services()[0] self.peripheral.discoverCharacteristics_forService_([wx2_characteristic_data], self.service) def peripheral_didDiscoverCharacteristicsForService_error_(self, peripheral, service, error): #(5) for characteristic in self.service.characteristics(): if characteristic.properties() == 18: peripheral.readValueForCharacteristic_(characteristic) break def peripheral_didWriteValueForCharacteristic_error_(self, peripheral, characteristic, error): print 'In error handler' print 'ERROR:' + repr(error) def peripheral_didUpdateNotificationStateForCharacteristic_error_(self, peripheral, characteristic, error): print "Notification handler" def peripheral_didUpdateValueForCharacteristic_error_(self, peripheral, characteristic, error):#(6) print repr(characteristic.value().bytes().tobytes()) value = characteristic.value().bytes().tobytes() temp = decode_value(value[1:3],0.01) print 'temprature:' + str(temp) humid = decode_value(value[3:5],0.01) print 'humidity:' + str(humid) lum = decode_value(value[5:7]) print 'lumix:' + str(lum) uvi = decode_value(value[9:7], 0.01) print 'UV index:' + str(uvi) atom = decode_value(value[9:11], 0.1) print 'Atom:' + str(atom) noise = decode_value(value[11:13], 0.01) print 'Noise:' + str(noise) disco = decode_value(value[13:15], 0.01) print 'Disco:' + str(disco) heat = decode_value(value[15:17], 0.01) print 'Heat:' + str(heat) batt = decode_value(value[17:19],0.001) print 'Battery:' + str(batt)
- (1) アプリケーション起動後、Bluetoothモジュールの初期化が完了したらペリフェラルのアドバタイズをスキャンするモードになります。
- (2) ペリフェラルが見つかったらUUIDと比較を行い、目的のペリフェラルか調べます。接続処理とスキャンを停止しています。
- (3) ペリフェラルとの接続が呼ばれたらこのメソッドが呼ばれます。続いてサービスの検索を実行しています。
- (4) サービスが見つかったら続けて、キャラクタリスティックの検索を行います。
- (5) 検索したキャラクタリスティックが見つかったらデータの取得要求を行います。
- (6) データを受信したら呼ばれるメソッドです。データは必ずバイト列で送られてくるので、データを分解し各センサの値に変換します。
以下のメソッドでWxBeacon2のデータをデコードします。
#Decoding sensor value from Wx2Beancon Data format. def decode_value(value, multi=1.0): if(len(value) != 2): return None lsb,msb = struct.unpack('BB',value) result = ((msb << 8) + lsb) * multi return result
まとめ
今回はCoreBluetoothをPythonから呼び出し、BLEからデータを読み出してみました。CoreBluetooth自体がわかりやすいAPIになっているため、比較的に簡単に実装することができました。 BLEの基礎やCoreBluetoothの詳細については以下の書籍をお勧めします。今回の実装でも一部参考にさせていただきました。
- 作者: 堤修一,松村礼央
- 出版社/メーカー: ソシム
- 発売日: 2015/03/23
- メディア: 単行本
- この商品を含むブログを見る
その他参考
WxBeaconのデータのパース方法
PyObjCとCoreBluetooth