Doorbell – GitHub
… das Doorbell-Projekt ist, aufgrund großer Beliebtheit, nun auf GitHub zu finden!
… das Doorbell-Projekt ist, aufgrund großer Beliebtheit, nun auf GitHub zu finden!
Eigentlich wollte ich Heute die Notification auf Email umstellen, nach einem Test hab ich es aber wieder verworfen, da es mir eigentlich nur Nachteile bringt.
Wollte das Bild direkt in der Email einbetten, aber dann muss ich jedes mal den Mailserver ärgern und das Bild hochladen, dass muss doch nicht sein :)
Da viel mir ein, als ich in Italien war auf ca. 3200m und wollte sehen wer denn gerade Daheim klingelt, dauerte dies ewig. Ich hatte ca. zwei Balken EDGE (durch EU-Day Pack kostet zum Glück das Internet nicht viel).
Die Dateigröße der Bilder ist jeweils zwischen 2-3MB, was eindeutig zu viel ist.
Nun wusste ich bereits dass es den Parameter –quality gibt, also habe ich diesen hinzugefügt. Als Wert gibt man die gewünschte Qualität in Prozent an… getestet mit 80, nix bemerkt. Komisch…
Anscheinend komprimiert raspistill nicht so stark… habe testweise den Parameterwert auf 10 gesetzt und siehe da, 315k bei HD.
Das ist vollkommen in Ordnung und die Qualität ist immer noch super.
Anbei der Vergleich:
@Rotstich: Es ist gerade Abenddämmerung (20Uhr)
@Rasen: Da es eine IR-Kamera ist, sind die Farben nicht Orginal, dafür nimmt es super Bilder in der Nacht auf :)
Letzter Teil, denn ich bin fertig :^)
Da die Steckbrett-Lösung nicht so toll aussieht und bei der kleinsten Bewegung nichtmehr funktionierte, habe ich mir ein Humble-Pi bestellt.
Super nette Platine … nicht teuer und praktisch. Sie passt direkt auf den Pi. Einziger Nachtteil ist, dass keine Aussparung für das Camera-Modul vorgesehen ist.
Alles klar, los gehts mit Löten… da bin ich ein Profi, nicht. Aber hat mir viel Spaß gemacht und würde gerne mehr Löten :^)
Mein Spielzimmer Büro ist dafür auch gut ausgerüstet (Lötkolben & Lötroboter).
Nach ca. einer Stunde sah die Platine dann so aus…
Hier sei dazugesagt:
Wie erwähnt hat leider das Kamera Flachband keinen Platz darum habe ich die Platine bisl eingeschnitten. Als Gehäuse habe ich ein Plexiglaskonstrukt gewählt. Hoffte dass hier von der Höhe alles reinpasst. Leider nicht. Klemmen und LEDs stehen bisl an, ist aber nicht schlimm (Deckel geht nicht mehr zu).
Nächstes Projekt ist schon in Planung… Zweiter Raspberry ist schon eingetroffen :^)
Die Doorbell war nun eine Woche im Betatest und schnell stellte sich heraus da stimmt was nicht.
Wird ein Lichtschalter, welcher über ein Eltako-Relais geführt wird betätigt, klingelt sporadisch die Doorbell. Denke es gibt hier ein Problem mit der Abschirmung der Leitung zur Klingel. Leider kann ich diese nicht prüfen, da ich an die Abzweigdosen nicht herankomme.
Ich hab mal getestet ob es mit einer Pull-Up Schaltung besser funktioniert. Ja, es tritt nicht mehr so häufig auf. Aber Problem ist noch da.
OK, als nächstes einen Vorwiderstand von 2,2kOhm reingepackt.
Problem fast nicht mehr reproduzierbar aber noch da. Also bisl einen Umweg und eine Einschaltverzögerung einprogrammiert. Ist keine saubere Lösung aber geht. Bessere wäre es galvanisch zu Trennen über z.B. einen Optokoppler.
if not (GPIO.input(11)):
time.sleep(0.01)
if not (GPIO.input(11)):
Gerade getestet mit 100ms. Das war zu viel, wenn man schnell auf den Knopf drückt läutet es nicht. dann hab ich es auf 10ms gestellt. Siehe da, Problem gelöst.
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.
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.
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.
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)