Konzept – Umsetzung – Code

Konzept

Sensoren

  • BME280: Temperatur[°C], Druck[hPa], relative Feuchte [%]
  • LDR: Helligkeit, dimensionslos (0..100)

Kommunikation

Das ESP-Board ist mit dem lokalen WLAN verbunden und überträgt die Sensordaten in die Datenbank beim Web-Provider. Dazu wird über den Aufruf einer HTML-Seite ein php-Skript ausgeführt, das die Datenbank befüllt. Der Timestamp des Datensatzes stammt dabei vom DB-Server.

  • Aktuelle Daten: ein Datensatz je Minute, gemittelt über 10 Messwerte, max. 360 Datensätze
  • Wochendaten: ein Datensatz alle 10 Minuten, über aktuelle Daten je Minute gemittelt, max. 1008 Datensätze (10080min=168h=7d)
  • Monatsdaten: ein Datensatz je Stunde, über aktuelle Daten gemittelt, max 744 Datensätze (365,25d)
  • Jahresdaten: wie Monatsdaten, aber max. 8766 Datensätze

Visualisierung

Die Daten werden mit php aus der Datenbank ausgelesen und mit jpgraph visualisiert. Über Woody snippets werden die php-Skripte in WordPress eingebunden.

Verbindungen

Schaltung v1

Schaltung v2

Da die Schaltung so einfach ist, habe ich diesmal keine Platine geätzt, sondern direkt auf einer Lochrasterplatine aufgebaut.

Da lag noch ein durchsichtiges Geäuse in der Bastelkiste. Touch funktioniert auch durch den Deckel hindurch! Ist quick-and-dirty mit Tesa angeklebt. An der Seite und unten sind Löcher für die Belüftung gebohrt.

Programmierung

  • Arduino-IDE für ESP2866
  • Brackets für php
  • WordPress

Bauteile

  • ESP2866
  • BME280
  • LDR + Widerstand 1-10k, je nach LDR. Unkritisch, da keine physikalische Grösse gemessen werden soll.
  • OLED-Display 128 x 64
  • Touch-Board oder Taster (Display an/aus, mit 3min Display-TimeOut)
  • Platine oder „fliegende Verkabelung“
  • Gehäuse
  • USB-Kabel
  • USB-Netzteil

Display

OLED-Display mit 128 x 64 Pixel. Für die Ausgabe der Sensordaten wäre ein OLED-Display mit 128 x 32 Pixel ausreichend.

Skripte & Code:

Vielleicht sind die folgenden Skripte für den einen oder anderen hilfreich – Lesen und Verwendung auf eigene Gefahr! :o) Hinweise und Anmerkungen gerne an mich. Bei den mysql und php-Skripten bitte keinen Herzanfall bekommen.

Arduino

Wetterstation2.07.ino

/******************************************************
Wetterstation mit ESP2866, BME280, LDR, OLED-Display 128x64
Sensoren für Temperatur, Druck, relative Feuchtigkeit und Helligkeit (dimensionslos).
BM_E_280 hat zusätzlich einen Feuchtesensor, BM_P_280 nicht.

Die gesammelten Daten werden via http-GET an php-Skript auf dem Webserver geschickt. 
Das Skript legt die Daten in einer  mysql-DB in verschiedenen Tabellen ab.
Timestamp stammt vom Server.

Daten           Tabelle    max.  Datensätze
Aktuelle Daten  h6_1min        360   6h x 60min
Monatsdaten     monat_1h       744  31d x 24h
Wochendaten     woche_10min   1008   7d x 24h x 6
Jahresdaten     jahr_1h       8766  365,25d x 24h
5-Jahresdaten   jahre5_1h    43830  5 x 365,25d x 24h

Wemos-D1-Board D1:SCL(GPIO5), D2:SDA(GPIO4) oder Wire.begin(int sda, int scl)
D5: Touch-Taste für Display on/off, Timeout für Abschaltung
GND---LDR---R---3V3   U=3V3*LDR/(LDR+R)
       |      Board hat Spannungsteiler von A0 auf ADC von 3V3 auf 1V1 
       A0     R an LDR-Werte anpassen, unkritisch. R~Wurzel(LDRmin*LDRmax)

(ca. 950..1060hPa min/max in DL)
******************************************************/

#define Version "Wetterstation_2.07"

//===== Eintragen! =======================================================
char *ssid = "";      // SSID des lokalen WLAN
char *password = "";  // Passwort -"-
String DBKEY = "";    // Schlüssel zum Übertragen der Daten zum Server.
                      // Muss mit $dbkey in tab_insert.php übereinstimmen 
//========================================================================

#include <Wire.h>
#include "SparkFunBME280.h"
#include <ESP8266WiFi.h>
#include <WiFiClient.h>

//#include <ArduinoOTA.h>     // OTA-Handling, darf nicht auskommentiert werden, sonst wirft es Fehler
#include "SSD1306Ascii.h"   // Display
#include "SSD1306AsciiWire.h"

#define headerString "Uptime \tT[°C] \tP[hPa] \tRH[%] \tLDR" //Für serielle Ausgabe

WiFiClient client;

BME280 BME;

char   strBuf[20];
String s, hs;
double BME_p, BME_T, BME_RH, LDR;
double st, sp, sf, sl, sth, sph, sfh, slh; 
double mint, minp, minf, minl, maxt, maxp, maxf, maxl;
int hi, mi;
char TAB = 0x09;

//----- Display -----
#define DISPLAY_ADDRESS 0x3C    //0x3C or 0x3D
SSD1306AsciiWire oled;
const int TouchPin = 14;      //für Touch-Taster an D5
const int OLED_TimeOut = 3 * 60; //TimeOut für OLED-Display in Sekunden. 
int OLED_Timer = OLED_TimeOut;
volatile boolean Display_on = true;
//-------------------

//===== structs =============================================
struct SensorData {
  double t, p, f, l;
  String ts, ps, fs, ls;
};

SensorData sensors; //Globale Variable, um mitteln zu können

//===========================================================


void php_tab_insert (String tabname, int maxnum) {
  //aktuelle Werte an Tabelle tabname anhängen, falls mehr als maxnum Datensätze vorhanden,
  //werden die ersten Datensätze entfernt bis Länge stimmt.
  String server = "cuprum.de";  //Als Konstant ganz nach oben ziehen!
  String s;
  if (client.connect(server, 80)) {
    Serial.println("connecting...");
    // send the HTTP PUT request:
    // GET /wetter/tab_insert.php?tabelle=TABNAME&max=MAXNUM&key=xxxx&temperatur=23.29&druck=998.96&feuchte=33.22&licht=90.00
    String s = "GET /wetter/tab_insert.php?tabelle=" + tabname + "&max=";
    s += maxnum; 
    s += "key=";  //key
    s += DBKEY;
    //Daten aus globaler Variable sensors holen
    s += "&temperatur=";
    s += sensors.t;
    s += "&druck=";
    s += sensors.p;
    s += "&feuchte=";
    s += sensors.f;
    s += "&licht=";
    s += sensors.l;
    
    Serial.println("tab_insert");
    Serial.println(s);

    client.print(s);
    client.println(" HTTP/1.1");
    client.print("Host: ");
    client.println(server);
    client.println("Connection: close");
    client.println();

    while (client.available()) { //unnötig?
      char c = client.read();
      Serial.write(c);
      //s = client.read();
      //Serial.println (c);
    }
    client.stop();
  }
  else {
    // if you couldn't make a connection:
    Serial.println("Verbindung gescheitert.");
    Serial.println("Trennung.");
    client.stop();
  }
} //php_tab_insert

ICACHE_RAM_ATTR void handleTouchInterrupt(){ 
//Hatte vorher ohne ICACHE_RAM_ATTR funktioniert, jetzt Fehlermeldung
  Display_on = !Display_on;
  if (Display_on == true){
    Serial.println("Display EIN");
    OLED_Timer = millis();
    oled.clear();
    oled.println ("Display EIN");
  }
  else{oled.clear();
    }
}

void setminmax (double v, double &minv, double &maxv){
   if (v < minv){minv = v;}
   if (v > maxv){maxv = v;}
} //setminmax

//=== Setup =======================================================================
void setup() {

  Serial.begin(115200);
  Serial.print ("Version: "); Serial.println (Version);

  //--- OLED-Display ---
  Wire.begin ();
  Wire.setClock(100000);
  oled.begin(&Adafruit128x64, DISPLAY_ADDRESS);
  //oled.set400kHz();
  oled.setFont(Callibri15); //Aus Adafruit-Library
  oled.clear();
  oled.setContrast (100);
  oled.println("Version: "); oled.println (Version);
  oled.println("Mit WLAN verbinden.");

  //--- Touch-Pin ---
  pinMode(TouchPin, INPUT);
  attachInterrupt(digitalPinToInterrupt(TouchPin), handleTouchInterrupt, RISING);
  //--- WLAN ---
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  //Verbindung zum WLAN aufbauen
  int counter = 0; //Counter für Timeout
  while (WiFi.status() != WL_CONNECTED) {
    delay (250);
    Serial.print(".");
    oled.print(".");
    counter++;
    if (counter > 100) {
      Serial.println("Kein WLAN. Restart!");
      ESP.restart();
    }
  }
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  oled.println(WiFi.localIP());

  //----- BME280-Settings -----
  BME.settings.commInterface = I2C_MODE;
  BME.settings.I2CAddress = 0x76;  //oder 0x77
  BME.settings.runMode = 3; //Normal mode
  BME.settings.tStandby = 0;
  BME.settings.filter = 2;
  BME.settings.tempOverSample = 1;
  BME.settings.pressOverSample = 1;
  delay(10); //Für Startup BMP280
  
  Serial.print ("BME280...");
  if (!BME.begin()) {
    Serial.println("nicht gefunden!");
  }
  else {
    Serial.println("OK.");
  }
  for (int i = 0; i < 5; i++) { //die ersten fünf Werte verwerfen
    BME_p  = BME.readFloatPressure() / 100; // [hPa]
    BME_T  = BME.readTempC();
    BME_RH = BME.readFloatHumidity(); //hat BMP nicht
  }

  //----- Header -----
  Serial.println("");
  Serial.println(headerString);

  oled.clear();

  st = 0; sp = 0; sf = 0; sl = 0;
  sth = 0; sph = 0; sfh = 0; slh = 0;
  hi = 0; mi = 0;

  mint =  100; minp = 2000; minf = 101; minl = 101;
  maxt = -100; maxp =    0; maxf =   0; maxl =   0;
  
} //setup

//=== Loop =======================================================================
void loop() {
  int sec;
  int osec;
  sec = millis() / 1000;
  osec = sec;
  while (osec == sec) {
    sec = millis() / 1000;  //auf neue Sekunde warten
    yield();
  }
  sec = millis() / 1000;

  if ((sec % 60) == 0) {    //1 x pro Minute
    int ms0 = millis();
    //--- Mitteln ---
    float v;
    v = BME.readTempC();                st += v; sth += v; //setminmax (v, mint, maxt);
    v = BME.readFloatPressure() / 100;  sp += v; sph += v; //setminmax (v, minp, maxp);
    v = BME.readFloatHumidity();        sf += v; sfh += v; //setminmax (v, minf, maxf);
    v = getLight();                     sl += v; slh += v; //setminmax (v, minl, maxl);
    mi += 1;
    hi += 1;
    //--------------
    getSensorData();
    php_tab_insert ("h6_1min", 360); //Daten an Server senden
    ms0 = millis() - ms0;
  }
  if ((sec % 600) == 0) { //Alle 10 Minuten
    int ms0 = millis();
    sensors.t = st/mi;    //Gemittelte Daten, statt getSensorData()
    sensors.p = sp/mi;
    sensors.f = sf/mi;
    sensors.l = sl/mi;
    php_tab_insert ("woche_10min", 1008); //Daten an Server senden
    st = 0; sp = 0; sf = 0; sl = 0;
    mi = 0;
    ms0 = millis() - ms0;
    Serial.print (ms0);
    Serial.println (" ms");
  }

  if ((sec % 3600) == 0) { //Alle 60 Minuten
    int ms0 = millis();
    sensors.t = sth/hi;    //Gemittelte Daten, statt getSensorData()
    sensors.p = sph/hi;
    sensors.f = sfh/hi;
    sensors.l = slh/hi;
    //Daten an Server senden
    php_tab_insert ("monat_1h", 744);    //Diese drei Aufrufe könnten ggf. in ein PHP-Skript
    php_tab_insert ("jahr_1h", 8766);
    php_tab_insert ("jahre5_1h", 43830); 
    sth = 0; sph = 0; sfh = 0; slh = 0;
    hi = 0;
    ms0 = millis() - ms0;
    Serial.print (ms0);
    Serial.println (" ms");
  }

  if ((sec % 2) == 0) {   //Alle zwei Sekunden Werte seriell ausgeben
    s = getSensorString();  // für Ausgabe auf Display
    Serial.print (millis() / 1000);
    Serial.print (" ");
    Serial.println(s);
    //-----  Display ---
    BME_p  = BME.readFloatPressure() / 100; // [hPa]
    BME_T  = BME.readTempC();
    BME_RH = BME.readFloatHumidity();
    LDR    = getLight();

    if (millis() > OLED_Timer + 1000*OLED_TimeOut){Display_on = false; oled.clear();}
    
    if  (Display_on == true) {
      int i;
      char c[10];
      oled.home();
      dtostrf(BME_T, 5, 1, c);
      oled.print(c); oled.print(" C    ");
      dtostrf(BME_RH, 2, 0, c);
      oled.print(c); oled.print(" %RH  ");
      oled.println(); // oled.clearToEOL();

      dtostrf(BME_p, 4, 0, c);
      oled.print(c); oled.print(" hPa ");
      dtostrf(LDR, 4, 1, c);
      oled.print(c); oled.print(" L  ");
      oled.println();

      oled.println(millis() / 1000); oled.print(" ");
      oled.print(WiFi.localIP());
      oled.print(" D:"); 
      if ((OLED_Timer + 1000*OLED_TimeOut - millis())/1000 < OLED_TimeOut){
        oled.print((OLED_Timer + 1000*OLED_TimeOut - millis())/1000);}
      oled.println("    ");
    }
    //------------------
  }
} //end loop

String getSensorString () {
  String s;
  //msh = millis(); Serial.print ("Sensoren ");
  double t, p, f, l;
  t = 0; p = 0; f = 0; l = 0;
  int num_samples = 10; // ~ 43ms
  for (int i = 0; i < num_samples; i++) {
    p += BME.readFloatPressure() / 100; // [hPa]
    t += BME.readTempC();
    f += BME.readFloatHumidity();
    l += getLight();
    //yield();
  }
  t = t / num_samples;
  p = p / num_samples;
  f = f / num_samples;
  l = l / num_samples;    
  s  = dtostrf(t, 4, 1, strBuf);  //s = formatSensorString(t, 6, 1);
  s += TAB;
  s += dtostrf(p, 6, 1, strBuf);
  s += TAB;
  s += dtostrf(f, 4, 1, strBuf);
  s += TAB;
  s += dtostrf(l, 4, 1, strBuf);
  s.replace(".", ",");
  return s;
} //getSensorString

float getLight (){
  float l = (1024 - analogRead(A0))/10.23;   //auf 0..100 normieren 
  return l;
}

void getSensorData () {
  //Werte in globale Variable sensors speichern
  sensors.t = BME.readTempC();
  sensors.p = BME.readFloatPressure() / 100; // [hPa]
  sensors.f = BME.readFloatHumidity();
  sensors.l = getLight();
  String s;
  s = dtostrf(sensors.t, 5, 1, strBuf); s.replace(".", ",");
  sensors.fs = s;
  s = dtostrf(sensors.p, 5, 1, strBuf); s.replace(".", ",");
  sensors.ps = s;
  s = dtostrf(sensors.f, 2, 0, strBuf); s.replace(".", ",");
  sensors.fs = s;
  s = dtostrf(sensors.l, 5, 1, strBuf); s.replace(".", ",");
  sensors.ls = s;
} //getSensorData

PHP (Serverscripte)

Als absoluter Neuling in PHP, mysql und WordPress, musste ich einiges „zusammenbasteln“, um es ans Laufen zu bringen.

db_header.php

<?php

/*
Alle Variablen testen?
if (isset($_GET['aid']) && is_numeric($_GET['aid'])) {
    $aid = (int) $_GET['aid'];
} else {
    $aid = 1;
}
== 
$aid = $_GET['aid']) ?? 1;
*/

$LF = "<br \>";

//----- in include file verlagern -----
$servername = ""; //Anpassen!
$username   = "";
$password   = "";
$dbname     = ""; //muss mit DBKEY aus dem Arduino-Skript übereinstimmen

$mysqli = new mysqli("$servername", "$username", "$password", "$dbname");
// Check connection
if ($mysqli->connect_errno) {
    echo "Error: MySQL connection failed:" . $LF;
    echo "Errno: [" . $mysqli->connect_errno . "]" . $LF;
    echo "Error: [" . $mysqli->connect_error . "]" . $LF;
    exit;
}

//echo "Connection OK." . $LF;

function sql_query ($mysqli, $query){
	if (!$result = $mysqli->query($query)) {
    		echo "Error: Query failed to execute:";
            echo $LF;
    		echo "Query: [" . $query . "]" . $LF;
    		echo "Errno: [" . $mysqli->errno . "]" . $LF;
    		echo "Error: [" . $mysqli->error . "]" . $LF;
    		echo mysqli_errno ();
            echo $LF;
    		exit;
		}
    return $result;
} //sql_query 

mysqli_select_db ($dbname);

?>

tab_insert.php

<?php

//----- in include file verlagern -----
//bzw. include 'db_header.php'; weiter oben
$dbkey = 'xxx'; //muss mit DBKEY aus dem Arduino-Skript übereinstimmen
$LF = "<br \>";
$datum = date ("Y-m-d H:i:s", time());
echo $datum . $LF;

$tabelle =  $_GET['tabelle'];
$maxcount = $_GET['max'];

$key = $_GET['key'];
if ($key <> $dbkey){
    echo "Key ungültig";
    exit();} //Script beenden, falls key falsch ist

$temperatur = $_GET['temperatur'];
$druck = $_GET['druck'];
$feuchte = $_GET['feuchte'];
$licht = $_GET['licht'];
echo "$temperatur °C" . $LF;
echo "$druck hPa " . $LF;
echo "$feuchte %RH " . $LF;
echo "$licht L " . $LF;
?>

<?php
include 'db_header.php';
//===== Werte eintragen =====
$query = "INSERT INTO " . $tabelle . " (Date, T, p, RH, L) VALUES ('$datum', '$temperatur', '$druck', '$feuchte', '$licht')";
echo $sql . $LF;
$result = sql_query ($mysqli, $query);

//************
//schenller als "SELECT * FROM .."
$query = "SELECT COUNT(*) AS anzahl FROM " . $tabelle;
echo $sql . $LF;
$result = sql_query ($mysqli, $query);
$row = $result->fetch_assoc();
$count = $row['anzahl'];
echo $count . " Datensätze" . $LF;
//************


//----- falls mehr als $maxcount Einträge dann erste raus -----
//langsam und umständlich
echo "Erstes raus: " . $LF;
$query = "SELECT * FROM " . $tabelle . " ORDER BY DATE";
$result = sql_query ($mysqli, $query);
$count = $result->num_rows;
$geloescht = 0;

/* 
//ungefähr so? PDO??? benutzen 
$sql = "DELETE FROM " . $tabelle . " WHERE Date = '" . $row['Date'] . "'";
$result = $conn->query ($sql);
while count > maxcount do begin
    $row = $result->fetch_assoc();
    $sql = "DELETE FROM " . $tabelle . " WHERE Date = '" . $row['Date'] . "'";
    $result_x = $conn->query ($sql); //result und result_x gleichzeitig möglich ?
    $count = $count - 1;
end

*/

echo "max: " . $maxcount . $LF;
echo "count: " . $count . $LF;
while ($count > $maxcount){
$row = $result->fetch_assoc();
if ($count > $maxcount){
        $geloescht = $geloescht + 1;
        $query = "DELETE FROM " . $tabelle . " WHERE Date = '" . $row['Date'] . "'";
        $result = sql_query ($mysqli, $query);
        //print_r($result); echo "<br \>";

        $query = "SELECT * FROM " . $tabelle . " ORDER BY DATE";
        $result = sql_query ($mysqli, $query);
        $count = $result->num_rows;
        echo $count . " ";
    }
}

echo "<br \>"; 
echo "Gelöschte Datesätze: " . $geloescht  . "<br \>";
$result->free();
$mysqli->close();
//===================================

$conn->close();

echo "Ende";  
?>

PHP-Script für die graphische Darstellung, über Woody snippets direkt in WordPress eingebunden.

Auf dem Webserver muss jpgraph installiert werden. Ich habe noch keine Möglichkeiten gefunden die Achsen mit Datumsangaben zu beschriften.

Aufruf über den Shortcode wbcr_php_snippet id=“##“ tabelle=“Tabellenname“

//tab_graph
//$LF = "<br \>";

include ("jpgraph/src/jpgraph.php"); 
include ("jpgraph/src/jpgraph_bar.php"); 
include ("jpgraph/src/jpgraph_line.php"); 

include 'meine/db_header.php';

$TempKorr = 0.0; // !!!!!

$query = "SELECT * FROM " . $tabelle;
$result = sql_query ($mysqli, $query);

if ($result->num_rows === 0) {
    echo "ID $aid nicht gefunden";
    exit;
}

$count = $result->num_rows;
//echo $count; echo " Datensätze" . $LF; 

while ($row = $result->fetch_row()) {$rows[] = $row;}

//echo microtime($asfloat) - $ms . " s" . $LF;

//Datensätze in einzelne Array umsortieren
$num = $count;
$datax = array();
$datayl = array();
$datayp = array();
$datayf = array();
$datayt = array();
$numdatashow = 0;
if ($numdatashow > 0){echo "Ausgabe:" . $LF;}
for ($i=0;$i<$num;$i++){
    if ($i < $numdatashow) {echo $i . "  "; }
    for ($j=0; $j < 5; $j++){
		if ($i < $numdatashow) {echo $rows[$i][$j]; echo "  ";} 
        if ($j == 0){$y = $rows[$i][$j]; array_push($datax, strtotime($y)/60); //Minuten
                    //echo $i . " " . $y/60 . $LF;
                    } //In Minuten
        if ($j == 1){$y = $rows[$i][$j]; array_push($datayt, $y);}
        if ($j == 2){$y = $rows[$i][$j]; array_push($datayp, $y);}
        if ($j == 3){$y = $rows[$i][$j]; array_push($datayf, $y);}
        if ($j == 4){$y = $rows[$i][$j]; array_push($datayl, $y);}
	}
	if ($i < $numdatashow) {echo $LF;}
}

//Minimum von allen x-Werten (Timestamp) suchen und abziehen
$minx = min($datax);
for ($i = 0; $i < $num; $i++) {$datax[$i] = $datax[$i] - $minx;}

//Temperatur korrigieren  + $TempKorr
for ($i = 0; $i < $num; $i++) {$datayt[$i] = $datayt[$i] + $TempKorr;}

$minx = min($datax);  $maxx = max($datax);
$mint = min($datayt); $maxt = max($datayt);
$minp = min($datayp); $maxp = max($datayp);
$minf = min($datayf); $maxf = max($datayf);
$minl = min($datayl); $maxl = max($datayl);

echo "Letzte Werte:" . $LF;
echo "<table>";
echo "<thead><tr><th>T[°C]</th><th>p[hPa]</th><th>F[%RH]</th><th>L[]</th></tr></thead>";
echo "<tbody>";
echo "<tr>";
$i = intval(10*$datayt[$num-1]); echo '<th>' . $i/10 . '</th>';
$i = intval(10*$datayp[$num-1]); echo '<th>' . $i/10 . '</th>';
$i = intval(10*$datayf[$num-1]); echo '<th>' . $i/10 . '</th>';
$i = intval(10*$datayl[$num-1]); echo '<th>' . $i/10 . '</th>';
echo "</tr>\n";
echo "</tbody>";
echo "</table>";

switch ($tabelle){
    case "h6_1min":     $xtitle = "t[min]";    break;
    case "woche_10min": $xtitle = "t[10min]";  break;
    case "monat_1h":    $xtitle = "t[h]";      break;
    case "jahr_1h":     $xtitle = "t[h]";      break;
    case "jahre5_1h":   $xtitle = "t[h]";      break;
}

function graph_plot($w, $h, $gtitle, $xtitle, $ytitle, $datay){
    $graph = new Graph($w, $h, 0); //($w, $h, "auto");
    $graph->SetScale("intint");
	$graph->title->Set($gtitle);
    $graph->xaxis->SetTitle($xtitle); 
    $graph->xaxis->SetTitleSide("SIDE_TOP");
    $graph->xaxis->SetTitleMargin(-8);     
    $graph->yaxis->SetTitle($ytitle, "high"); 
    $graph->yaxis->SetTitleSide("SIDE_RIGHT");
    $graph->yaxis->SetTitleMargin(12);
    $bplott = new LinePlot($datay);
    $graph->Add($bplott);
    $pngname = $gtitle . ".png";
    $graph->Stroke($pngname); 
    echo "<img src='" . $pngname . "'>" . $LF; 
}

graph_plot (800, 200, "Temperatur", $xtitle, "T[°C]", $datayt);
graph_plot (800, 200, "Druck", $xtitle, "p[hPa]", $datayp);
graph_plot (800, 200, "Feuchte", $xtitle, "RH[%]", $datayf);
graph_plot (800, 200, "Helligkeit", $xtitle, "E[]", $datayl);

//echo microtime($asfloat) - $ms . " s" . $LF;

$result->free();
$mysqli->close();

?>