Getting started with JavaFX on Raspberry Pi

The inexpensive single-board computer works great with Java, making hardware development as easy as traditional software development.

July 6, 2020

Download a PDF of this article

[Curious about the Raspberry Pi single-board computer? A few months ago, Java Magazine contributor Alexa Weber Morales wrote an excellent overview, “Why Oracle Engineers Love Raspberry Pi Projects.” —Ed.]

The Raspberry Pi single-board computer is the ideal starting point if you want to experiment with electronic components. And if you can combine this inexpensive hardware with the software tools Java developers use every day, a new world opens for you. The first time I had a blinking LED controlled by Java was a true aha moment.

This article walks you through building a JavaFX dashboard-style application using Gerrit Grunwald’s TilesFX library. Figure 1 shows the user interface.

 JavaFX application’s user interface

Figure 1. JavaFX application’s user interface

You can also view a video of this application running on a Raspberry Pi 3B+ board. That video also demonstrates a touchscreen interface.


The code and techniques used in this article are valid only for Raspberry Pi computers with an ARM v7 or ARM v8 processor. In the Raspberry Pi specifications table on Wikipedia, you can get a clear overview of the boards available with this type of processor:

  • Model A+, version 3
  • Model B, version 2, 3, and 4
  • Compute Module, version 3

You can find the other electronic components that are used in this project in most Arduino/Pi starter kits. But if you have some other components you want to use, you can start with the components I used for this project and adapt them to your needs. These are the components I used:

  • Raspberry Pi 3 Model B+
  • 32 GB (or more) SD card with Raspbian OS
  • Screen, mouse, and keyboard
  • LED and resistor (330 ohms will work for most)
  • Any type of push button
  • HC-SR04 distance sensor
  • Breadboard and wires

Preparing the Raspberry Pi board

If you start from scratch with a new Raspberry Pi board, prepare an SD card with an operating system. This project uses the full Raspbian OS. Download the imager tool. The version 1.2 imager I used was released March 2020 (see Figure 2 and Figure 3). Be sure to select Raspbian Full.

Download site for the imager tool

Figure 2. Download site for the imager tool

Choose the Raspbian Full option for the OS

Figure 3. Choose the Raspbian Full option for the OS.

When the SD card is ready, put it in the Raspberry Pi, launch the operating system and go through the steps to configure it and to connect to your Wi-Fi network.

Install the JDK with JavaFX

The Raspbian release notes indicate that the version I’m using, 2019-06-20, includes OpenJDK Java 11.


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

You can see the Java version:


$ 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)

The board is now good to run any Java 11–based program. But because JavaFX is no longer part of the JDK (since Java 11), running a JavaFX program on Raspberry Pi will not work out of the box.

Luckily, BellSoft provides the Liberica JDK. The version dedicated for the Raspberry Pi includes JavaFX, so you will be able to run a packaged JavaFX application with the simple java -jar yourapp.jar start command. Use the download link from BellSoft to install an alternative JDK, like this:


$ 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

When this is done, check the version again and it should look like this:


$ 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)

On my test Pi board, I even keep different versions of Liberica JDK. Switching between the versions is very easy using the update-alternatives command (see Figure 4).

Switching between Liberica JDK versions

Figure 4. Switching between Liberica JDK versions

On GitHub, in the source code’s Chapter_04_Java/scripts folder, you can find installation scripts for multiple versions of Liberica JDK. Those contain the correct download link for each version. See Figure 5.

Scripts for Liberica JDK versions

Figure 5. Scripts for Liberica JDK versions

Different Raspberry Pi numbering schemes

Before connecting the components to the board’s GPIO (general-purpose input/output) connectors, stop to consider the three numbering schemes used to identify the pins. It can be confusing to work with the GPIO connectors. Refer to the comprehensive GPIO pinout guide for in-depth information, but here’s a brief summary.

Header pin number. This is the logical number in the header. One row has all the even-numbered pins and another row has all the odd-numbered pins. See Figure 6.

Pin numbering in the header

Figure 6. Pin numbering in the header

BCM number. This refers to the Broadcom channel number, which is the numbering inside the chip that is used on the Raspberry Pi.

WiringPi number. WiringPi is the underlying framework used by Pi4J (which I use as the library in the Java project) to control the GPIOs. This different numbering scheme has a historical reason. When development for the very first Raspberry Pi boards was still ongoing, only eight pins were foreseen. But when the designs further evolved and more pins were added, the numbering in WiringPi was extended to be able to address the extra pins.

To make it easier for Java developers to understand the difference between the different header types, pins, and functionalities, I created a small library that you can find in the Maven repository at be.webtechie.pi-headers. By using this library and a small JavaFX application, I created the overview image shown in Figure 7 to make it easy to find and match a number to the appropriate pin on the board. For more information, see “Raspberry Pi history, versions, pins and headers as a Java Maven library.”

Matching numbers to pins on the board

Figure 7. Matching numbers to pins on the board

Connect the hardware

Let’s add some hardware to use some of the full power of the Pi board: an LED, a push button, and a distance sensor. See Table 1, Figure 8, and Figure 9.

Mapping pins to the appropriate devices

Table 1. Mapping pins to the appropriate devices

Physical wiring

Figure 8. Physical wiring

Wiring schematic

Figure 9. Wiring schematic

Figure 10 shows my setup using a RasPiO breadboard bridge, which makes it easier to find the correct pin. The bridge connector puts the BCM numbers in logical order, but I still use a separate breadboard to have a bit more space. Portsplus offers a similar handy board.

Photo of the setup on a RasPIO breadboard bridge

Figure 10. Photo of the setup on a RasPiO breadboard bridge

To test whether the LED is connected in the correct direction for its polarity, unplug the cable between the LED and the GPIO pin (the orange cable in Figure 10) and connect it directly to the 3.3V pin (or the + row on the breadboard). If the LED doesn’t turn on, you need to swap its direction.

Test the LED and button from the terminal

To test the connections, run the gpio command via the terminal.

Important note: If you are using a Raspberry Pi 4 board, be sure to use version 2.52 of the gpio utility. Because the internal wiring of the processor on the Pi 4 board is different from the previous boards, an update is available for the utility, if needed. Check your version by using the gpio -v command via the terminal and, if needed, install the new version using the following commands:


$ 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

Turn the LED on (1) and off (0):


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

Read the button state (1 = pressed, 0 = not pressed) of WiringPi pin 27:


$ gpio mode 27 in 
$ gpio read 27
1

Toggle the LED with Java

Now the real fun begins! The following code configures WiringPi pin 29 and toggles it 10 times between on and off, with an interval of 500 milliseconds, using the same command I used before in the terminal. Create a file named HelloGpio.java that contains the following:


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());
        }
    }
}

Since the code will use Java 11 (or higher), the Java file can be executed without compilation:


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

Introducing the distance sensor

The example application for this article uses a common ultrasound distance sensor that you can find in many starter kits for Arduino and Pi. It is a module known as HC-SR04; you can find more information and examples online. The module needs both input and output GPIO connections. This sensor works the same way as the system used by a bat to fly in the dark without hitting walls: It uses the reflection of ultrasound to calculate the distance to an object off which the sound reflects.

Measuring a distance requires the example application and the module to perform these steps:

  1. The module needs to be powered with 5 volts.
  2. The application needs to set the trigger pin high for at least 10 µs.
  3. The module sends multiple (typically eight) 40 kHz signals and detects when the signal is received back.
  4. The echo pin is set high for the same duration as the duration needed for the ultrasound to return to the sensor.
  5. The application measures the duration of the high state of the echo pin so it can calculate the distance based on the speed of sound.

The full application

Running one Java file is good for testing the setup, but it is only a first step. The example application here uses the Pi4J library as the connection between Java and the GPIO ports on the Raspberry Pi board. Pi4J needs to be installed on the Raspberry Pi and can be integrated in the Java application with a Maven dependency. You can find the full source code for this example application on GitHub.

The following Maven dependencies are specified in a limited POM file:

  • For JavaFX, the extended javafx-web dependency, which includes
    • javafx-controls, which is the base for a JavaFX application
    • The web components needed by TilesFX
  • Logging
  • TilesFX for the dashboard Tiles
  • Pi4J to use the GPIO ports

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

Here are the classes for interacting with the hardware.

GpioHelper class. All the functionality related to the GPIO ports is grouped in this class. It starts with the definition of the pins to which the hardware components are connected and the initialization of the Pi4J GpioController. Extended functionality is handled in separate classes: ButtonChangeEventListener and DistanceSensorMeasurement (described shortly). Additional methods and getters will be used by the UI later.


public class GpioHelper {

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

    /**
     * The pins being used in the example.
     */
    private static final Pin PIN_LED = RaspiPin.GPIO_29;        // BCM 21, Header pin 40
    private static final Pin PIN_BUTTON = RaspiPin.GPIO_27;     // BCM 16, Header pin 36
    private static final Pin PIN_ECHO = RaspiPin.GPIO_05;       // BCM 24, Header pin 18
    private static final Pin PIN_TRIGGER = RaspiPin.GPIO_01;    // BCM 18, Header pin 12

    /**
     * The connected hardware components.
     */
    private GpioController gpioController;

    /**
     * The Pi4J GPIO input and outputs.
     */
    private GpioPinDigitalOutput led = null;

    /**
     * The GPIO handlers.
     */
    private ButtonChangeEventListener buttonChangeEventListener = null;
    private DistanceSensorMeasurement distanceSensorMeasurement = null;

    /**
     * Constructor.
     */
    public GpioHelper() {
        try {
            // Initialize the GPIO controller
            this.gpioController = GpioFactory.getInstance();

            // Initialize the LED pin as a digital output pin with an initial low state
            this.led = gpioController.provisionDigitalOutputPin(PIN_LED, "RED", PinState.LOW);
            this.led.setShutdownOptions(true, PinState.LOW);

            // Initialize the input pin with pulldown resistor
            GpioPinDigitalInput button = gpioController
                    .provisionDigitalInputPin(PIN_BUTTON, "Button", PinPullResistance.PULL_DOWN);

            // Initialize the pins for the distance sensor and start thread
            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);

            // Attach an event listener
            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;
    }

    /**
     * Set the state of the LED.
     *
     * @param on Flag true if the LED must be switched on
     */
    public void setLed(boolean on) {
        if (this.led != null) {
            if (on) {
                this.led.high();
            } else {
                this.led.low();
            }
        }
    }

    /**
     * Get the data from the button.
     *
     * @return {@link XYChart.Series}
     */
    public XYChart.Series<String, Number> getButtonEvents() {
        if (this.buttonChangeEventListener != null) {
            return this.buttonChangeEventListener.getData();
        } else {
            return new Series<>();
        }
    }

    /**
     * Get the data from the distance measurement.
     *
     * @return {@link XYChart.Series}
     */
    public XYChart.Series<String, Number> getDistanceMeasurements() {
        if (this.distanceSensorMeasurement != null) {
            return this.distanceSensorMeasurement.getData();
        } else {
            return new Series<>();
        }
    }
}

ButtonChangeEventListener class. Because this class implements the Pi4J GpioPinListenerDigital, it can handle the button change and store the change in an XYChart.Series with the time stamp.


@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 class. This class is a Runnable that adds the measured distance to a similar data series with the time stamp for each run.


@Override
public void run() {
    // Set trigger high for 0.01 ms
    this.trigger.pulse(10, PinState.HIGH, true, TimeUnit.NANOSECONDS);

    // Start the measurement
    while (this.echo.isLow()) {
        // Wait until the echo pin is high, indicating the ultrasound was sent
    }
    long start = System.nanoTime();

    // Wait until measurement is finished
    while (this.echo.isHigh()) {
        // Wait until the echo pin is low,  indicating the ultrasound was received back
    }
    long end = System.nanoTime();

    // Output the distance
    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));
}

User interface. Thanks to TilesFX, a dashboard-style application can be created quickly. The complete construction of the interface is done in the class DashboardScreen.java. The following snippet shows a switch button to turn the LED on and off:


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

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

To show the distance measurement, I use a tile of the type SMOOTHED_CHART, which uses the XYChart.Series from DistanceSensorMeasurement, which is made available through GpioHelper.


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

Application class. All elements are now ready to be combined into a JavaFX application class. Here’s how to initialize the GpioHelper and use it to initialize the DashboardScreen, and there’s some additional code to nicely close the application.


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();

        // Make sure the application quits completely on close
        stage.setOnCloseRequest(t -> CleanExit.doExit(this.gpioHelper.getGpioController()));
    }

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

}

Running the application on the Raspberry Pi board

You can start the application on your PC from an IDE and the UI will be shown, but not a lot will happen because Pi4J and the hardware components are needed. So let’s move to the Raspberry Pi! You first need to install Pi4J. This can be done with a single-line command:


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

The final step is to get the compiled JAR file from your PC to the Pi board, which can be done with SSH, a USB stick, a download, or an SD card. Put the file in /home/pi and start it with 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

Some logging information will be shown on the screen first, and a bit later the JavaFX screen is opened. The distance chart gets a new value every second, and the button chart is updated with every push or release of the button. The LED can be toggled with the switch button on the screen in one of the tiles. See Figure 11.

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

Figure 11. Photo of the running application with the Raspberry Pi and other hardware components

Conclusion

Once you know which Java version needs to be used on the Pi, you can get started very quickly with simple Java test files and extend them with a JavaFX user interface. Building a complex application requires some more work to set up everything in your IDE and to be able to do a test run, but doing so allows you to develop on a PC and run on a Pi very easily.

I am curious what you are going to build with Raspberry Pi. Please share your work on Twitter with the hashtag #JavaOnRaspberryPi. To learn more, please check out my book, Getting Started with Java on Raspberry Pi.

Acknowledgments: Many thanks to Gluon HQ for all the work on JavaFX and to BellSoft for Liberica JDK including JavaFX for the Raspberry Pi. Code for this example application has been reviewed by Pieterjan Deconinck, my most critical—in the best meaning of the word—colleague.

Frank Delporte

Frank Delporte is the author of Getting Started with Java on Raspberry Pi and is technical product lead at Televic Rail in Izegem, Belgium. He is also the lead coach for CoderDojo Belgium.

Share this Page