Anbindung an JavaFX

Internet of Things mit Java 8 und TinkerForge, Teil 2

Sven Ruppert
© S&S Media

Nach dem Einstieg in die Entwicklung mit der Hardware-Plattform TinkerForge im letzten Teil dieser Serie werden wir uns heute einem konkreten Beispiel widmen. Eines der markanten Merkmale von TinkerForge ist die Möglichkeit, verschiedene Elemente wie Lego-Teile zusammen stecken zu können. Genau das werden wir heute mit zwei Sensoren demonstrieren und uns mit der parallelen Auswertung mehrerer Sensoren auseinandersetzen. Die Sensordaten werden dabei in Echtzeit ausgewertet und per JavaFX angezeigt.      

Das Barometer

In dem hier verwendeten Setup kommt ein MasterBrick mit zwei Sensoren zusammen. Den ersten Sensor kennen wir schon aus dem ersten Teil dieser Serie: Es ist der Temperatursensor. Dazu wird heute das Barometer hinzugefügt. Mit diesem Bricklet kann man den Luftdruck im Bereich von 10 bis 1200 mbar mit einer Auflösung von 0,012 mbar messen, wobei die Messung intern temperaturkompensiert wird. Dieses Bricklet basiert auf einem MS5611-01BA01 Sensor, der außerdem als Altimeter verwendet werden kann. Und damit sind wir bei der Besonderheit dieses Sensors im Vergleich zu dem Temperatursensor: Es werden zwei Messwerte geliefert.

Anbindung von n-Sensoren

Die Anbindung eines einzelnen Sensors erfolgt über die Konfiguration einer kapselnden Instanz des Sensors und der Implementierung eines ActionListeners. Innerhalb dessen werden einem die Werte geliefert. An dieser Stelle nochmals das Listing 1 als kurze Erinnerung.

import com.tinkerforge.BrickletTemperature;
import com.tinkerforge.IPConnection;

public class ExampleCallback {
    private static final String host = "localhost";
    private static final int port = 4223;
    private static final String UID = "dXj"; 
    public static void main(String args[]) throws Exception {
        IPConnection ipcon = new IPConnection(); 
        BrickletTemperature temp = new BrickletTemperature(UID, ipcon); 
        ipcon.connect(host, port); 
        temp.setTemperatureCallbackPeriod(1000);
        temp.addTemperatureListener(new 
          BrickletTemperature.TemperatureListener() {
            public void temperature(short temperature) {
                System.out.println("Temperature: " 
                   + temperature/100.0 + " °C");
            }
        });
        ipcon.disconnect();
    }
}

Genauso verhält es sich mit dem Barometer. Auch hier gibt es eine Klasse, die den Sensor kapselt. In diesem Fall ist es die Klasse BrickletBarometer. In Listing 2 ist zu sehen, dass die Konfiguration bis auf die UID identisch mit der von dem Listing 1 ist. Das liegt daran, dass beide Sensoren an demselben MasterBrick angeschlossen sind. Zusätzlich kann man hier sehen, wie die beiden Messwerte aus dem Sensor herausgeholt werden. Es ist sehr simpel, da es für jeden Messwert einen eigenen ActionListener gibt.

public class ExampleCallback {
    private static final String host = "localhost";
    private static final int port = 4223;
    private static final String UID = "jY4";
    public static void main(String args[]) throws Exception {
        IPConnection ipcon = new IPConnection(); 
        BrickletBarometer b = new BrickletBarometer(UID, ipcon); 
        ipcon.connect(host, port); 
        b.setAirPressureCallbackPeriod(1000);
        b.setAltitudeCallbackPeriod(1000);
        b.addAirPressureListener(
            new BrickletBarometer.AirPressureListener() {
                public void airPressure(int airPressure) {
                    System.out.println("Air Pressure: " 
                        + airPressure/1000.0 + " mbar");
                }
            }
        );
        b.addAltitudeListener(new BrickletBarometer.AltitudeListener() {
            public void altitude(int altitude) {
                System.out.println("Altitude: " + altitude/100.0 + " m");
            }
        });
        ipcon.disconnect();
    }
}

Anbindung an JavaFX

Die Anbindung der Sensoren an JavaFX kann recht trivial realisiert werden. Das Grundprinzip ist, dass aus der JavaFX-Anwendung heraus ein Thread gestartet wird. Dieser konfiguriert den Sensor und fügt in der run() – Methode den ActionListener hinzu. Wichtig hierbei ist, dass die Anteile, die JavaFX-GUI-Elemente aktualisieren, wiederum in ein Platform.runLater() gekapselt werden. Da es sich in diesem Beispiel um mehrere Sensoren handelt, sind die jeweiligen Sensoren in jeweils eine Klasse ausgelagert worden. Für den Temperatursensor sieht das wie in Listing 3 aus. Der Aufruf ist dann sehr einfach:

Platform.runLater(new Temp(„dXj“, seriesTemp));


public static class Temp implements Runnable {

        private String UID;
        private ObservableList seriesData;

        public Temp(final String UID, final XYChart.Series series) {
            this.UID = UID;
            this.seriesData = series.getData();
        }

        @Override
        public void run() {
            IPConnection ipcon = new IPConnection();
            BrickletTemperature temp = new BrickletTemperature(UID, ipcon);

            try {
                ipcon.connect(host, port);
                temp.setTemperatureCallbackPeriod(1000);
                temp.addTemperatureListener(
                  new BrickletTemperature.TemperatureListener() {
                    public void temperature(short temperature) {
                        Platform.runLater(new Runnable() {
                            @Override
                            public void run() {
                                final double temp = temperature / 100.0;
                                System.out.println("Temperature: " + temp 
                                    + " °C");
                                final XYChart.Data data 
                                    = new XYChart.Data(new Date(), temp);
                                seriesData.add(data);
                            }
                        });
                    }
                });
            } catch (IOException 
                       | AlreadyConnectedException 
                       | TimeoutException 
                       | NotConnectedException e) {
                e.printStackTrace();
            }
        }
    }

Für das Barometer wurden zwei solche Klassen geschrieben: die Klasse Luftdruck und die Klasse Altitude. Beide sind bis auf den ActionListener identisch und legen ihre Werte jeweils in eine eigene Serie. Jede Serie wird in einem eigenen LineChart dargestellt. In dieser Implementierung wurde bewusst in Kauf genommen, dass es für den einzelnen physikalischen Sensor zwei Repräsentationen in jeweils einem Thread gibt. In den Tests ist diese Kombination stabil gelaufen. Zusätzlich wurde in allen Beispielen darauf verzichtet, bei Beendigung der JavaFX-Anwendung die Sensoren mittels disconnect() zu schließen. In Listing 4 ist das JavaFX Programm zu sehen.

public class Barometer extends Application {
    public static final String host = "localhost";
    public static final int port = 4223;


    public static void main(String args[]) throws Exception {
        launch(args);
    }

    public static XYChart.Series seriesTemp = new XYChart.Series();
    public static XYChart.Series seriesLuftdruck = new XYChart.Series();
    public static XYChart.Series seriesAltitude = new XYChart.Series();

    @Override
    public void start(Stage stage) {
        stage.setTitle("Line Chart TinkerForge Sample");

        final VBox box = new VBox();
        seriesTemp.setName("Temp");
        seriesLuftdruck.setName("Luftdruck");
        seriesAltitude.setName("Altitude");

        final ObservableList boxChildren = box.getChildren();
        boxChildren.add(createLineChart("Temp", seriesTemp));
        boxChildren.add(createLineChart("Luftdruck", seriesLuftdruck));
        boxChildren.add(createLineChart("Altitude", seriesAltitude));

        Scene scene = new Scene(box, 2000, 1500);

        stage.setScene(scene);
        stage.show();
        Platform.runLater(new Temp("dXj", seriesTemp));
        Platform.runLater(new Luftdruck("jY4", seriesLuftdruck));
        Platform.runLater(new Altitude("jY4", seriesAltitude));

    }

    private LineChart createLineChart(
        final String chartName,
        final XYChart.Series series ){

        final DateAxis dateAxis = new DateAxis();
        dateAxis.setLabel("Time");
        final NumberAxis yAxis = new NumberAxis();

        final LineChart lineChart 
            = new LineChart<>(dateAxis, yAxis);
        lineChart.setTitle(chartName);
        lineChart.getData().add(series);

        return lineChart;
    }
}

Fazit

In diesem Beitrag haben wir den ersten Schritt in die Richtung der Auswertung von n-Sensoren beschrieben. Bei den Tests zeigte sich schnell, dass eine mittlere Abtastrate von 1 Messung/Sekunde schon zu einigen Prozent CPU-Auslastung führt. Somit ist in dieser Form die Skalierbarkeit begrenzt. Wir werden uns in den nächsten Artikeln mit der Abstraktion der Sensoren als JavaFX-GUI-Elemente und mit dem Speichern von Werten in DBMS (SQL/NoSQL) beschäftigen.    

Die Quelltexte zu diesem Text sind unter [1] zu finden. Wer umfangreichere Beispiele zu diesem Thema sehen möchte, dem empfehle einen Blick auf [2].

Geschrieben von
Sven Ruppert
Sven Ruppert
Sven Ruppert arbeitet seit 1996 mit Java und ist Developer Advocate bei Vaadin. In seiner Freizeit spricht er auf internationalen und nationalen Konferenzen, schreibt für IT-Magazine und für Tech-Portale. Twitter: @SvenRuppert
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu: