Whether the weather be cold, or whether the weather be hot

Internet of Things mit Java 8 und TinkerForge, Teil 5

Sven Ruppert
© S&S Media

Ist es gerade Winter oder eher doch Herbst? So sicher kann man sich leider gerade nicht sein. Wetter ist immer ein wichtiges Gesprächsthema. Und wir werden heute zum Profi-Wetterfrosch – mit Java, JavaFX und TinkerForge.

Um Aussagen über das Wetter zu treffen, braucht es keinen armen Laubfrosch, der in einem Glas ein trauriges Dasein fristet und ab und zu eine Leiter hoch- und hinunterklettert. Man kann auch eine froschfreundliche Wetterstation bauen.

Die Bausteine

Für die Wetterstation heute benötigen wir folgende Zutaten: einen Temperatur-, einen Licht- und einen Barometer-Sensor. Damit sind wir in der Lage, die Temperatur, den Luftdruck, barometrische Höhe und die Lichtstärke in Lux zu messen. Den Temperatursensor und Barometersensor kennen wir schon aus den vorherigen Artikeln. Neu ist heute der Lichtsensor. Für die Anzeige wird zusätzlich eine 20×4-LCD-Anzeige verwendet.

Der Lichtsensor

Der Lichtsensor (Ambient Light Sensor) kann die Umgebungshelligkeit messen und liefert den Wert in der Einheit Lux. Die Auflösung (0 Lux-900 Lux) liegt bei 0,1 Lux, das entspricht 12 Bit. Der Lichtsensor ist mit ca. 2 g einer der leichtesten und verbraucht gerade mal 1 mA.

Die Wetterstation

Wie gehabt (siehe vorhergehende Artikel) werden alle Elemente an den Master angeschlossen und erscheinen kurz darauf in der Anzeige des BrickViewers. Bitte vergessen Sie nicht, ein Update der Sensorsoftware durchzuführen, falls Ihnen eine neue Version angezeigt wird. Alle Sensoren sind mit einer Java-Schnittstelle ausgestattet, die das gewohnte ActionListener-Konzept anbietet. Zusätzlich kann jeder Sensor auch direkt abgefragt werden. Das allerdings sollte man nach Möglichkeit nicht verwenden, da es schnell zu unnötig hoher Bandbreitennutzung führt. Die Sensoren werden nun in einer JavaFX-losen Version geschrieben, da diesmal keine Visualisierung per LineChart durchgeführt wird. Als Beispiel im Listing 1 das Barometer. Anstelle der JavaFX-Elemente wird diesmal direkt in die 20×4-LCD Anzeige geschrieben. Soweit ist also alles wie gewohnt.

public class Light implements Runnable {

    private String UID;
    private int callbackPeriod;
    private LCD20x4 lcd20x4 = new LCD20x4("jvX");

    public Light(final String UID, int callbackPeriod) {
        this.UID = UID;
        this.callbackPeriod = callbackPeriod;
    }

    @Override
    public void run() {
        IPConnection ipcon = new IPConnection();
        BrickletAmbientLight bricklet 
            = new BrickletAmbientLight(UID, ipcon);
        try {
            ipcon.connect(Localhost.HOST, Localhost.PORT);

            bricklet.setIlluminanceCallbackPeriod(callbackPeriod);
            bricklet.addIlluminanceListener(illuminance -> {
                final double lux = illuminance / 10.0;
                final String text = "Lux   : " 
                    + lux + " Lux";
                lcd20x4.printLine(3, text);
            });
        } catch (IOException
                | AlreadyConnectedException
                | TimeoutException | NotConnectedException e) {
            e.printStackTrace();
        }
    }
}

Das 20×4-LCD-Bricklet

Das LCD Bricklet enthält eine 20×4-Punktematrix inklusive einer blauen Hintergrundbeleuchtung. Zusätzlich befinden sich vier Taster an der unteren Kante der Platine. Das API ermöglicht es, einzelne Zeichen oder ganze Zeilen bis zu einer Länge von 20 Zeichen direkt zu schreiben. Die Hintergrundbeleuchtung kann an- und ausgeschaltet sowie der Zustand der Taster ausgelesen werden. Mit diesem LCD-Bricklet werden die Daten der angeschlossenen Sensoren angezeigt. Jeder Sensorwert bekommt eine eigene Zeile. In unserem Beispiel sind die Zeilen wie folgt belegt:

  1. Temperatur in Grad Celsius
  2. Luftdruck in Millibar
  3. Barometrische Höhe in Metern
  4. Lichtstärke in Lux

Der Vorteil bei der Implementierung der 20×4-LCD-Anzeige ist, dass sie Thread-safe ist. Das Bricklet kann von verschiedenen Threads konkurrierend mit Daten versorgt werden, ohne dass Probleme zu  erwarten sind. Die Aufteilung der Zeilen vereinfacht die Programmierung erheblich, da nur der jeweilige Sensor für die klare Darstellung seiner eigenen Zeile verantwortlich ist. Da hier vereinfachend pro Sensoreinheit eine Instanz des LCD Bricklets erzeugt wird, wird auch die Initialisierung mehrfach durchlaufen. Für unsere Beispiele reicht das derzeit aus.

Um die gewünschten Zeichen auf dem LCD-Bricklet anzuzeigen, müssen sie erst in ein Bitmuster umgerechnet werden. Hierfür kann aus der TinkerForge-Dokumentation die Methode String utf16ToKS0066U(String utf16) (auch im Beispielquelltext enthalten) entnommen werden. Nach dem Kodieren wird der Text von max. 20 Zeichen Länge pro Zeile mit der Methode writeLine() (Listing 2) zur Anzeige gebracht.

public void printLine(short lineNr, final String text) {
        try {
            lcd.writeLine(lineNr, (short)0, clearLine);
            lcd.writeLine(lineNr, (short)0, utf16ToKS0066U(text));
        } catch (TimeoutException | NotConnectedException e) {
            e.printStackTrace();
        }
}

[ header = Einsatz ]

Listing 3 zeigt die vollständige Implementierung der Klasse LCD20x4.

public class LCD20x4 {

    private BrickletLCD20x4 lcd;

    public LCD20x4(final String UID) {
        IPConnection ipcon = new IPConnection();
        lcd = new BrickletLCD20x4(UID, ipcon);
        try {
            ipcon.connect(Localhost.HOST, Localhost.PORT);
            lcd.backlightOn();
            lcd.clearDisplay();
            lcd.setDefaultTextCounter(-1);
        } catch (IOException 
                    | AlreadyConnectedException 
                    | TimeoutException | NotConnectedException e) {
            e.printStackTrace();
        }
    }


    private final String clearLine 
        = utf16ToKS0066U("                    ");


    public void printLine(short lineNr, final String text) {
        try {
            lcd.writeLine(lineNr, (short)0, clearLine);
            lcd.writeLine(lineNr, (short)0, utf16ToKS0066U(text));
        } catch (TimeoutException | NotConnectedException e) {
            e.printStackTrace();
        }
    }

    public void printLine(int lineNr, final String text) {
        printLine((short) lineNr, text);
    }

    static String utf16ToKS0066U(String utf16)
    {
        String ks0066u = "";
        char c;

        for (int i = 0; i < utf16.length(); i++) {
            int codePoint = utf16.codePointAt(i);

            if (Character.isHighSurrogate(utf16.charAt(i))) {
                // Skip low surrogate
                i++;
            }

            // ASCII subset from JIS X 0201
            if (codePoint >= 0x0020 && codePoint <= 0x007e) {
                // The LCD charset doesn't include '' 
                // and '~', use similar characters instead
                switch (codePoint) {
                    case 0x005c: c = (char)0xa4; break; 
                        // REVERSE SOLIDUS maps to IDEOGRAPHIC COMMA
                    case 0x007e: c = (char)0x2d; break; 
                        // TILDE maps to HYPHEN-MINUS
                    default: c = (char)codePoint; break;
                }
            }
            // Katakana subset from JIS X 0201
            else if (codePoint >= 0xff61 
                        && codePoint <= 0xff9f) {
                c = (char)(codePoint - 0xfec0);
            }
            // Special characters
            else {
                switch (codePoint) {
                    case 0x00a5: c = (char)0x5c; break; 
                        // YEN SIGN
                    case 0x2192: c = (char)0x7e; break; 
                        // RIGHTWARDS ARROW
                    case 0x2190: c = (char)0x7f; break; 
                        // LEFTWARDS ARROW
                    case 0x00b0: c = (char)0xdf; break;
                        // DEGREE SIGN maps 
                        // to KATAKANA SEMI-VOICED SOUND MARK
                    case 0x03b1: c = (char)0xe0; break; 
                        // GREEK SMALL LETTER ALPHA
                    case 0x00c4: c = (char)0xe1; break; 
                        // LATIN CAPITAL LETTER A WITH DIAERESIS
                    case 0x00e4: c = (char)0xe1; break; 
                        // LATIN SMALL LETTER A WITH DIAERESIS
                    case 0x00df: c = (char)0xe2; break; 
                        // LATIN SMALL LETTER SHARP S
                    case 0x03b5: c = (char)0xe3; break;
                        // GREEK SMALL LETTER EPSILON
                    case 0x00b5: c = (char)0xe4; break; 
                        // MICRO SIGN
                    case 0x03bc: c = (char)0xe4; break; 
                        // GREEK SMALL LETTER MU
                    case 0x03c2: c = (char)0xe5; break; 
                        // GREEK SMALL LETTER FINAL SIGMA
                    case 0x03c1: c = (char)0xe6; break; 
                        // GREEK SMALL LETTER RHO
                    case 0x221a: c = (char)0xe8; break; 
                        // SQUARE ROOT
                    case 0x00b9: c = (char)0xe9; break; 
                        // SUPERSCRIPT ONE maps to SUPERSCRIPT (minus) ONE
                    case 0x00a4: c = (char)0xeb; break; 
                        // CURRENCY SIGN
                    case 0x00a2: c = (char)0xec; break; 
                        // CENT SIGN
                    case 0x2c60: c = (char)0xed; break; 
                        // LATIN CAPITAL LETTER L WITH DOUBLE BAR
                    case 0x00f1: c = (char)0xee; break; 
                        // LATIN SMALL LETTER N WITH TILDE
                    case 0x00d6: c = (char)0xef; break; 
                        // LATIN CAPITAL LETTER O WITH DIAERESIS
                    case 0x00f6: c = (char)0xef; break; 
                        // LATIN SMALL LETTER O WITH DIAERESIS
                    case 0x03f4: c = (char)0xf2; break; 
                        // GREEK CAPITAL THETA SYMBOL
                    case 0x221e: c = (char)0xf3; break; 
                        // INFINITY
                    case 0x03a9: c = (char)0xf4; break; 
                        // GREEK CAPITAL LETTER OMEGA
                    case 0x00dc: c = (char)0xf5; break; 
                        // LATIN CAPITAL LETTER U WITH DIAERESIS
                    case 0x00fc: c = (char)0xf5; break; 
                        // LATIN SMALL LETTER U WITH DIAERESIS
                    case 0x03a3: c = (char)0xf6; break; 
                        // GREEK CAPITAL LETTER SIGMA
                    case 0x03c0: c = (char)0xf7; break; 
                        // GREEK SMALL LETTER PI
                    case 0x0304: c = (char)0xf8; break; 
                        // COMBINING MACRON
                    case 0x00f7: c = (char)0xfd; break; 
                        // DIVISION SIGN

                    default:
                    case 0x25a0: c = (char)0xff; break; // BLACK SQUARE
                }
            }

            // Special handling for 'x' followed by COMBINING MACRON
            if (c == (char)0xf8) {
                if (!ks0066u.endsWith("x")) {
                    c = (char)0xff; // BLACK SQUARE
                }
                if (ks0066u.length() > 0) {
                    ks0066u = ks0066u.substring(0, ks0066u.length() - 1);
                }
            }
            ks0066u += c;
        }
        return ks0066u;
    }
}

Der Einsatz

Die Inbetriebnahme erfolgt in der main-Methode der Klasse WeatherStation (Listing 4). Es werden alle Sensoren in einem separaten Thread gestartet. Damit das Programm nicht vorzeitig terminiert, wird auf der Kommandozeile solange gewartet, bis der Benutzer ein „Q“ eingibt, gefolgt von ENTER.

public class WeatherStation {

    private static int callbackPeriod = 10000;

    public static void main(String args[]) throws Exception {
        new Thread(
            new Temperature("dXj", callbackPeriod)).start();
        new Thread(new Barometer("jY4", callbackPeriod)).start();
        new Thread(new Light("jy2", callbackPeriod)).start();

        final  BufferedReader in 
            = new BufferedReader(new InputStreamReader(System.in));

        final Thread t = new Thread(() -> {
            System.out
                .println("press Q THEN ENTER to terminate");
            int quit=0;
            while(true){
                try {
                    Thread.sleep(1000);
                    String msg = null;
                    while(true){
                        try{
                            msg=in.readLine();
                        }catch(Exception e){}
                        if(msg != null &&
                            msg.equals("Q")) { quit = 1; }
                        if(quit==1) break;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                break;
            }

        });
        t.start();
    }
}

Fazit

In dieser Version ist die Wetterstation noch per USB an den Rechner angeschlossen. In einem der nächsten Artikel werde ich zeigen, wie die Wetterstation von dem PC entkoppelt werden kann. Damit ist es dann möglich, die Wetterstation z. B. draußen an einem trockenen Platz mit Stromversorgung zu betreiben, die Auswertung dennoch komfortabel am Rechner in JavaFX zu erhalten. Bei Ideen, Anregungen oder Fragen: bitte einfach per Twitter melden: @SvenRuppert

Stay tuned. Happy coding!

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: