Hier beschreibe ich, wie der Feinstaubsensor Nova PM SDS011 bei mir auf dem Raspberry Pi läuft und die Daten an das Projekt
luftdaten.info übermittelt.
Ich habe meine Messstation ähnlich gebaut wie bei
www.luftdaten.info beschrieben; nur dass ich einen Raspberry Pi Zero W verwendet habe und zusätzlich noch den Sensor DHT22 für Temperatur und Luftdruck. Meine Messergebnisse vom Feinstaubsensor werden an das Projekt
www.luftdaten.info übermittelt.
Vom Prinzip her laufen bei mir im Hintergrund immer wieder kleine Skripte als Cron-Jobs, welche die Werte der Sensoren in der rrd-Datenbank ablegen. Damit kann ich die Daten dann für eine Vierlzahl von Zwecken weiterverwenden, ohne immer wieder die Sensoren abfragen zu müssen. Gerade beim SDS011 war mir wichtig, dass er nur alle 10 Minuten läuft, damit sich die Lebensdauer des Geräts erhöht.
Leider werden bei luftdaten.info die Werte für Temperatur und Luftfeuchtigkeit noch nicht angezeigt. Daher nutze ich die bisher nur für andere Zwecke, z.B. zur Anzeige über meinen FHEM-Server mit der TabletUI.
1. Liste der Bauteile
- 2x Abwasserrohrbogen in der größten Ausführung. Gibts beim Baumarkt für wenig Geld.
- Raspberry Pi 3 mit Case
- Sensor für Temperatur und Luftfeuchtigkeit DHT22
- Feinstaubsensor SDS011 mit USB-Modul; habe meinen bei eBay über eckstein besorgt. Immer aufpassen, dass auch das USB-Modul mit dabei ist. Es ist wichtig für den einfachen Betrieb am Pi.
- Kleiner Schlauch zum Ansaugen der Außenluft; im Prinzip genügt hier ein Strohhalm
- Heißkleber, Schrauben und doppelseitiges Klebeband zur Befestigung der Bauteile
2. Zusammenbau
Der grundlegende Zusammenbau wird bei
www.luftdaten.info beschrieben - ein so geniales wie einfaches Konzept!
Mit dem Heißkleber habe ich den Pi samt Gehäuse im Innenraum fixiert. Den SDS011 habe ich mit zwei kleinen Holzschrauben am Rohrbogen befestigt, so dass der Schlauch unten aus einem der Rohrbögen herausragen kann. Der DHT22 sitzt mit einem doppelseitigen Klebeband am unteren Ende. Besser wäre es aber eigentlich, ihn außen zu montieren, weil die Temperatur innen im Rohr besonders bei Sonneneinstrahlung von der Außentemperatur abweichen kann.
Die Stromversorgung läuft bei mir auf dem Balkon direkt per (wetterfestem) USB-Adapter.
3. Konfiguration DHT22
Der DHT22 wird einfach an die GPIO-Pins des Raspberry Pi angeschlossen. Dafür und für die Basisinbetriebnahme über das Binary "loldht" gibt es tonnenweise Beschreibung im Netz, z.B.
hier
Dann habe ich mir ein kleines Skipt geschrieben, welches die Werte für Temperatur und Luftfeuchtigkeit alle paar Minuten in Dateien schreibt:
#!/bin/bash
#Einstellungen
MINTEMP=-20
MAXTEMP=50
MINHUM=0
MAXHUM=100
###########################################
DIR=/opt/templog
cd $DIR
GPIO=$1
INPUT=$(sudo ./loldht $GPIO |grep Temperature)
#echo $INPUT
HUM=$(echo $INPUT|cut -d " " -f3)
TEMP=$(echo $INPUT|cut -d " " -f7)
if [ $(echo "if (${HUM} > ${MAXHUM}) 1 else 0" | bc) -eq 1 -o $(echo "if (${HUM} < ${MINHUM}) 1 else 0" | bc) -eq 1 ]; then
if [ -f $DIR/dht_hum.txt ]; then
cat $DIR/dht_hum.txt
fi
else
echo $HUM
echo $HUM > $DIR/dht_hum.txt
fi
if [ $(echo "if (${TEMP} > ${MAXTEMP}) 1 else 0" | bc) -eq 1 -o $(echo "if (${TEMP} < ${MINTEMP}) 1 else 0" | bc) -eq 1 ]; then
if [ -f $DIR/dht_temp.txt ]; then
cat $DIR/dht_temp.txt
fi
else
echo $TEMP
echo $TEMP > $DIR/dht_temp.txt
fi
4. Konfiguration SDS011
Dank des USB-Moduls ist der SDS011 kinderleicht zu betreiben. Bei mir sieht das dann so aus:
pi@sensorium:/opt/feinstaub/dusty/luftdaten-python $ lsusb
Bus 001 Device 004: ID 1a86:7523 QinHeng Electronics HL-340 USB-Serial adapter
Die Ansteuerung nehme ich dann über ein Python-Skript aus dem Netz vor, ein gutes gibt es z.B.
hier
Damit werden dann die Werte für PPM10 und PPM2.5 in zwei Dateien geschrieben.
5. Lokales sammeln und persistieren der Daten
Die Skripte zum Sammeln der Daten von den Sensoren laufen bei mir als Cronjob alle 10 Minuten:
*/10 * * * * cd /opt/feinstaub;./feinstaub.py
*/10 * * * * /opt/templog/dht22.sh
*/10 * * * * /opt/templog/rrdlogger_novadht.py
Für die Persistierung der Werte mit rrdtool habe ich die Datenbasis so angelegt:
#!/bin/sh
rrdtool create /opt/templog/data/templog_novadht.rrd --step 600 \
DS:humidity:GAUGE:1200:0:100 \
DS:temperature:GAUGE:1200:-50:100 \
DS:pm10:GAUGE:1200:-1:9999 \
DS:pm25:GAUGE:1200:-1:9999 \
RRA:AVERAGE:0.5:1:144 \
RRA:AVERAGE:0.5:3:336 \
RRA:AVERAGE:0.5:6:720 \
RRA:AVERAGE:0.5:36:1460
Für das Einlesen der Daten aus den Dateien in die Datenbank nutze ich dann wiederum ein Skript:
#!/usr/bin/python
import logging
import logging.handlers
import rrdtool
import time
import sys
import commands
def do_update():
timestamp = time.time()
humidity = commands.getstatusoutput("cat /opt/templog/dht_hum.txt")
temperature = commands.getstatusoutput("cat /opt/templog/dht_temp.txt")
pm10 = commands.getstatusoutput("cat /opt/feinstaub/ppm10.txt")
pm25 = commands.getstatusoutput("cat /opt/feinstaub/ppm25.txt")
rrdtool.update("/opt/templog/data/templog_novadht.rrd","%d:%s:%s:%s:%s" % (timestamp, humidity[1], temperature[1], pm10[1], pm25[1]))
print timestamp
print humidity[1]
print temperature[1]
print pm10[1]
print pm25[1]
# set up logging to syslog to avoid output being captured by cron
syslog = logging.handlers.SysLogHandler(address="/dev/log")
syslog.setFormatter(logging.Formatter("templogger: %(levelname)s %(message)s"))
logging.getLogger().addHandler(syslog)
do_update()
Das Skript läuft ebenfalls als Cronjob alle 10 Minuten (siehe unter 5).
6. Senden der Daten an luftdaten.info
Übernahme und Anpassung der Skripten von
https://github.com/corny/luftdaten-python
Ich habe das Skript allerdings etwas anpassen müssen, da ich keinen Luftdruck übermittle und die Messwerte außerdem nicht direkt vom Sensor, sondern aus dem rrdtool auslese.
Eine angepasste Version gibt es am Ende des Blogs.
Eine Herausforderung bei der Übermittlung der Daten war zudem, dass luftdaten.info mindestens alle fünf Minuten ein Update erwartet, sonst fliegt die Meldestelle von der Webseite. Das ist so nicht beschrieben und man muss beim Timing der Skripte entsprechend aufpassen.
Außerdem muss man sich vorher bei luftdaten.info per Email registrieren und damit eine ID für seine Messstation generieren lassen. Diese Infos trägt man dann in der config.yml ein, die Teil des luftdaten-pythonm Projekts ist.
7. Backup des Servers
Bash-Skript als Cron-job mit tar auf lokales NAS.
8. Angepasste Version der Datei main.py
Wie unter (6) beschrieben, zum auslesen der Werte aus dem rrdtool
#!/usr/bin/env python3
import sys
import os
import yaml
# Import-Pfade setzen
sys.path.append(os.path.join(sys.path[0],"sds011"))
sys.path.append(os.path.join(sys.path[0],"bme280"))
import time
import json
import subprocess
import requests
import numpy as np
#from sds011 import SDS011
#from Adafruit_BME280 import *
# Config
with open("config.yml", 'r') as ymlfile:
config = yaml.load(ymlfile)
# Logging
import logging
logging.basicConfig(level=logging.DEBUG)
#bme280 = BME280(
# address=0x76,
# t_mode=BME280_OSAMPLE_8,
# p_mode=BME280_OSAMPLE_8,
# h_mode=BME280_OSAMPLE_8,
#)
# Create an instance of your bme280
#dusty = SDS011('/dev/ttyUSB0')
# Now we have some details about it
#print("SDS011 initialized: device_id={} firmware={}".format(dusty.device_id,dusty.firmware))
# Set dutycyle to nocycle (permanent)
#dusty.dutycycle = 0
class Measurement:
def __init__(self):
# pm25_values = []
# pm10_values = []
# dusty.workstate = SDS011.WorkStates.Measuring
# try:
# for a in range(8):
# values = dusty.get_values()
# if values is not None:
# pm10_values.append(values[0])
# pm25_values.append(values[1])
# finally:
# dusty.workstate = SDS011.WorkStates.Sleeping
items1 = subprocess.getstatusoutput('rrdtool lastupdate /opt/templog/data/templog_novadht.rrd | tail -1 | cut -d" " -f2,3,4,5')
items = items1[1].split()
self.pm25_value = float(items[3])
self.pm10_value = float(items[2])
self.temperature = float(items[1])
self.humidity = float(items[0])
self.pressure = 0
def sendInflux(self):
cfg = config['influxdb']
if not cfg['enabled']:
return
data = "feinstaub,node={} SDS_P1={:0.2f},SDS_P2={:0.2f},BME280_temperature={:0.2f},BME280_pressure={:0.2f},BME280_humidity={:0.2f}".format(
cfg['node'],
self.pm10_value,
self.pm25_value,
self.temperature,
self.pressure,
self.humidity,
)
requests.post(cfg['url'],
auth=(cfg['username'], cfg['password']),
data=data,
)
def sendLuftdaten(self):
if not config['luftdaten']['enabled']:
return
self.__pushLuftdaten('https://api-rrd.madavi.de/data.php', 0, {
"SDS_P1": self.pm10_value,
"SDS_P2": self.pm25_value,
"BME280_temperature": self.temperature,
"BME280_pressure": self.pressure,
"BME280_humidity": self.humidity,
})
self.__pushLuftdaten('https://api.luftdaten.info/v1/push-sensor-data/', 1, {
"P1": self.pm10_value,
"P2": self.pm25_value,
})
self.__pushLuftdaten('https://api.luftdaten.info/v1/push-sensor-data/', 11, {
"temperature": self.temperature,
"pressure": self.pressure,
"humidity": self.humidity,
})
def __pushLuftdaten(self, url, pin, values):
requests.post(url,
json={
"software_version": "python-dusty 0.0.1",
"sensordatavalues": [{"value_type": key, "value": val} for key, val in values.items()],
},
headers={
"X-PIN": str(pin),
"X-Sensor": sensorID,
}
)
# extracts serial from cpuinfo
def getSerial():
with open('/proc/cpuinfo','r') as f:
for line in f:
if line[0:5]=='Serial':
print(line[10:26])
raise Exception('CPU serial not found')
def run():
m = Measurement()
print('pm2.5 = {:f} '.format(m.pm25_value))
print('pm10 = {:f} '.format(m.pm10_value))
print('Temp = {:0.2f} deg C'.format(m.temperature))
print('Humidity = {:0.2f} %'.format(m.humidity))
print('Pressure = {:0.2f} hPa'.format(m.pressure/100))
m.sendLuftdaten()
m.sendInflux()
sensorID = "raspi-00000000a42a67d9"
starttime = time.time()
run()
print("Stopped")
Kommentare
Kommentar veröffentlichen