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.
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.
Note "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.
!!! 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);
}
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.
Snapshot builds of the library are available in the Nexus Repository Manager. For convenience a ZIP of all diozero JARs 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
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
Note "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
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)
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 |
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 |
This project is hosted on GitHub, please feel free to join in:
- Make suggestions for fixes and enhancements
- Provide sample applications
- Contribute to development
- 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)
- Firmata SPI support (via USB cable)
- Wireless access to Firmata devices (network and Bluetooth). E.g. ESP32 Firmata GitHub issue #315
- Particle Photon support (via wifi using VoodooSpark "firmware" - JavaScript implementation)
This work is provided under the MIT License.