X

A blog about Oracle Technology Network Japan

Raspberry PiでJavaFXを使ってみる

Guest Author

※本記事は、Frank Delporteによる"Getting started with JavaFX on Raspberry Pi"を翻訳したものです。


Javaも動作する安価な単一ボード・コンピュータにより、従来のソフトウェア開発並みにハードウェア開発が簡単に

著者:Frank Delporte
2020年7月6日

 [編集注:Raspberry Pi単一ボード・コンピュータに興味がある方は、Java Magazineの寄稿者であるAlexa Weber Moralesが数か月前に執筆した記事「OracleのエンジニアがRaspberry Piプロジェクトを気に入っている理由」(英語)もご覧ください。]

電子部品を使った実験を行いたいなら、理想的な開始点になるのがRaspberry Pi単一ボード・コンピュータです。そして、この安価なハードウェアとJava開発者が毎日使っているソフトウェア・ツールとを組み合わせることができれば、新たな世界が開かれることになります。初めてJavaで制御してLEDを点滅させたとき、筆者はアハ体験を味わいました。

本記事では、Gerrit Grunwald氏のTilesFXライブラリを使って、JavaFXのダッシュボード型アプリケーションを構築する方法を説明します。図1にユーザー・インタフェースを示します。

 JavaFX application’s user interface

図1:JavaFXアプリケーションのユーザー・インタフェース

Raspberry Pi 3B+ボードでこのアプリケーションが動作している動画を見ることもできます。この動画は、タッチスクリーン・インタフェースのデモにもなっています。

本記事で使用するコードと技術は、ARM v7またはARM v8プロセッサを搭載したRaspberry Piコンピュータでのみ有効です。Wikipediaに掲載されている、Raspberry Piの仕様表で、このタイプのプロセッサが搭載されているボードの概要がわかります。

  • Model A+、バージョン3
  • Model B、バージョン2、3、4
  • Compute Module、バージョン3

このプロジェクトで使っているその他の電子部品は、ほとんどがArduino/Piスターター・キットに含まれています。しかし、キットにない部品を使ってみたい場合でも、まずこのプロジェクトで使った部品で始めてから、ニーズに合わせて適応させることができます。今回使った部品は以下のとおりです。

  • Raspberry Pi 3 Model B+
  • Raspbian OSをインストールした32 GB(以上)のSDカード
  • ディスプレイ、マウス、キーボード
  • LEDと抵抗(通常は330Ωで可)
  • 任意の押しボタンスイッチ
  • HC-SR04距離センサー
  • ブレッドボードと導線

 

Raspberry Piボードの準備

新しいRaspberry Piボードを使ってゼロから始める場合は、オペレーティング・システムをインストールしたSDカードを準備します。このプロジェクトでは、フル・バージョンのRaspbian OSを使います。イメージ・ツールをダウンロードしてください。今回使ったイメージャは、2020年3月にリリースされたバージョン1.2です(図2および図3参照)。Raspbian Fullを必ず選択してください。
 

Download site for the imager tool

図2:イメージャ・ツールのダウンロード・サイト
 

Choose the Raspbian Full option for the OS

図3:OSでRaspbian Fullオプションを選択

SDカードの準備ができたら、Raspberry Piに挿入してオペレーティング・システムを起動し、手順に従って設定し、Wi-Fiネットワークに接続します。

 

JDKとJavaFXのインストール

Raspbianのリリース・ノートには、今回使用するバージョン2019-06-20にOpenJDK Java 11が含まれていることが示されています。

2019-06-20:
* Based on Debian Buster
* Oracle Java 7 and 8 replaced with OpenJDK 11

Javaのバージョンは、次のようにして確認できます。

$ java -version
openjdk version "11.0.3" 2019-04-16 
OpenJDK Runtime Environment (build 11.0.3+7-post-Raspbian-5) 
OpenJDK Server VM (build 11.0.3+7-post-Raspbian-5, mixed mode)


つまり、このボードではJava 11ベースの任意のプログラムを実行できるようになっています。しかし、Java 11以降、JavaFXはすでにJDKの一部ではなくなっているため、Raspberry PiですぐにJavaFXプログラムを実行することはできません。

ありがたいことに、BellSoftがLiberica JDKを提供しています。Raspberry Pi向けのバージョンにはJavaFXが含まれているため、単純な開始コマンドjava -jar yourapp.jarを使って、パッケージ化したJavaFXアプリケーションを実行することができます。代替JDKをインストールするためには、次のようにしてBellSoftのダウンロード・リンクからダウンロードします。

$ cd /home/pi 
$ wget https://download.bell-sw.com/java/13/bellsoft-jdk13-linux-arm32-vfp-hflt.deb 
$ sudo apt-get install ./bellsoft-jdk13-linux-arm32-vfp-hflt.deb 
$ sudo update-alternatives --config javac 
$ sudo update-alternatives --config java

これが終わったら、バージョンを再度確認します。次のように表示されるはずです。

$ java --version 
openjdk version "13-BellSoft" 2019-09-17 
OpenJDK Runtime Environment (build 13-BellSoft+33) 
OpenJDK Server VM (build 13-BellSoft+33, mixed mode)

筆者のテスト用Piボードでは、別のバージョンのLiberica JDKも保持しています。バージョンの切り替えはとても簡単で、update-alternativesコマンドを使います(図4参照)。

Switching between Liberica JDK versions

図4:Liberica JDKのバージョンの切り替え

GitHubのソース・コードのChapter_04_Java/scriptsフォルダには、複数のバージョンのLiberica JDKに対応したインストール・スクリプトがあります。各バージョンの正しいダウンロード・リンクも含まれています。図5をご覧ください。

Scripts for Liberica JDK versions

図5:Liberica JDKの各バージョン用のスクリプト 

 

Raspberry Piのさまざまな番号体系

ボードのGPIO(汎用入出力)コネクタに部品を接続する前に、ピンを特定する3つの番号体系について見ていきます。GPIOコネクタを扱う際に、紛らわしい場合があります。ここで簡単に説明しますが、詳しい情報についてはGPIOピンアウト総合ガイド(英語)をご覧ください。

ヘッダーのピン番号:これはヘッダーの論理番号です。片方の列には偶数番号のピンが、もう片方の列には奇数番号のピンが並んでいます。図6をご覧ください。

Pin numbering in the header

図6:ヘッダーのピン番号 

BCM番号:これはBroadcomチャネル番号と呼ばれ、Raspberry Piで使われているチップ内部の番号を指します。

WiringPi番号:WiringPiは、Pi4J(このJavaプロジェクトでライブラリとして使っています)がGPIOを制御するために使用するベース・フレームワークです。異なる番号体系が使われていることには、歴史的な理由があります。もっとも初期のRaspberry Piボードの開発が続いているときは、8つのピンしか想定されていませんでした。しかし、設計が進化してピンが追加されたとき、追加されたピンを指定できるようにWiringPiの番号が拡張されました。

Java開発者が各種ヘッダー・タイプ、ピン、機能の違いを理解しやすくするために、小さなライブラリを作成しました。このライブラリは、be.webtechie.pi-headers Mavenリポジトリで公開しています。番号を見つけてボード上の該当するピンに対応付けることが簡単になるように、このライブラリと小さなJavaFXアプリケーションを使って図7に示す概略イメージを作成しました。詳しくは、「Java MavenライブラリとしてのRaspberry Piの歴史、バージョン、ピンとヘッダー」(英語)をご覧ください。

Matching numbers to pins on the board

図7:ボード上のピンに対応する番号 

 

ハードウェアの接続

Piボードをフル活用に近づけるために、ハードウェアを追加してみます。ここでは、LED、押しボタンスイッチ、距離センサーを接続します。表1、図8、図9をご覧ください。

Mapping pins to the appropriate devices

表1:ピンと適切なデバイスとの対応付け

Physical wiring

図8:実体配線図

Wiring schematic

図9:回路図 

図10は筆者のセットアップです。正しいピンを見つけやすくなっているRasPiOブレッドボード・ブリッジを使っています。ブリッジのコネクタにはBCM番号が論理順に並んでいますが、もう少しスペースを確保するために、別のブレッドボードも使っています。Portsplusも、同様の便利なボードを提供しています。 

Photo of the setup on a RasPIO breadboard bridge

図10:RasPiOブレッドボード・ブリッジを使ったセットアップの写真 

 

LEDが正しい極性の向きで接続されているかどうかをテストするため、LEDとGPIOピンとの間のケーブル(図10のオレンジ色のケーブル)を抜き、3.3Vピン(またはブレッドボードの+の列)に直接接続します。LEDが点灯しない場合、向きを変える必要があります。

 

ターミナルからLEDとボタンをテストする

接続をテストするためには、ターミナルからgpioコマンドを実行します。

重要な注:Raspberry Pi 4ボードを使っている場合は、必ずバージョン2.52のgpioユーティリティを使ってください。Pi 4ボードのプロセッサ内部の配線は以前のボードとは異なるため、必要に応じてユーティリティのアップデートが提供されています。バージョンの確認は、ターミナルからgpio -vコマンドで行います。必要に応じて、次のコマンドで新しいバージョンをインストールしてください。

$ gpio -v
gpio version: 2.50
$ cd /tmp
$ wget https://project-downloads.drogon.net/wiringpi-latest.deb
$ sudo dpkg -i wiringpi-latest.deb
$ gpio -v
gpio version: 2.52


次のようにして、LEDをオン(1)、オフ(0)することができます。

$ gpio mode 29 out
$ gpio write 29 1
$ gpio write 29 0


WiringPiピン27のボタンの状態(1=押されている、0=押されていない)を読み取るためには、次のようにします。

$ gpio mode 27 in 
$ gpio read 27
1



JavaでLEDをオン/オフする

本当におもしろくなるのはここからです。次のコードでは、500ミリ秒間隔でオン/オフを10回切り替えるようにWiringPiピン29を設定しています。コマンドは、先ほどターミナルで使ったものと同じものを使っています。以下の内容を含む、HelloGpio.javaという名前のファイルを作成します。

public class HelloGpio {
    public static void main (String[] args) {
        System.out.println("Hello Gpio");

        try {
            Runtime.getRuntime().exec("gpio mode 29 out");

            var loopCounter = 0;
            var on = true;

            while (loopCounter < 10) {
                System.out.println("Changing LED to " + (on ? "on" : "off"));
                Runtime.getRuntime().exec("gpio write 29 " + (on ? "1" : "0"));

                on = !on;

                Thread.sleep(500);

                loopCounter++;
            }
        } catch (Exception ex) {
            System.err.println("Exception from Runtime: " + ex.getMessage());
        }
    }
}

 

このコードではJava 11(以降)を使っているため、Javaファイルはコンパイルせずに実行できます。

$ java HelloGpio.java
Hello Gpio
Changing LED to on
Changing LED to off
Changing LED to on
…


距離センサーの導入

本記事のサンプル・アプリケーションでは、ArduinoやPiの多くのスターター・キットに含まれる、一般的な超音波距離センサーを使います。このセンサーはHC-SR04というモジュールで、詳しい情報やサンプルはオンラインで入手できます。このモジュールには、入力と出力の両方のGPIO接続が必要です。このセンサーは、コウモリが闇の中を壁にぶつからずに飛ぶのと同じ仕組みで動作します。つまり、超音波の反射を使い、音波を跳ね返す物体との距離を計算します。

サンプル・アプリケーションとこのモジュールを使って距離の測定を行うためには、以下の手順を実行する必要があります。

  1. モジュールに5ボルトの電源を供給する必要があります。
  2. アプリケーションで、少なくとも10マイクロ秒の間、トリガー・ピンをhighにセットする必要があります。
  3. モジュールでは、40 kHzのシグナルを複数回(通常は8回)送信し、シグナルがいつ戻ってくるかを検出します。
  4. 超音波がセンサーに戻ってくるために必要な時間と同じ時間だけ、エコー・ピンがhighにセットされます。
  5. アプリケーションで、エコー・ピンがhigh状態になっている時間を計測することにより、音速に基づいて距離を計算できます。

 

完全なアプリケーション

1つのJavaファイルを実行するのは、セットアップのテストにはよいですが、これは最初の一歩にすぎません。このサンプル・アプリケーションでは、JavaとRaspberry Piボード上のGPIOポートとの間の接続にPi4Jライブラリを使っています。Pi4Jは、Raspberry Piにインストールする必要があります。Mavenの依存性を使ってPi4JをJavaアプリケーションに組み込むこともできます。このサンプル・アプリケーションの完全なソース・コードは、GitHubに掲載しています。

以下のMaven依存性を、POMファイルで指定しています。

  • JavaFXの拡張javafx-web依存性(以下を含む) 
  • JavaFXアプリケーションのベースとなるjavafx-controls
  • TilesFXが必要とするWebコンポーネント
  • ロギング
  • ダッシュボード・タイル用のTilesFX
  • GPIOポートを使用するためのPi4J
<dependency>
    <groupId>org.openjfx</groupId>
    <artifactId>javafx-web</artifactId>
    <version>11.0.2</version>
</dependency>

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.13.1</version>
</dependency>

<dependency>
    <groupId>eu.hansolo</groupId>
    <artifactId>tilesfx</artifactId>
    <version>11.13</version>
</dependency>

<dependency>
    <groupId>com.pi4j</groupId>
    <artifactId>pi4j-core</artifactId>
    <version>1.2</version>
</dependency>

 

以下に、ハードウェアを操作するためのクラスを示します。

GpioHelperクラス:GPIOポートに関連するすべての機能をこのクラスにまとめています。最初に、ハードウェア・コンポーネントを接続するピンの定義と、Pi4JのGpioControllerの初期化を行っています。拡張機能は別のクラスで扱います。具体的には、ButtonChangeEventListenerとDistanceSensorMeasurementですが、詳しくは後ほど説明します。その他のメソッドとgetterは、後ほどUIで使います。

public class GpioHelper {

    private static final Logger logger = LogManager.getLogger(GpioHelper.class);

     /**
     * サンプルで使用するピン
     */
    private static final Pin PIN_LED = RaspiPin.GPIO_29;        // BCM 21、ヘッダー・ピン40
    private static final Pin PIN_BUTTON = RaspiPin.GPIO_27;     // BCM 16、ヘッダー・ピン36
    private static final Pin PIN_ECHO = RaspiPin.GPIO_05;       // BCM 24、ヘッダー・ピン18
    private static final Pin PIN_TRIGGER = RaspiPin.GPIO_01;    // BCM 18、ヘッダー・ピン12

    /**
     * 接続するハードウェア・コンポーネント
     */
    private GpioController gpioController;

    /**
     * Pi4JのGPIO入出力
     */
    private GpioPinDigitalOutput led = null;

    /**
     * GPIOハンドラ
     */
    private ButtonChangeEventListener buttonChangeEventListener = null;
    private DistanceSensorMeasurement distanceSensorMeasurement = null;

    /**
     * コンストラクタ
     */
    public GpioHelper() {
        try {
            // GPIOコントローラを初期化
            this.gpioController = GpioFactory.getInstance();

            // LEDピンを初期状態lowでデジタル出力ピンとして初期化
            this.led = gpioController.provisionDigitalOutputPin(PIN_LED, "RED", PinState.LOW);
            this.led.setShutdownOptions(true, PinState.LOW);

            // 入力ピンをプルダウン抵抗で初期化
            GpioPinDigitalInput button = gpioController
                    .provisionDigitalInputPin(PIN_BUTTON, "Button", PinPullResistance.PULL_DOWN);

            // 距離センサー用ピンを初期化してスレッドを開始
            GpioPinDigitalOutput trigger = gpioController.provisionDigitalOutputPin(PIN_TRIGGER, "Trigger", PinState.LOW);
            GpioPinDigitalInput echo = gpioController.provisionDigitalInputPin(PIN_ECHO, "Echo", PinPullResistance.PULL_UP);
            this.distanceSensorMeasurement = new DistanceSensorMeasurement(trigger, echo);
            ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
            executorService.scheduleAtFixedRate(this.distanceSensorMeasurement, 1, 1, TimeUnit.SECONDS);

            // イベント・リスナーをアタッチ
            this.buttonChangeEventListener = new ButtonChangeEventListener();
            button.addListener(this.buttonChangeEventListener);
        } catch (UnsatisfiedLinkError | IllegalArgumentException ex) {
            logger.error("Problem with Pi4J!Probably running on non-Pi-device or Pi4J not installed.Error: {}",
                    ex.getMessage());
        }
    }

    public GpioController getGpioController() {
        return this.gpioController;
    }

    /**
     * LEDの状態を設定します。
     *
     * @param on LEDをオンにする必要がある場合に、trueに設定します。
     */
    public void setLed(boolean on) {
        if (this.led != null) {
            if (on) {
                this.led.high();
            } else {
                this.led.low();
            }
        }
    }

    /**
     * ボタンのデータを取得します。
     *
     * @return {@link XYChart.Series}
     */
    public XYChart.Series<String, Number> getButtonEvents() {
        if (this.buttonChangeEventListener != null) {
            return this.buttonChangeEventListener.getData();
        } else {
            return new Series<>();
        }
    }

    /**
     * 距離測定データを取得します。
     *
     * @return {@link XYChart.Series}
     */
    public XYChart.Series<String, Number> getDistanceMeasurements() {
        if (this.distanceSensorMeasurement != null) {
            return this.distanceSensorMeasurement.getData();
        } else {
            return new Series<>();
        }
    }
}


ButtonChangeEventListenerクラス:このクラスではPi4JのGpioPinListenerDigitalを実装しているため、ボタンの変更に反応し、その変更をタイムスタンプとともにXYChart.Seriesに保存することができます。

@Override
public void handleGpioPinDigitalStateChangeEvent(GpioPinDigitalStateChangeEvent event) {
    var timeStamp = LocalTime.now().format(DateTimeFormatter.ofPattern("HH.mm.ss"));
    this.data.getData().add(new XYChart.Data<>(timeStamp, event.getState().isHigh() ? 1 : 0));

    logger.info("Button state changed to {}", event.getState().isHigh() ? "high" : "low");
}

DistanceSensorMeasurementクラス:このクラスはRunnableで、実行されるたびに、測定した距離をタイムスタンプとともに同様のデータ系列に追加します。

@Override
public void run() {
    // トリガーを0.01ミリ秒間highにセット
    this.trigger.pulse(10, PinState.HIGH, true, TimeUnit.NANOSECONDS);

    // 測定を開始
    while (this.echo.isLow()) {
        // エコー・ピンがhighになる(超音波が送信されたことを示す)まで待機
    }
    long start = System.nanoTime();

    // 測定が完了するまで待機
    while (this.echo.isHigh()) {
        // エコー・ピンがlowになる(超音波が返ってきたことを示す)まで待機
    }
    long end = System.nanoTime();

    // 距離を出力
    float measuredSeconds = getSecondsDifference(start, end);
    int distance = getDistance(measuredSeconds);
    logger.info("Distance is: {}cm for {}s ", distance, measuredSeconds);

    var timeStamp = new SimpleDateFormat("HH.mm.ss").format(new Date());
    this.data.getData().add(new XYChart.Data<>(timeStamp, distance));
}

 

ユーザー・インタフェース:TilesFXのおかげで、ダッシュボード形式のアプリケーションを短時間で作ることができます。インタフェースの総合的な構築は、クラスDashboardScreen.javaで行っています。次のスニペットは、LEDのオンとオフを切り替えるスイッチ・ボタンのコードを示しています。

var ledSwitchTile = TileBuilder.create()
        .skinType(SkinType.SWITCH)
        .prefSize(200, 200)
        .title("LED")
        .roundedCorners(false)
        .build();

ledSwitchTile.setOnSwitchReleased(e -> gpioHelper.setLed(ledSwitchTile.isActive()));

測定した距離の表示には、SMOOTHED_CHART型のタイルを使っています。このタイルには、GpioHelper経由で利用できる、DistanceSensorMeasurementのXYChart.Seriesを使っています。

var distanceChart = TileBuilder.create()
        .skinType(SkinType.SMOOTHED_CHART)
        .prefSize(500, 280)
        .title("Distance measurement")
        //.animated(true)
        .smoothing(false)
        .series(gpioHelper.getDistanceMeasurements())
        .build();

アプリケーション・クラス:ここまでで、すべての要素をJavaFXのアプリケーション・クラスに組み込む準備ができました。次に示すのは、GpioHelperを初期化し、それを使ってDashboardScreenを初期化する方法です。また、アプリケーションを適切な方法で閉じるためのコードも追加しています。

public class DashboardApp extends Application {

    private GpioHelper gpioHelper;

    @Override
    public void start(Stage stage) {
        Platform.setImplicitExit(true);

        this.gpioHelper = new GpioHelper();

        var scene = new Scene(new DashboardScreen(this.gpioHelper), 640, 480);
        stage.setScene(scene);
        stage.setTitle("JavaFX demo application on Raspberry Pi");
        stage.show();

        // 閉じるときにアプリケーションが完全に終了するようにする
        stage.setOnCloseRequest(t -> CleanExit.doExit(this.gpioHelper.getGpioController()));
    }

    public static void main(String[] args) {
        launch();
    }

}

 

Raspberry Piボードでアプリケーションを実行する

PCのIDEでこのアプリケーションを起動できます。その場合、UIは表示されますが、あまり多くのことは起こりません。Pi4Jとハードウェア・コンポーネントが必要になるからです。そこで、Raspberry Piで実行してみます。まず、Pi4Jをインストールする必要があります。これは、1行のコマンドで行うことができます。

$ curl -sSL https://pi4j.com/install | sudo bash

最後の手順は、コンパイルしたJARファイルをPCからPiボードに移すことです。この移動は、SSH、USBメモリ、ダウンロード、SDカードのいずれかを使って行うことができます。このファイルを/home/piに配置し、java -jarで起動します。

$ cd /home/pi
$ ls *.jar
javamagazine-javafx-example-0.0.1-jar-with-dependencies.jar
$ java -jar javamagazine-javafx-example-0.0.1-jar-with-dependencies.jar
INFO  be.webtechie.gpio.DistanceSensorMeasurement - Distance is: 103cm for 0.006021977s 
INFO  be.webtechie.gpio.DistanceSensorMeasurement - Distance is: 265cm for 0.01544218s 
INFO  be.webtechie.gpio.DistanceSensorMeasurement - Distance is: 198cm for 0.011520567s 
INFO  be.webtechie.gpio.ButtonChangeEventListener - Button state changed to high
INFO  be.webtechie.gpio.ButtonChangeEventListener - Button state changed to low

最初にいくつかのログ情報が画面に表示され、その少し後にJavaFXの画面が開きます。距離のグラフには1秒ごとに新しい値が追加されます。ボタンのグラフは、ボタンを押すかまたは放すたびに更新されます。LEDは、画面上のタイルの1つに表示されているスイッチ・ボタンでオン/オフを切り替えることができます。図11をご覧ください。

Photo of the running application with the Raspberry Pi and other hardware component

図11:Raspberry Piとその他のハードウェア・コンポーネントを使ってアプリケーションを実行したときの写真

 

まとめ

Piで使うにはどのバージョンが必要かがわかれば、単純なテスト・ファイルを使ってすぐにJavaを試し、JavaFXユーザー・インタフェースを使って拡張することができます。複雑なアプリケーションを構築する場合は、IDEでさまざまな設定を行うためや、テスト実行できるようにするために、さらに作業が必要になります。しかし、そうすることによって、PCで開発してPiで実行するという作業をとても簡単に行えるようになります。

皆さんは、Raspberry Piを使ってどんなものを作ろうとしているのでしょうか。ハッシュタグ#JavaOnRaspberryPiを付けて、皆さんの作品をTwitterで共有してください。詳しく知りたい方は、筆者による書籍『Getting Started with Java on Raspberry Pi』をご覧ください。

謝辞:JavaFX関連のすべての作業に関してGluon HQに、Raspberry Pi用のJavaFXを含むLiberica JDKに関してBellSoftにそれぞれ深く感謝します。本記事のサンプル・アプリケーションのコードは、(もっともよい意味で)一番批判的な同僚であるPieterjan Deconinck氏のレビューを受けています。


Frank Delporte

Frank Delporte:『Getting Started with Java on Raspberry Pi』の著者。ベルギーのイゼゲムにあるTelevic Railのテクニカル・プロダクト・リード。CoderDojo Belgiumのリード・コーチも務める。

Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.