Feinstaubmessung mit dem Raspberry Pi

Luftfiltergeräte hört man aktuell an allen Ecken, die Corona Pandemie hat dies deutlich befördert. Da ich im Mai immer mal für 4 Wochen an einer Pollenallergie leide und ich schon einen Tipp für ein Luftfiltergerät bekommen habe, habe ich mich mit den Geräten etwas mehr auseinander gesetzt. Die Geräte messen in der Regel die Feinstaubmenge in der Luft und sind dann in verschiedenen Stufen unterwegs. Also dachte ich mir, messen wir doch erst mal den Feinstaub in der Wohnung, bevor wir Geräte aufstellen. 😉

Auf die Suche gegangen, bin ich dann auf den PM-Sensor SDS011 gestoßen, dieser kann per USB angeschlossen werden und misst in PM 2,5 und PM 10. Infos zu diesen Werten findet ihr in Wikipedia beim Suchbegriff Feinstaub. Auch die europäischen Grenzwerte findet Ihr dort. Hier nun der Sensor.

Das kleine eckige oben drauf ist ein kleiner Lüfter, dieser ist auch leicht zu hören. Also Obacht wenn ihr das Teil im Wohnzimmer aufstellen wollt. Bei mir steht er im Esszimmer und stört mich von der Geräuschkulisse nicht. Man kann das Teil jedoch auch gut in einen Schrank verlegen, denn es hat diesen silbernen Anschluss. Dort kann ein Schlauch angeschlossen werden, dieser muss am Ende eben nur die zu testende Luft frei ansaugen können. Dies gibt einem vielfältige Möglichkeiten das Teil auch verdeckt zu verbauen.

Nach dem Einstecken im Raspberry PI meldet sich dieses mit (Befehloutput -> dmesg)

[ 8.003492] usbcore: registered new interface driver ch341
[ 8.003588] usbserial: USB Serial support registered for ch341-uart
[ 8.003704] ch341 1-1.3:1.0: ch341-uart converter detected
[ 8.026555] usb 1-1.3: ch341-uart converter now attached to ttyUSB0

Zum Auslesen des Sensors nutze ich das Python Programm von marw -> https://gist.github.com/marw/ read_nova_pm_sensor.py

Ich habe das Python Programm leicht angepasst, sodass statt ttyUSB0 ein fester Name verwendet wird, den ich per UDEV Rule definiert habe. Soll einfach dafür sorgen, wenn sich ein 2. Gerät im System befindet, dass es immer das Richtige für den Sensor verwendet.

pi@blackberrypi:~ $ cat /usr/local/bin/feinstaub.py#!/usr/bin/env python3

"""
Get reading from Nova PM Sensor SDS011
(dust sensor, air quality sensor, PM10, PM2,5)
Designed to run from cron and append CSV file.
Script tested using Python3.4 on Ubuntu 14.04.
TODO: choose by dev name using udev, add dev id info, python package pyudev
    udevadm info -q property --export /dev/ttyUSB_ch341
"""

import os
import csv
import io

import logging
import datetime
import argparse

try:
    import serial
except ImportError:
    print('Python serial library required, on Ubuntu/Debian:')
    print('    apt-get install python-serial python3-serial')
    raise


LOG_FORMAT = '%(asctime)-15s %(levelname)-8s %(message)s'


def append_csv(filename, field_names, row_dict):
    """
    Create or append one row of data to csv file.
    """
    file_exists = os.path.isfile(filename)
    with io.open(filename, 'a', encoding='utf-8') as csvfile:
        writer = csv.DictWriter(csvfile,
                                delimiter=',',
                                lineterminator='\n',
                                fieldnames=field_names)
        if not file_exists:
            writer.writeheader()
        writer.writerow(row_dict)


def read_nova_dust_sensor(device='/dev/ttyUSB_ch341'):
    dev = serial.Serial(device, 9600)

    if not dev.isOpen():
        dev.open()

    msg = dev.read(10)
    assert msg[0] == ord(b'\xaa')
    assert msg[1] == ord(b'\xc0')
    assert msg[9] == ord(b'\xab')
    pm25 = (msg[3] * 256 + msg[2]) / 10.0
    pm10 = (msg[5] * 256 + msg[4]) / 10.0
    checksum = sum(v for v in msg[2:8]) % 256
    assert checksum == msg[8]
    return {'PM10': pm10, 'PM2_5': pm25}


def main():
    logging.basicConfig(format=LOG_FORMAT, level=logging.INFO)

    parser = argparse.ArgumentParser(description='Read data from Nova PM sensor.')
    parser.add_argument('--device', default='/dev/ttyUSB_ch341',
                        help='Device file of connected by USB RS232 Nova PM sensor')
    parser.add_argument('--csv', default=None,
                        help='Append results to csv, you can use year, month, day in format')
    args = parser.parse_args()

    data = read_nova_dust_sensor(args.device)
    logging.info('PM10=% 3.1f ug/m^3 PM2.5=% 3.1f ug/m^3', data['PM10'], data['PM2_5'])

    if args.csv:
        field_list = ['date', 'PM10', 'PM2_5']
        today = datetime.datetime.today()
        data['date'] = today.strftime('%Y-%m-%d %H:%M:%S')
        csv_file = args.csv % {'year': today.year,
                               'month': '%02d' % today.month,
                               'day': '%02d' % today.day,
                               }
        append_csv(csv_file, field_list, data)

if __name__ == '__main__':
    main()

Damit das Programm funktioniert, müssen die Pakete python-serial und python3-serial installiert sein.

pi@blackberrypi:~ $ sudo apt install python-serial python3-serial

Beim Aufruf des Python Programm ergibt sich folgender Output.

pi@blackberrypi:~ $ feinstaub.py
2022-01-15 02:09:03,905 INFO PM10= 10.7 ug/m^3 PM2.5= 3.0 ug/m^3

Die UDEV Rule sieht wie folgt aus.

pi@blackberrypi:~ $ cat /etc/udev/rules.d/88-ch341.rules
ACTION=="add", ATTRS{serial}=="3f980000.usb", SYMLINK+="ttyUSB_ch341"

Der folgende Befehl aktiviert die Rule

pi@blackberrypi:~ $ sudo udevadm control --reload

Danach ist der Sensor immer unter /dev/ttyUSB_ch341 zu finden.

Um die Werte aus dem Python Programm für mein Zabbix einfacher lesbar zu machen, habe ich dafür ein kleines Shell Script geschrieben. Diese liegt zwei Dateien /tmp/pm10 und /tmp/pm25 ab und liest den Wert jede Minute aus. Das Programm läuft dann dauerhaft im Hintergrund.

pi@blackberrypi:~ $ cat /usr/local/bin/feinstaub.sh
#!/bin/bash
# Script das feinstaub.py aufruft und die beiden Werte in pm10 und pm25 teilt um diese dann aus zabbix auszulesen 
# pm10 steht für partikel 10 und pm25 für partikel 2,5
while true
do
    feinstaub.py 2>&1 | cut -f 9,12 -d ' '| { read pm10 pm25; 
    echo $pm10 > /tmp/pm10;
    echo $pm25 > /tmp/pm25; }
    sleep 60
done

Die Zabbix Config hierfür sieht wie folgt aus.

Sidetip: Wem vielleicht in meinen Zabbix Configs die History storage period und der 0 Wert bei Trend storage period aufgefallen ist. Diese habe ich bewusst so gewählt. Ich möchte eine lange Zeit Echtwerte (vollständig Detailtiefe). Daher habe ich Trend völlig abgeschaltet und die History auf 8 Jahre (8×365 = 2920) hoch gesetzt.

So sieht nun ein ganz „normaler“ Tag im Haus im Zabbix Graph aus. 🙂

feinstaubgraphzabbixnormalertaginnen

BTW bei diesen Feinstaubwerten wäre kein einziger Luftfilter angelaufen, da sich die Feinstaubwerte völlig im Normalbereich befinden.

Automatisierung? Dann los mit Ansible.

# cat /etc/ansible/feinstaub.yml
---
- name: Transfer feinstaub.sh Script and feinstaub.py Python PGM and execute the script with user pi
  hosts: blackberrypi.home,raspberrypi.home
  become: yes
  tasks:
     - name: Install python3-serial python-serial 
       apt:
        pkg:
        - python3-serial
        - python-serial
     - name: Transfer the feinstaub.sh script and feinstaub.py python PGM Code and check_feinstaub.sh for cron.d  
       copy: src={{ item }} dest=/usr/local/bin/
       with_fileglob:
         - feinstaub/*
     - name: Changing Permissions shell script
       file: dest=/usr/local/bin/feinstaub.sh owner=root group=root mode=0755
     - name: Changing Permissions feinstaub.py python pgm
       file: dest=/usr/local/bin/feinstaub.py owner=root group=root mode=0755
     - name: Transfer cron.d Entry for automatic start at reboot and check script
       copy: src=/etc/ansible/cron.d/feinstaub dest=/etc/cron.d/
     - name: Transfer feinstaub tty Symlink UDEV Rule in  rules.d (/dev/ttyUSB_ch341)
       copy: src=/etc/ansible/udev/rules.d/88-ch341.rules dest=/etc/udev/rules.d/
     - name: reload udev
       command: "udevadm control --reload-rules"
     - name: kill all running feinstaub process
       shell: "killall feinstaub.sh --wait"
       ignore_errors: true # In case there is no process
     - name: Start feinstaub.sh in Backgroud
       become_user: pi
       shell: "(/usr/local/bin/feinstaub.sh >/dev/null 2>&1 &)"
       async: 10
       poll: 0

Das Ansible Playbook (YML) kann dann einfach mit folgendem Befehl verteilt werden.

ansible-playbook -u pi feinstaub.yml

Wer das Ansible Playbook genau betrachtet, dem ist sicherlicht nicht entgangen, dass 2 Zielhosts hier hinterlegt sind. Das ist richtig. Nachdem der 1. Feinstaubsensor so erfolgreich in meinem Heim lief, fragte ich mich, welche Feinstaubwerte denn draußen existieren und welchen Einfluß diese wiederum auf meine Werte im Haus haben. Lohnt es sich bei schlechten Werten im Haus zu lüften oder sind draußen gar noch schlechterer Werte?

Durch Ansible ist dies schnell auf einen 2. Raspberry bei mir in der Garage neben dem Haus verteilt. Den 2. gekauften Sensor einfach angesteckt und mit einem kleinen Schlauch an die Außenluft verbunden. Zu beachten ist bei den Sensoren, dass diese auch bei Nebel reagieren und bei ganz feinem Nieselregen. Daher nicht gleich in Panik verfallen, wenn die Werte mal explodieren und es einfach nur regnet draußen. 😉

Feinstaubsensoraussen
feinstaubansaugschlauch

So sieht das dann zusammen im Zabbix Graph aus.

feinstaubmessung_aussen_innen

Bei der Spitze morgens um 7 Uhr sieht man, was passiert wenn einer der Nachbarn seinen Kamin anwirft. 😀

Sideinfo: Wußtet Ihr, dass beim Kochen massiv Feinstaub entsteht (weit über die Grenzwerte)? Hier mal ein Graph wo in der Pfanne 10 Minuten etwas Gemüse angebraten wurde. Seitdem ist bei mir die Dunstabzugshaube immer an. 🙂 Die gute Nachricht: Mit Lüften konnte das Problem relativ schnell entschärft werden. Und nein die Küche war nicht blau. 😀

Hier noch ein Link zu einem wissenschaftlichen Bericht dazu.

https://www.leibniz-gemeinschaft.de/ueber-uns/neues/forschungsnachrichten/forschungsnachrichten-single/newsdetails/mehr-feinstaub-durch-kochen-und-heizen

Wenn Ihr Fragen oder Anmerkungen habt, schreibt mir gerne eine Mail -> info AT svenczaja.de