Luft und Wetter Daten auf einer Node-RED Map

Im Umfeld eines kleinen Forschungsprojekts zu Fiware (fiware.org) ist eine Node-RED Map entstanden die eine lokale Visualisierung der Luftdaten.info Daten entstanden.

Es werden die Daten eines Bereichs über die API von Luftdaten.info gelesen und mittels dem Node-RED Node Worldmap auf einer Map angezeigt, ge nach Sensor Status werden die Pins eingefärbt und im Message Objekt werden dort die jeweiligen Daten in einer Info Box gezeigt.

Übersicht des Map Flow in Node-RED

Zentraler Punkt ist die API Abfrage „Filter Area Südschleswig“ in der die Daten via API geholt werden.

Node für die Abfrage der Luftdaten API

In diesem Node wird die URL der Abfrage eingetragen und als UTF 8 Codierte Rückgabe in den Flow getragen.

Die URL der API Abfrage für den Bereich Südschleswig

Aus der Rückgabe wird mittels eines JSON Node ein JSON Objekt erstellt.

Ein JSON Node wandelt die empfangenen Daten in ein JSON Objekt um.
Als Aktion „Konvertieren zwischen JSON-Zeichenfolge und Objekt“ auswählen

Das Ergebnis der Abfrage und Umwandlung ist ein Objekt welches alle im ausgewählten Bereich eingetragenen Sensoren liefert.

Ergebnis der Abfrage

Diese beinhaltet nun die ungefilterte und nicht sortierten Daten aller Sensoren. Ein Feinstaubsensor nach Stuttgarter Modell hat neben seinem Feinstaubsensor immer auch einen Wetter Sensore der als eigenständiges Objekt angesehen wird. Die Sensoren haben alle eine Typ ID nach der die unterschiedlichen Sensoren mit ihren unterschiedlichen Rückgabewerten identifiziert werden können. Dadurch können die Sensoren je nach Typ unterschiedlich behandelt werden um die Messwerte dann später auch interpretieren zu können.

Nach dem oben beschriebenen Schritt teilt sich die Verarbeitung der Ergebnisse in 3 Verarbeitungslinien auf:

  • CSV für die Fiware Aufbereitung
  • Visualisierung als Wetter Map
  • Visualisierung als Feinstaub Map
Aufteilen der Verarbeitungslinien

Die CSV Verarbeitung lasse ich an dieser Stelle erst einmal links liegen, hier werden nur die einzelnen Sensoren mit ihren Geo Positionen und den Sensor IDs in eine CSV Datei auf den Server geschrieben um daraus später Fiware Objekte zu erstellen.

Interessanter sind die beiden MAP Linien. Hier werden die Daten in einer JS Funktion so vorbereitet das sie vom Worlsmap Node angenommen und als Karte angezeigt werden können.

Verarbeitung Wetter

Die Wetter Verarbeitung behandelt die Sensor Typen:

  • Typ ID 8 = BME180 liefert Temperatur und Luftfeuchte
  • Typ ID 9 = DHT22 liefert Temperatur und Luftfeuchte
  • Typ ID 17 = BME280 liefert Temperatur, Luftfeuchte und Luftdruck

Die Aufbereitung der Sensordaten für die Map

var NumOfSens = Object.keys(msg.payload).length; 
var sensArr=[];
var mapArr=[];
var numfss = 0;
var v = 0;

// Sensoren in ein Array einlesen, den SDS011 (Typ 14) ignorieren
for (var i = 0; i< (NumOfSens); i++){
    if (msg.payload[i].sensor.sensor_type.id !== 14) {
    sensArr.push(Object.keys(msg.payload)[i]);
    numfss++;
    } else {}
}

// Sensoren Pins nach Typ eine Farbe zuweisen
for (i = 0; i< numfss; i++){
if (typeof(msg.payload[sensArr[i]].location)!== "undefined"){
    message = {
    lat: msg.payload[sensArr[i]].location.latitude,
    lon: msg.payload[sensArr[i]].location.longitude,
    name: msg.payload[sensArr[i]].sensor.id,
    };
    if (msg.payload[sensArr[i]].sensor.sensor_type.id === "undefined"){
        message.iconColor="green";
    }
    else if (msg.payload[sensArr[i]].sensor.sensor_type.id==14){
        message.iconColor="orange";
    }
    else if (msg.payload[sensArr[i]].sensor.sensor_type.id==9){
        message.iconColor="blue";
    }
    else if (msg.payload[sensArr[i]].sensor.sensor_type.id==17){
        message.iconColor="red";
    }
    else if (msg.payload[sensArr[i]].sensor.sensor_type.id==8){
        message.iconColor="black";
    }
    else {
        message.iconColor="green";
    }
// Den Hersteller dem Info Objekt zuweisen
    message.Hersteller = msg.payload[sensArr[i]].sensor.sensor_type.manufacturer;

// Die Sensor ID als Name festlegen
    message.Name = msg.payload[sensArr[i]].sensor.sensor_type.name;

//Messwerte je Sensortyp
//DHT11 (Typ9)
    if (msg.payload[sensArr[i]].sensor.sensor_type.id==9){
       for (v = 0; v< msg.payload[sensArr[i]].sensordatavalues.length; v++){  
       if (msg.payload[sensArr[i]].sensordatavalues[v].value_type =="temperature"){
       message.Temperatur = msg.payload[sensArr[i]].sensordatavalues[v].value + " °C" ;
       } else {
       message.Humidity = msg.payload[sensArr[i]].sensordatavalues[v].value + " %";
       }
       }
    }

//BME280 (Typ17)
    if (msg.payload[sensArr[i]].sensor.sensor_type.id==17){
        for (v = 0; v< msg.payload[sensArr[i]].sensordatavalues.length; v++){ 
           if (msg.payload[sensArr[i]].sensordatavalues[v].value_type =="temperature"){
               message.Temperatur = msg.payload[sensArr[i]].sensordatavalues[v].value + " °C";
           }
           if (msg.payload[sensArr[i]].sensordatavalues[v].value_type =="humidity"){
               message.Humidity = msg.payload[sensArr[i]].sensordatavalues[v].value + " %";
           }
           if (msg.payload[sensArr[i]].sensordatavalues[v].value_type =="pressure"){
               message.Luftdruck = parseFloat((msg.payload[sensArr[i]].sensordatavalues[v].value) / 100).toFixed(2)  + " hPa" ;
           }
           if (msg.payload[sensArr[i]].sensordatavalues[v].value_type =="pressure_at_sealevel"){
               message.Luftdruck_NN = parseFloat((msg.payload[sensArr[i]].sensordatavalues[v].value) / 100).toFixed(2)  + " hPa";
           }
        }
    }
    
//BMP180 (Typ8)
    if (msg.payload[sensArr[i]].sensor.sensor_type.id==8){
        for (v = 0; v< msg.payload[sensArr[i]].sensordatavalues.length; v++){ 
           if (msg.payload[sensArr[i]].sensordatavalues[v].value_type =="temperature"){
               message.Temperatur = msg.payload[sensArr[i]].sensordatavalues[v].value + " °C";
           }
           if (msg.payload[sensArr[i]].sensordatavalues[v].value_type =="pressure"){
               message.Luftdruck = parseFloat((msg.payload[sensArr[i]].sensordatavalues[v].value) / 100).toFixed(2)  + " hPa" ;
           }
           if (msg.payload[sensArr[i]].sensordatavalues[v].value_type =="pressure_at_sealevel"){
               message.Luftdruck_NN = parseFloat((msg.payload[sensArr[i]].sensordatavalues[v].value) / 100).toFixed(2)  + " hPa";
           }
        }
    }

// Den Sensor PIN je nach Temperatur einfärben
    
    if (parseFloat(message.Temperatur) < -5){
       message.iconColor="blue";  
    } else if (parseFloat(message.Temperatur) < 5){
       message.iconColor="deepskyblue";  
    } else if (parseFloat(message.Temperatur) < 15){
       message.iconColor="aquamarine";  
    } else if (parseFloat(message.Temperatur) < 20){
       message.iconColor="lightgreen";  
    } else if (parseFloat(message.Temperatur) < 25){
       message.iconColor="greenyellow";  
    } else if (parseFloat(message.Temperatur) < 28){
       message.iconColor="yellow";  
    } else if (parseFloat(message.Temperatur) < 30){
       message.iconColor="orange";  
    } else if (parseFloat(message.Temperatur) < 33){
       message.iconColor="orangered";  
    } else if (parseFloat(message.Temperatur) < 36){
       message.iconColor="firebrick";  
    } else {
       message.iconColor="red"; 
    }

    //message.Owner = msg.payload[sensArr[i]].owner;

// Wann wurde der letzte Wert erfasst
    message.lastSeen = msg.payload[sensArr[i]].last_seen;

// Den Link zur Grafana Auswertung hinterlegen
    message.link = "<a href=" + "https://maps.luftdaten.info/grafana/d/000000004/single-sensor-view?orgId=1&var-node="+ message.name + ">Statistik</a>";
    //message.radius = 5000;
    mapArr.push(message);
   }
}

// Das erstellte MapArray dem Payload übergeben
msg.payload=mapArr;

// Rückgabe an den Flow 
return msg;

Hier werden die Sensoren gezählt und einem Array zugefügt, die SDS011 Feinstaubsensoren werden nicht ausgewählt. Danach werden die Messwerte je nach Sensor Typ abgefragt und ab Ende werden die Pins je nach Temperatur eingefärbt. Aus dem Sensor Namen, der die Sensor ID beinhaltet wird der Link zur Langzeitstatistik erzeugt und dann komplett in den Payload geschrieben. Nun sind die Daten so aufbereitet das sie in dem Worldmap Node zur Anzeige übergeben werden können.

Der Worldmap Node für die Wetter Map

Die Konfiguration des Worldmap Node beinhaltet die Auswahl des Map Zentrums, des Zoom Faktors, die hinterlegte Basiskarte so wie der Maplink um die Karte aufzurufen.

Einstellungen das Worlsmap Nodes für die Wetter Map

Der Web Path ist der Ort an dem die Karte später angezeigt werden soll.
Somit wird aus /wetterssl die Map URL https://node-red.iot-usergroup.de/wetterssl/

Damit dieser Node richtig funktioniert muss noch ein weiterer Node in den Flow eingebaut werden der die Abfrage triggert so das der Besucher auf der Map die aktuellen Daten erhält.

In diesem Trigger Node wird der Web Path eingetragen, sobald ein Nutzer die Karte öffnet wird hier ein Event ausgelöst.

Trigger Node für die Wetter Map Südschleswig

Diese wird an ein Funktions Node weiter gereicht welches ein „connected“ liefert mit dem dann die Abfrage der API ausgelöst wird.

if (msg.payload.action === "connected") 
return msg;

Mit der selben Logik wird auch die Feinstaub Linie gestartet deren einziger Unterschied das Mapziel ist. Es lautet https://node-red.iot-usergroup.de/fssssl/ und zeigt für den Bereich die Feinstaubsensoren Sensoer Typ ID 14 an.

Der Code im Funktions-Node für die Feinstaublinie ist der folgende:

var NumOfSens = Object.keys(msg.payload).length; 
var sensArr=[];
var mapArr=[];
var numfss = 0;
var v = 0;

// Sensoren des Typs 14 in das Array laden
for (var i = 0; i< (NumOfSens); i++){
    if (msg.payload[i].sensor.sensor_type.id==14) {
    sensArr.push(Object.keys(msg.payload)[i]);
    numfss++;
    } else {}
}

// Sensor Pins ja nach Typ einfärben
for (i = 0; i< numfss; i++){
if (typeof(msg.payload[sensArr[i]].location)!== "undefined"){
    message = {
    lat: msg.payload[sensArr[i]].location.latitude,
    lon: msg.payload[sensArr[i]].location.longitude,
    name: msg.payload[sensArr[i]].sensor.id,
    };
    if (msg.payload[sensArr[i]].sensor.sensor_type.id === "undefined"){
        message.iconColor="green";
    }
    else if (msg.payload[sensArr[i]].sensor.sensor_type.id==14){
        message.iconColor="orange";
    }
    else if (msg.payload[sensArr[i]].sensor.sensor_type.id==9){
        message.iconColor="blue";
    }
    else if (msg.payload[sensArr[i]].sensor.sensor_type.id==17){
        message.iconColor="red";
    }
    else if (msg.payload[sensArr[i]].sensor.sensor_type.id==8){
        message.iconColor="black";
    }
    else {
        message.iconColor="green";
    }
// Hersteller ermitteln und zuweisen
    message.Hersteller = msg.payload[sensArr[i]].sensor.sensor_type.manufacturer;

// Sensor ID als Name festlegen
    message.Name = msg.payload[sensArr[i]].sensor.sensor_type.name;    
    message.Description = "Messwert P1 = PM10 P2 = PM2.5";

// Messwerte abfragen und zuweisen
    for (v = 0; v< msg.payload[sensArr[i]].sensordatavalues.length; v++){
        if (msg.payload[sensArr[i]].sensordatavalues[v].value_type =="P1"){
            message.p1 = msg.payload[sensArr[i]].sensordatavalues[v].value;
        } else if (msg.payload[sensArr[i]].sensordatavalues[v].value_type =="P2"){
            message.p2 = msg.payload[sensArr[i]].sensordatavalues[v].value;
        }
    } 

//Letzer Zeit der Messung
    message.lastSeen = msg.payload[sensArr[i]].last_seen;

//Statistik Link erstellen
    message.link = "<a href=" + "https://maps.luftdaten.info/grafana/d/000000004/single-sensor-view?orgId=1&var-node="+ message.name + ">Statistik</a>";

// Einfärben der Map Pins ja nach Feinstaubwert
    if ((parseFloat(message.p1) < 10)){
       message.iconColor="green";  
    } else if ((parseFloat(message.p1) < 25)){
       message.iconColor="GreenYellow";  
    } else if ((parseFloat(message.p1) < 50)) {
       message.iconColor="yellow"; 
    } else if ((parseFloat(message.p1) < 75)) {
       message.iconColor="orange"; 
    } else if ((parseFloat(message.p1) < 100)) {
       message.iconColor="red"; 
    } else {
       message.iconColor="magenta"; 
    }
    
    mapArr.push(message);
   }
}

msg.payload=mapArr;
return msg;

Viel Spaß mit den Karten, übrigens gibt es diese Karten auch für den Bereich um Kiel:

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Ich akzeptiere

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.