Pronghorn IoT: A Declarative Java Approach to Programming Embedded Systems
By Bo Huang, OCI Software Engineer
September 2016
Introduction
If current trends continue, the number of Internet of Things (IoT) devices in circulation may triple to 38.5 billion by 2020, far outpacing the number of PCs. However, programming an IoT device still remains difficult. Often in-depth knowledge of both hardware specifications and difficult low-level concepts is required to get even the simplest of examples to work.
With Pronghorn IoT, we provide an easy-to-use, declarative, low-garbage API that teaches the maker (even one with no applicable experience) how to create a working example in as little as 15 minutes. In doing so, we also provide a top-down approach to teaching students Java.
Pronghorn IoT
The IoT is a system of interrelated devices, machines, and objects that automatically exchange data over a network without requiring human-to-computer interaction.
Pronghorn IoT is an open source API designed to program IoT connected devices that run on the Java Virtual Machine (JVM). Pronghorn IoT has low system requirements, including:
- A compact1 profile in the IoT device that comes in less than 16MB
- A JAR file sent to the IoT device is about 1.5MB
- An open Java Development Kit (JDK)
When Pronghorn IoT is loaded to a local machine, Maven is required to build it. PronghornIoT-Archetype may be used to build templates for new IoT projects and IoT example projects with the following steps:
- Download the Pronghorn Maven Archetype from GitHub by typing
git clone https://github.com/oci-pronghorn/PronghornIoT-Archetype
- Install the Maven projects by typing in the PronghornIoT-Archetype folder
mvn clean install
- Build a new Pronghorn project from Archetype by typing in the PronghornIoT-Archetype folder
mvn archetype:generate -DarchetypeGroupId=com.ociweb.iot.archetype -DarchetypeArtifactId=PronghornIoT-Archetype -DarchetypeVersion=1.0-SNAPSHOT
- Enter the information related to the project naming. You can leave the version field blank
- Download Pronghorn IoT example projects from GitHub by typing
git clone https://github.com/oci-pronghorn/PronghornIoT-Examples.git
- Each example project may be built individually by typing
mvn clean install
in the example folder
Pronghorn IoT Template
A typical template of Pronghorn IoT is shown below, and it may be generated from the Pronghorn IoT-Archetype shown above.
- package ${package};
-
- import static com.ociweb.iot.grove.GroveTwig.*;
-
- import com.ociweb.iot.maker.Hardware;
- import com.ociweb.iot.maker.CommandChannel;
- import com.ociweb.iot.maker.DeviceRuntime;
- import com.ociweb.iot.maker.IoTSetup;
-
- public class IoTApp implements IoTSetup
- {
- ///////////////////////
- //Connection constants
- ///////////////////////
- // // by using constants such as these you can easily use the right value to reference where the sensor was plugged in
-
- //private static final int BUTTON_CONNECTION = 4;
- //private static final int LED_CONNECTION = 5;
- //private static final int RELAY_CONNECTION = 6;
- //private static final int LIGHT_SENSOR_CONNECTION = 0;
-
-
- public static void main( String[] args ) {
- DeviceRuntime.run(new IoTApp());
- }
-
-
- @Override
- public void declareConnections(Hardware c) {
- ////////////////////////////
- //Connection specifications
- ///////////////////////////
-
- // // specify each of the connections on the harware, eg which component is plugged into which connection.
-
- //c.connectDigital(Button, BUTTON_CONNECTION);
- //c.connectDigital(Relay, RELAY_CONNECTION);
- //c.connectAnalog(LightSensor, LIGHT_SENSOR_CONNECTION);
- //c.connectAnalog(LED, LED_CONNECTION);
- //c.useI2C();
-
- }
-
-
- @Override
- public void declareBehavior(DeviceRuntime runtime) {
- //////////////////////////////
- //Specify the desired behavior
- //////////////////////////////
-
- // //Use lambdas or classes and add listeners to the runtime object
- // //CommandChannels are created to send outgoing events to the hardware
- // //CommandChannels must never be shared between two lambdas or classes.
- // //A single lambda or class can use mulitiple CommandChannels for cuoncurrent behavior
-
-
- // final CommandChannel channel1 = runtime.newCommandChannel();
- // //this digital listener will get all the button press and un-press events
- // runtime.addDigitalListener((connection, time, value)->{
- //
- // //connection could be checked but unnecessary since we only have 1 digital source
- //
- // if (channel1.digitalSetValue(RELAY_CONNECTION, value)) {
- // //keep the relay on or off for 1 second before doing next command
- // channel1.digitalBlock(RELAY_CONNECTION, 1000);
- // }
- // });
- }
-
- }
The main()
function is the same for almost every project. Therefore, only the declareBehavior()
and declareConnections()
methods need to be completed.
The declareConnections()
method is related to the hardware connections.
For Intel Edison and Raspberry Pi, there are three types of connection ports on the base board:
- analog
- digital
- I2C
To declare the connection, both the port number and the twig device type must specified, for example:
c.connectDigital(Button, 6)
;
This indicates that the button is a digital device, and it is connected to port 6. The various ports of a Grove base board on Pi are shown below:
The declareBehavior()
method contains methods that describe the behaviors of the hardware, and it is the main part of the code.
In addition, Pronghorn has the following main features:
- Declarative programming
- Manage timing and concurrency
- Same code runs on many devices
Declarative Programming
Imperative programming is what most professional programmers use in their daily jobs. The imperative approach requires the maker to provide step-by-step procedures to the compiler.
Declarative programming is more like describing the end result, rather than the process to achieve it. Declarative programming minimizes mutability, reduces side effects, and leads to more understandable code.
For example, if we intend to find all odd numbers in Listcollection = new List{ 1, 2, 3, 4, 5 };
Declarative Approach Example | Imperative Approach Example |
{ var results = collection.Where( num => num % 2 != 0); } |
{ List |
Pronghorn IoT uses a declarative approach to abstract the low-level native integration and interrupts monitoring, so it is easier to learn and understand. This enables makers to focus on implementing their application business logic.
Alternatives to Pronghorn IoT include:
- Libmraa – a low level C/C++ library with bindings to mainly Python and JavaScript
- J4Pi – a Java API that runs only on Raspberry Pi
Both Libmraa and J4Pi utilize the imperative approach and expose some low-level concepts and programming to makers.
To change the background light color of the LCD-RGB screen, the maker’s code in Pronghorn IoT and the code needed for Libmraa are compared below:
Java Using Pronghorn IoT | Python Using LibMraa |
{ final CommandChannel channellcd =runtime.newCommandChannel(); runtime.addStartupListener(() -> {Grove_LCD_RGB.commandForColor(channellcd, 170,255,255); }); } |
{ import mraa # Change the LCD back light x = mraa.I2c(0) x.address(0x62) # initialise device x.writeReg(0, 0) x.writeReg(1, 0) # sent RGB color data x.writeReg(0x08, 0xAA) x.writeReg(0x04, 255) x.writeReg(0x02, 255) } |
In Pronghorn IoT:
- By using
commandForColor()
, the makers do not need to know the address of each color or the pin configuration needed to set up the LCD-RGB screen. Makers only need to specify red, green, and blue values within the range (0 - 255) to change the color of the screen. addStartupListener()
uses lambda expressions introduced in Java 8 to facilitate functional programming. A lambda expression is usually written using syntax,(argument) -> (body)
. In this example, no parameters are needed to change the background color of the LCD-RGB screen.
Manage Timing and Concurrency
Timing problems in IoT programming are generally reflected in the polling rate, which is critical to receiving data from IoT devices. On one hand, the polling rate must be fast enough that no information is missed. On the other hand, polling too frequently unnecessarily burdens the CPU.
The polling rate of each IoT twig is different. For example, the pulse sensor needs to be polled frequently to avoid missing a peak detection. In contrast, the temperature sensor may be polled at a much lower rate, because temperature is very stable in a normal environment.
The concept of timing should be managed manually in APIs like Arduino's. However, Pronghorn IoT provides a default polling rate for each twig device, so makers don’t need to know the timing concepts and may still poll each twig at the optimal rate.
Arduino Timing Management | Pronghorn IoT Timing Management |
int buttonPin1 = 5; int buttonPin2 = 6; int ledPin1 = 7; int ledPin2 = 8; int buttonDelta = 10; // polling rate for the button int lightDelta = 5000; long lastButton1 = 0; long lastButton2 = 0; long lightEnd = 0; void setup() { pinMode(buttonPin1, INPUT); pinMode(buttonPin2, INPUT); pinMode(ledPin1, OUTPUT); pinMode(ledPin2, OUTPUT); } void loop() { if(lastButton1 + buttonDelta < millis()){// poll every buttonDelta lastButton1 = millis(); digitalWrite(ledPin1, digitalRead(buttonPin1)); } if(lastButton2 + buttonDelta < millis()){ lastButton2 = millis(); lightEnd = millis() + lightDelta; } digitalWrite(ledPin2, millis() > lightEnd); } |
public class IoTApp implements IoTSetup { private static final int BUTTON_CONNECTION = 5; private static final int BUTTON2_CONNECTION = 6; private static final int LED_CONNECTION = 7; private static final int LED2_CONNECTION = 8; public void declareConnections(Hardware c) { c. connectDigital(Button, BUTTON_CONNECTION); c. connectDigital(Button, BUTTON2_CONNECTION); c. connectDigital(LED, LED_CONNECTION); c. connectDigital(LED, LED2_CONNECTION); } public void declareBehavior(IOTDeviceRuntime runtime) { final CommandChannel channel1 = runtime.newCommandChannel(); final CommandChannel channel2 = runtime.newCommandChannel(); runtime.addDigitalListener((connection, time, value)->{ switch(connection){ case(BUTTON_CONNECTION): channel1.digitalSetValue(LED_CONNECTION, value); break; case(BUTTON2_CONNECTION): channel2.digitalPulse(LED2_CONNECTION, 5000); break; } }); } public static void main( String[] args ) { DeviceRuntime.run(new IoTApp()); } } |
In Pronghorn IoT:
blinkerChannel
is aCommandChannel
created to transport data.addDigitalListener((argument)->{body})
— the argument part contains (connection, time, value), which is the reading from the button connected to the digital port.digitalPulse (Connection, Duration)
— sets the digital device, alternating between ON and OFF everyDuration
time. The command channel is blocked onConnection
so no other commands may interrupt.
When sending commands to the hardware, Pronghorn IoT blocks the command channel for the duration of the time specified in the command. In the example below, when channel 1 calls port 2 to wait 1,000 seconds, channel 2 cannot access port 2 during that time interval. Port 2 is blocked until channel 1 is no longer using it.
Similarly, concurrency is managed when the data is received through the channel from the hardware. When data from several hardware ports are received at the same time, the listener triggers one event at a time, and the rest of the events are blocked. This ensures that the data is received asynchronously to avoid concurrency.
Same Code Runs on Many Devices
Pronghorn IoT was developed for Intel Edison and Raspberry Pi, and it will support more IoT systems in the future.
Pronghorn is used to tie an application to specific hardware at runtime after it first detects the attached shield. This technique makes it far easier to develop portable code that may be run across platforms.
Conclusion
Pronghorn IoT is an ongoing project, and we’ve shared just a glimpse of it here. The full source may be found at https://github.com/oci-pronghorn.
Resources
- OCI Training in React Web Development,
http://ocitraining.com/react-web-development - Pronghorn IoT Examples Wiki,
https://github.com/oci-pronghorn/PronghornIoT-Examples/wiki - Pronghorn IoT Project,
https://github.com/oci-pronghorn - Pronghorn IoT Example Projects,
https://github.com/oci-pronghorn/PronghornIoT-Examples - Pronghorn IoT Archetype,
https://github.com/oci-pronghorn/PronghornIoT-Archetype
Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.