masato-ka's diary

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

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であれば全てのプラットフォームに対応できるようです。