PIR-Sensor-Daten sammeln mit InfluxDB auf dem Raspberry Pi

Nachdem InfluxDB nun auf dem Raspberry Pi läuft, soll die Datenbank auch einige Daten sammeln dürfen. Eine sehr einfache Möglichkeit bietet ein PIR-Sensor (engl. passive infrared), oder auch Bewegungsmelder. Derartige Sensoren sind für wenig Geld erhältlich und können direkt ohne komplizierte Schaltung an die GPIO-Ports des Raspberry Pi angeschlossen werden.

PIR-Sensor anschließen…

Der Anschluss und die ersten Schritte werden sehr ausführlich unter „Bewegungsmelder Typ PIR am Raspberry Pi“ beschrieben. PIR-Sensoren können bei den einschlägigen Elektronik-Versendern oder natürlich auch von Amazon bezogen werden. Der PIR-Sensor besitzt drei Pins, die direkt mit passenden Kabeln (female-female-Anschluss!) am Raspberry Pi angeschlossen werden können. Bei der Belegung habe ich mich ebenfalls an der Beschreibung des genannten Blog-Eintrags gehalten, diese sieht wie folgt aus:

 

PIR-Sensor Raspberry Pi
+5V +5V (Pin 2)
OUT GPIO 7 (Pin 26)
GND Ground (Pin 6)

Fertig aufgebaut sieht der Raspberry Pi inkl. PIR-Sensor und zum Testen im Flur stehend so aus:

raspberry_pi_mit_pir_sensor

…und testen

Für einen ersten Test des PIR-Sensors kann z.B. die WiringPi-Library verwendet werden. Leider befindet sich WiringPi nicht im Standard-Umfang der verwendeten Raspbian-Linux-Distribution, daher muss die Library zunächst installiert werden. Zunächst muss das Versionsverwaltungssystem Git installiert werden, falls es auf dem System noch fehlen sollte:

sudo apt-get install git

Danach kann der Quellcode per git kopiert – oder auch „geclont“ werden:

git clone git://git.drogon.net/wiringPi

Die Installation ist mit einem Schritt erledigt:

cd wiringPi
./build

Nach kurzer Wartezeit ist WiringPi einsatzbereit. Bemerkenswerterweise sorgt das build-Skript nicht nur für die Kompilierung, sondern kopiert auch das erzeugte Binary nach /usr/local/bin, somit ist kein explizites Installieren notwendig. Ob es funktioniert hat, kann am besten durch Aufruf von „gpio“ geprüft werden, der Befehl ohne weitere Optionen zeigt eine Liste der möglichen Aktionen.

Nun kann der PIR-Sensor ausgelesen werden. Jedoch besitzt die WiringPi-Library eine eigene Nomenklatur bzgl. der Pin-Belegung. Die folgenden Beispiele gehen von der Raspberry-Pi-Belegung aus. Bei der Nutzung des gpio-Kommandos muss daher die Option „-g“ angegeben werden. Der einfachste Befehl zum Auslese des Pin 7 lautet insofern:

gpio -g read 7

Damit wird Pin 7 zum Zeitpunkt der Ausführung ausgelesen. Als Ergebnis wird daraufhin entweder eine „0“ für „keine Bewegung“ oder eine „1“ für „Bewegung erkannt“ ausgegeben. Um den Bewegungsmelder ein wenig auf seine Reichweite und Empfindlichkeit zu testen, kann etwa folgendes Shell-Skript verwendet werden:

#!/bin/bash

while true
do
        gpio -g read 7
done

Innerhalb der Endlosschleife wird der Zustand permanent ausgelesen und auf der Konsole ausgegeben. Jedoch ist dieses Verfahren nicht gerade ressourcenschonend.

PIR-Sensor mit Python auslesen

Eine bessere Möglichkeit ist ein eventbasiertes Verfahren, in dem die Aktion des GPIO-Ports mit einer Callback-Funktion verknüpft wird. Dies ist im folgenden Beispiel mit Python und der GPIO-Library umgesetzt:

import RPi.GPIO as GPIO
import time
import os
GPIO.setmode(GPIO.BCM)
PIR_PIN = 7
GPIO.setup(PIR_PIN, GPIO.IN)
def motion_detected(PIR_PIN):
     os.system("echo \"Aktion erkannt\"")
     print GPIO.input(PIR_PIN)

print "PIR Testscript (STRG+C zum beenden)"
time.sleep(2)
print "Bereit"
try:
    GPIO.add_event_detect(PIR_PIN, GPIO.BOTH, callback=motion_detected)
    while 1:
        time.sleep(100)
except KeyboardInterrupt:
    print "Beenden"
GPIO.cleanup()

Das Skript ist denkbar einfach. Nach der Initialisierung wird die Funktion motion_detected() definiert. Als Parameter wird die Nummer des GPIO-Pins übergeben. Der Hauptteil des Skriptes definiert einen Event, der die Funktion auslöst. Im Beispiel wird die Funktion bei einer Zustandsänderung des GPIO-Pins aufgerufen. Im Original-Beispiel von o.g. Website wurde hingegen durch den Event-Typ „GPIO.RISING“ die Funktion nur dann aufgerufen, wenn eine Bewegung (bzw. eine „steigende Flanke“) erkannt wurde. Zusätzlich wird im hier gezeigten Skript der Wert des GPIO-Pins ausgegeben – also die zuvor genannte 0 oder 1. Die Endlosschleife im Skript besitzt zwar ebenfalls eine Endlosschleife, jedoch wird darin nur eine Wartezeit von 100 Sekunden zwischen den Durchläufen definiert, was zu einer spürbaren CPU-Entlastung führt. Durch Strg-C kann das Skript beendet werden.

Das Skript könnte nun etwa als Systemdienst eingerichtet werden, so dass es beim Booten automatisch gestartet wird. Im o.g. Blog-Eintrag wird dieses Verfahren genauer beschrieben.

PIR-Sensor-Daten mit InfluxDB speichern

Das gezeigte Skript alleine gibt jedoch nur die jeweilige Aktion aus – interessanter wäre die Speicherung in einer Datenbank zur nachfolgenden Auswertung. Dazu ist nur eine kleine Erweiterung notwendig, die bei Auftreten eines Events bzw. einer Aktion (d.h. Bewegung oder Rückgang in den Null-Zustand) den Zeitstempel sowie den Event an sich in der Datenbank speichert. Als Zeitreihendatenbank (Time Series database) ist InfluxDB dafür prädestiniert. Glücklicherweise unterstützt die Python-Library bereits die hier verwendete Version InfluxDB 0.9.

Zur Installation dient die Paketverwaltung für Python-Module namens „pip“. Leider wird bei Nutzung des „python-pip“-Paketes aus der Raspbian-Distribution die Python-Version 2.6 installiert, während standardmässig 2.7 eingerichtet ist (zur Kontrolle: python -V aufrufen). Da dies nicht erwünscht ist, erfolgt zunächst die Einrichtung von Python-PIP per Skript „get–pip.py“:

wget https://bootstrap.pypa.io/get-pip.py
sudo python get-pip.py
sudo pip install virtualenv

Danach kann die InfluxDB mit Hilfe von „pip“ installiert werden:

sudo pip install influxdb

Für einen ersten Test auf der Kommandozeile siehe folgendes Beispiel:

pi@raspberrypi ~ $ python
Python 2.7.3 (default, Mar 18 2014, 05:13:23)
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from influxdb import InfluxDBClient
>>> json_body = [
...     {
...         "name": "cpu_load_short",
...         "tags": {
...             "host": "server01",
...             "region": "us-west" 
...         },
...         "timestamp": "2009-11-10T23:00:00Z",
...         "fields": {
...             "value": 0.64
...         }
...     }
... ]
>>> client = InfluxDBClient('localhost', 8086, '<username>', '<password>', 'example')
>>> client.create_database('example')
>>> client.write_points(json_body)
True

>>> result = client.query('select * from cpu_load_short')
>>> print("Result: {0}".format(result))
Result: {u'cpu_load_short': [{u'value': 0.64, u'time': u'2009-11-10T23:00:00Z'}]}

Dabei wird eine Datenbank namens „example“ angelegt, in der die Daten im JSON-Format gespeichert werden. Die darauf folgende Abfrage liest den Wert aus.

Im Folgenden das komplette Skript zum Speichern der PIR-Sensor-Werte:

import RPi.GPIO as GPIO
import time
import os
from influxdb import InfluxDBClient
from influxdb.client import InfluxDBClientError

GPIO.setmode(GPIO.BCM)
PIR_PIN = 7
GPIO.setup(PIR_PIN, GPIO.IN)
DBNAME = 'pir_sensors'
DBHOST = 'localhost'
DBPORT = 8086
DBUSER = '<insertuserhere>'
DBPASSWORD = '<insertpasswordhere>'

client = InfluxDBClient(DBHOST, DBPORT, DBUSER, DBPASSWORD, DBNAME)

print("Create database: " + DBNAME)
try:
    client.create_database(DBNAME)
except InfluxDBClientError:
    # Drop and create
    client.drop_database(DBNAME)
    client.create_database(DBNAME)

def insert_data(client, sensorPin, motion):
    pointValues = [
        {
            "name": "pir",
            'fields':  {
                'value': str(motion),
            },
            'tags': {
                "sensorPin": str(sensorPin),
            }
        }
    ]
    client.write_points(pointValues)



def motion_detected(PIR_PIN):
    insert_data(client,PIR_PIN,GPIO.input(PIR_PIN))

print "PIR Testscript (STRG+C zum beenden)"
time.sleep(2)
print "Bereit"
try:
    GPIO.add_event_detect(PIR_PIN, GPIO.BOTH, callback=motion_detected)
    while 1:
        time.sleep(100)
except KeyboardInterrupt:
    print "Beenden"
GPIO.cleanup()

Der grundlegende Aufbau ist bereits bekannt. Zusätzlich initialisiert das Skript zu Beginn die Datenbank. Achtung – falls die Datenbank bereits existiert, wird hier rigoros eine neue, leere Datenbank unter gleichem Namen angelegt. Dies ist für Tests praktikabel, für den Betrieb hingegen weniger empfehlenswert.

Innerhalb der motion_detected()-Funktion erfolgt ein weiterer Funktionsaufruf von insert_data(). Dabei wird die passende JSON-Struktur erzeugt und anschließend gespeichert. Als Bezeichner (in etwa analog zu „Tabelle“ aus RDBMS) dient der Name „pir“. Der Wert 0 oder 1 für Bewegung oder keine Bewegung wird innerhalb von „fields“ abgelegt. Dabei ist zu beachten, dass der Aufruf eventgesteuert erfolgt, d.h. nur in dem Moment stattfindet, in der eine Bewegung erkannt wird oder in dem der Sensor in den Zustand „keine Bewegung“ über geht. Um ggf. weitere Sensoren speichern zu können, wird die Unterscheidung per „Tag“ definiert, d.h. die GPIO-Pin wird als Tag abgelegt. Zwar sind sowohl Zustände, d.h. 0 oder 1, als auch die GPIO-Pins numerisch, jedoch müssen diese Werte in einen String konvertiert werden – dies wird nicht vom InfluxDB-Treiber übernommen.

Bemerkenswert ist, dass der Zeitstempel automatisch hinzugefügt wird, falls dieser nicht explizit angegeben ist. Im gezeigten Skript ist es somit nicht nötig, die aktuelle Zeit auszulesen und in die JSON-Struktur einzubauen. Die Speicherung erfolgt als UTC, dies ist bei der Abfrage zu berücksichtigen.

Die einfachste Abfrage besteht aus der Auflistung aller Events:

select * from pir

Dabei werden alle gespeicherten Werte inklusive Zeitstempel ausgegeben.

Interessanter ist die Anzahl der Bewegungen pro Zeiteinheit, etwa innerhalb von 10 Minuten in einem bestimmten Zeitraum:

SELECT count(value) FROM pir where  time >= '2015-03-30' and time < '2015-04-04' group by time(10m)

In einem Diagramm dargestellt und mit einer minimalen GUI ausgestattet lässt sich damit eine Art Bewegungsmuster anzeigen. Genau das wird Thema eines der nächsten Blog-Einträge sein…

 

Ein Gedanke zu „PIR-Sensor-Daten sammeln mit InfluxDB auf dem Raspberry Pi“

  1. Für alle die eine Fehlermeldung bei dem „finalen“ Skript bekommen:

    In den neuen InfluxDB Versionen heißt ’name‘ = ‚measurement‘

    d.h. in Zeile 29 ’name‘ mit ‚measurement‘ austauschen.

    kleiner Tipp: Wenn ihr ’select * from pir‘ ausprobiert und es gibt keinen output, oben rechts im Web-Interface muss die richtige Database gewählt werden (pir_sensors), dann klappts.

    Vielen Dank für die Anleitung !

Schreibe einen Kommentar

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

Tags:
Kategorien: Hardware Programmierung