RIoT Control is a tool that automates the deployment of your Java code to a Raspberry Pi or similar device. It finds your device on a local network, copies your java archives to it, creates a service configuration file, and starts your application or sets it as a service.
Currently, it is only available as a plugin for the SBT build tool.
The easy way first: There’s a template that will create an empty project that’s already set up. After installing sbt, execute the following from your root git directory or similar:
sbt new riot-framework/streams.g8
Check the template’s github template for more details.
If you have an existing SBT project, add
sbt-riotctl to your
addSbtPlugin("org.riot-framework" % "sbt-riotctl" % "0.5")
RIoT Control depends on SBT’s
JavaServerAppPackaging, and will only be active if you’ve enabled it in your
In general, RIoT Control will then try to use sane default for everything, including the host name, username, and password used to log in to your Pi (it will use Raspbian’s defaults). If you need to override any of them, add the corresponding configuration to your project’s settings in
build.sbt, for example:
lazy val root = (project in file(".")) .enablePlugins(JavaServerAppPackaging) .settings( // Deployment Targets (hostname, username, password): riotTargets := Seq( riotTarget("raspberrypi", "pi", "raspberry") ), // Port to use for remote debugging: riotDbgPort := 8000, // Packages and features needed by your code: riotPrereqs := "oracle-java8-jdk wiringpi", riotRequiresI2C := false, riotRequiresSPI := false )
RIoT Control will deploy the files generated by SBT in the
stage directory, and use the scripts generated by SBT Native Packager’s
JavaServerAppPackaging to start your Application.
To copy your application to your device and run it:
Then press [Return] twice to terminate it.
To debug your application, similarly run:
and point your Remote Debugger to the device’s address and the port defined in the
riotDbgPort setting (e.g.
If you want run the application in the background:
then stop it with:
Once you’re happy with the result, you can enable the Service that RIoT has created for your Application, which will cause it to start automatically when your device boots:
To disable it, use the command:
In general, if issuing more than one command, it’s considerably faster to start SBT in interactive mode:
then enter the commands above (without sbt, for example just
riotRun). This allows for quick development: Modify your code in the editor, then change to your terminal and execute
riotRun in the interactive SBT shell.
Many IDE have some sort of plugin to integrate with SBT, speeding up the development cycle even more.
Problems tend to arise most often when installing prerequisite packages. This only happens during the first deployment of an Application, and may take a long time depending on the packages installed.
As the apt-get traffic is proxied through the installation tool (your Pi may not have internet access), it may run out of memory when downloading large packages (such as the Java JDK). The following error is an indication that this is happening:
[info] E: Failed to fetch http://debian.anexia.at/raspbian/raspbian/pool/main/o/openjdk-11/openjdk-11-jdk-headless_11.0.3+7-5_armhf.deb Connection failed [IP: 127.0.0.1 8080] [info] E: Unable to fetch some archives, maybe run apt-get update or try with --fix-missing?
If this is the case, make sure
sbt has enough memory. The easiest way to do this is to add a file named
.jvmopts in your project’s root directory:
-Xms1024M -Xmx4096M -Xss2M -XX:MaxMetaspaceSize=1024M
A freshly installed Pi without internet access will have a system date that is far in the past. RIoT tries to detect this and set the time to your development machine’s time, but if this fails for some reason, subsequent package installations will fail with rather confusing messages such as:
[info] E: Release file for http://raspbian.raspberrypi.org/raspbian/dists/buster/InRelease is not valid yet (invalid for another 80d 10h 26min 56s). Updates for this repository will not be applied.
In this case, make sure your Pi’s system clock is somewhat correct. It may help to wait for a while after the Raspberry Pi has booted, to allow it to synchronise to internet time sources or others.
If you have Java 11 installed on your system, which handles access to certain classes in a more restrictive manner, you’ll see the following:
WARNING: An illegal reflective access operation has occurred WARNING: Illegal reflective access by com.pi4j.io.file.LinuxFile (file:/usr/local/streaming-i2c-bma280/lib/com.pi4j.pi4j-core-1.2.jar) to field java.nio.Buffer.address WARNING: Please consider reporting this to the maintainers of com.pi4j.io.file.LinuxFile WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations WARNING: All illegal access operations will be denied in a future release Uncaught error from thread [riot-bma280-demo-akka.actor.default-dispatcher-3]: sun/misc/SharedSecrets, shutting down JVM since 'akka.jvm-exit-on-fatal-error' is enabled for ActorSystem[riot-bma280-demo] java.lang.NoClassDefFoundError: sun/misc/SharedSecrets at com.pi4j.io.file.LinuxFile.getFileDescriptor(LinuxFile.java:215) at com.pi4j.io.file.LinuxFile.ioctl(LinuxFile.java:103) at com.pi4j.io.i2c.impl.I2CBusImpl.selectBusSlave(I2CBusImpl.java:291) at com.pi4j.io.i2c.impl.I2CBusImpl.runBusLockedDeviceAction(I2CBusImpl.java:258) at com.pi4j.io.i2c.impl.I2CBusImpl.writeByte(I2CBusImpl.java:185) at com.pi4j.io.i2c.impl.I2CDeviceImpl.write(I2CDeviceImpl.java:131)
The Pi4J library, on which RIoT builds, is currently not compatible with some newer JDKs. Until this is the case, downgrade to a more widely used version of Java, such as Java 8:
sudo apt-get -y install openjdk-8-jdk-headless sudo apt-get -y remove openjdk-9-jre-headless openjdk-10-jre-headless openjdk-11-jre-headless
Parts of Akka seem to rely on classes that aren’t present in headless JREs. Install the headless JDK:
sudo apt-get -y install openjdk-8-jdk-headless
RIoT control runs after SBT Native Packager’s
JavaServerAppPackaging, and uses the files it generates.
First, it checks that the prerequisite packages defined in the
riotPrereqs setting are installed (by default the JDK 8 and the WiringPi library), and otherwise installs them with
apt-get. A local SOCKS5 proxy is started locally through which
apt-get accesses the package repositories, so this step works even if your Pi has no direct internet access (this is often the case if it’s connected directly to your laptop).
Then, it checks that certain features, such as SPI or I2C ports, are enabled, depending on the
stage directory is copied, in which
JavaServerAppPackaging has built or copied all JARs, and generated start scripts. The target directory on the Pi is
/usr/local/ followed by your project’s name.
A systemd ‘service unit configuration’ file is generated in
/etc/systemd/system/ and named after your project (followed by
.service, as is the convention).
This systemd service is then started, stopped, enabled or disabled depending on the RIoT Control command issued.
RIoT Control will first try to access your Raspberry Pi by Hostname (e.g. if it is called ‘raspberrypi’, RIoT Control will attempt to connect to
If this fails, then RIoT Control will attempt to use mDNS (a service provided by Bonjour / Zeroconf) to discover an SSH service with the name provided. If your Raspberry Pi has the Avahi service installed and running (this is the case by default), it will publish its SSH port this way, as well as its current IP address.