diozero

A Device I/O library written in Java that provides an object-orientated interface for a range of GPIO / I2C / SPI devices (LEDs, buttons, sensors, motors, displays, etc) connected to Single Board Computers like the Raspberry Pi. Actual GPIO / I2C / SPI device communication is delegated to pluggable service providers for maximum compatibility across different boards. This library is known to work on the following boards: all models of the Raspberry Pi, Odroid C2, BeagleBone Black, C.H.I.P and Asus Tinker Board. It should be portable to any Single Board computer that runs Linux and Java 8.

This library makes use of modern Java features such as automatic resource management, Lambda Expressions and Method References where they simplify development and improve code readability.

Created by Matt Lewis (email deviceiozero@gmail.com), inspired by GPIO Zero and Johnny Five. If you have any issues please use the GitHub issues page. For any other comments or suggestions, please use the diozero Google Group.

Concepts

The aim of this library is to encapsulate real-world devices as classes with meaningful operation names, for example, LED (on / off), LDR (get luminosity), Button (pressed / released), Motor (forward / backwards / left / right). All devices implement Closeable hence will get automatically closed by the try (Device d = new Device()) { d.doSomething(); } statement. This is best illustrated by some simple examples.

Pin Numbering

All pin numbers are device native, i.e. Broadcom for the Raspberry Pi, ASUS for the Tinker Board. Pin layouts:

LED control:

try (LED led = new LED(pin)) {
    led.on();
    SleepUtil.sleepSeconds(.5);
    led.off();
    SleepUtil.sleepSeconds(.5);
    led.toggle();
    SleepUtil.sleepSeconds(.5);
    led.toggle();
    SleepUtil.sleepSeconds(.5);
    led.blink(0.5f, 0.5f, 10, false);
}

Turn on an LED when you press a button:

try (Button button = new Button(buttonPin, GpioPullUpDown.PULL_UP); LED led = new LED(ledPin)) {
    button.whenPressed(led::on);
    button.whenReleased(led::off);
    SleepUtil.sleepSeconds(10);
}

Or a random LED flicker effect:

Random random = new Random();
try (PwmLed led = new PwmLed(pin)) {
    DioZeroScheduler.getNonDaemonInstance().invokeAtFixedRate(RANDOM::nextFloat, led::setValue, 50, 50, TimeUnit.MILLISECONDS);
}

All devices are actually provisioned by a Device Factory with a default NativeDeviceFactory for provisioning via the host board itself. However, all components accept an optional Device Factory parameter for provisioning the same set of components via an alternative method. This is particularly useful for GPIO expansion boards and Analog-to-Digital converters.

Device Factory

Unless you are implementing a new device you shouldn't need to use any of the Device Factory interfaces or helper classes (within the com.diozero.internal package).

Some boards like the Raspberry Pi provide no analog input pins; attempting to create an AnalogInputDevice such as an LDR using the Raspberry Pi default native device factory would result in a runtime error (UnsupportedOperationException). However, support for Analog to Digital Converter expansion devices such as the MCP3008 has been added which are implemented as analog input device factories hence can be used in the constructor of analog devices like LDRs:

try (McpAdc adc = new McpAdc(McpAdc.Type.MCP3008, chipSelect); LDR ldr = new LDR(adc, pin, vRef, r1)) {
    System.out.println(ldr.getUnscaledValue());
}

Repeating the previous example of controlling an LED when you press a button but with all devices connected via an MCP23017 GPIO expansion board:

try (MCP23017 mcp23017 = new MCP23017(intAPin, intBPin);
        Button button = new Button(mcp23017, inputPin, GpioPullUpDown.PULL_UP);
        LED led = new LED(mcp23017, outputPin)) {
    button.whenPressed(led::on);
    button.whenReleased(led::off);
    SleepUtil.sleepSeconds(10);
}

Analog input devices also provide an event notification mechanism. To control the brightness of an LED based on ambient light levels:

try (McpAdc adc = new McpAdc(McpAdc.Type.MCP3008, chipSelect); LDR ldr = new LDR(adc, pin, vRef, r1); PwmLed led = new PwmLed(ledPin)) {
    // Detect variations of 10%, get values every 50ms (the default)
    ldr.addListener((event) -> led.setValue(1-event.getUnscaledValue()), .1f);
    SleepUtil.sleepSeconds(20);
}

Supported Devices

diozero has out of the box support for the following Single Board Computers:

The builtin sysfs provider is designed to be portable across different boards. In addition, the JDK Device I/O providers add an alternative method of compatibility, for example on the Udoo Quad.

Getting Started

Snapshot builds of the library are available in the Nexus Repository Manager. For convenience a ZIP of all diozero JARs will be maintained on Google Drive.

Javadoc for the core library is also available via javadoc.io.

Unfortunately Java doesn't provide a convenient deployment-time dependency manager such as Python's pip therefore you will need to manually download all dependencies and setup your classpath correctly. You can do this either via setting the CLASSPATH environment variable or as a command-line option (java -cp <jar1>:<jar2>). The dependencies have been deliberately kept to as few libraries as possible, as such this library is only dependent on tinylog v1.1.

To compile a diozero application you will need 2 JAR files - tinylog, and diozero-core. To run a diozero application, you will also need one of the supported device provider libraries and the corresponding diozero provider wrapper library. Note the built-in sysfs device provider gives maximum portability but has some limitations such as not being able to configure internal pull up/down resistors.

SBC Provider Provider Jar diozero wrapper-library
All sysfs N/A Built-in
32bit boards JDK Device I/O 1.0 dio-1.0.1.jar diozero-provider-jdkdeviceio10-<version>.jar
32bit boards JDK Device I/O 1.1 dio-1.1.jar diozero-provider-jdkdeviceio11-<version>.jar
Raspberry Pi Pi4j pi4j-core-1.1.jar diozero-provider-pi4j-<version>.jar
Raspberry Pi wiringPi pi4j-core-1.1.jar diozero-provider-wiringpi-<version>.jar
Raspberry Pi pigpio pigpioj-java-1.0.1.jar diozero-provider-pigio-<version>.jar
Raspberry Pi, Odroid C2 mmap N/A diozero-provider-mmap-<version>.jar

To get started I recommend first looking at the classes in com.diozero.sampleapps. To run the LEDTest sample application using the pigpioj provider:

sudo java -cp tinylog-1.1.jar:diozero-core-0.9-SNAPSHOT.jar:diozero-sampleapps-0.9-SNAPSHOT.jar:diozero-provider-pigpio-0.9-SNAPSHOT.jar:pigpioj-java-1.0.1.jar com.diozero.sampleapps.LEDTest 12

For an experience similar to Python where source code is interpreted rather than compiled try Groovy (sudo apt-get update && sudo apt-get install groovy2). With the CLASSPATH environment variable set as per the instructions above, a simple test application can be run via the command groovy <filename>. There is also a Groovy shell environment groovysh.

A Groovy equivalent of the LED controlled button example:

import com.diozero.Button
import com.diozero.LED
import com.diozero.util.SleepUtil

led = new LED(12)
button = new Button(25)

button.whenPressed({ led.on() })
button.whenReleased({ led.off() })

println("Waiting for button presses. Press CTRL-C to quit.")
SleepUtil.pause()

To run:

sudo groovy -cp $CLASSPATH test.groovy

Groovy JAVA_HOME config when running via sudo

I was getting the error:

groovy: JAVA_HOME is not defined correctly, can not execute: /usr/lib/jvm/default-java/bin/java

I tried setting JAVA_HOME in /etc/environment and /etc/profile.d/jdk.sh to no affect. Eventually the following fixed it for me. Please let me know if there is a better way to fix this issue.

ln -s /usr/lib/jvm/jdk-8-oracle-arm32-vfp-hflt /usr/lib/jvm/default-java

Devices

This library provides support for a number of GPIO / I2C / SPI connected components and devices, I have categorised them as follows:

Performance

I've done some limited performance tests (turning a GPIO on then off, see GpioPerfTest) on a Raspberry Pi 2 and 3 using the various native device factory providers. I've also run tests using JNI APIs directly without going via my DIO-Zero wrapper to assess the overhead of using my library (see WiringPiRawPerfTest and PigpioPerfTest) - the overhead of DIO-Zero is approximately 25% for both pigpio and wiringPi. Here are the results:

Provider Device Frequency (kHz)
Pi4j 1.0 Pi2 0.91
JDK DIO 1.1 Pi2 8.23
Pi4j 1.1 Pi2 622
pigpio Pi2 2,019
pigpio Pi3 2,900
pigpio (JNI) Pi2 2,509
pigpio (JNI) Pi3 3,537
wiringPi Pi2 2,640
wiringPi Pi3 3,446
wiringPi (JNI) Pi2 3,298
wiringPi (JNI) Pi3 4,373
mmap Pi3 7,686
mmap (JNI) Pi3 11,007

Performance

For a discussion on why Pi4j 1.0 was so slow, see this issue. These results are in-line with those documented in the book "Raspberry Pi with Java: Programming the Internet of Things". For reference, the author's results were:

Library Frequency (kHz)
Pi4j 1.0 0.751
JDK DIO 1.0 3.048
wiringPi (direct) 1,662

Development

This project is hosted on GitHub, please feel free to join in:

To-Do

  • Thorough testing (various types of devices using each service provider)
  • A clean object-orientated API for IMUs
  • Native support for all devices via mmap (/dev/mem), in particular to improve performance and add support for GPIO pull up/down configuration.
  • Cleanup the logic for handling capabilities of different boards in a generic fashion (no more if / then / else)
  • Arduino support (via USB cable)
  • Particle Photon support (via wifi)

Change-log

  • Release 0.2: First tagged release.
  • Release 0.3: API change - analogue to analog.
  • Release 0.4: Bug fixes, servo support.
  • Release 0.5: Testing improvements.
  • Release 0.6: Preparing for 1.0 release.
  • Release 0.7: Support for non-register based I2C device read / write
  • Release 0.8: Added Analog Output device support (added for the PCF8591). Introduced Java based sysfs and jpi providers. Bug fix to I2CLcd. Added support for BME280.
  • Release 0.9: Native support for I2C and SPI in the sysfs provider. Support for CHIP, BeagleBone Black and Asus Tinker Board. Moved sysfs provider into diozero-core, use as the default provider. Preliminary support for devices that support the Firmata protocol (i.e. Arduinos).

License

This work is provided under the MIT License.