D1 Mini Datenlogger – Teil 5: Datenbank und grafische Aufbereitung mit Grafana

Features: RTC, WLAN, SD-Karte, Temperatur-, Luftdruck- und Luftfeuchtigkeitssensor und OLED-Display, Solarzelle

In diesem Beitrag wollen wir Grafana nutzen um die Sensordaten von unserem D1 Mini Datenlogger Modul grafisch darzustellen und speichern sie „ausfallsicher“ in einer Datenbank.

In dieser Beitragsserie wollen wir einen Datenlogger bauen der Temperatur, Luftfeuchtigkeit und Luftdruck in eine .csv-Datei auf eine SD-Karte schreibt und nebenbei auch noch mobil von einer Solarzelle versorgt wird. In Teil 1 wird der Sensor und das LCD Display eingebunden. Anschließend folgt in Teil 2 die Erweiterung um eine Real-Time-Clock für präzise Zeitstempel und die SD-Karte für langfristiges Speichern der Messwerte. In Teil 3 machen wir die Anwendung mittels Solarzelle mobil und optimieren den Stromverbrauch. Dann folgt in Teil 4 noch das zusätzliche Senden der Daten an einen Datenserver und abschließend stellen wir diese Daten in Teil 5 noch grafisch dar.

Bauteile

Alle Bauteile wurden bereits in den vorherigen Teilen aufgelistet.

Server einrichten

Zur Erinnerung: Mit Abschluss von Teil 4 waren wir in der Lage Daten des D1 Mini mit dem Rasperry Pi entgegen zu nehmen. Nun sollen die Daten langfristig, also durchaus für mehrere Jahre zugreifbar gemacht werden. Dafür eignet sich eine Datenbank. In diesem Fall setzen wir dafür die opensource Datenbank mysql (=mariadb) ein. Wir könnten aber auch eine auf Timeseries (also Messwerte über eine Zeitachse aufgetragen) optimierte Datenbank wie InfluxDB setzen. Hierzu folgt in Zukunft noch mal ein separater Beitrag. Grundsätzlich arbeitet Grafana mit einer Vielzahl von Datenanbindungen. Die Datenbank lassen wir lokal auf dem Raspberry Pi laufen.

Installation mysql

Wir installieren die client- und serverseitigen Datenbankfunktionen mit dem Paketmanager:

sudo apt install mariadb-client-10.0 mariadb-server-10.0

Root-Passwort setzen mit

sudo mysql -uroot -p

Neuen Benutzer für node.js anlegen (Passwort anpassen! 😉 )

CREATE USER 'node'@'localhost' IDENTIFIED BY '12345678';

Datenbank für node.js anlegen

CREATE DATABASE nodedb;
USE nodedb;

Dann dem Benutzer node alle Rechte an der Datenbank nodedb geben und die Berechtigungen aktualisieren

GRANT ALL PRIVILEGES ON nodedb.* TO 'node'@'localhost';
FLUSH PRIVILEGES;

OPTIONAL: Testtabelle anlegen, Werte einfügen und Abfragen

create table bme280(id int auto_increment not null primary key, timestamp int, temp double, rf double, press double);
insert into bme280(timestamp, temp, rf, press) values (1611165336, 21.1, 73.5, 1004);
select * from bme280;

Das Ergebnis der Testabfrage sieht dann so aus:

+----+------------+------+------+-------+
| id | timestamp  | temp | rf   | press |
+----+------------+------+------+-------+
|  1 | 1611165336 | 21.1 | 73.5 |  1004 |
+----+------------+------+------+-------+
1 row in set (0.01 sec)

Wichtige SQL-Befehle sind z.B.

SHOW DATABASES;
USE <database>;
SHOW TABLES;
SELECT * FROM <table>;
UPDATE, INSERT, DELETE, 

Zugriff mittels Programm node.js ermöglichen

ALTER USER 'node'@'localhost' IDENTIFIED WITH mysql_native_password BY '12345678';
FLUSH PRIVILEGES;

Auf einem anderen pi (mariadb Version 10.0.28 und raspbian 11 bullseye) wurde hier ein Syntaxfehler angezeigt. Den User für die node.js-Applikation habe ich dann wie folgt angelegt: CREATE USER 'pinode'@'localhost' IDENTIFIED VIA mysql_native_password USING '*54958E764CE10E50764C2EEC
BB71D01F08549980';

Datenauswertung mit Grafana

Nun installieren wir noch Grafana lokal auf dem Raspberry Pi. Einfach der Anleitung aus der offiziellen Dokumentation folgen. Hier sind die Schritte kurz und knackig zusammengefasst:

wget -q -O - https://packages.grafana.com/gpg.key | sudo apt-key add -
echo "deb https://packages.grafana.com/oss/deb stable main" | sudo tee -a /etc/apt/sources.list.d/grafana.list
sudo apt-get update
sudo apt-get install -y grafana

Wenn man will dass Grafana beim Hochfahren des Raspberry’s mit starten soll, dann kann man Grafana als Dienst aktivieren

sudo /bin/systemctl daemon-reload
sudo /bin/systemctl enable grafana-server

Ansonsten einfach manuell starten mit

sudo /bin/systemctl start grafana-server

Dann kann man schon die Oberfläche über den Webbrowser an der IP-Adresse des Raspberry’s mit dem Port 3000 erreichen.

Der Default-Benutzername lautet admin und das Default-Passwort ebenfalls admin. Nach dem ersten Login wird man eingeladen das Passwort zu ändern.

Falls man das Passwort mal vergessen hat kann man es mit einem Kommandozeilenprogramm (unter Windows als Administrator ausgeführt) wieder zurücksetzen:

C:\Program Files\GrafanaLabs\grafana\bin>grafana-cli admin reset-admin-password mynewpassword
...
Admin password changed successfully

Ist man eingeloggt muss zunächst eine Datenquelle angelegt werden. Dazu auf „Add your first data source“ klicken und nach MySQL suchen und dort die oben angelegten Parameter für die Verbindung eingeben.

Sobald die Verbindung gespeichert und erfolgreich getestet wurde kann man das Dashboard, also die Ansicht für die Aufbereitung der Daten, anlegen. Wenn man ein neues Dashboard anlegt (wir nennen unseres BME280) kann man nach Klick auf „Add Visualization“ die Sicht auf die Daten kreiren.

Als Datenquelle wird automatisch die soeben angelegte Verbindung zu MySQL ausgewählt. Wir wechseln von der Builder in die etwas flexiblere Code Ansicht und geben dort unsere erste Query ein in der wir einerseits den Namen der Variable anpassen können und andererseits die Formatierung für den Zeitstempel im Unix-Format:

SELECT timestamp AS time, 
temp AS "Temperatur",
rf AS "Luftfeuchtigkeit",
press AS "Luftdruck" 
FROM nodedb.bme280 
WHERE
$__unixEpochFilter(timestamp)

Mit „Run query“ kann die Abfrage getestet werden. Dabei beachten dass das Zeitinterval passend eingestellt ist. Wenn „No data“ angezeigt wird dann liegen in dem gewählten Zeitintervall keine Daten in der Datenbank vor. Es gibt einen Haufen verschiedener Einstellungen mit denen die Darstellung, Skalierung usw. angepasst werden können. Ich möchte hier kurz zeigen wie man mehrere Y-Achsen ergänzt und noch Einheiten verwenden kann. Dies geschieht mit den „field overrides„. Diese ergänzen wir 3 mal und stellen über „Standard options -> unit“ die Einheit und über „Axis -> Placement“ die Ausrichtung der Y-Achse ein.

Mit Klick auf „Apply“ wird die Bearbeitung dieses Visualisierungselements abgeschlossen und zurück ins Dashboard gewechselt. Ein Speichern mit „Save“ lohnt sich natürlich auch zwischendurch wenn man mit dem Zwischenergebnis halbwegs zufrieden ist 😉 Da aufgrund der Skalierung die drei Kurven nun überlagert sind kann man mit Klick auf den Namen in der Legende einzelne Kurven hervorheben bzw. die anderen ausblenden. Das macht allerdings einen manuellen Eingriff erforderlich. Wir erstellen einfach noch drei mal Visualisierungselemente vom Typ „TimeSeries“ und stellen in jedem einfach nur eine einzelne Kurve dar. In der Gestaltung sind der Phantasie dann keine Grenzen mehr gesetzt 🙂 Das komplette Dashboard sieht dann so aus:

Nun beschreiben wir noch wie wir eigentlich die angezeigten Daten erzeugt haben.

Code

Die Daten in Grafana müssen ja auch irgendwo herkommen. Der D1 liest die Werte bei dem BME280 aus. Anschließend schickt er sie per UDP weiter an den Raspberry Pi. Darauf teilt eine kleine node.js Applikation die per UDP empfangenen Datenpakete in Spalten auf und trägt sie entsprechend in die lokale Datenbank ein.

D1 mini

Den String der an den Server geschickt wird gestalten wir etwas um damit er leichter zerlegt werden kann.

void datenPerWifiSchicken() 
{
  char buffer[120];

  sprintf(buffer, "%s;Temperatur=%4.1f;Feuchte=%.0f;Druck=%.0f;", 
      Stationsname.c_str(),
      temp1, hum1, press1);
      
  Udp.beginPacket(unicastIP, PORT);
  Udp.printf(buffer);
  Udp.endPacket();
}
Vollständig anzeigen

Die Ergebniszeile die abgeschickt wird sieht dann beispielsweise so aus:

Messstation 1;Temperatur=26.3;Feuchte=82;Druck=1008;

Raspberry Pi

Das node.js Skript aus Teil 4 welches bereits Daten per UDP empfangen konnte wird noch etwas erweitert. Es muss den empfangenen String noch „zerpflücken“ und an die Datenbank schicken. Es wurde so vorbereitet, dass man die Beschriftung der Werte auch weglassen könnte, z.B.:

Messstation 1;26.3;82;1008;

Die index.js Datei sieht dann so aus:

'use strict';
const mysql = require('mysql');
const util = require('util');
const udp = require('dgram');

const buffer = require('buffer');
const server = udp.createSocket('udp4');

const UDP_PORT = 8266;

const MYSQL_HOST  = 'localhost';
const MYSQL_USER  = 'node';
const MYSQL_PASS  = '12345678';
const MYSQL_DB    = 'nodedb';
const MYSQL_TABLE = 'bme280';

var con = mysql.createConnection({
  host: MYSQL_HOST,
  user: MYSQL_USER,
  password: MYSQL_PASS,
  database: MYSQL_DB,
  port: '/var/run/mysqld/mysqld.sock'
});

// dieser event wird ausgelöst wenn eine 'message' eintrifft
server.on('message', function (msg, info) {
  //console.log(`RX from ${info.address}:${info.port} ${msg.toString()}`);

  // Beispiel Nachricht: 'Messstation 1;Temperatur=26.3;Feuchte=82;Druck=1008;'
  // Beispiel Nachricht: 'Messstation 1;26.3;82;1008;' ohne ueberschrift wuerde auch klappen
  var cols = msg.toString().split(';');
  var temp  = parseFloat(cols[1].split('=')[1] || cols[1] || 0.0);
  var rf    = parseFloat(cols[2].split('=')[1] || cols[2] || 0.0);
  var press = parseFloat(cols[3].split('=')[1] || cols[3] || 0.0);
  
  //console.log(`temp = ${temp}°C; rf = ${rf}; press = ${press}`);
  var myquery = `INSERT INTO ${MYSQL_TABLE} (timestamp, temp, rf, press) VALUES (${parseInt(Date.now() / 1000)}, ${temp}, ${rf}, ${press})`;
  console.log(`query=${myquery}`);
  con.query(myquery, (err, res) => {
    if (err) {
      return console.error(`query error: ${err.toString()}`);
    } else {
      console.log(util.inspect(res));
    }
  });
});

//emits when socket is ready and listening for datagram msgs
server.on('listening',function(){
  var address = server.address();
  var port = address.port;
  var family = address.family;
  var ipaddr = address.address;
  console.log('Server is listening at port' + port);
  console.log('Server ip :' + ipaddr);
  console.log('Server is IP4/IP6 : ' + family);
});

server.bind(UDP_PORT); // UDP server starten
Vollständig anzeigen

Wird das Skript mit

node index.js

gestartet, sieht das Ergebnis nach Eintreffen einiger Daten vom D1 Mini in der Kommandozeile so aus:

Fazit

Damit wäre diese Beitragsserie fertig. Wir haben einen D1 Mini Datenlogger gebaut und können uns auch in ein paar Jahren noch die Messwerte grafisch in einem schönen Grafana-Dashboard anschauen. Vielen Dank für die Aufmerksamkeit 🙂


Beitrag veröffentlicht

in

von

Schlagwörter:

Kommentare

Schreibe einen Kommentar

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