Doorbell – Teil 1: Schaltplan und Programm

War irgendwann klar dass ich mir einen Raspberry Pi hole, fand auch (natürlich erst nach dem Kauf) einen Anwendungsfall >>> unsere Türklingel.

Funktion JETZT:
Klingel ist an ISDN-Anlage (ohne sonstige Funktion) angeschlossen. Telefon klingelt wenn jemand an der Tür leutet.

Funktion SOLL:
Jemand klingelt, Audio wird abgespielt, Bild wird vom Besucher gemacht, Upload per SFTP auf Webserver, Pushmessage auf iPhone/iPad/Android

Die Schaltung ist überschaubar. Verwende einen Pullup / Pulldown Resistor (muss noch testen was besser funktioniert, da ich gemerkt habe dass statische Aufladung den Eingang setzt). Eine LED für die Anzeige dass das Python-Script läuft und eine LED für Aktivierung der Doorbell-Funktion.

Doorbell_Schaltplan

Hab es jetzt zum testen auf eine Steckplatine aufgebaut, funktioniert soweit. Werde es aber noch auf eine Platine löten, damit dass schön kompakt wird.

Zuerst wollte ich das ganze mit Mono programmieren, da ich Mono noch eine Chance geben wollte. Aber da es an einigen Ecken zu beginn schon zum knarzen begann, habe ich auf Python zurückgegriffen.

Finde Python keine schöne Sprache, aber mächtig. Naja, mein Anwendungsfall ist überschaubar, somit auch die paar Zeilen Programmcode.

Ich gehe hier nicht auf die Standard-Funktionen des Raspberrys ein, da es viele gute Anleitungen im Internet gibt.

Initialisierung

Alle benötigten Abhängigkeiten + Eventhandler (ausschalten der Status-LED) + Logging Methode. Wichtig ist hier auch noch das Audio auf 100% zu stellen. Da ich das Signal per Funk an Boxen weitergebe, ist hier wichtig dass der AUX Anschluss auf Maximum ist. Als letztes werden die IOs konfiguriert.

from time import gmtime, strftime, sleep
from datetime import datetime
import RPi.GPIO as GPIO
import os
import subprocess
import os.path
import httplib, urllib
import pysftp
import atexit
import traceback
import logging
import time

# On exit
def exit_handler():
    GPIO.output(22, False)
atexit.register(exit_handler)

# Logging
logger = logging.getLogger('doorbell')
hdlr = logging.FileHandler('/home/pi/Desktop/doorbell/web/doorbell.txt')
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
hdlr.setFormatter(formatter)
logger.addHandler(hdlr)
logger.setLevel(logging.INFO)

# Start
print(strftime("%Y-%m-%d %H:%M:%S", gmtime()) + " Doorbell started")
logger.info("Doorbell started")

# Volume
cmdVolume='amixer set PCM -- 1000'
subprocess.call(cmdVolume, shell=True)

# Pins
GPIO.setmode(GPIO.BOARD)
GPIO.setup(15, GPIO.OUT)
GPIO.setup(22, GPIO.OUT)
GPIO.output(15, False)
GPIO.output(22, True)
GPIO.setup(11, GPIO.IN)

Alles eingepackt in eine While-Dauerschleife für ich folgenden Code nacheinander aus:

# Setup
now=strftime("%Y-%m-%d %H:%M:%S", gmtime())
filename=strftime("%Y-%m-%d_%H.%M.%S", gmtime())  + '.jpg'

print(now + " Button pressed")
logger.info("Button pressed")
GPIO.output( 15, True)

Hier wird einfach nur geloggt, wenn der Button gedrückt wurde (Datum, Uhrzeit) und zusätzlich die LED für den Programmablauf eingeschaltet. Ich speichere mir gleich das Datum in eine Variable um es später für die Bilddatei zu verwenden.

# Audio
print("--> Audio")
logger.info("--> Audio")
subprocess.Popen(['aplay', '/home/pi/Desktop/doorbell/ringtone.wav'])

Nun spielen wir das Audiofile (in meinem Fall eine WAV-Datei) ab. Hier ist die Besonderheit dass es parallel zum Ablauf des Scripts passiert. Dass heißt hier wird ein separater Thread gestartet. Achja, TIP: nehmt am besten einen nervigen Sound (habe mich für Super Mario 1 Theme entschieden) dass kommt in der Frauenwelt sehr gut an :^)

# Camera
print("--> Camera")
logger.info("--> Camera")
cmdCam='raspistill -o ' + '/home/pi/Desktop/doorbell/web/photos/' +  filename
subprocess.call(cmdCam, shell=True)

Der Kamera Aufruf ist denkbar einfach. Hier habe ich versucht auch im separaten Thread das Bild zu speichern, hatte aber bei jedem 10. Bild ein Problem, welches ich (noch) nicht nachvollziehen konnte. Ihr seht schon dass sich die Bilder in einem Ordner web befinden. Ich habe mich im Nachhinein dafür entschieden einen Webserver auf dem Pi laufen zu lassen. Dadurch hab ich mehr Kontrolle und kann von außen auch z.B. einen Livestream der Cam anschauen sowie die Logdatei oder einfach per SSH verbinden.

Wie oben erwähnt habe ich angedacht per SFTP das Bild auf meinen Webserver bei Uberspace zu laden. Habe ich auch getestet und das wäre der Code dafür (auskommentiert):

# Upload Foto
#print("--> Upload")
#logger.info("--> Upload")
#srv = pysftp.Connection(host="MeinServer.de", username="MyUsername",
#                        password="MyPassword", log="True")
#srv.chdir("/Mein/Ordner/Mit/Photos/Auf/Server")
#srv.put('/Mein/Ordner/Mit/Photos/Lokal/' + filename)
#srv.close()

Nun zum Teil der mir am meisten Spaß gemacht hat. Funktion ist genial und einfach zu implementieren. Der Dienst nennt sich Pushover, ist für meine Anwendung kostenlos da ich unter 7500 Pushes im Monat bleibe (außer ich bekomme sehr viel Besucher). Die API ist sehr gut dokumentiert und es gibt Beispiele für viele Sprachen.

# Pushover
print("--> Pushover")
logger.info("--> Pushover")
conn = httplib.HTTPSConnection("api.pushover.net:443")
conn.request("POST", "/1/messages.json",
             urllib.urlencode({
                              "token": "app-token",
                              "user": "user-token",
                              "title": "Doorbell",
                              "message": "KNOCK KNOCK",
                              "url": "http://irgendwo.de/photos/" + filename,
                              "url_title": "Image",
                              }),
             { "Content-type": "application/x-www-form-urlencoded" })
conn.getresponse()

Das ganze wird bei mir wie erwähnt auf Android, iPad und iPhone gepusht und dauert ca. 1-3 Sekunden nach drücken der Klingel, was mehr als ausreicht.

2013-11-30 00.11.54 2013-11-30 00.10.19  2013-11-30 00.10.23

 

Lockscreen > Pushover App Übersicht > Detail

Mit einem Tap auf Image wird mir dann auch das Bild des Besuchers angezeigt. Die Farben sind Tagsüber OK. In der nacht wird das ganze schwarz/weiß, aber die Qualität und Erkennbarkeit ist durchgehend gut.

2013-11-30 08.30.54

Hier sei erwähnt dass ich nicht die Standard Raspberry Pi Camera gekauft habe sondern die IR-Version.

Zusätzlich habe ich mir einen HTTP-Livestream gebaut, damit ich per iPhone/iPad die Kamera live betrachten kann (diente nur zum einstellen).

Anbei das ganze Script:

from time import gmtime, strftime, sleep
from datetime import datetime
import RPi.GPIO as GPIO
import os
import subprocess
import os.path
import httplib, urllib
import pysftp
import atexit
import traceback
import logging
import time

# On exit
def exit_handler():
    GPIO.output(22, False)
atexit.register(exit_handler)

# Logging
logger = logging.getLogger('doorbell')
hdlr = logging.FileHandler('/home/pi/Desktop/doorbell/web/doorbell.txt')
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
hdlr.setFormatter(formatter)
logger.addHandler(hdlr)
logger.setLevel(logging.INFO)

# Start
print(strftime("%Y-%m-%d %H:%M:%S", gmtime()) + " Doorbell started")
logger.info("Doorbell started")

# Volume
cmdVolume='amixer set PCM -- 1000'
subprocess.call(cmdVolume, shell=True)

# Pins
GPIO.setmode(GPIO.BOARD)
GPIO.setup(15, GPIO.OUT)
GPIO.setup(22, GPIO.OUT)
GPIO.output(15, False)
GPIO.output(22, True)
GPIO.setup(11, GPIO.IN)

# Loop
while 1:
    if GPIO.input(11):
        try:
            # Setup
            now=strftime("%Y-%m-%d %H:%M:%S", gmtime())
            filename=strftime("%Y-%m-%d_%H.%M.%S", gmtime())  + '.jpg'

            print(now + " Button pressed")
            logger.info("Button pressed")
            GPIO.output( 15, True)

            # Audio
            print("--> Audio")
            logger.info("--> Audio")
            subprocess.Popen(['aplay', '/home/pi/Desktop/doorbell/ringtone.wav'])

            # Camera
            print("--> Camera")
            logger.info("--> Camera")
            cmdCam='raspistill -o ' + '/home/pi/Desktop/doorbell/web/photos/' +  filename
            subprocess.call(cmdCam, shell=True)

            # Pushover
            print("--> Pushover")
            logger.info("--> Pushover")
            conn = httplib.HTTPSConnection("api.pushover.net:443")
            conn.request("POST", "/1/messages.json",
                         urllib.urlencode({
                                          "token": "app-token",
                                          "user": "user-token",
                                          "title": "Doorbell",
                                          "message": "KNOCK KNOCK",
                                          "url": "http://irgendwo.de/photos/" + filename,
                                          "url_title": "Image",
                                          }),
                         { "Content-type": "application/x-www-form-urlencoded" })
            conn.getresponse()

            # Finished
            print("--> Finished")
            logger.info("--> Finished")
            time.sleep(2)

        except Exception, e:
            traceback.print_exc()
            logging.exception("!!!")
    else:
        GPIO.output( 15, False)

 

Projektübersicht