Hallo alle zusammen,
in letzter Zeit habe ich doch einige Fragen hier gestellt und jetzt will ich euch mal zeigen, mit was ich mich die letzten Wochen beschäftigt habe.
Ziel und Problemstellung:
Bei den Eltern meiner Freundin haben wir im Garten ein Schildkrötengehege gebaut. Es besteht aus einem großen Außenbereich und einem kleineren, beheizten "Wohnbereich", der mit einer Türe vom Rest abgetrennt werden kann. In der Jahreszeit, in der sich Nachts die Temperaturen unter 10°C bewegen können, die Schildkröten aber noch nicht in den Winterschlaf gehen, müssen wir danach schauen, dass sie Abends in ihrem beheizten Wohnbereich sind. Geheizt wird mit speziellen Wärmelampen, die über Temperaturfühler gesteuert sind. Doch was ist wenn so eine Lampe kaputt geht? Wann ist die Zeit erreicht, in der die Lampen das Gehege nicht mehr genügend heizen können und die Schildkröten umziehen müssen in ihre Wintersuit? Naja ich war nicht so begeistert davon, das ich morgens um halb 7 bei den Schildkröten halten musste um zu sehen wie die Temperatur ist. Also beschloss ich einen Raspberry mit Temperatursensor zu bestücken und diesen die Arbeit erledigen zu lassen. Das ganze sollte auf einer App angesehen werden können. Davon bin ich mit meinem Wissen noch weit entfernt, deswegen haben wir uns auf eine Webseite geeinigt. Die aktuelle Temperatur so wie ein 12 stündiger Temperaturverlauf soll angezeigt werden. Beim unterschreiten einer minimalen Temperatur wäre es schön, wenn sie eine Benachrichtigung bekommt. Das Problem war überschaubar.
Wenn es kälter wird essen die Schildkröten nicht mehr soviel, muss man dann täglich hinfahren und nachsehen ob noch Essen drin ist oder muss man immer die Schwiegermutter darum bitten? Ne, die Kamera für den Pi ist ja nicht teuer und es genügt wenn diese Fotos aufnimmt von denen immer das aktuelle auf der Webseite zu sehen ist. Nächstes Problem abgehackt, sie war zufrieden, ich nicht mehr. ich habe an der Geschichte richtig Spass gefunden. Schnell war mit auch klar, das es nicht zumutbar ist, dass die Schwiegermutter morgens immer die Türe in den Außenbereich öffnen muss, weil wir arbeiten sind. Das soll ein Servomotor erledigen. Da ich an den Schildkröten keinen GPS-Tracker anbringen durfte um die Türe automatisch zu schließen, wenn Abends alle drin sind, haben wir uns auf einen Taster geeinigt, der die Türe wieder schließt.
Zum Schluss kam noch die Idee auf, die Vorbereitungen die für den Winterschlaf nötig sind, in einem Art News-Ticker auf der Webseite darzustellen.
Hardware:
-Raspberry Pi Zero WH
-DHT22 Temperatursensor
-Raspberry Pi Camera V2
-Jamara Q7 Standard Servomotor
-USB-Stick auf dem die Sensordaten und Kamerabilder gespeichert werden
Problemlösung:
Die Arbeiten an den Programmen und Webseiten sind soweit abgeschlossen, das sie funktionieren.
Die Webseite sieht so aus:
Das "Lampen-Bild" ist das Foto, dass die Kamera macht. Das Bild der Schildkröte mit "Zu" wechselt, je nach dem ob das Gehege auf oder zu ist.
Die *.php Datei sieht so aus:
Spoiler anzeigen
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Schildi-Land</title>
<style>
media only screen
and (min-device-width: 320px)
and (max-device-width: 759px)
and (-webkit-min-device-pixel-ratio: 2) {
div#tortoise { width: 4em; margin: auto; display: grid; grid-template-columns: auto auto auto auto; justify-content: center; }
div#hello { width: auto; margin: 0; display: grid; grid-template-columns: auto; justify-content: center; }
div#campicture { width: auto; margin: 0; display: grid; grid-template-columns: auto; justify-content: center; }
div#statustemp { width: 6em; margin: auto; display: grid; grid-template-columns: auto auto; justify-content: center; }
div#dia { width: auto; margin: 0; display: grid; grid-template-columns: auto; justify-content: center; }
div#ticker { width: auto; margin: 0; display: grid; grid-template-columns: auto; justify-content: center; }
img#selfie { display: block; width: 5em; height: 3.5em; margin: auto; }
img#welcome { display: block; width: 20em; height: 2em; margin: auto; padding-top: 1em; }
img#cam { display: block; width: 12em; height: 12em; margin: auto; padding-top: 1em; }
img#status { display: block; width: 7em; height: 10em; margin: auto; }
iframe#temperature { display: block; width: 12em; height: 10em; margin: auto; }
iframe#diagram { display: block; width: 19.5em; height: 10em; margin: auto; }
iframe#news { display: block; width: 19.5em; height: 40em; margin: auto; }
}
media only screen
and (min-device-width: 760px)
and (max-device-width: 1000px)
and (-webkit-min-device-pixel-ratio: 2) {
div#tortoise { width: 4em; margin: auto; display: grid; grid-template-columns: auto auto auto auto; justify-content: center; }
div#hello { width: auto; margin: 0; display: grid; grid-template-columns: auto; justify-content: center; }
div#campicture { width: auto; margin: 0; display: grid; grid-template-columns: auto; justify-content: center; }
div#statustemp { width: 6em; margin: auto; display: grid; grid-template-columns: auto auto; justify-content: center; }
div#dia { width: auto; margin: 0; display: grid; grid-template-columns: auto; justify-content: center; }
div#ticker { width: auto; margin: 0; display: grid; grid-template-columns: auto; justify-content: center; }
img#selfie { display: block; width: 7em; height: 5.5em; padding-right: 3.3em; padding-left: 3.3em; }
img#welcome { display: block; width: 48em; height: 5em; margin: auto; padding-top: 1em;}
img#cam { display: block; width: 24em; height: 24em; margin: auto; padding-top: 1em;}
img#status { display: block; width: 9em; height: 12em; margin: auto; }
iframe#temperature { display: block; width: 14em; height: 14em; margin: auto; }
iframe#diagram { display: block; width: 45em; height: 14em; margin: auto; justify-content: center;}
iframe#news { display: block; width: 45em; height: 42em; margin: auto; }
}
media screen
and (min-width: 1410px) {
div#tortoise { width: 4em; margin: auto; display: grid; grid-template-columns: auto auto auto auto; justify-content: center; }
div#hello { width: auto; margin: 0; display: grid; grid-template-columns: auto; justify-content: center; }
div#campicture { width: auto; margin: 0; display: grid; grid-template-columns: auto; justify-content: center; }
div#statustemp { width: 6em; margin: auto; display: grid; grid-template-columns: auto auto; justify-content: center; }
div#dia { width: auto; margin: 0; display: grid; grid-template-columns: auto; justify-content: center; }
div#ticker { width: auto; margin: 0; display: grid; grid-template-columns: auto; justify-content: center; }
img#selfie { display: block; width: 15em; height: 12em; padding-right: 1.5em; padding-left: 1.5em; }
img#welcome { display: block; width: 70em; height: 5em; margin: auto; padding-top: 5em;}
img#cam { display: block; width: 35em; height: 35em; margin: auto; padding-top: 5em;}
img#status { display: block; width: 20em; height: 20em; margin: auto; padding-right: 2em; padding-top:5em; }
iframe#temperature { display: block; width: 35em; height: 35em; margin: auto; padding-left: 2em; }
iframe#diagram { display: block; width: 120em; height: 30em; margin: auto; }
iframe#news { display: block; width: 60em; height: 40em; margin: auto; padding-top: 10em; }
</style>
</head>
<body>
<div id="tortoise">
<img id="selfie" src="Spike.png">
<img id="selfie" src="Whisky.png">
<img id="selfie" src="Willy.png">
<img id="selfie" src="Rosi.png">
</div>
<div id="hello">
<img id="welcome" src="welcome.png" alt="Begrüßung">
</div>
<div id="campicture">
<img id="cam" src="cam.jpg" alt="Kamerabild">
</div>
<div id="statustemp">
<img id="status" src="status.png" alt="Aktuelle_Temperatur">
<iframe id="temperature" src="http://192.168.0.67:3000/d-solo/2qYQh4z…h=10s&panelId=4"
frameborder="0"></iframe>
</div>
<div id="dia">
<iframe id="diagram" src="http://192.168.0.67:3000/d-solo/2qYQh4z…h=10s&panelId=8" frameborder="0"></iframe>
</div>
<div id="ticker">
<iframe id="news" src="http://192.168.0.67:3000/d-solo/2qYQh4z…=10s&panelId=10" frameborder="0"></iframe>
</div>
</body>
</html>
Visualisiert wurde mit Grafana. Die Sensordaten werden in eine MySQL-Datenbank geschrieben und von dort dann wieder abgerufen.
Für die Funktionalität sind zwei Python-Programme entstanden. Beide werden über systemd-Units gesteuert. Jeden Morgen startet das "erste" Programm zu einer definierten Uhrzeit und bewegt den Servomotor um die Türe zu öffnen. Da ich keine passende Lösung gefunden habe, wie ich das Bild "Zu" und "Offen" in Abhängigkeit des Servomotos ändern kann, habe ich beide Bilder in einem Ordner und je nach Position werden die Bilder entsprechend umbenannt. An dieser Stelle wäre ich euch für eine ordentlichere Lösung dankbar. Dieses Programm sieht so aus:
#!/usr/bin/env python3
from gpiozero.pins.pigpio import PiGPIOFactory
from gpiozero import Servo,Button
from time import sleep
import os, sys
SERVOPIN=17
button = Button(3)
CORRECTION=0.45
MAXPW=(2.0+CORRECTION)/1000
MINPW=(1.0-CORRECTION)/1000
PATH = '/var/www/html/'
factory = PiGPIOFactory(host='192.168.0.67')
servo = Servo(SERVOPIN, min_pulse_width=MINPW,max_pulse_width=MAXPW, pin_factory=factory)
servo.max()
os.rename(f"{PATH}status.png", f"{PATH}status_close.png")
os.rename(f"{PATH}status_open.png", f"{PATH}status.png")
def motor():
servo.min()
sleep(2)
os.rename(f"{PATH}status.png", f"{PATH}status_open.png")
os.rename(f"{PATH}status_close.png", f"{PATH}status.png")
button.wait_for_press()
motor()
Alles anzeigen
Das nächste Programm, dass die Kamera und den Sensor steuert und je nach Temperaturdaten E-mails versendet, wird jede viertel Stunde aufgerufen und sieht so aus:
#!/usr/bin/env python3
import Adafruit_DHT
import smtplib, ssl
import mysql.connector as connector
from datetime import datetime
from picamera import PiCamera
from time import sleep
from email import encoders
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
SENSOR = 22
GPIO_READ = 18
MAX_TEMP = '25'
MIN_TEMP = '21'
SENDER = "xxx.dennis@gmx.de"
RECIPIENT = "xxx.yyy@web.de"
SMTP_SERVER = "mail.gmx.net"
SERVER_PORT = 465
MAIL_USER = "xxx.yyy@gmx.de"
MAIL_PASSWORD = "ABCDE12345"
MAIL_PIC = "/home/pi/"
# Check the temperature
humidity, temperature = Adafruit_DHT.read_retry(SENSOR, GPIO_READ)
temperature = '{:0.1f}'.format(temperature)
time_out = datetime.now().strftime("%Y-%m-%d %H:%M")
connection = connector.connect (host = "localhost",
user = "XXX",
passwd = "123ABC",
db = "messstation")
cursor = connection.cursor(buffered=True)
cursor.execute("SELECT temperature FROM dht22 ORDER BY ID DESC LIMIT 0,1")
last_temperature = cursor.fetchone()
last_temperature = str(last_temperature).rstrip(',)').lstrip('(')
sql_command = """
INSERT INTO dht22 (time_out, temperature)
VALUES(%s,%s);"""
entry = (time_out, temperature)
cursor.execute(sql_command,entry)
connection.commit()
cursor.close()
connection.close()
# Make a picture
camera = PiCamera()
camera.start_preview()
sleep(5)
camera.capture('/var/www/html/tortoise_pic.jpg')
camera.stop_preview()
# Check about sending warning E-mails
def mailing():
message = MIMEMultipart('related')
message['Subject'] = 'Wir haben ein Problem :('
message['From'] = SENDER
message['To'] = RECIPIENT
message.preamble = 'Multi-part message in MIME format.'
message_alternative = MIMEMultipart('alternative')
message.attach(message_alternative)
message_text = MIMEText('Temperaturprobleme')
message_alternative.attach(message_text)
message_text = mail_text
message_alternative.attach(message_text)
picture = open(mail_image, 'rb')
image = MIMEImage(picture.read())
picture.close()
image.add_header('Content-ID', '<image1>')
message.attach(image)
context = ssl.create_default_context()
with smtplib.SMTP_SSL(SMTP_SERVER, SERVER_PORT, context=context) as server:
server.login(MAIL_USER, MAIL_PASSWORD)
server.sendmail(SENDER, RECIPIENT, message.as_string())
server.quit()
# Send E-mail only once. Don't want to have more then on Mail with the same Information.
if last_temperature < MAX_TEMP and temperature > MAX_TEMP:
mail_image = f"{MAIL_PIC}hot.jpg"
mail_text = MIMEText('<b>Uns schmilzt der Panzer!</b><br><img src="cid:image1"><br><b>Viele Grüße</b><br><b>Spike, Whiskey, Willy und Rosi</b>','html')
mailing()
elif last_temperature > MIN_TEMP and temperature < MIN_TEMP:
mail_image = f"{MAIL_PIC}cold.png"
mail_text = MIMEText('<b>Wir frieren!</b><br><img src="cid:image1"><br><b>Viele Grüße</b><br><b>Spike, Whiskey, Willy und Rosi</b>','html')
mailing()
Alles anzeigen
Es wird nur einmal eine E-mail gesendet. Das heißt fällt der Wert unter die minimale Temperatur oder steigt über die Maximale, dann schaut Python erst in die Datenbank ob der Wert davor auch schon außerhalb der Grenzen war, wenn nicht wird die E-mail gesendet, wenn ja ist eine erneute E-mail nicht notwendig. So meine Gedankengänge.
Was noch zu tun ist:
-Die Kamerabilder werden noch in '/var/www/html/' gespeichert, ich muss noch rausfinden, wie ich dem Pi sagen kann, dass er das Bild von einem anderen Pfad abrufen soll.
-Eine Lösung für das Auf-Geschlossen-Problem finden.
-DIe Mechanik für das Türe öffnen konstruieren und bauen.
-Mein für mich größtes Problem wird sein, das Ganze über einen VPN-Tunnel so öffentlich zu machen, das wir die Webseite von überall sehen können.
Was das ganze Projekt kippen könnte:
Durch meine Begeisterung und Freude habe ich glatt vergessen, dass der W-lan Empfang am Schildkrötengehege sehr schlecht sein könnte. Eventuell gibt es irgendwelche Verstärker
Habe jetzt ziemlich viel geschrieben und weis gar nicht mehr ob ich alles erwähnt habe, was ich wollte
Wer sich das bis hier her durchgelesen hat und Kritik/Verbesserungen aller Art hat, auch wenn es nur ein unbedeutender Schönheitsfehler im Programm ist, der darf mir das sehr gerne sagen. Bin nach bestem Wissen und Gewissen vorgegangen und hoffe das ich alle Konvektionen eingehalten habe und die aktuellen "Programmbefehle" verwendet habe.
Ansonsten wenn keiner was schreibt, lebe ich nach dem Mottto: "NIchts gesagt ist genügend gelobt"
Wenn die Mechanik fertig ist, gibt es einen Testaufbau. Ich werde berichten.
Grüße
Dennis
Edit am 20.11: Absolute Pfade in die E-mail Abfrage hinzugefügt.