SETT
SETT

Pronghorn IoT: A Declarative Java Approach to Programming Embedded Systems

by Bo Huang, Software Engineer

September 2016

If current trends continue, the number of IoT (Internet of Things) 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 are required to get even the simplest of examples to work. With Pronghorn IoT, we provide an easy-to-use, declarative, and 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.

The Internet of Things (IoT) is a system of interrelated devices, machines, and objects that automatically exchanges data over a network without requiring human-to-computer interaction. 

Photo of 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 the Pronghorn IoT is loaded to the 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 (commands that we want to execute in a command line will be in bold text, for example: mvn clean install):

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: analog, digital, and 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 is 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 that. 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 Pronghorns IoT include:

Both LibMraa and J4Pi utilize the imperative approach and expose some low level concepts and programming to the 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 mainly reflects in the polling rate, which is crucial in receiving data from IoT devices. On one hand, the polling rate must be fast enough so 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 pulled frequently in order not to miss 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, the 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 1000 seconds, channel 2 cannot access port 2 during that time interval. Port 2 will be blocked until channel 1 is no longer using it.

Similarly, the concurrency is managed when the data is received through the channel from the hardware. When data from several ports of the hardware are received at the same time, the listener triggers one event at a time, and the rest of the events will be blocked. This makes sure the data is received asynchronously, to avoid concurrency.

Same Code Runs on Many Devices

Pronghorn IoT is developed based on Intel Edison and Raspberry Pi, and it will support more IoT systems in the future.

Pronghorn is used to tie the application to specific hardware at runtime after it first detects the attached shield. This technique makes it is far easier to develop portable code that may be run across platforms.

Conclusion

Pronghorn IoT is an ongoing project, and we’ve only shared just a glimpse of it here. The full source may be found at https://github.com/oci-pronghorn.

Resources

WebSanity Top Secret