Pronghorn IoT

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. 

A Grove-Pi IoT device

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:

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:

  1. Download the Pronghorn Maven Archetype from GitHub by typing git clone https://github.com/oci-pronghorn/PronghornIoT-Archetype
  2. Install the Maven projects by typing in the PronghornIoT-Archetype folder mvn clean install
  3. 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
  4. Enter the information related to the project naming. You can leave the version field blank
  5. Download Pronghorn IoT example projects from GitHub by typing git clone https://github.com/oci-pronghorn/PronghornIoT-Examples.git
  6. 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.

  1. package ${package};
  2.  
  3. import static com.ociweb.iot.grove.GroveTwig.*;
  4.  
  5. import com.ociweb.iot.maker.Hardware;
  6. import com.ociweb.iot.maker.CommandChannel;
  7. import com.ociweb.iot.maker.DeviceRuntime;
  8. import com.ociweb.iot.maker.IoTSetup;
  9.  
  10. public class IoTApp implements IoTSetup
  11. {
  12. ///////////////////////
  13. //Connection constants
  14. ///////////////////////
  15. // // by using constants such as these you can easily use the right value to reference where the sensor was plugged in
  16.  
  17. //private static final int BUTTON_CONNECTION = 4;
  18. //private static final int LED_CONNECTION = 5;
  19. //private static final int RELAY_CONNECTION = 6;
  20. //private static final int LIGHT_SENSOR_CONNECTION = 0;
  21.  
  22.  
  23. public static void main( String[] args ) {
  24. DeviceRuntime.run(new IoTApp());
  25. }
  26.  
  27.  
  28. @Override
  29. public void declareConnections(Hardware c) {
  30. ////////////////////////////
  31. //Connection specifications
  32. ///////////////////////////
  33.  
  34. // // specify each of the connections on the harware, eg which component is plugged into which connection.
  35.  
  36. //c.connectDigital(Button, BUTTON_CONNECTION);
  37. //c.connectDigital(Relay, RELAY_CONNECTION);
  38. //c.connectAnalog(LightSensor, LIGHT_SENSOR_CONNECTION);
  39. //c.connectAnalog(LED, LED_CONNECTION);
  40. //c.useI2C();
  41.  
  42. }
  43.  
  44.  
  45. @Override
  46. public void declareBehavior(DeviceRuntime runtime) {
  47. //////////////////////////////
  48. //Specify the desired behavior
  49. //////////////////////////////
  50.  
  51. // //Use lambdas or classes and add listeners to the runtime object
  52. // //CommandChannels are created to send outgoing events to the hardware
  53. // //CommandChannels must never be shared between two lambdas or classes.
  54. // //A single lambda or class can use mulitiple CommandChannels for cuoncurrent behavior
  55.  
  56.  
  57. // final CommandChannel channel1 = runtime.newCommandChannel();
  58. // //this digital listener will get all the button press and un-press events
  59. // runtime.addDigitalListener((connection, time, value)->{
  60. //
  61. // //connection could be checked but unnecessary since we only have 1 digital source
  62. //
  63. // if (channel1.digitalSetValue(RELAY_CONNECTION, value)) {
  64. // //keep the relay on or off for 1 second before doing next command
  65. // channel1.digitalBlock(RELAY_CONNECTION, 1000);
  66. // }
  67. // });
  68. }
  69.  
  70. }

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:

  1. analog
  2. digital
  3. 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:

  1. Declarative programming
  2. Manage timing and concurrency
  3. 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 results = new List(); 
for (int i=0; i collection.length; i++) { 
if (collection[i] % 2 != 0){
 results.Add(num); }
}
}

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:

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:

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:

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



Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.


secret