IoT with Java 8 and TinkerForge – 005 - codecentric AG Blog

:

Is it winter or not rather fall right now ? We do not know for sure right now . Weather is always an important topic for conversation. So we are going to be a professional weatherman – with Java, JavaFX and Tinkerforge.

To be able to make a statement about the weather we do not need a poor frog that is confined in a glass and climbs up and down a ladder every once in a while. We can also build a frog-friendly weather station.

The components
==============
For our weather station we need the following ingredients today: a temperature-, a light- and a barometer sensor. This way we are able to measure the air pressure, the barometric height and the luminosity (in Lux).
The temperature-(Link) and barometer sensor(Link) was already part of the previous articles. New for us today is the light sensor. Additionally we are using an 20×4-LCD-display.

The Light Sensor
========================
The light sensor (Ambient Light Sensor) is able to measure the brightness of the environment and provides the value in Lux. The resolution (0 Lux-900 Lux) is 0,1 which equals 12 bit. With approximately 2 g the light sensor is one of the lightest and uses only 1 mAt.

The Weatherstation
==================
As always (see previous articles) all elements are plugged into the master and shortly after they appear on the display of the BrickViewer. Note: Do not forget to update your sensor software if a newer version is available.
All sensors can be used with the Java-Interface which provides the usual ActionListener concept. Additionally every sensor can be requested directly. But keep in mind that this method leads to an unnecessary high bandwidth. The sensorsin this example are programmed without JavaFX because this time no visualization with LineChart is shown. As an example in listing 1: the barometer. Instead of the JavaFX-Elements this time we write directly into the 20×4-LCD display. So far nothing special.

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();
        }
    }
}

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(); } } }

The 20×4-LDC-Bricklet
=========================

The LCD brick let contains 20×4 dot matrix inclusively a blue background lightning. Additionally there are four buttons at the lower edge of the board. The API enables us to directly write individual characters or whole rows up to a length of 20 characters. The background lightning can be switched on and off and also the state of the buttons can be read. With this LCD bricklet the data of the connected sensors are displayed. Every sensor value is assigned an own row. In our example the following rows are assigned as follows:

  • Temperature in degree Celsius
  • Air pressure in Millibar
  • Barometric height in meters
  • Luminosity in Lux

The advantage with the implementation of the 20×4-LCD display is that it is Thread-safe (italic). The brick let can be provided with data competitively by different threads without having to expect problems. The dispribution of the rows simplifies the programming a lot since only the particular sensor is responsible for the clear display of its own row. Since here for reasons of simplification per sensor unit only one instance of the LCD brick let is generated also the initialization is scrolled through multiple times. For our example this is sufficient for now.

In order to show the requested characters on the LCD-bricklet they first have to be converted in a bit pattern. For this purpose the method String utf16ToKS0066U(String utf16) (also contained in the exemplary source code) can be extracted from the TinkerForge documentation. After the coding the text of max. 20 characters per row can be displayed with the method writeLine() (Listing 2)

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(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(); } }

Listing 3 shows the complete implementation of the class LCD20x4 (italic).

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;
    }
}

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; } }

The usage
——————————

The implementing happens in the main-method of the class WeatherStation (Listing 4). All sensors are started in a separate thread. In order for the program to not be terminated ahead of time we wait on the command line as long as the user types in an „O“ followed by 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();
    }
}

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(); } }

Summary
————————

In this version the weather station is still connected to the computer via USB. In one of the next articles I am going to show how the weather station can be decoupled from the PC. It then will be possible to run the weather station e.g. outside in a dry place with power supply and still get the evaluation comfortably at the computer in JavaFX. For ideas, suggestions or questions please just contact me on Twitter: @SvenRuppert

Stay tuned. Happy coding!

The source code to this article can be found under
https://bitbucket.org/rapidpm/jaxenter.de-0012-iot-tinkerforge .
For people who are interested in more complex examples I recommend the following
https://bitbucket.org/rapidpm/modules .