Monday, December 17, 2012

ArduinoFX: A JavaFX GUI for Home Automation with Raspberry Pi and Arduino. Part II

Hi There!

If you haven't read the first part of this series, please take your time, and read about the basic configuration required for the application I'm blogging about in this second part.

Briefly, as a recap, we have an Arduino with a RHT03 temperature and relative humidity sensor and a XBee antenna, plugged in an Arduino shield. The measures are sent wirelessly to a second XBee antenna, plugged via a XBee Explorer to a Raspberry Pi USB port.

In the Raspberry Pi, with soft-float Debian wheezy distro, we have installed Java Embedded Suite 7.0. For now, we are just reading the measures.

Now, if you keep on reading this second part, we are going to create the embedded server in the Raspberry Pi, with the following main tasks: 
  • Back-end task: it will repeatedly read the serial port to get the measures from the Arduino and store them in a database.
  • Web services: it will respond to web requests returning the current values measured.
  • RESTful web services: it will respond to REST requests returning lists in json format with the data measured between two dates.
And then we will create the JavaFX based client application, that will mainly:
  • Connect to the server to get last measures and show them in a LED Matrix Panel control.
  • Connect to the server to get a list of measures between two dates, showing them in a chart. 
  • Show the status of the connection anytime.
Embedded application development is fundamentally cross-platform work. The target device on which the application will be deployed, the Raspberry Pi in this case, doesn't have the hardware resources to support development tools such as Netbeans. Therefore, we must build on our host computer, copy the required build artifacts to the target, and debug and tune the application running on the target. 

1. Setting the Environment

Basically, we should recreate the embedded environment in our PC, so we can create the project in our favourite Java IDE, compile and build it, and then move the jar/war files by ssh to the Raspberry Pi, where we'll use the provided scripts by the Java EmbeddedSuite to run the application. 

As pointed out here, we need at least JDK 1.7, NetBeans 7.1 and Ant 1.8. In that case, follow the next steps to add the required Ant variable JES_HOME:
  • Download JES from here, in case you haven't done it yet following Part I of this series.
  • Unzip jes-7.0-ga-bin-b11-linux-arm-runtime-15_nov_2012 and move all its content to a local folder like C:\Java
  • In Netbeans go to Tools, select Ant Variables, click Add, and define JES_HOME and browse to the folder "jes7.0", and click Ok.

Now we can open the embedded samples projects on NetBeans and build them. We can make any change, rebuild again, and send them by ssh to the Pi, and run them.

2. Modifying HelloService sample

Open this project and check the content of the Ant file build.xml. You will see several customized targets. The first one is used to set the javac compiler arguments, specifying the rt.jar from JES_HOME instead from the regular JAVA_HOME.

<target depends="-pre-init,-init-private" name="-init-user">
    <property file="${user.properties.file}"/>
    <property name="javac.compilerargs" value="-bootclasspath ${var.JES_HOME}/jre/lib/rt.jar"/>
    <property name="default.javac.source" value="1.7"/>
    <property name="default.javac.target" value="1.7"/>
</target>

Now I'm going to make a slight change in the Message string of HelloService.java:

@Path("/")
public class HelloService {

    public static final String MESSAGE = "Hello World from <b>ArduinoFX</b> Embedded Server!";
    
    @GET
    @Produces({MediaType.TEXT_HTML, MediaType.TEXT_PLAIN})
    public String getText() {
        return MESSAGE;
    }

    @GET
    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    public HelloBean getTextAsBean() {
        return new HelloBean(MESSAGE);
    }
}

Build the project, and send jar/war files to /usr/java/jes7.0/samples/helloservice/dist/ in your Pi, by ssh. Please check Installation in the first post in case you haven't installed Java Embedded Suite on your Raspberry Pi yet.



Now on your Pi run the sample with the gfhost script and the path to the war file:

cd /usr/java/jes7.0/samples/dist/run/
./gfhost.sh ../../helloservice/dist/helloservice.war


Note it takes around one minute to deploy, so be patient.

Now invoke the service from your web browser. The first time it's called, takes around 15 seconds to respond, but subsequent calls are faster.



Finally, on the server, click Enter to undeploy.
 
3. Embedded Server

Now it's time to create from the scratch our server, with a little help of the provided samples.

These are the steps you need to follow carefully in order for the server to run in your Pi with Java Embedded Suite 7.0.

1. Copy RXTXcomm-2.2pre2.jar to JES_HOME/jre/lib/ext local folder. Do the same in the Pi, copying it to /usr/java/jes7.0/jre/lib/ext.

2. In your Pi, edit /usr/java/jes7.0/samples/dist/run/config.sh and create this variable:

JES_EXT_CLASSPATH="$JES_HOME/jre/lib/ext/RXTXcomm-2.2pre2.jar"

and add it to the classpath:

JES_CLASSPATH="$JES_JAVADB_CLASSPATH:$JES_GLASSFISH_CLASSPATH:

   $JES_JERSEY_SERVLET_CLASSPATH:$JES_EXT_CLASSPATH"

3. Now in Netbeans create a new Java SE application. I'll name it Embedded. Uncheck Create main class.

4. Edit project properties. In Sources, change Source Packages to src/java. In Libraries, add the following jars:
  • JES_HOME/javadb/lib/derby.jar
  • JES_HOME/glassfish/lib/glassfish-jes.jar
  • JES_HOME/jersey/lib/jsr311-api.jar
  • JES_HOME/jre/lib/ext/RXTXcomm-2.2pre2.jar
Finally, in Build/Packaging, uncheck Copy Dependency Libraries

5. Create Embedded/src/webapp and Embedded/src/webapp/WEB-INF folders, and copy web.xml from HelloSuite/src/webapp/WEB-INF.
 
6. Edit build.xml from HelloSuite sample, copy the four last targets, and paste them at the end of Embedded/build.xml. In this way rt.jar will be selected from JES_HOME to compile the project, and the war will be created. In the last two targets change hellosuite.jar for embedded.jar and hellosuite.war for embedded.war.

7. Add a class with all the required JAX-RS web services, com.jpl.embedded.EmbeddedREST. Basically it should have two services: 
  • Return the last reading of the sensor, in json format.
  • Return an array of readings between two dates, in json format.
8. Add a class com.jpl.embedded.JAXRXConfig to register this latter class:

@ApplicationPath("/")
public class JAXRXConfig extends Application {

    @Override public Set<Class<?>> getClasses() {
        final Set<Class<?>> classes = new HashSet<Class<?>>();
        // register root resource the first time there is a REST request
        classes.add(EmbeddedREST.class);
        return classes;
    }
}

This servlet must be registered in the web.xml file, modifying the servlet from HelloSuite:

<servlet>
   <servlet-name>com.jpl.embedded.JAXRXConfig</servlet-name>
</servlet>

9. Finally, add a servlet com.jpl.embedded.ConfigServlet. It must be loaded just after deploying the project, so the database is created (only the first time) and the connection is established. Also we start a scheduled task to read the serial port, and storing the measures periodically. When the project is undeployed, it should stop this task and close the serial port.

In order to load RXTXcomm, which uses native code, a system property must be set before starting the task:

@WebServlet(urlPatterns={"/ArduinoOnline"}, loadOnStartup=0)
public class ConfigServlet extends HttpServlet {

    static {
        System.setProperty( "java.library.path", "/usr/lib/jni" );
    }
    
    private XBee xbee;
    
    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        
        // Initialize BDD
        CStore.getInstance();
        
        // Initialize Serial communication, starts reading port
        xbee=new XBee();
    }
    
    @Override public void destroy() {
        xbee.disconnect();
    }
}

10. The rest of auxiliary classes we'll use are:
  • com.jpl.embedded.comms.Serial. Connect to serial port and read measures from Arduino, via XBee antennas.
  • com.jpl.embedded.model.BeanHT. JavaBean with temperature and humidity values and time of reading.
  • com.jpl.embedded.model.CSensor. Singleton with the most recent BeanHT read.
  • com.jpl.embedded.service.CStore. Singleton to open and close the connection to the database, and to write and read BeanHT values.
  • com.jpl.embedded.service.XBee. Open and close the connection to the serial port, and start a scheduled task to record BeanHT values every 30 seconds in the database.
All the source code for java classes mentioned in points 7 to 10 can be found in my GitHub repository. Feel free to clone it and use the code to test your own embedded server.

So finally, we can compile and build the project. If everything is in place, we could send embedded.jar and war to /usr/java/jes7.0/samples/embedded/dist in the Pi by ssh.

11. To deploy the server, in your Raspberry Pi:

cd /usr/java/jes7.0/samples/dist/run/
./gfhost.sh ../../embedded/dist/embedded.war

Note the deploy takes around one minute. After that, the servlet is initialized, so the database connection is established and the serial port connection is opened. The background task starts and the measures coming from the Arduino are read and stored in the database.



12. Now we can test the different web services, from a browser. 

Test the servlet. It takes around 18 seconds to load, but only the first time.



Test the RESTful web services. The first run takes around 80 seconds to complete.





Finally, to undeploy the server, just press <Enter> twice on the terminal. All the tasks will be smoothly stopped, and serial port and database connections will be closed.



4. The JavaFX GUI

Now we are going to design the JavaFX based client, a simple UI with three main tasks:
  • Connect to the embedded server to get last measures and show them in a MatrixPanel control.
  • Show the status of the connection anytime.
  • Connect to the server to get a list of measures between two dates, showing them in a chart. 
User Interface

First, let's start by describing the user interface. With the help of the JavaFX Scene Builder the basic layout is done.


There're three main boxes, ready to hold several custom controls from the JFXtras project, that will be added from the controller:
  • A top VBox for the MatrixPanel control. This LED panel like control will show the last values of temperature and relative humidity read from the server every 30 seconds.
  • A central HBox for the CalendarTextField and ChoiceBox controls. These controls will allow the user to choose an initial date and an ending date to ask for the values stored in the database in the server. The ChoiceBox allows you to select the number of items readed between these two dates.
  • A bottom HBox for the SimpleIndicator. It will simply show if the connection is established to the server (green) or not (red).
In the controller class, these controls are first created, and later are added when the initialize method is called.
 
    private final MatrixPanel animatedPanel = 
          MatrixPanelBuilder.create()
                            .ledWidth(192).ledHeight(18)
                            .prefWidth(650.0).prefHeight(400.0)
                            .frameDesign(FrameDesign.DARK_GLOSSY)
                            .frameVisible(true)
                            .build();
    
    private final CalendarTextField lStartCalendarTextField = 
                                new CalendarTextField().withShowTime(true);
    private final CalendarTextField lEndCalendarTextField = 
                                new CalendarTextField().withShowTime(true);
        
    private final SimpleIndicator indicator = SimpleIndicatorBuilder.create()
                               .prefHeight(40).prefWidth(40)
                               .innerColor(Color.rgb(0,180,0).brighter())
                               .outerColor(Color.rgb(0,180,0).darker())
                               .build();
    @Override
    public void initialize(URL url, ResourceBundle rb) {
   
        vbox.getChildren().add(0,animatedPanel);
        hbox.getChildren().addAll(lStartCalendarTextField,lEndCalendarTextField);
        hbox.getChildren().add(sizeChoiceBox);
        hboxStatus.getChildren().add(0, indicator);

        ...
    }

So when the application is launched, it will look like this:


Rest services

When the application starts, a scheduled task is launched with the purpose of repeatedly ask the server for the last values of temperature and humidity. This values are then passed to the content of the matrixpanel.

    private long EVENT_CYCLE = 30000; // ms
    private final ScheduledExecutorService scheduler = 
                    Executors.newSingleThreadScheduledExecutor();
    private ScheduledFuture scheduleAtFixedRate = null;
    
    @Override
    public void start(Stage stage) throws Exception {
        
        scheduleAtFixedRate = scheduler.scheduleAtFixedRate(new LastHT(), 0, 
                               EVENT_CYCLE, TimeUnit.MILLISECONDS);
        ...
   }

Where LastHT class perform a web request to http://<IP>:<PORT>/embedded/last REST service and reads the last measured values from response in json format, deserializing it to a BeanHT object.

This object is then stored, and if any of temperature or humidity have changed, the matrixpanel content is updated. For that, a JavaFX thread must be used.
 
Platform.runLater(new Runnable() {

    @Override
    public void run() {
        Content contentTemp = ContentBuilder.create()
                        .color(MatrixColor.GREEN)
                        .type(Type.TEXT)
                        .txtContent("Temperature: " + String.format("%.1f",
             MonitoringServiceStub.getInstance().getLastMeasure().getTemp()) + " ºC")
                        .font(MatrixFont.FF_8x16).fontGap(Gap.SIMPLE)
                        .origin(0, 1).area(0, 0, 191, 18)
                        .align(Align.RIGHT).effect(Effect.SCROLL_LEFT)
                        .lapse(20).postEffect(PostEffect.PAUSE)
                        .pause(3000).order(RotationOrder.FIRST)
                        .build();
        ...
        animatedPanel.setContents(Arrays.asList(contentTemp, contentHum));
    }
});

When the user clicks on the Evaluation button, the request for a list of values between two dates starts. But since the server is a tiny computer with low CPU power, it will take some time to be performed. For that reason, we need to set this as a task from a service.

Service<void> chartService=new Service<void>(){

    @Override
    protected Task<void> createTask() {

        return new Task<void>(){

            @Override
            protected Void call() throws Exception {

                ChartHT ht=new ChartHT(
                      lStartCalendarTextField.getValue().getTimeInMillis(),
                      lEndCalendarTextField.getValue().getTimeInMillis(),
                      sizeChoiceBox.getSelectionModel().getSelectedItem().toString());
                
                ht.run();

                return null;
            }                    
        };
    }
};

@FXML
public void getEvolution(ActionEvent a){        
    chartService.restart(); 
}

The way we will know the task has been completed (maybe a minute later) is looking for changes in the stateProperty of the service. 

When its value reach State.SUCCEEDED, we can proceed with creating the chart and plotting the data deserialized from the json array returned by the server, again in a new JavaFX thread:

chartService.stateProperty().addListener(new ChangeListener<state>(){

    @Override
    public void changed(ObservableValue ov, State t, State t1) {
        if(t1==State.SUCCEEDED) {

            Platform.runLater(new Runnable() {

                @Override
                public void run() {

                    final CategoryAxis xAxis = new CategoryAxis();
                    final NumberAxis yAxis = new NumberAxis();

                    final LineChart<String,Number> lineChart = new 
                             LineChart<String,Number>(xAxis,yAxis);

                    XYChart.Series series1 = new XYChart.Series();
                    series1.setName("Temperature (ºC)");
                    XYChart.Series series2 = new XYChart.Series();
                    series2.setName("Relative Humidity (%)");

                    for(IObservableMeasure d : 
                          MonitoringServiceStub.getInstance().getChartMeasures()){
                        series1.getData().add(new XYChart.
                                  Data(d.getTime(),d.getTemp()));
                        series2.getData().add(new XYChart.
                                  Data(d.getTime(),d.getHum()));
                    }

                    lineChart.getData().addAll(series1,series2);

                    hbox2.getChildren().add(1,lineChart);
                }
            });
        }                
    }
});

All the source code of this project is in my GitHub repository. Feel free to clone it, and test it. Once you have installed the embedded server in your Raspberry Pi, you will need to set the IP address and port in the client:

public class MonitoringServiceStub implements MonitoringService {    
    /* SET YOUR SERVER IP AND PORT HERE */
    public static final String urlServer="http://192.168.0.39:8080";  
    ...

Finally, this is a short video of the JavaFX client in action.


Conclusion

Here I conclude this new proof of concept. As a recap, we have communicated an Arduino with a RHT03 temperature and relative humidity sensor and a XBee antenna, wirelessly to a second XBee antenna, plugged via a XBee Explorer to a Raspberry Pi USB port.

In the Raspberry Pi, with soft-float Debian wheezy distro, we have installed Java Embedded Suite 7.0. We have created an embedded server in the Pi, with the following main tasks: 
  • Back-end task: it repeatedly reads the serial port to get the measures from the Arduino and store them in a database.
  • Web services: it responds to web requests returning the current values measured.
  • RESTful web services: it responds to REST requests returning lists in json format with the data measured between two dates.
And then we have created the JavaFX based client application, that mainly:
  • Connects to the server to get last measures and show them in a LED Matrix Panel control.
  • Connects to the server to get a list of measures between two dates, showing them in a chart. 
  • Shows the status of the connection anytime.
If you have followed this two part series post, I hope it has caught your attention, and you're willing to give it a try. Let me say here: PLEASE, DO IT AT HOME!!

I'd love to hear from you if you have any questions regarding the setup of this project, or any part of it.

5 comments:

  1. Thank you, I'm just trying to explain the basis to start with XBee. Thanks for pointing out your nice video tutorials, they are quite remarkable!

    ReplyDelete
  2. Hi José,

    I have been testing your tutorial, but I can't download soft-float Debian wheezy distro image. I have been looking for this image version but I don't find this image. Could you provided me any download link ? Why there isn't this version on http://www.raspberrypi.org/downloads ?

    Regards.

    ReplyDelete
  3. Thanks Antonio for pointing out this broken link. It seems the soft-float image has been discontinued.

    I've updated the first part of the post with a new link to a mirror with the very same version from 2012 I used, though there's also a new version from May 2013.

    ReplyDelete
  4. This comment has been removed by a blog administrator.

    ReplyDelete
  5. This comment has been removed by a blog administrator.

    ReplyDelete

Note: Only a member of this blog may post a comment.