Raspberry Pi Zero in einen Wassersensor mit Kamera verwandeln

Seit einiger Zeit wollte ich schon ein kleines Projekt mit meinem Raspberry Pi Zero umsetzen, um den Wasserstand bei Starkregen im Außenbereich zu erfassen und bei einem bestimmten Level eine Warnnachricht per E-Mail an mich zu verschicken. Dieses Projekt habe ich nun endlich in Angriff genomen und soweit abgeschlossen.

Verwendete Hardware

Sämtliche Hardware, bis auf die Kamera habe ich bei Conrad Electronic SE (conrad.de) bestellt.
Ich habe die einzelnen Komponenten verlinkt (ohne Gewähr, bitte noch einmal selbst prüfen).

Der Versuchsaufbau

Der Versuchsaufbau sieht wie folgt aus:

Wassersensor - Versuchsaufbau
Wassersensor – Versuchsaufbau
Wassersensor - Raspberry Pi GPIO
Wassersensor – Raspberry Pi GPIO
Wassersensor - Breadboard
Wassersensor – Breadboard

Beim Anschluss der Kabel am Pi solltet ihr zwingend an die gleichen GPIOs anschließen, um das nachfolgende Python-Skript verwenden zu können. Die Verdrahtung auf den Breadboard braucht ihr nicht exakt so nachzubauen, der Stromfluss sollte jedoch entsprechend gewährleistet sein.

Wassersensor
Der oben aufgeführte Wassersensor WS 4010 wird mit einem 10 Meter langen Kabel geliefert. Ich habe versucht den Sensor mit der gesamten Kabellänge am Raspberry Pi Zero zu betreiben, jedoch ohne Erfolg. Das Kabel ist zu lang. Ich habe es deshalb auf 2,5 Meter gekürzt. Diese Kabellänge hat bei mir funktioniert und bei Wasserkontakt macht es was es soll.
Wie bereits oben beschrieben, können auch andere Kabel verwendet werden. Der Sensor ist nicht zwingend für den Bau eines Wassersensors erforderlich.

Wassersensor-Skript

Das Python-Skript „watersensor.py“ habe ich in das Verzeichnis /etc/skripte gelegt.

sudo nano /etc/skripte/watersensor.py

#!/usr/bin/python

# by strobelstefan.org
# https://strobelstefan.org/?p=7430

##################
# ATTENTION!!!!!!!
##################
# TO avoid any problems like python IndentationError: unexpected indent
# !!!! DO NOT use TAB use 4 times blank instead!!!!
##################

# Run the script on SHELL with the command
# python -t ./<name-of-script>.py

############################
# Libraries                #
############################
import RPi.GPIO as GPIO
import time
import string
import datetime
import time
import smtplib, ssl
import os
from email.mime.text import MIMEText


############################
# Defines the GIPOs        #
############################
GPIO.setmode(GPIO.BCM)
GPIO.setup(15, GPIO.IN,pull_up_down=GPIO.PUD_UP)
GPIO.setup(17, GPIO.OUT)
GPIO.setup(18, GPIO.OUT)

############################
# Defines the time        #
############################
localtime = time.localtime(time.time())


########################################
# E-Mail login credentials                #
########################################
username = "wassersensor@email.de"     # enter your complete e-mail addess!
password = "password"    # enter password for your e-mail addres

############################
# General Email Variables  #
###########################
# We call this values in the section "def email(condition)"
From = "wassersensor@email.de"            # E-Mail address that is displayed in the from field
To =  "wassersensor@email.de"            # Define the recipient of your e-mail(s)


############################
# message1 - WET Message   #
############################
# Defines the complete e-mail message1
# To this we refer in the section "def email(condition) as wet"
message1 = """From: Water Sensor <wassersensor@email.de>
To: Water Sensor <wassersensor@email.de>
MIME-Version: 1.0
Content-type: text/html
Subject: Water Sensor - I am wet - RUN!!!

<h1>Water Sensor is WET - RUN!</h1>
<b>I just encountered that the water sensor is wet.</b>
<br>
<b>You should check immediately to avoid damage to your property.</b>
"""

############################
# message2 - DRY MESSAGE   #
############################
# Defines the complete e-mail message2
# To this we refer in the section "def email(condition) as dry"
message2 = """From: Water Sensor <wassersensor@email.de>
To: Water Sensor <wassersensor@email.de>
MIME-Version: 1.0
Content-type: text/html
Subject: Water Sensor - Dry - Relax!!!

<h1>Water Sensor is DRY - RELAX!</h1>
<b>I just encountered that the water sensor is dry.</b>
<br>
<b>Relax and enjoy</b>
"""

############################
# E-mail conditions        #
############################
# The different e-mail conditions are defined in this section
# You can setup more conditions if necessary in the same fashion
def email(condition):
    if condition == 'wet':
        print 'Send E-Mail - Wet'
        mail = smtplib.SMTP('smtp.strato.de', 587)
        mail.ehlo()
        mail.starttls()
        mail.login(username,password)
        mail.sendmail(From,To,message1)
        mail.close()
        print 'Sending completed - Wet'
    if condition == 'dry':
        print 'Send E-Mail - Dry'
        mail = smtplib.SMTP('smtp.strato.de', 587)
        mail.ehlo()
        mail.starttls()
        mail.login(username,password)
        mail.sendmail(From,To,message2)
        mail.close()
        print "Sending completed - Dry!"



############################
# LED conditions           #
############################
# The different LED conditions are defined in this section
# You can setup more conditions if necessary in the same fashion
def LED(condition):
    if condition == 'LEDwet':
        print 'LED RED GPIO 18 - Section LEDwet Start'
        GPIO.output(17, True)
        GPIO.output(18, False)
        print 'LED RED GPIO 18 - Section LEDwet End'
    if condition == 'LEDdry':
        print "LED WHITE GPIO 17 - Section LEDdry Start"
        GPIO.output(18, True)
        GPIO.output(17, False)
        print 'LED RED GPIO 17 - Section LEDdry End'


############################
# Tests whether water is present.
############################
# returns 1 for dry
# returns 0 for wet
# tested to work on pin 15
print 'Test for water - Is water present?'
def RCtime (RCpin):
    reading = 0
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(RCpin, GPIO.OUT)
    GPIO.output(RCpin, GPIO.LOW)
    # time.sleep(0.1)
    time.sleep(1)
    GPIO.setup(RCpin, GPIO.IN)
    # This takes about 1 millisecond per loop cycle
    while True:
        if (GPIO.input(RCpin) == GPIO.LOW):
            reading += 1
        if reading >= 1000:
            return 0
        if (GPIO.input(RCpin) != GPIO.LOW):
            return 1

############################
# Water testing
############################
print "Wait for Water ... (Start while True)", localtime
while True:
    time.sleep(5)
#    print water_count
    water_count = 0
    if RCtime(15) == 0:
        print "Sensor is wet (Value = 0)", localtime
        LED('LEDwet')
#        email('wet') # just uncomment if you would like to get tons of messages
        while True:
            time.sleep(180)
            if RCtime(15) == 0:
                print "Sensor is still wet (Value = 01)", localtime
#                email('wet') # just uncomment if you would like to get tons of messages
                LED('LEDwet')
		if water_count == 1:
        	    email('wet')
#                   Calls script camera.sh to take a picture
                    os.system('sh /etc/skripte/camera.sh')
#                   Calls the script newest-file.sh to send you the pic via e-mail
                    os.system('sh /etc/skripte/newest-file.sh')
                water_count = water_count + 1
                continue
            if RCtime(15) == 1:
                print "Sensor is dry (Value = 1)", localtime
                email('dry')
                LED('LEDdry')
#               Calls script camera.sh to take a picture
                os.system('sh /etc/skripte/camera.sh')
#               Calls the script newest-file.sh to send you the pic via e-mail
                os.system('sh /etc/skripte/newest-file.sh')
                print "Wait for Water again ... (Start while True again)", localtime
            break

Wenn das Skript nach eurer Zufriedenheit funktioniert, könnt ihr sämtliche „print“-Befehle aukommentieren. Das Ausgeben dieser Meldungen ist dann nicht mehr erforderlich, die dienen nur dem Überprüfen des Skripts.

Die Beschreibung für die Skripte „camera.sh“ und „newest-file.sh“ findet ihr weiter unten in diesem Artikel.

Das Skript könnt ihr mit dem Befehl starten:

sudo python /etc/skripte/watersensor.py

Es kann dabei vorkommen, dass es Probleme mit der Formatierung von Leerzeichen und Tabs im Skript gibt, dann wird der Fehler ausgegeben:

Fehler: IndentationError: unexpected indent

Es gibt eine Möglichkeit, das Python-Skript automatisch prüfen zu lassen. Dazu geht ihr am Besten wie folgt vor

sudo cp /etc/skripte/watersensor.py /home/pi/
cp /home/pi/watersensor.py /home/pi/watersensor.py-bak

Mit diesem Befehl prüft ihr das Skript auf die korrekte Formatierung von Tabs zu Leerzeichen:

expand -t 4 /home/pi/watersensor.py > /home/pi/watersensor-geprüft.py

Kopiert dann das Skript zurück an seinen Platz und führt es erneut aus

sudo cp /home/pi/watersensor-geprüft.py /etc/skripte/watersensor.py

Kamera am Raspberry Pi aktivieren

Als weiteres Feature soll eine angeschlossene Kamera in regelmäßigen Abständen Standbilder aufnehmen.
Bevor die Kamera an einem Pi verwendet werden kann, muss das aktiviert werden.

sudo raspi-config

Kamera aktivieren
Kamera aktivieren
Kamera aktivieren
Kamera aktivieren
Kamera aktivieren
Kamera aktivieren

Externen USB-Stick einbinden

Da die Schwachstelle eines Rasperry Pis die SD-Karte darstellt, lasse ich die Bilder auf einen angeschlossenen alten USB-Stick speichern. Der Stick wird automatisch engebunden und kann dann als Laufwerk verwendet werden.

Einbinden externer USB-Stick

sudo mkrid /mnt/usb
sudo aptitude install ntfs-3g
sudo nano /etc/fstab

Dort die folgende Zeile eintragen:

# Einbinden USB-Stick
UUID=123456789 /mnt/usb ntfs defaults,auto,umask=000,users,rw 0 0

Die UUID des externen USB-Sticks lässt sich mit dem Befehl herausfinden.

sudo blkid

Regelmäßige Standbilder

Das Skript zum erstellen regelmäßger Standbilder sieht wir folgt aus. Ich habe mich dazu entschieden die kleine App „raspistill“ zu verwenden, die bei jedem Raspian mit an Board ist. Möchte jemand Videos erzeugen, wäre „raspivid“ das Gegenstück zu „raspistill„.

sudo mkdir /etc/skripte
sudo nano /etc/skripte/camera.sh

#!/bin/bash

# by strobelstefan.org
# https://strobelstefan.org/?p=7430

DATE=$(date +"%Y-%m-%d_%H%M")

# Takes an image in FULL size
#raspistill -o /mnt/usb/$DATE.jpg -rot 90

# Takes an image in SMALL size
raspistill -q 10 -e jpg -o /mnt/usb/$DATE.jpg -rot 90

Erklärung:
Im Skript sind zwei Möglichkeiten für die Aufnahme von Bildern vorgesehen, einmal in voller Auflösung/Qualität (= FULL size) und einmal in kleiner Auflösung/Qualität (= SMALL size). Die kleinen Bilder sind u.U. dann zu bevorzugen, wenn Ihr euch die Bilder aufs Handy schicken wollt und die Verbindung nicht die schnellste ist oder Ihr euer Datenvolumen nicht zu arg beanspruchen wollt.

Der Unterschied ist in etwa folgender

  • FULL size = ~ 3 MB
  • SMALL size = ~ 300 kb

Die Einstellung -rot 90 rotiert das Bild um 90°.

Versand des Standbilds via E-Mail

Damit das Standbild nicht auf dem Raspberry versauert, bis ich mich dort einlogge und das Bild anschaue, möchte ich es sofort als E-Mail auf eine definierte E-Mail-Adresse zugesendet bekommen.
Eine Anleitung zur Installation von Postfix ist hier im Blog zu finden ➡ Motion mit E-Mail-Versand
Als Ergänzung habe ich noch eine „sender_canonical“ angelegt.

Dann enthält die „main.cf“ eine weitere Zeile:

relayhost= smtp.strato.de:587
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
sender_canonical_maps = hash:/etc/postfix/sender_canonical
smtp_sasl_security_options = noanonymous
smtp_use_tls = yes
smtp_enforce_tls = no
smtp_tls_enforce_peername = no
smtpd_tls_security_level = may

Die „sender_canonical“ sieht folgendermaßen aus

sudo nano /etc/postfix/sender_canonical

Inhalt der „sender_canonical

pi 

Anschließend die sender_canonical-Datei noch in deine db-Datei um wandeln, postfix neustarten und fertig

sudo postmap /etc/postfix/sender_canonical
sudo /etc/init.d/postfix restart

Damit nun das aktuellste Standbild per E-Mail gesendet wird, habe ich ein weiteres Skript angelegt. Dieses Skript verwendet das Tool „mutt“ ( ➡ http://www.mutt.org/doc/manual/), dass ggf.s noch auf eurem Pi zu installieren ist.

sudo aptitude install mutt

sudo nano /etc/skripte/newest-file.sh

#!/bin/bash

# by strobelstefan.org
# https://strobelstefan.org/?p=7430

# E-Mail where the log file should be mailed to
EMAIL="mycam@strobelstefan.de"
picpath='/mnt/usb/'

newest=$(ls -rt ${picpath}*.jpg | tail -n1)
echo $(date +%Y-%m-%d_%H-%M-%S) '- Standbild aus dem Keller' | mutt ${EMAIL} -a $newest -s "Standbild aus dem Keller"

Regelmäßiges Löschen alter Standbilder

Eine weitere sinnvolle Funktion ist das regelmäßige Löschen der alten Standbilder, damit euer USB-Stick nicht volläuft. Das würde dazu führen, dass ihr nicht mehr aktuelle Bilder per E-Mail erhaltet.
Das Löschen habe ich ebenfalls über ein eigenes kleines Skript erledigt.

sudo nano /etc/skripte/delete_files.sh

In meinem Skript werden die Bilder, die älter als 2 Tage sind automatisch gelöscht. Der Wert kann über den Befehlt „-mtime +x“ beliebig geändert werden.

#!/bin/bash

# by strobelstefan.org
# https://strobelstefan.org/?p=7430

find /mnt/usb/* -mtime +2 -exec rm {} \;

Zum Schluss sind die Skripte noch ausführbar zu machen

cd /etc/skripte
sudo chmod+x camera.sh  delete_files.sh  newest-file.sh

Skripte automatisch starten und ausführen

Damit die 3 Skripte auch nach einem Neustart des kleinen Pis laufen und mir Bilder schicken habe ich diese in die crontab eingetragen.
Die beiden Skripte camera.sh und newest-file.sh könnt ihr auch auskommentieren oder die Zeiten ändern, da bei einem Wasserkontakt die beiden sh-Dateien über das Python-Skript angestartet werden.
Für eine regelmäßige visuelle Prüfung, wäre aber ein Start der Skripte über CRON erforderlich.

sudo crontab -e

Dort die folgenden Zeilen eintragen:

# Starts the Water Sensor Python Script
@reboot python /etc/skripte/watersensor.py &

# Takes a still image every hour
0 * * * * /bin/bash /etc/skripte/camera.sh

# Send newest still iamge file via e-mail every hour + 1 min
1 * * * * /bin/bash /etc/skripte/newest-file.sh

# deletes still image files older than 1 day once a day
0 0 * * * /bin/bash /etc/skripte/delete_files.sh

Weitere Anpassungen

Als weitere Anpassungen für meinen Wassersensor habe ich noch folgende Pakete installiert und angepasst, um die Sicherheit ein wenig zu erhöhen.

Zudem habe ich die Logs in den Arbeitsspeicher ausgelagert.
Der Vorteil, die Schreibvorgänge auf die SD-Karte werden reduziert und die Lebensdauer der Karte erhöht sich.

Der Nachteil, die Logs sind nach einem Neustart des Pis nicht mehr verfügbar.

Für mich war der Vorteil ausschlaggebend, weshalb ich die Anpassungen wie folgt vorgenommen habe:

sudo nano /etc/fstab

In die fstab ist folgende Zeile einzutragen:

#Auslagern Arbeitsspeicher
none        /var/log        tmpfs   size=5M,noatime         00

Eine weitere kleine Anpassung, um die Lebensdauer der SD-Karte zu erhöhen ist das Deaktivieren von Swapping. Das sog. Swapping lagert bestimmte Teile des Arbeitsspeicher auf die Festplatte eines PCs aus. Im Falle eines Raspberry Pis ist das eben die SD-Karte.

Deaktivieren lässt sich das Ganze über den Befehl:

sudo systemctl disable dphys-swapfile

Überprüfen kann man die Deaktiviereung mit dem Befehl:

sudo systemctl status dphys-swapfile

Swapping abschalten
Swapping abschalten

Den Erfolg kann man ganz gut mit dem kleinen Tool „htop“ verfolgen (htop muss noch installiert werden)

sudo aptitude install htop
htop

Weitere Ideen

Anstatt die Bilder per E-Mail zu versenden, wäre auch ein Upload zu Dropbox möglich oder parallel zum E-Mail-Versand. Dazu könnte man evtl. das Skript ➡ Dropbox-Uploader nutzen. Sollte das jemand auprobieren, bitte lasst mich eure Erfahrung wissen.

Man kann auch mehrere Wassersensoren an den Pi anschließen und so z.B. Füllstände (Minimum, Maximum, Nachfüllen, etc.) überwachen.
Die Wassersensoren wären dann an GPIO15 und an zwei anderen GPIOs anzuschließen. Die Logik des Python-Skrits könnte dafür analog übernommen und entsprechend erweitert werden.

Auch eine Türe könnte damit überwacht werden. Hierbei wäre „Dry“ offen und „Wet“ geschlossen.

That´s it!

Verbesserungsvorschläge sind herzlich willkommen 🙂

Bildquelle: raspberrypi.org

Schreibe einen Kommentar

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

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.