Allerdings geht mein Drahtlosnetzwerk nachts schlafen und so können z.B. zwischen 0 Uhr und 6 Uhr keine Daten übertragen werden. Damit die mühsam gewonnenen Messwerte nicht verloren gehen, müssen sie während der fehlenden Konnektivität zwischengespeichert werden. Allerdings ist mir die SD-Karte im Raspi zu schade dafür und finde, dass ein Array im RAM völlig ausreicht. Sobald der Raspi wieder mit dem Internet verbunden ist, werden alle gesammelten Daten übermittelt (JSON). Da nun an dieser Stelle egal ist, ob ich 1 JavaScript-Objekt oder 24 Objekte übertragen möchte, habe ich diesen ganzen Prozess in allen beteiligten Dateien angepasst (logdata.js, data2sql.php). Für die Umsetzung dieser 2. Variante werden viele Vorgänge aus dem vorangegangenen Projekt übernommen. Die Vorbereitung des Raspberry Pi Zero W ist im großen und ganzen identisch. Der Zero ist aber leistungstechnisch schwächer auf der Brust als der große Bruder, weshalb ich einen Versuch mit einer abgespeckten Raspbian-Variante (Stretch Lite) unternehmen wollte. Leider ließen sich manche Bibliotheken für Bluetooth und Sensortag nicht richtig installieren. Bevor ich bei der Fehlersuche (noch mehr) graue Haare bekomme, bin ich lieber gleich wieder auf Raspbian Stretch Desktop umgestiegen. Zusätzlich zum Punkt 1c) aus Variante 1 (JavaScript-Bibliotheken) muss nun noch das node-Modul für das E-Paper-Display (node-epd) installiert werden. Dieses Modul setzt wiringPi und libfreetype6-dev voraus. Ersteres ist oft Bestandteil der Raspbian-Distribution. Ein Test zeigt, ob wiringPi installiert ist:
pi@raspberrypi:~ $ gpio -v
Wenn dabei etwas ausgegeben wird, sollte alles in Ordnung sein.
pi@raspberrypi:~ $ sudo apt-get install libfreetype6-dev
pi@raspberrypi:~ $ npm install node-epd
Leider ist die Dokumentation des node-epd-Moduls unvollständig und fehlerhaft. 😡 Den Umgang damit erlernt man größtenteils nur durch Versuch und Irrtum (Aha-Effekte waren eher selten 😔).pi@raspberrypi:~ $ npm install node-epd
Bedingt durch die Größe des Displays ist die Anzahl der darstellbaren Informationen beschränkt. Ob die Messwerte von Innen (DHT) oder Außen (Sensortag) stammen wird symbolisch ( und ) dargestellt. Das E-Paper-Modul wird mit einem 8-poligen Stecker geliefert - das andere Ende passt auf die Stiftleiste des Raspberry und ist wie folgt verdrahtet:
An die zugewiesenen Anschlüsse sollte man sich unbedingt halten, weil diese in der vorgeschlagenen Bibliothek (node-epd) fest programmiert sind. Die Argumente aus dem Beispiel (new Lcd(0, 4, 10, 5, 1, 55)) sind nicht nötig, weil sie gar nicht verwendet werden. |
|
// Importieren der notwendigen Module
var dht = require('dht-sensor');
var SensorTag = require('sensortag');
var request = require('request');
var async = require('async');
var Lcd = require('node-epd');
const Gpio = require('onoff').Gpio;
// GPIO-Pins für LED's als Ausgang festlegen:
const redLED = new Gpio(27, 'out'); // SensorTag-Verbindungsabbruch
const yellowLED = new Gpio(23, 'out'); // DHT-Lesefehler
const greenLED = new Gpio(22, 'out'); // Erfolgreiche SensorTag-Initialisierung
const blueLED = new Gpio(26,'out'); // HTTP-/Übertragungsfehler
//Anzeige initialisieren & löschen, Schriftart/-größe setzen
var lcd = new Lcd();
//var lcd = new Lcd(0, 4, 10, 0, 1); //die Argumente (lt. Modul-Beispiel) spielen aktuell keine Rolle, Pins sind fest programmiert:
//fest programmierte Argumente/[Verbindungen]: SPI-Channel: 0 (SPI0-MOSI, 19), DC: 25 (22), CS: 8 (CE0, 24), RST: 17 (11), LED: 18 (PWM0, 12), [BUSY: 24 (18)] -> BCM-PinNummer (reale PinNummer)
//normale Bedeutung der Argumente (vgl. Modul-Dokumentation): SPI-Channel: 0 (SPI0-MOSI, 19), DC: 4 (16), CS: 10 (CE0, 24), RST: 0 (11), LED: 1 (PWM0, 12) -> wiringPi-PinNummer (reale PinNummer)
// Schriftart Segoe UI Symbol (seguisym.ttf) für Schrift/Symbole
const segoefont1 = lcd.font('/myFolderStructure/seguisym.ttf', 14);
const segoefont2 = lcd.font('/myFolderStructure/seguisym.ttf', 13);
const segoefont3 = lcd.font('/myFolderStructure/seguisym.ttf', 28);
lcd.clear(); //LCD löschen
lcd.rect(0, 0, 296, 128); // zeichne Rechteck für Rahmen
lcd.line(0, 64, 296, 64); // zeichne Trennlinie
lcd.drawChar(segoefont3, 8, 53, 0xe10f); // Symbol 'Indoor' (utf16)
lcd.drawString(segoefont1, 70, 25, "Temperatur:");
lcd.drawChar(segoefont2, 250, 25, 0x2103); // Symbol Grad Celsius (utf16)
lcd.drawString(segoefont1, 70, 55, "Luftfeuchte:");
lcd.drawString(segoefont2, 250, 55, "%rH");
lcd.drawChar(segoefont3, 6, 118, 0xe1c3); // Symbol 'Outdoor' (utf16)
lcd.drawString(segoefont1, 70, 90, "Temperatur:");
lcd.drawChar(segoefont2, 250, 90, 0x2103); // Symbol Grad Celsius (utf16)
lcd.drawString(segoefont1, 70, 120, "Luftfeuchte:");
lcd.drawString(segoefont2, 250, 120, "%rH");
lcd.update(function() {
lcd.partClear();
lcd.drawString(segoefont1, 193, 25, ""); //draw dummy-string to set part?
lcd.drawString(segoefont1, 193, 55, ""); //draw dummy-string to set part?
lcd.drawString(segoefont1, 193, 90, ""); //draw dummy-string to set part?
lcd.drawString(segoefont1, 193, 120, "");//draw dummy-string to set part?
lcd.partUpdate();
});
// Variablen für DHT-Lesefehler und Durchschnittstemperatur intialisieren:
var oldhum = 0, oldtemp = 0, avgOutTemp = 0;
// Sensor-Lese-Intervall in Minuten setzen:
var readInterval = 15;
// Headers für http-request setzen:
var headers = {
'User-Agent': 'Super Agent/0.0.1',
'Content-Type': 'application/x-www-form-urlencoded'
};
// options für http-request konfigurieren:
// (bei myURL.com die URL des Webspace eintragen)
var options = {
url: 'http://myURL.com/jsondata2sql.php',
method: 'POST',
headers: headers
};
var DataBufferArray = []; // leeres Daten-Puffer-Array
var readings = { sensorId: '00000000' }; // Objekt für Sensordaten anlegen
var sensorTag; // globale Variable (SensorTag Instanzkopie)
SensorTag.discover(function (myTag) {
console.log('discovered: ', new Date().toLocaleString());
sensorTag = myTag;
myTag.on('disconnect', function () {
redLED.writeSync(1); // bei SensorTag-Verbindungsabbruch rote LED einschalten
console.log('disconnected! ', new Date().toLocaleString() );
process.exit(0);
});
async.series([
function (callback) {
sensorTag.connectAndSetUp(callback);
redLED.writeSync(0);
},
function (callback) {
sensorTag.readManufacturerName(function (error, manufacturerName) {
console.log('\tHersteller: ' + manufacturerName);
callback();
});
},
function (callback) {
sensorTag.readDeviceName(function (error, deviceName) {
console.log('\tGeraetetyp: ' + deviceName);
callback();
});
},
function (callback) {
sensorTag.readSystemId(function (error, systemId) {
console.log('\tSystem-ID: ' + systemId);
callback();
});
},
function (callback) {
sensorTag.readFirmwareRevision(function (error, firmwareRevision) {
console.log('\tFirmware-Revision: ' + firmwareRevision);
greenLED.writeSync(1); // bei erfolgreicher SensorTag-Initialisierung grüne LED einschalten
callback();
});
}
]);
});
setInterval(function () {
readings.sensorId = sensorTag.id;
try {
var dhtdata = dht.read(22, 4); // DHT22 an GPIO4 (Pin 7)
readings.indoorhum = dhtdata.humidity;
readings.indoortemp = dhtdata.temperature;
yellowLED.writeSync(0); // gelbe LED ausschalten
}
catch (err) {
yellowLED.writeSync(1); // bei DHT-Fehler gelbe LED einschalten
readings.indoorhum = oldhum; // letzten Messwert verwenden
readings.indoortemp = oldtemp; // letzten Messwert verwenden
}
finally {
oldhum = readings.indoorhum;
oldtemp = readings.indoortemp;
};
async.series([
function (callback) {
sensorTag.enableIrTemperature(callback); // Sensor einschalten
},
function (callback) {
setTimeout(callback, 5000); // warten
},
function (callback) {
sensorTag.readIrTemperature(function (error, objectTemperature, ambientTemperature) {
//readings.objectTemperature = objectTemperature; // Oberflächentemperaturmessung nicht sinnvoll
readings.temperatureFromIr = ambientTemperature; // Sensordaten speichern
callback();
});
},
function (callback) {
sensorTag.disableIrTemperature(callback); // Sensor ausschalten
},
function (callback) {
sensorTag.enableHumidity(callback);
},
function (callback) {
setTimeout(callback, 5000);
},
function (callback) {
sensorTag.readHumidity(function (error, temperature, humidity) {
readings.humidity = humidity;
readings.temperatureFromHumidity = temperature;
callback();
});
},
function (callback) {
sensorTag.disableHumidity(callback);
},
function (callback) {
sensorTag.enableBarometricPressure(callback);
},
function (callback) {
setTimeout(callback, 5000);
},
function (callback) {
sensorTag.readBarometricPressure(function (error, pressure) {
readings.pressure = pressure;
callback();
});
},
function (callback) {
sensorTag.disableBarometricPressure(callback);
},
function (callback) {
sensorTag.enableLuxometer(callback);
},
function (callback) {
setTimeout(callback, 5000);
},
function (callback) {
sensorTag.readLuxometer(function (error, lux) {
readings.lux = lux;
callback();
});
},
function (callback) {
sensorTag.disableLuxometer(callback);
}
], function () {
readings.dataTime = new Date();
// Temperatur-Mittelwert von IR-Temperatur- und Feuchtesensor bilden:
avgOutTemp = (readings.temperatureFromIr + readings.temperatureFromHumidity) / 2;
// temporäres Daten-Objekt mit Sensordaten und Zeitstempel (UNIX) füllen (vgl. jsondata2csv.php auf Webserver):
var tempData = {
"TimeKey": readings.dataTime.getTime(), "avgOutdoorTemp": avgOutTemp, "OutdoorHum": readings.humidity,
"IndoorTemp": readings.indoortemp, "IndoorHum": readings.indoorhum,
"Pressure": readings.pressure, "Light": readings.lux
};
// Sensordaten auf Display anzeigen
data2lcd(readings.indoortemp, readings.indoorhum, avgOutTemp, readings.humidity);
DataBufferArray.push(tempData); // temporäres Daten-Objekt in Daten-Puffer-Array schreiben (anhängen)
var myJSON = JSON.stringify(DataBufferArray); // aus Daten-Puffer JSON-String erstellen
options.form = {"json": myJSON}; // Daten für HTTP-Post deklarieren
request(options, function (error, response, body) {
if (!error && response.statusCode == 200) {
greenLED.writeSync(0);
blueLED.writeSync(0);
DataBufferArray = []; // wenn alle Daten übertragen sind, Daten-Puffer leeren
} else {
blueLED.writeSync(1);
console.log(new Date().toLocaleString());
console.log("statusCode:", response && response.statusCode);
console.log("Fehler:", error);
};
})
})
}, readInterval * 60 * 1000);
function data2lcd(itemp, ihum, otemp, ohum) {
lcd.rect(191, 5, 57, 55, true, 0); // zeichne Rechteck, um Bereich zu löschen (Indoor)
lcd.drawString(segoefont1, 193, 25, itemp.toFixed(1)); // 1 Nachkommastelle
lcd.drawString(segoefont1, 193, 55, ihum.toFixed(1));
lcd.rect(191, 72, 57, 55, true, 0); // zeichne Rechteck, um Bereich zu löschen (Outdoor)
lcd.drawString(segoefont1, 193, 90, otemp.toFixed(2)); // 2 Nachkommastellen
lcd.drawString(segoefont1, 193, 120, ohum.toFixed(2));
lcd.partUpdate();
};
Auf dem Webserver wird die bestehende Datei 'data2sql.php' kopiert und in 'jsondata2sql.php' umbenannt. Anschließend wird der Inhalt geändert:
<?php
isset($_POST['json']) ? $json0=$_POST['json'] : $json0=''; //JSON-String
$data = json_decode($json0, TRUE);
$mysql_host = "localhost"; // wenn SQL-Datenbank über die gleiche URL erreichbar ist
$mysql_db = "SQL-Datenbankname";
$mysql_user = "DB-Benutzername";
$mysql_pw = "DB-Passwort";
$connection = mysql_connect($mysql_host, $mysql_user, $mysql_pw) or die("Verbindung zur Datenbank fehlgeschlagen.");
mysql_select_db($mysql_db, $connection) or die("Datenbank konnte nicht ausgewaehlt werden.");
foreach ($data as $object) {
$a0 = $object["TimeKey"]; //Raspi-Zeit
$a1 = $object["avgOutdoorTemp"]; //mittlere Temperatur (außen)
$a2 = $object["OutdoorHum"]; //rel. Feuchte (außen)
$a3 = $object["IndoorTemp"]; //Temperatur (DHT, innen)
$a4 = $object["IndoorHum"]; //rel. Feuchte (DHT, innen)
$a5 = $object["Pressure"]; //Luftdruck (außen)
$a6 = $object["Light"]; //Helligkeit (außen)
$insert_data = "INSERT INTO weather_data (TimeKey, avgOutdoorTemp, OutdoorHum, IndoorTemp, IndoorHum, Pressure, Light) VALUES ($a0, $a1, $a2, $a3, $a4, $a5, $a6)";
mysql_query($insert_data, $connection) or die("Fehler beim Eintragen der Daten in die Datenbank!");
//echo $a0." ".$a1." ".$a2." ".$a3." ".$a4." ".$a5." ".$a6."\n"; // Test-Rückgabe im HTML-Body (Debugging)
};
?>
Keine Kommentare:
Kommentar veröffentlichen