Szenarien für den Einsatz von IoT und BigData: Die Wetterstation (Teil 2)

von Florian Mutter

In diesem Teil der Blog-Serie "Szenarien für den Einsatz von IoT und BigData" betrachten wir die Wetterstation, die für unser Projekt Messwerte aus dem "Exensio-Garten" liefert.

Die Außen-Wetterstation

 der Wetterstation selber handelt es sich um das Modell KS300-4 von ELV.

Diese kann folgende Werte messen:

  • Temperatur
  • Luftfeuchtigkeit
  • Windgeschwindigkeit
  • Regenmenge
Die Wetterstation muss nur zusammengesteckt und aufgestellt werden. Sobald die Batterien eingesetzt sind, fängt die Station an zu messen und sendet für die ersten 5 Minuten alle 3 Sekunden und danach ca. alle 3 Minuten Messwerte.

Der Datenempfänger

Als Empfänger dient der USB-WDE1. Da wir den Empfänger als Bausatz bestellt hatten, war erst einmal etwas handwerkliches Geschick gefragt, um den Empfänger zusammen zu löten.

Mit dem fertig gestellten Empfänger kann mit der Datenerfassung begonnen werden. Der Treiber cp210x für die USB-zu-UART-Brücke, die im USB-WDE1 verbaut ist, ist im Linux-Kernel bereits enthalten. Nach dem Verbinden an einen Raspberry Pi kann direkt mit lsusb geprüft werden, ob der Empfänger richtig erkannt wurde. Falls ja, sollte folgende Zeile ausgegeben werden:

Bus 001 Device 005: ID 10c4:ea60 Cygnal Integrated Products, Inc. CP210x UART Bridge / myAVR mySmartUSB light

Um die Daten auszulesen, kommt das Programm socat zum Einsatz. Es lässt sich einfach über die Raspbia-Packetverwaltung installieren:

sudo apt-get install socat

Nun muss man noch wissen, wie das Device heißt, das der Kernel für den Empfänger angelegt hat. Das schauen wir mit dmesg nach:

$ dmesg
...
[  153.413376] usb 1-1.2: new full-speed USB device number 5 using dwc_otg
[  153.519931] usb 1-1.2: New USB device found, idVendor=10c4, idProduct=ea60
[  153.519970] usb 1-1.2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[  153.519991] usb 1-1.2: Product: ELV USB-WDE1 Wetterdatenempf\xffffffc3\xffffffa4nger
[  153.520010] usb 1-1.2: Manufacturer: Silicon Labs
[  153.520026] usb 1-1.2: SerialNumber: PCQGPSWVHEUIHAWM
[  154.339074] usbcore: registered new interface driver usbserial
[  154.339273] usbcore: registered new interface driver usbserial_generic
[  154.339377] usbserial: USB Serial support registered for generic
[  154.346571] usbcore: registered new interface driver cp210x
[  154.346716] usbserial: USB Serial support registered for cp210x
[  154.346859] cp210x 1-1.2:1.0: cp210x converter detected
[  154.453542] usb 1-1.2: reset full-speed USB device number 5 using dwc_otg
[  154.557667] usb 1-1.2: cp210x converter now attached to ttyUSB0

Der USB-WDE1 ist in diesem Fall unter /dev/ttyUSB0 erreichbar. Mit socat wird auf die Daten gewartet, die nach /dev/ttyUSB0 ausgegeben werden. Der Empfänger speichert keine Werte zwischen sondern gibt immer dann etwas über /dev/ttyUSB0 aus, wenn neue Daten vorliegen. Es kann also bis zu 3 Minuten dauern, bis socat eine Ausgabe erzeugt:

$ sudo socat /dev/ttyUSB0,b9600 STDOUT
$1;1;;;;;;;;;;;;;;;;;;22,0;52;0,0;15;0;0
$1;1;;;;;;;;;;;;;;;;;;23,2;51;0,0;15;0;0

Mit einem kleinen Skript werden diese Daten zusammen mit der Uhrzeit in eine Datei geschrieben. Zusätzlich werden sie als JSON zur Java-Anwendung geschickt, die ebenfalls auf dem Raspberry Pi läuft und andere Sensoren abfrägt. Das Skript sieht wie folgt aus:

#!/bin/bash
# Receive remote weather data from USB-WDE1 and store it into database

# Loop forever to read data from USB-WDE1
while read line
do
    if [[ "${line%%;*}" == '$1' ]] ; then
        date=`date +%s%3N`
        data="{\"date\":$date,\"data\":\"$line\"}"
        echo $data >> /opt/weatherlogger/weather.log
        curl -X POST -H "Content-Type: application/json" -d $data http://localhost:8080/weatherData
    fi
done < <(socat -s -d -d -lf /var/log/weatherlogger.log /dev/ttyUSB0,b9600 STDOUT)

Die Uhrzeit wird als Unix Timestamp inklusive Millisekunden gespeichert. Die etwas ungewöhnliche Form der while-Schleife bewirkt, dass keine Subshell zum Lesen der einzelnen Zeilen gestartet wird. So kann das Skript über ein Init-Skript beim Booten gestartet und vor allem auch wieder beendet werden. Die Daten, die das Skript erzeugt sehen wie folgt aus:

{"date":1401289714912,"data":"$1;1;;;;;;;;;;;;;;;;;;21,3;37;0,5;0;0;0"}
{"date":1401289726412,"data":"$1;1;;;;;;;;;;;;;;;;;;21,2;37;0,0;0;0;0"}

Zwischenspeicherung der Daten mit Spring Boot

Nun zum Java-Teil, der die Daten der Wetterstation als JSON entgegennimmt, aufbereitet und zwischenspeichert.
Das Parsen der zwei Parameter date und data übernimmt das Spring Web MVC Framework für uns. Folgender Code-Ausschnitt zeigt wie einfach das gemacht werden kann (Achtung, das ist nicht aller Code, der benötigt wird):

public class WeatherData {
    private Date date;
    private String data;
}

@RequestMapping("/weatherData")
public class WeatherDataController {
    @RequestMapping(method = RequestMethod.POST)
    public ResponseEntity<string> push(@RequestBody final WeatherData weatherData) {
        measureService.storeWeatherData(weatherData);
        return new ResponseEntity<string>(HttpStatus.OK);
    }
}</string></string>

Nun fehlt noch das Parsen des data-Strings. Da die Positionen der Messwerte fest sind, können die Werte für Temperatur, Luftfeuchtigkeit und Windgeschwindigkeit einfach ausgelesen werden:

String[] d = weatherData.getData().split(";");
Double temperatur = parseDecimal(d[19]);

Beim Regenwert muss etwas mehr Aufwand betrieben werden. Die Wetterstation hat eine Wippe, die immer dann auslöst, wenn sich eine bestimmte Menge Regen auf ihr gesammelt hat. Es werden die Wippenschläge von 0 bis 4095 gezählt und dann beginnt der Zähler wieder bei 0. Im Java-Programm muss dieser "Überlauf" behandelt werden. Außerdem werden die Wippenschläge in eine sinnvolle Einheit (mm/Minute bzw. L/m²/Minute) umgerechnet. Falls die Zeit seit der letzten Messung zu groß ist oder die gemessene Regenmenge unrealistisch ist, wird die Regenmenge nicht gespeichert. Dies kann z.B. passieren wenn die Batterien in der Wetterstation gewechselt werden. Dann beginnt der Wippenschlägezähler wieder bei 0. Folgender Code-Abschnitt zeigt zeigt die Lösung:

if (previousRainCount > rainCount) {
    // rainCount overflow detected
    previousRainCount -= 4096;
}

double minSinceLastMeasure;
if (measurements.size() > 0) {
    Date lastMeasureTime = measurements.get(0).getCreated();
    // time since last measurement in minutes
    minSinceLastMeasure = (weatherData.getDate().getTime() - lastMeasureTime.getTime())/(100.0*60.0);
} else {
    // No previous measurement found.
    minutesSinceLastMeasure = MAX_TIME_BETWEEN_MEASUREMENTS + 1;
}

if (minSinceLastMeasure <= MAX_TIME_BETWEEN_MEASUREMENTS) {
    // rainfall in L/m^2 (also rain height in mm) per minute
    Double rainfall = ((rainCount - previousRainCount) * RAINHEIGHT_PER_COUNT) / minSinceLastMeasure;

    if (rainfall <= MAX_RAIN_IN_1MIN) {
        // This seems to be a valid quantity of rainfall so we save it
        s.setRainfall(rainfall);
    }
}

previousRainCount = rainCount;

So sind alle Werte der Wetterstation in der Applikation verfügbar und können für die weitere Verarbeitung bereitgestellt werden. Was die Java-Applikation sonst noch kann und wie die Daten benutzt werden, wird in den nächsten Teilen dieser Blog-Serie erläutert.

Die Serie gliedert sich folgendermaßen: 

Kategorien: Big DataElasticsearchInternet of ThingsSpring Boot

Zurück