DIOZero - a Java Device I/O wrapper for GPIO / I2C / SPI control
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 [email protected]) (blog), 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 NumberingAll 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.
!!! note "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:
- Raspberry Pi (all versions).
- Odroid C2.
- Beagle Bone Black.
- Asus Tinker Board.
- The Next Thing Co CHIP.
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 (currently v0.13) will also be available 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 v2.1.2.
To compile a diozero application you will need 3 JAR files - tinylog (API and Impl), and diozero-core. To run a diozero application, you can also include one of the supported device provider libraries and the corresponding diozero provider wrapper library. Note that 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-api-2.1.2.jar:tinylog-impl-2.1.2.jar:diozero-core-0.13.jar:diozero-sampleapps-0.13.jar:diozero-provider-pigpio-0.13.jar:pigpioj-java-2.4.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 sudoI 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:
- API for lower-level interactions
- Input Devices
- Output Devices
- Expansion Boards for adding additional GPIO / Analog / PWM pins
- Motor Control (support for common motor controller boards)
- Sensor Components (support for specific sensors, e.g. temperature, pressure, distance, luminosity)
- HC-SR04 Ultrasonic Ranging Module, Bosch BMP180, Bosch BME280, TSL2561 Light Sensor, STMicroelectronics HTS221 Humidity and Temperature Sensor, STMicroelectronics LPS25H Pressure and Temperature Sensor, 1-Wire Temperature Sensors e.g. DS18B20, Sharp GP2Y0A21YK distance sensor, Mifare MFRC522 RFID Reader
- LCD Displays
- IMU Devices Work-in-progress API for interacting with Inertial Measurement Units such as the InvenSense MPU-9150 and the Analog Devices ADXL345
- LED Strips Support for LED strips (WS2811B / WS2812B / Adafruit NeoPixel)
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 diozero wrapper to assess the overhead of using my library (see WiringPiRawPerfTest and PigpioPerfTest) - the overhead of diozro 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 |
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:
- Make suggestions for fixes and enhancements
- Provide sample applications
- Contribute to development
To-Do
- Thorough testing (various types of devices using each service provider)
- A clean object-orientated API for IMUs
- Remote API for board capabilities
- SPI support for Arduino devices
- Introduce Servo as a device type
- Try out ConfigurableFirmata - is there actually any difference to the StandardFirmata protocol?
- Create a Maven archetype
- Check docs (.md and Javadoc) regarding constructors with device factories
- DONE Complete ADS1x15
- DONE I2C Javadoc - make it clear regarding SMBus 32 byte limits
- DONE BME680
- DONE Native support for all devices via mmap (/dev/gpiomem), in particular to improve performance and add support for GPIO pull up/down configuration.
- DONE Wireless access to Firmata devices (network and Bluetooth). E.g. ESP32 Firmata GitHub issue #315
License
This work is provided under the MIT License.