masato-ka's diary

日々思ったこととか、やったことの備忘録。

BLEモジュールRN4020をスクリプトから制御する。

この記事について

 この記事では前回に引き続き、RN4020を制御する方法について記載しています。前回まではシリアル通信から制御をおこなっていました。しかし、今回はRN4020のスクリプト機能を使って、スタンドアローンで制御する方法を紹介します。RN4020については以下の記事を参照してください。

masato-ka.hatenablog.com

下記写真のようにスマフォのアプリからBLE経由でLEDの制御が可能です。作業時間10分程度でここまでのことが実現できます。

f:id:masato-ka:20170927225702j:plain

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のスクリプト動作

まとめ

 前回まではシリアルから制御していたため、必ずホストとなるコンピュータが必要になりました。しかしスクリプトを利用することでRN4020単体でもデバイスの動作を実現できることがわかりました。制御文などが十分ではないため動作としてはあまりリッチな動きができそうにありませんが、非常に単純な機能であれば使いどころはありそうです。

BLEモジュールRN4020のIOポートをシリアルから制御する。

この記事について

 この記事ではBLEモジュールRN4020のIOポートを制御する方法について紹介します。RN4020については前回の記事をみてください。

masato-ka.hatenablog.com

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制御ピンと兼用になっています。

f:id:masato-ka:20170926001343p:plain

シリアルからの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円程度で、秋月電子通商などで購入できます。

f:id:masato-ka:20170925200254j:plain

akizukidenshi.com

また、使いやすくしたブレークアウトボード版も販売されています。(製造も秋月電子通商)今回はこちらのブレークアウトボードを利用しています。

akizukidenshi.com

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シリアル変換は秋月電子通商から発売されている以下のモジュールを利用しました。

akizukidenshi.com

実は上記ブレークアウトボードとこのUSBシリアル変換機のピンアサインが向かい合わせでぴったりとはまるようになっています。写真のように向かい合わせでブレットボード上に刺すとGND、電源、がお互い接続され、RXとTXはクロスに接続されるようになっています。

f:id:masato-ka:20170925200517j:plain

ですが、実際の配線は製造ロットによって変わる場合もあるので、予め確認して接続するようにしてください。接続はそれぞれのモジュールが以下の表のように対応していれば大丈夫です。お互いの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コマンドで見た通りのサービスとキャラクタリスティックを確認できます。キャラクタリスティックの値はまた表示されていません。

f:id:masato-ka:20170925205434p:plain

以下のコマンドでキャラクタリスティックに値を書き込みます。2A37は心拍数の値を返すコマンドのようです。予め、Lightblue上でサブスクライブしておきます。

SUW,2A37,50

上記のコマンドはキャラクタリスティック2A37に対して0x50(80)を設定する値となります。 またはハンドラに対する書き込みだと以下のようになり結果は同じになります。

SHW,0018,50

Lightblueを見ると値が書き込まれているのがわかります。このようにキャラクタリスティックに値を書き込むとサブスクライブしているセントラルに値を通知し、またReadが許可されている場合はReadしたセントラルに値を送信します。

f:id:masato-ka:20170925205443p:plain

キャラクタリスティクからの値の読み取り

続いて、セントラルから書き込まれた値を読み出してみます。同じく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デバイスと通信を実現するJavaScriptAPIです。実装は各ブラウザごとに 違いますが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 ブラウザ上に表示されます。接続したいデバイスを選択し、ペア設定を押します。

f:id:masato-ka:20170924144104p:plain

(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上であれば動かせると思います。

github.com

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のインストール

JavaOracle 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/

実はこのライブラリの読み込みにかなりはまりました。

github.com

これで実行の準備が整いました。実行にはsudoをつけて実行します。Raspbian STRECH + Bluez 5.43ではD-BUSにBluezのD-BUSインタフェースに接続するのにbluetoothユーザグループかルート権限が必要になります。 以下のフォーラムのようにpiユーザにユーザグループを追加すればsudoは必要なくなるはずです。(私はやってみましたが結局sudoが必要でした。。。)

raspberrypi.stackexchange.com

$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を対象としました。次にサンプルソースの説明を記載します。

masato-ka.hatenablog.com

サンプルコードの解説

サンプルコードの全文はGitHubで確認してください。やっつけで書いたので例外処理などあれな部分も含まれています。 サンプルコードの処理は全部で大きく以下の3つの部分に分かれています。

  1. DeviceManagerオブジェクトの取得
  2. BLEデバイスのスキャン
  3. BLEデバイスへの接続とキャラクタリスティックの取得

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のデータを取得する。

この記事で紹介すること

この記事ではMacOSXBluetoothライブラリである、CoreBluethoothをPythonから呼び出し、WxBeacon2(OMRON 2JCIE-BL01)から環境データを取得する方法について解説しています。 CoreBlutoothはObjectiveCのライブラリのため、PythonとObjectiveCのブリッジライブラリのPyObjCを利用しています。 以下の順番で説明します。

  1. WxBeacon2の概要+BLEの基礎
  2. PyObjeCのインストール
  3. WxBeacon2の読み出しプログラム

WxBeacon2

前回の記事PythonからBLEを制御するライブラリの調査でも記載しましたが、WxBeacon2はウェザーニュースから販売されているBLEインタフェースの環境センサです。OMRONの2JCIE-BL01とほぼ同等の製品になります。外観は以下のようになっており、WxBeacon2にはウェザーニュースさんのロゴがプリントされているのが特徴です。

f:id:masato-ka:20170912231711j:plain
WxBeacon2(2JCIE-BL01)

センサとして取得できる項目は以下の通りです、

センサ名 単位
温度センサ
湿度センサ %
気圧センサ hPa
照度センサ lum
紫外線センサ ?
騒音センサ(マイク) dB

またこれ以外の値としてバッテリーの値が取得できます。 WxBeacon2はBLEにおいて、ペリフェラルとして動作します。またはBeaconのブロードキャスターとしても動作します。今回はWxBeacon2をBLEペリフェラルとして接続します。 話は脱線しますが、BLEの通信について少し記載しておきます。

BLEの通信について

BLEの通信ではセントラルとペリフェラルという2つの役割があります。BLEを使うユースケースの例としてスマートフォン活動量計を考えます。たいていの場合、スマートフォンがセントラル、活動量計ペリフェラルとなります。

セントラル

親機のようなイメージ。ペリフェラルを発見後接続要求を送り、データをやりとりを行う。また、ペリフェラルへNotificationの依頼を送れる。

ペリフェラル

アドバタイズと呼ばれるデータを送信しラントラルに発見させます。 ペリフェラルの機能は大分類のサービスと小分類のキャラクタリスティックで定義されています。さらに個々のキャラクタリスティックのデータに対してRead, Write, Notifyのどの操作が可能か定義されてます。キャラクタリスティックはデータが格納されているアドレスと考えるとすんなりわかります。

セントラルとペリフェラルの通信の流れ

通信は以下のような流れになります。

  1. ペリフェラルがアドバタイズと呼ばれるデータをブロードキャストする。
  2. ペリフェラルのアドバタイズを受信したセントラルが接続を要求する。
  3. ペリフェラルに接続したセントラルがペリフェラルのサービスとキャラクタリスティックを検索
  4. 目的のキャラクタリスティックにRead Write Notifyなどの処理を行う。

f:id:masato-ka:20170912231504p:plain
BLEセントラルとペリフェラルの通信手順

PyObjCのインストール

PyObjCPythonから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からデータを取得するまでのコードを解説します。以下にソースコードを置いています。

github.com

最初に必要なモジュールをインポートします。

#!/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の詳細については以下の書籍をお勧めします。今回の実装でも一部参考にさせていただきました。

iOS×BLE Core Bluetoothプログラミング

iOS×BLE Core Bluetoothプログラミング

その他参考

PythonからBLEを制御するライブラリの調査

はじめに

この記事ではPythonのBLE制御ライブラリに調査を行った結果をまとめています。2017年のMaker Fair Tokyoウェザーニュースブースにて、WxBeacon2というBLEの環境センサを購入しました。このデバイスはOMRONの2JCIE-BL01まんまの代物です。本家は加速度センサが入っているようですが、ブースの方曰くこちらは入っていないかもとのこと。

OMRON環境センサ

WxBeacon2

しばらくはiPhoneから接続して専用アプリで遊んでみました。せっかくのなので、自分でアプリを作って遊んでみることにしました。今回はRaspberry Pi Zero WとMac OSX上で動作するアプリケーションを作成してみようと考えてみます。実装に利用する言語はPythonを選択しました。PythonのBLEライブラリを検索するといくつか種類が出てきたので、目に付いたものを調査してみました。

Python用BLEライブラリの現状

Python用のBLEライブラリを各プロジェクトのページから調べた結果は以下の表の通りです。4ライブラリを調査しました。

ライブラリ名 ライセンス 対応OS Python 機能 備考
pybluez GPL-v2 Linux,
Mac,
Windows
2.7系,3系 セントラルの機能 El Capitan or Sierraで動作し無さそう
bluepy GPL-v2 Linux 2.7系 3.3 3.4系 セントラルの機能
pyGATT Apache2.0
MITライセンス
Linux, Windows 2.7系 BGAPIのラッパー(Windows, Linuxのみ)
bluezのgatttoolラッパ(Linuxのみ)
WindowsではBLEが使えない?
lightblue GPL v3 Linux,
Mac
2.7系,3系 セントラルの動作のみ メンテされていない。
OSXに関しては10.4時代で更新停止している。
BluefruitLE MITライセンス Linux(Bluez),
Mac(CoreBluetooth)
2.7系 BLEのセントラルの動作のみ

以下に各ライブラリに対する所感を記載します。

pybluez

 一番使われていそうなpybluezですが、その名の通り、Linuxの標準BluetoothスタックのBluezのラッパのようです。Macにインストールした場合はlightblueと言う別のライブラリ経由でOSXのCoreBluetoothライブラリを利用しています。ですがEl CapitanやSierraではインストールに失敗します。リポジトリ上は修正されているようですが、そもそも依存しているlightblueがOSX10.4時代から更新されていないようなので使えないでしょう。またBluezとリンクして動くため、GPLライセンスとなってしまいます。ホビーの場合問題ないですが、個人的に好きではありません。

bluepy

BluezをベースにしたGPL-v2ライセンス。Linuxのみ対応しているがPython3系にも対応している。2017年9月現在もリポジトリに変化あり。開発自体は継続中の模様。

pyGATT

 次にpyGATTはOSX非対応のため、要件から外れます。Bluezのgatttoolコマンドのラッパのようです。WindowではBGAPIというBluetoothシリアルのAPIを提供するようです。特定メーカのドングルでないとダメそう。

lightblue

lightblueはpyblueのMac対応に使われているライブラリです。が、メンテされておらず、前述の通り最新のOSXには対応していません。

BluefruitLE

最後に残ったBluefruitLEですが、更新もされており、上記の中では一番筋がいいように思えます。Windowsも動けば尚良しですが、今回の要件からは外れるので問題ありません。

まとめ

PythonでBLEを制御するライブラリをまとめてみました。どのライブラリもBLEの制御はOS依存のライブラリをラップしているため、すべてのプラットフォームで共通するライブラリは難しそうです。またLinuxのBluez自体がGPLライセンスのため、コピーレフトに敏感な場合は注意が必要そうです。Windows環境を無視すればBluefruitLEが一番筋が良さそう?Macについてはpyobjc経由でCoreBluetoothを叩いているようでした。自分でもpyobjcからBLE制御してみましたが、あっさりと動いたので、個別のOSごとに対応するのは簡単そうです。同じコードで動かすのは骨が折れそうです。