(Anfängerprojekt) Temperatursteuerung mit Datenspeicherung und Graphenausgabe

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
JymaXx
User
Beiträge: 4
Registriert: Mittwoch 19. Februar 2020, 09:08

Hallo liebes Python Forum,
ich habe im Zuge eines Projektes die Aufgabe, auf einem Raspberry Pi mit Python eine smarte Temperatursteuerung mit Datenspeicherung und grafischer Darstellung zu erstellen.
Da ich kompletter Neuling bin und erst am 14.02 mit Python angefangen habe, sitze ich seit Tagen vor einem Problem.
Die Aufgabe: Mit einem Raspberry Pi, Temperaturfühler und einer RGB LED einen Serverschrank bewachen. Die LED soll je nach Temperatur grün, gelb und rot sein. Bei längerer Überhitzung soll eine E-Mail gesendet werden.
Für die Langzeitüberwachung sollen die Daten auch alle 15 Minuten gespeichert werden. Diese sollen beziehungsweise können später dann mit einem Programm geöffnet und ausgelesen werden.
Für die Temperatursteuerung habe ich getrennt zur grafischen Darstellung ein einzelnes Programm erstellt, welches so gerade so funktioniert.
Dieses sieht wie folgt aus: (Die Werte sind wegen Testzwecken verändert, bis ich fertig bin)

Code: Alles auswählen

import RPi.GPIO as GPIO
import time
import Adafruit_DHT
import smtplib, ssl
from datetime import datetime
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import sys

DHTSensor=Adafruit_DHT.DHT11

LED_ROT = 26
LED_GRUEN = 5
LED_BLAU = 6
TEMP = 23

GPIO.setmode(GPIO.BCM)

GPIO.setup(LED_ROT, GPIO.OUT, initial= GPIO.LOW)
GPIO.setup(LED_GRUEN, GPIO.OUT, initial= GPIO.LOW)
GPIO.setup(LED_BLAU, GPIO.OUT, initial= GPIO.LOW)

print ("Temperaturmessung startet [druecken Sie bitte STRG+C, um die Messungen zu beenden]")

while True:
    try:
        t1=time.strftime("%H:%M:%S")
        t2=time.strftime("%H:%M")
        t3=time.strftime("%H")
        t4=time.strftime("%M")
        d1=time.strftime("%d.%m.%y")
        Luftfeuchte, Temperatur=Adafruit_DHT.read_retry(DHTSensor, TEMP)
        print("_________________________________________________________")
        if Temperatur is not None:
            print("Temperatur={0:0.1f} Grad | rel. Luftfeuchtigkeit = {1:0.1f}%\n".format(Temperatur,Luftfeuchte))
            print("Die Temperatur betraegt um {} am {} {} Grad".format(t1,d1,Temperatur))
            if Temperatur <=25:#Alles Gruen
                GPIO.output(LED_ROT,GPIO.LOW) #Rote LED bekommt 0V Spannung
                GPIO.output(LED_GRUEN,GPIO.HIGH) #Gruene LED bekommt 3.3V Spannung
                GPIO.output(LED_BLAU,GPIO.LOW) #Blaue LED bekommt 0V Spannung
            if Temperatur >=26 <=27:#Gelbe (Vorsicht!)
                GPIO.output(LED_ROT,GPIO.HIGH) #Rote LED bekommt 0V Spannung
                GPIO.output(LED_GRUEN,GPIO.HIGH) #Gruene LED bekommt 3.3V Spannung
                GPIO.output(LED_BLAU,GPIO.LOW) #Blaue LED bekommt 0V Spannung
            if Temperatur >=28:#Rot (Warnung!)
                GPIO.output(LED_ROT,GPIO.HIGH) #Rote LED bekommt 3.3V Spannung
                GPIO.output(LED_GRUEN,GPIO.LOW) #Gruene LED bekommt 0V Spannung
                GPIO.output(LED_BLAU,GPIO.LOW) #Blaue LED bekommt 0V Spannung
                print("Achtung die Temperatur ist zu hoch! Messung wird gleich wiederholt!")
                now=datetime.now()         
                if datetime.now().minute in {0,5,10,15,20,25,30,35,40,45,50,55}:
                    f= open("/home/pi/Desktop/Temperatur/Daten/Temperaturen am %s.txt" %(d1),"a+")
                    f.write("{},{}\n".format(t2,t4 , Temperatur))
                    f.close
                    print("Temperatur wurde gespeichert!\n")
                time.sleep(59)
                Luftfeuchte, Temperatur=Adafruit_DHT.read_retry(DHTSensor, TEMP)
                if Temperatur >=28:
                    print("Temperatur immernoch zu hoch! Bei naechster zu hoher Messung wird eine E-Mail gesendet!")
                    now=datetime.now()         
                    if datetime.now().minute in {0,5,10,15,20,25,30,35,40,45,50,55}:
                        f= open("/home/pi/Desktop/Temperatur/Daten/Temperaturen am %s.txt" %(d1),"a+")
                        f.write("{},{}\n".format(t2, Temperatur))
                        f.close
                        print("Temperatur wurde gespeichert!\n")
                    time.sleep(59)
                    Luftfeuchte, Temperatur=Adafruit_DHT.read_retry(DHTSensor, TEMP)
                    if Temperatur >=28:
                        try:
                            print("Temperatur zu hoch! E-Mail wird gesendet!\n")
                            sendEmail="___________"
                            empfEmail="___________"
                            port=465
                            smtp_server="smtp.gmail.com"
                            print("Login auf den Server und E-Mail wird jetzt gesendet!")
                        
                            #msg=MIMEMultipart("alternative")
                            #msg["Subject"]="Die Temperatur des Serverschrankes ist zu hoch!"
                            #msg["From"]=sendEmail
                            #msg["To"]=empfEmail     
                            emailText="Subject:Die Temperatur des Serverschrankes ist zu hoch!\n\nDie Temperatur betrug am {} um {} {}Grad".format(d1,t2,Temperatur)
                            #text=MIMEText(emailText)
                            #msg.attach(text)
                                
                            context=ssl.create_default_context()
                            with smtplib.SMTP_SSL(smtp_server, port, context=context) as server:
                                server.login (sendEmail,"_________")
                                server.sendmail(sendEmail, empfEmail, emailText)
                                print("E-Mail wurde erfolgreich gesendet!\n")
                                server.quit()
                            for ino in range(0,6):
                                Luftfeuchte, Temperatur=Adafruit_DHT.read_retry(DHTSensor, TEMP)
                                print("_________________________________________________________")
                                if Temperatur is not None:
                                    print("Temperatur={0:0.1f} Grad | rel. Luftfeuchtigkeit = {1:0.1f}%\n".format(Temperatur,Luftfeuchte))
                                    print("Die Temperatur betraegt um {} am {} {} Grad".format(t1,d1,Temperatur))
                                    if Temperatur <=25:#Alles Gruen
                                        GPIO.output(LED_ROT,GPIO.LOW) #Rote LED bekommt 0V Spannung
                                        GPIO.output(LED_GRUEN,GPIO.HIGH) #Gruene LED bekommt 3.3V Spannung
                                        GPIO.output(LED_BLAU,GPIO.LOW) #Blaue LED bekommt 0V Spannung
                                    if Temperatur >=26 <=27:#Gelbe (Vorsicht!)
                                        GPIO.output(LED_ROT,GPIO.HIGH) #Rote LED bekommt 0V Spannung
                                        GPIO.output(LED_GRUEN,GPIO.HIGH) #Gruene LED bekommt 3.3V Spannung
                                        GPIO.output(LED_BLAU,GPIO.LOW) #Blaue LED bekommt 0V Spannung
                                    if Temperatur >=28:#Rot (Warnung!)
                                        GPIO.output(LED_ROT,GPIO.HIGH) #Rote LED bekommt 3.3V Spannung
                                        GPIO.output(LED_GRUEN,GPIO.LOW) #Gruene LED bekommt 0V Spannung
                                        GPIO.output(LED_BLAU,GPIO.LOW) #Blaue LED bekommt 0V Spannung
                                        print("Temperatur immernoch zu hoch! E-Mail wird nicht gesendet!\nWurde in den letzten 5 Minuten schon gesendet")
                                        now=datetime.now()         
                                        if datetime.now().minute in {0,5,10,15,20,25,30,35,40,45,50,55}:
                                            f= open("/home/pi/Desktop/Temperatur/Daten/Temperaturen am %s.txt" %(d1),"a+")
                                            f.write("{},{}\n".format(t2, Temperatur))
                                            f.close
                                            print("Temperatur wurde gespeichert!\n")
                                        time.sleep(59)
                        except:
                            print("E-Mail konnte nicht gesendet werden! Bitte uebepruefen Sie die Internetverbindung!\n")
            now=datetime.now()         
            if datetime.now().minute in {0,5,10,15,20,25,30,35,40,45,50,55}:
                f= open("/home/pi/Desktop/Temperatur/Daten/Temperaturen am %s.txt" %(d1),"a+")
                f.write("{},{}\n".format(t2, Temperatur))
                f.close
                print("Temperatur wurde gespeichert!\n")
                        
        else:
            print("Fehler beim Auslesen - Bitte auf naechsten Versuch warten!")
        time.sleep(59)
    except KeyboardInterrupt:
        GPIO.cleanup()
So bei meinem zweiten Programm komme ich nun nicht weit, es ist mir bisher gelungen die Daten aus der aktuellen .txt Datei zu filtern und in einen Graphen anzuzeigen, aber an den Rändern werden nur die Zahlen angezeigt, die das Programm aus der txt liest.
Und sieht in etwa so aus :

Bild

Meine Absicht ist aber, dass wenn man das Programm startet, dass es in eine sogenannte Tagesansicht startet, ich möchte, dass auf der X-Achse die Zeiten 00:00 - 23:00 angezeigt werden und auf der y-Achse 0 - 40 Grad. Das Programm soll lediglich, die verfügbaren Daten an den Stellen einfügen, zu dem die Zeit und die Temperatur passt. Die txt Datei sieht wie folgt aus und wird durch zwei "row.split(",")" Befehle zugewiesen:

Datei: Temperaturen am 18.02.20.txt

13:00,23.0
13:05,24.0
13:10,23.0
13:15,23.0
13:20,25.0

Später möchte ich noch historische Abfragen mit reinsetzen. dass man aus einem Monat pro Tag die höchste Temperatur auslesen kann, aber daran würde ich mich dann wieder selber setzen.
Ich hoffe, einer von euch hat evtl. eine Idee, da mir Google seit Tagen nicht mehr weiterhelfen kann.
Mit freundlichen Grüßen
JymaXx
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Anmerkungen zum Code:
Allgemein muß 131 Zeilen dringend in mehrere Funktionen aufgeteilt werden. Eine while-Schleife von 100 Zeilen ist nicht mehr überblickbar.
Das `as` bei Import ist zum Umbenennen da, GPIO wird aber gar nicht umbenannt.
`sys` wird importiert aber nicht genutzt.
Benutze keine Abkürzungen, TEMP -> TEMPERATUR_SENSOR_PIN; t1 bis t4 oder d1 sind alles nicht aussagekräftige Namen, was unterscheidet t1 von t3? Wenn man das immer erst bei der Definition nachschauen muß, dann ist da was falsch. Variablen schreibt man immer komplett_klein.
Im Programm wird 13! mal die aktuelle Zeit ermittelt, und dann wild miteinander kombiniert. Diese Zeitdarstellungen können sich aber zum Teil um Stunden unterscheiden. Ermittle einmal pro Schleifendurchlauf die Zeit und verwende sie immer wieder, dazu datetime.now benutzen und bei den Ausgaben die entsprechenden Format-Strings angeben, nicht schon im vorhinein formatieren und erst recht nicht mit time.strftime.
In Zeile 41 ist 26 <= 27 immer wahr, die Bedingung kann also weggelassen werden. Da Temperatur eine Kommazahl ist, fehlen die Bereiche zwischen 25.0 und 26.0 und zwischen 27.0 und 28.0 Grad. Benutze elif und else.
In Zeile 52 benutzt Du den Filemodus a+. Der ist nie sinnvoll. Die +-Varianten sind eigentlich nur für Binärdaten nutzbar. Dateien die man öffnet, sollte man auch wieder schließen, vor allem, wenn man hineinschreibt. Dazu muß man close aber auch aufrufen ()!. Besser das with-Statement benutzen.
Du benutzt %-Formatierung und .format, benutze immer das zweitere.
Zeile 53: falsche Format-Argumente.
Das Schreiben der Temperatur wiederholt sich auch vier mal. Das gehört in eine Funktion.
Die LED-Ansteuerung wiederholt sich auch, das gehört in eine Funktion.
Zeile 117: keine nackten except. Was da konkret falsch ist, wirst Du niemals herausbekommen. Im wahrscheinlichsten Fall hast Du Dich einfach bei einem Variablenamen verschrieben, behauptest aber immer, die Email konnte nicht gesendet werden. Daher nur die Exceptions an den Stellen abfangen, deren Ursache man auch kennt und behandeln kann.
Zeile 129: GPIO.cleanup sollte immer aufgerufen werden, nicht nur bei Ctrl+C, daher gehört das in einen finally-Block.

Dateinamen sollten keine Leerzeichen enthalten und das Datum immer YYYY-MM-DD formatiert werden, damit man sie einfach chronologisch sortieren kann.

Das zweite Programm hast Du gar nicht gezeigt, wie soll man dazu etwas sagen?
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Um den Punkt, den Sirius3 bezueglich der Zeitermittelung gemacht hat, nochmal etwas zu illustrieren: dein permanentes abgefrage der Zeit durch time.strftime fuehrt dazu, dass du einmal einen Bruchteil einer Sekunde *vor* einer neuen Stunde abfragst, und dann danach - und prompt unterscheiden sich die Zeitpunkte bezueglich ihrer Stunde um eine ganze! Das ist ein Fehler der nur darauf wartet, im unguenstigen Moment zuzuschlagen.
JymaXx
User
Beiträge: 4
Registriert: Mittwoch 19. Februar 2020, 09:08

Erst einmal, danke für deine Antwort,
mir ist bewusst, dass der Code komplett unübersichtlich ist, wie oben schon gesagt: Meine Codingerfahrung beruht auf etwa 4 Tagen, ich bitte um Verständnis.
Ebenfalls ist mir bewusst, dass es viele Wiederholungen gibt, weil ich mir nicht einfach anders helfen konnte.
Viele unbenutzte Importierungen oder Deklarierungen sind noch aus dem älteren Code und Tests, ist zwar unschön aber der Code funktioniert. Und anders geht immer, besonders beim Coden.

Bei dem Post geht es mir hauptsächlich um das zweite Programm, bzw. darum, wie man einen vordefinierten Graphen erstellt an dem sich die Daten aus der .txt orientieren.
Da ich den Code nicht gezeigt habe, liegt daran, dass nicht wirklich was habe und seit Tag rum experimentiere und alles voller Versuchen ist, die ich mit "#" ausgegraut habe.
Aber ich kann ihn dir ja trotzdem mal zeigen, aber bitte ohne "sowas hätte ich nicht gemacht", für jede andere Idee oder Anregung bin ich sehr dankbar. :)

Code: Alles auswählen

import matplotlib.pyplot as plt
import matplotlib.dates as md
import datetime
import time
import matplotlib
import numpy as np
from datetime import time
from datetime import datetime

now=datetime.now()
plt.style.use("classic")
d1=now.strftime("%d.%m.%y")


with open("/home/pi/Desktop/Temperatur/Daten/Temperaturen am %s.txt" %(d1)) as f:
    data=f.readlines()
    x=[row.split(",")[0]for row in data]
    #hours,minutes =map(int,"00:00".split(":"))
    y=[row.split(",")[1] for row in data]
    f.close()

#a=time()
#kt=(a.hour, a.minute)
#b=time(23)
#mt=(b.hour, b.minute)
#nn=[kt,mt]
#nni=list(range(len(nn)))
#p=[1,40]
#pii=list(range(len(p)))

fig, ax=plt.subplots()
#fig.canvas.draw()
#w=4
#h=3
#d=70
#plt.figure(figsize=(w, h), dpi=d)
#position=(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24)
#labels=("00:00","01:00","02:00","03:00","04:00","05:00","06:00","07:00","08:00","09:00","10:00","11:00","12:00","13:00","14:00","15:00","16:00","17:00","18:00","19:00","20:00","21:00","22:00","23:00")
#plt.xticks(position, labels)
plt.title("Temperaturmessung")
plt.xlabel("Zeit")
plt.ylabel("Temperatur")
ax.plot(x, y, c="r")
plt.show()
EDIT: Das mit der Zeitabfrage war mir irgendwie klar, dass es Abweichungen geben wird, da ich unter Zeitdruck stehe, war es mir erstmal wichtiger, die Messungen richtig in einen Graphen zu bringen.
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@JymaXx: Anmerkungen zum ersten Quelltext (einiges wurde bereits gesagt):

Die Importe sollten mal aufgeräumt werden. Nur ein Modul pro ``import`` und ein bisschen gruppiert und sortiert, und dann alles raus was überhaupt gar nicht verwendet wird: `sys`, `MIMEMultipart`, und `MIMEText`.

``as`` beim ``import`` ist dazu da etwas umzubennenen — `GPIO` wird aber gar nicht umbenannt, also ist auch das ``as`` unsinnig.

Um das Gleichheitszeichen bei Zuweisungen ausserhalb von Argumentlisten und nach Kommas erhöht jeweils ein Leerzeichen die Lesbarkeit.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase). `DHTSensor` ist eine Konstante und keine Klasse, also `DHT_SENSOR`. `Luftfeuchte` und `Temperatur` sind Werte und keine Klassen, also klein geschrieben.

Man kann das Programm gar nicht mit Strg+C abbrechen, denn dann landet man wieder in der äusseren ``while True``-Schleife. Ausserdem darf das `GPIO.cleanup()` nicht nur bei einem Strg+C ausgeführt werden sondern muss *immer* ausgeführt werden, zum Beispiel auch wenn es zu einer Ausnahme im Programm kommt. Beispielweise weil bei einem `format()` in dem Code mehr Werte angegeben sind als Platzhalter in der Zeichenkette stehen.

`GPIO.output()` kann man auch zwei Sequenzen mit Pin-Nummern und Werten übergeben. Dann kann man die Farben auch als Konstanten definieren und der Code wird kürzer und einfacher.

Kommentare sollten nicht kommentieren *was* der Code macht, denn das steht da bereits als Code, sondern warum er das so macht. Sofern das nicht offensichtlich ist. Schlimmer als keine Kommentare sind welche die dem Code offensichtlich widersprechen, weil die dann nichts klären, sondern noch mehr Fragen aufwerfen. Zum Beispiel wenn ein Pin auf HIGH gesetzt wird und dann kommentiert wird das die LED damit 0V Spannung bekommt an anderer Stelle für LOW aber auch steht das die LED 0V Spannung bekommt.

Durchnummerierte einbuchstabige Namen sind keine guten Namen. `t1` bis `t4` und `d1` sind zudem fehlerhaft, denn diese Werte beziehen sich nicht auf den gleichen Zeitpunkt weil die Zeit zwischen den Definitionen dieser Werte ja weiterläuft. So kann es passieren das die nicht zusammen passen. Einen Zeitpunkt muss man *einmal* ermitteln. Danach kann man den *einen* Zeitpunkt auf verschiedene Arten als Zeichenkette formatieren. `t3` wird zudem nirgends verwendet und `t4` eigentlich auch nicht, denn an der einzigen Stelle wo das dann benutzt wird, scheint es fehl am Platz zu sein. Was man wahrscheinlich schon beim Schreiben der entsprechenden Stelle bemerkt hätte wenn die Namen nicht so absolut nichtssagend wären.

Was dann auch so überhaupt nicht hinhaut ist das Schlafen zwischen Messungen aber danach weiterhin die Zeit vom Anfang der Schleife in die Datei zu schreiben. Die stimmt dann ja nicht mehr.

Es wird diverse Male der Name `now` an einen Zeitpunkt gebunden, aber nicht ein einziges mal wird das dann auch verwendet.

Nur zweimal wird nach einem `read_retry()` geprüft ob die Temperatur den Wert `None` hat, bei anderen Aufrufen wird einfach davon ausgegangen, dass das nicht der Fall ist. Kann das an der Stelle `None` sein? Falls ja, warum wird dann nicht geprüft statt in Fehler zu laufen? Falls nein, warum wird dann bei zwei Aufrufen auf etwas geprüft das nicht eintreten kann?

Die Grenzwerte sollten nicht als magische Zahlen im Quelltext stehen. Du schreibst ja selbst das die noch nicht endgültig sind, also nicht geändert werden müssen. Dazu musst Du alle Stellen im Quelltext suchen und anpassen. Solche Werte sollten aber nur *einmal* im Quelltext stehen. Als Konstanten am Anfang. Dann muss man sie nicht suchen und kann sie an *einer* Stelle anpassen ohne die Gefahr das man nicht alle Vorkommen findet und/oder sich an einer oder mehreren Stellen vertippt beim ändern.

Die Temperaturgrenzen für den mittleren Bereich sind in den Bedingungen falsch: ``temperatur >= 26 <= 27`` ist äquivalent zu ``temperatur >= 26 and 26 <= 27``. Der zweite Teil ist immer wahr und damit wird da nur getestet ob die Temperatur ≥26 ist. Also haben wir die Fälle:

Code: Alles auswählen

                    if temperatur <= 25:
                        ...
                    if temperatur >= 26:
                        ...
                    if temperatur >= 28:
                        ...
Und jetzt mal bitte überlegen welcher Fall greift wenn die Temperatur 25.5° beträgt und welcher da greifen sollte.

Das sollten auch keine drei ``if``\s sein, denn die Tests sind ja nicht unabhängig. Wenn die Temparatur Ok ist, braucht man die beiden anderen Tests nicht. Wenn sie im Warnbereich ist, braucht man die beiden anderen Tests nicht. Und man braucht auch keine drei Tests, denn wenn die Temperatur weder Ok noch im Warnbereich ist, *muss* sie ja im kritischen Bereich sein. ``if``/``elif``/``else`` ist hier gefragt.

Es gibt in dem Quelltext zu viele Codewiederholen. Wenn man Code oder Daten kopiert, einfügt und leicht oder gar nicht ändert, dann macht man etwas falsch. Das bläht den Code unnötig auf, und macht Änderungen und Weiterentwicklungen fehleranfällig, weil man alle Änderungen immer an allen kopierten Stellen machen muss und das auch immer auf die gleiche Weise wenn man keine Fehler einbauen will oder es mit der Zeit immer schwieriger machen will kopierte, also eigentlich gleiche Teile zu finden.

Der Test ob die aktuelle Minute durch 5 teilbar ist und das anschliessende Schreiben der Temperatur in eine Datei kommt beispielsweise ganze vier mal im Quelltext vor. Das sollte nur einmal vorkommen. Denn da sind Änderungen fällig, die man nur einmal machen müssen sollte.

Wenn man wissen möchte ob ein Wert durch 5 teilbar ist, dann schreibt man da kein `set` mit allen durch 5 teilbaren Werten hin, sondern schaut sich an ob die Division durch 5 keinen Rest hat: ``x % 5 == 0``.

Den Pfad zu den Dateien würde man auch besser als Konstante definieren, damit er leichter angepasst werden kann.

`f` ist kein guter Name. Wenn man `file` meint, sollte man das auch schreiben.

Dateien die man öffnet sollte man auch wieder schliessen. Einfach nur die `close`-Methode zu referenzieren reicht da nicht, man muss sie auch aufrufen. Noch besser ist es wenn man die ``with``-Anweisung verwendet.

"+" im Dateimodus ist in 99,9% der Fälle falsch, bei Textdateien würde ich sogar auf 100% gehen. So auch hier.

Bei Textdateien sollte man immer explizit eine Kodierung angeben, damit das auch auf jedem System in jeder Einstellung gleich kodierte Dateien erzeugt.

Um das E-Mail-Versenden steht ein ``try``/``except`` das bei *jeder* Ausnahme ausgibt das die E-Mail nicht versendet werden konnte. In dem ``try``-Block steht aber nicht nur das versenden der Mail sondern auch eine Schleife mit Messungen. Das heisst auch wenn die Mail versendet wurde, dann aber eine der 5 Messungen zu einer Ausnahme führt, wird fälschlicherweise ausgegeben das die Mail nicht versendet werden konnte. ``try``-Blöcke sollte man möglichst klein halten. Wenn nur das Mailversenden erfasst werden soll, dann sollte auch nur das im ``try``-Block stehen.

Nackte ``except``\s sind schlecht insbesondere wenn in der Ausnahmebehandlung dann einfach nur ein Text ausgegeben wird an dem man überhaupt nicht erkennen kann *was* denn nun *wo genau* zur Ausnahme geführt hat. Man kann sich ja auch bei einem Namen vertippt haben und in einen `NameError` laufen, den findet man so aber nicht, wenn man darauf hingewiesen wird seine Internetverbinung zu prüfen. Nackte ``except``\s ohne konkrete Ausnahme(n) sind nur okay wenn man entweder dafür sorgt das die Informationen (Ausnahme + Traceback) irgendwo einsehbar sind, beispielsweise in einer Protokolldatei, oder wenn man innerhalb des ``except`` auch ein nacktes ``raise`` stehen hat und die Ausnahme damit wieder aulöst damit sich an anderer Stelle angemessen darum gekümmert werden kann. In diesem Fall möchte man das auf `OSError` einschränken. Vielleicht auch noch zwischen `OSError` und `SMTPException` unterscheiden, denn dann kann man zwischen Ausnahmen die bei SMTP entstanden sind und grundlegenderen Fehlern unterscheiden.

Das Senden der E-Mail steht zwar nur einmal im Quelltext, ist aber auch ein Kandidat als in sich geschlossene Handlung in eine eigene Funktion herausgezogen zu werden.

Macht das tatsächlich Sinn einen Default-SSL-Kontext zu erstellen und an dem nichts zu ändern? Was ist denn an dem dann anders als an dem den `SMTP_SSL` erstellen würde wenn man den Kontext nicht übergibt?

`server.quit()` braucht man nicht aufrufen — dafür hat man ja ``with`` verwendet.

Für Werte denen man aus syntaktischen Gründen einen Namen geben muss, den man dann aber nicht verwendet, gibt es die Konvention `_` als Namen zu verwenden. Was hätte denn `ino` bedeuten sollen? Wieder ein schlechter Name.

Bis hierhin mal ein Zwischenstand (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
import smtplib
import ssl
import time
from datetime import datetime as DateTime
from pathlib import Path

import Adafruit_DHT
from RPi import GPIO

TEMPERATURE_LOG_PATH = Path("/home/pi/Desktop/Temperatur/Daten")

OK_TEMPARATURE = 25
WARNING_TEMPERATURE = 27

E_MAIL_SENDER = "___________"
E_MAIL_RECIPIENT = "___________"
SMTP_SERVER_NAME = "smtp.gmail.com"
SMTP_PORT = 465

DHT_SENSOR = Adafruit_DHT.DHT11
RGB_LED_PINS = [26, 5, 6]
RED = (1, 0, 0)
YELLOW = (1, 1, 0)
GREEN = (0, 1, 0)
TEMPERATURE_PIN = 23


def save_temperature(timestamp, value):
    if DateTime.now().minute % 5 == 0:
        with open(
            TEMPERATURE_LOG_PATH / f"Temperaturen am {timestamp:%d.%m.%Y}.txt",
            "a",
            encoding="ascii",
        ) as file:
            file.write(f"{timestamp:%H:%M},{value}\n")
        print("Temperatur wurde gespeichert!\n")


def send_mail(sender, recipient, server_name, port, timestamp, temperature):
    print("Login auf den Server und E-Mail wird jetzt gesendet!")
    text = (
        f"Subject: Die Temperatur des Serverschrankes ist zu hoch!\n\n"
        f"Die Temperatur betrug am {timestamp:%d.%m.%Y} um "
        f"{timestamp:%H:%M:%S} {temperature} Grad."
    )
    #
    # TODO Does it make sense to create a default context and give it as is to
    # `SMTP_SSL`?
    #
    context = ssl.create_default_context()
    with smtplib.SMTP_SSL(server_name, port, context=context) as server:
        server.login(sender, "_________")
        server.sendmail(sender, recipient, text)


def main():
    GPIO.setmode(GPIO.BCM)
    try:
        GPIO.setup(RGB_LED_PINS, GPIO.OUT, initial=GPIO.LOW)
        print(
            "Temperaturmessung startet [druecken Sie bitte STRG+C, um die"
            " Messungen zu beenden]"
        )
        try:
            while True:
                now = DateTime.now()
                luftfeuchte, temperatur = Adafruit_DHT.read_retry(
                    DHT_SENSOR, TEMPERATURE_PIN
                )
                print("_" * 57)
                #
                # FIXME This test ist either not necessary or should be done
                # *every* time!
                #
                if temperatur is not None:
                    print(
                        f"Temperatur={temperatur:0.1f} Grad"
                        f" | rel. Luftfeuchtigkeit={luftfeuchte:0.1f}%\n"
                    )
                    print(
                        f"Die Temperatur betraegt um {now:%H:%M:%S} am"
                        f" {now:%d.%m.%Y} {temperatur} Grad"
                    )
                    if temperatur <= OK_TEMPARATURE:
                        GPIO.output(RGB_LED_PINS, GREEN)
                    elif temperatur <= WARNING_TEMPERATURE:
                        GPIO.output(RGB_LED_PINS, YELLOW)
                    else:
                        GPIO.output(RGB_LED_PINS, RED)
                        print(
                            "Achtung die Temperatur ist zu hoch!"
                            " Messung wird gleich wiederholt!"
                        )
                        save_temperature(now, temperatur)
                        time.sleep(59)
                        luftfeuchte, temperatur = Adafruit_DHT.read_retry(
                            DHT_SENSOR, TEMPERATURE_PIN
                        )
                        if temperatur > WARNING_TEMPERATURE:
                            print(
                                "Temperatur immernoch zu hoch!"
                                " Bei naechster zu hoher Messung wird eine"
                                " E-Mail gesendet!"
                            )
                            save_temperature(now, temperatur)
                            time.sleep(59)
                            luftfeuchte, temperatur = Adafruit_DHT.read_retry(
                                DHT_SENSOR, TEMPERATURE_PIN
                            )
                            if temperatur > WARNING_TEMPERATURE:
                                try:
                                    print(
                                        "Temperatur zu hoch! E-Mail wird"
                                        " gesendet!\n"
                                    )
                                    send_mail(
                                        E_MAIL_SENDER,
                                        E_MAIL_RECIPIENT,
                                        SMTP_SERVER_NAME,
                                        SMTP_PORT,
                                        now,
                                        temperatur,
                                    )
                                    print(
                                        "E-Mail wurde erfolgreich gesendet!\n"
                                    )
                                except OSError:
                                    print(
                                        "E-Mail konnte nicht gesendet werden!"
                                        " Bitte uebepruefen Sie die"
                                        " Zugangsdaten oder die"
                                        " Internetverbindung!\n"
                                    )
                                else:
                                    for _ in range(6):
                                        (
                                            luftfeuchte,
                                            temperatur,
                                        ) = Adafruit_DHT.read_retry(
                                            DHT_SENSOR, TEMPERATURE_PIN
                                        )
                                        print("_" * 57)
                                        #
                                        # FIXME This test ist either not
                                        # necessary or should be done *every*
                                        # time!
                                        #
                                        if temperatur is not None:
                                            print(
                                                f"Temperatur={temperatur:0.1f}"
                                                f" Grad"
                                                f" | rel. Luftfeuchtigkeit="
                                                f"{luftfeuchte:0.1f}%\n"
                                            )
                                            print(
                                                f"Die Temperatur betraegt um"
                                                f" {now:%H:%M:%S} am"
                                                f" {now:%d.%m.%Y}"
                                                f" {temperatur} Grad"
                                            )
                                            if temperatur <= OK_TEMPARATURE:
                                                GPIO.output(
                                                    RGB_LED_PINS, GREEN
                                                )
                                            elif (
                                                temperatur
                                                <= WARNING_TEMPERATURE
                                            ):
                                                GPIO.output(
                                                    RGB_LED_PINS, YELLOW
                                                )
                                            else:
                                                GPIO.output(RGB_LED_PINS, RED)
                                                print(
                                                    "Temperatur immernoch zu"
                                                    " hoch! E-Mail wird nicht"
                                                    " gesendet!\n"
                                                    "Wurde in den letzten"
                                                    " 5 Minuten schon gesendet"
                                                )
                                                save_temperature(
                                                    now, temperatur
                                                )
                                                time.sleep(59)
                    save_temperature(now, temperatur)
                else:
                    print(
                        "Fehler beim Auslesen -"
                        " Bitte auf naechsten Versuch warten!"
                    )
                time.sleep(59)
        except KeyboardInterrupt:
            pass
    finally:
        GPIO.cleanup()


if __name__ == "__main__":
    main()
Die `main()` ist immer noch zu lang und hat Codewiederholungen, und der Ansatz das mit immer tiefer Verschachtelten ``if``\s zu lösen ist falsch. Man würde da nur eine Messung als Code in die Schleife schreiben und mit zählen wie oft am Stück eine zu hoge Temperatur gemessen wurde. Also einen Zähler immer auf 0 setzen wenn die Temperatur nicht zu hoch ist und um 1 erhöhen wenn die Temperatur zu hoch ist. Je nach Zählerstand entscheidet man dann was zu tun ist.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
JymaXx
User
Beiträge: 4
Registriert: Mittwoch 19. Februar 2020, 09:08

Ich freue mich darüber, dass ihr meinen Code verbessern wollt, doch meine eigentliche Frage wurde damit nicht beantwortet. Wie schon zwei mal geschrieben würde ich gerne wissen, wie man Daten in einem Graphen einlesen und in den bestimmten Bereich ziehen kann.
Die Daten sollen nicht den Graphen erstellen sondern nur in einen bereits erstellten mit vorgefertigter Uhrzeit und Temperatur, eingefügt werden. damit ich später, wenn ich den Graphen öffne, die Daten mit dem Graphen verglichen werden und dort eingefügt werden.
Momentan werden die Daten einfach nur wie sie gelesen werden eingefügt, ich habe lediglich X und Y Achse manuell beschriftet. Die echten Daten sind etwa aus 8 - 11 Uhr in 5 Minuten Schritten und die Temperatur ist eigentlich zwischen 22 und 25 Grad.
So sieht es bisher aus:

Bild

Der Code dafür: Bitte nicht verbessern oder sagen was alles unnütz ist, möchte nur den Lösungsansatz meiner Frage, bitte...

Code: Alles auswählen

import matplotlib.pyplot as plt
import matplotlib.dates as md
import pandas as pd
from matplotlib.ticker import (MultipleLocator, FormatStrFormatter,AutoMinorLocator)
#import time
import matplotlib
import numpy as np
from datetime import time
from datetime import datetime, timedelta,tzinfo

now=datetime.now()
plt.style.use("classic")
d1=now.strftime("%d.%m.%y")

#jahre=md.YearLocator()
#monate=md.MonthLocator()
#tage=md.DayLocator()
#stunden=md.HourLocator()
#minuten=md.MinuteLocator()
#jahre_fmt=md.DateFormatter("%Y")
#monate_fmt=md.DateFormatter("%m")
#tage_fmt=md.DateFormatter("%d")
#stunden_fmt=md.DateFormatter("%H")
#minuten_fmt=md.DateFormatter("%M")



with open("/home/pi/Desktop/Temperatur/Daten/Temperaturen am %s.txt" %(d1)) as f:
    data=f.readlines()
    x=[row.split(",")[0]for row in data]
    #hours,minutes =map(int,"00:00".split(":"))
    y=[raw.split(",")[1] for raw in data]
    timestamp=pd.to_datetime(x[:])
    timestamp=timestamp.strftime("%H:%M")
    #xf=[rdw.split(":")]
    #ädatetime.strptime(,"%h:%m")
    f.close()
    
#a=time()
#kt=(a.hour, a.minute)
#b=time(23)
#mt=(b.hour, b.minute)
#nn=[kt,mt]
#nni=list(range(len(nn)))
#p=[1,40]
#pii=list(range(len(p)))



#X-Achse formatieren
#ax=plt.subplot()
#fig=plt.figure()
#ax=fig.add_subplot(1,1,1)
#ax.xaxis.grid(True, wich="minor")
#ax.xaxis.set_major_locator(MultipleLocator(20))
#ax.yaxis.set_major_locator(MultipleLocator(20))
#ax.xaxis.set_major_formatter(FormatStrFormatter("%d"))
#ax.xaxis.set_minor_locator(MultipleLocator(5))
#ax.yaxis.set_minor_locator(MultipleLocator(5))

#Tagesreichweite einstellen
#tagmin=datetime.time(hour=0,minute=0)
#tagmax=datetime.time(hour=24,minute=0)
#ax.set_xlim(tagmin, tagmax)
#tagmax=np.datetime64(["date"][-1], "h") + np.timedelta64(1,"h")

#fig.canvas.draw()
#w=4
#h=3
#d=70
#plt.figure(figsize=(w, h), dpi=d)
position=(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24)
position2=(1,2,3,4,5,6,7,8,9)
labels2=("0","5","10","15","20","25","30","35","40")
labels=("00:00","01:00","02:00","03:00","04:00","05:00","06:00","07:00","08:00","09:00","10:00","11:00","12:00","13:00","14:00","15:00","16:00","17:00","18:00","19:00","20:00","21:00","22:00","23:00")

#Graph beschriften und Daten einfügen
plt.title("Temperaturmessung")
plt.xlabel("Zeit")
plt.ylabel("Temperatur")
plt.plot(timestamp,y, c="r")
plt.xticks(position, labels,rotation=45)
plt.yticks(position2,labels2)
#ax.set_xticks(np.arange(len(labels)))
plt.axis([1,24,0,9])
plt.grid(True)
#plt.xlim(1,24)
#plt.autofmt_xdate()

#X-Achse formatieren
ax=plt.subplot()
#ax.xaxis.set_major_locator(MultipleLocator(1))
#ax.yaxis.set_major_locator(MultipleLocator(5))
#ax.xaxis.set_major_formatter(FormatStrFormatter("%d"))
ax.xaxis.set_minor_locator(AutoMinorLocator(4))
ax.yaxis.set_minor_locator(AutoMinorLocator(4))
plt.show()
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@JymaXx: zuerst einmal bis Du hier, um Python zu lernen, denn ohne dass Du es lernst, wirst Du Dein Problem nicht lösen können und dazu solltest Du die Tipps, die Dir hier gegeben werden ernst nehmen. Zudem ist Dein gezeigtes Programm fehlerhaft und da sind wir hier wie der Elektriker bei Dir zu Hause.


Du benutzt schon Pandas, warum nicht auch zum Lesen der Temperaturdatei?
Pandas kann dann auch schon richtig mit Uhrzeit plotten. Der Rest ist soweit ich sehe nur die richtigen Grenzen (limit) zu setzen.
Also anstatt dir selbst Labels zu erfinden und zufällig an die vorhandenen Daten zu schreiben, einfach das die Experten (pandas, matplotlib, etc) machen lassen.

Code: Alles auswählen

import pandas as pd
import matplotlib.pyplot as plt

data = pd.read_csv('temperaturen.csv', delimiter=',', header=None, parse_dates=[0], index_col=0)
data.plot()
plt.show()
JymaXx
User
Beiträge: 4
Registriert: Mittwoch 19. Februar 2020, 09:08

@Sirius3 Dass das auch mit Pandas geht habe ich erst später gesehen, ich kenne mich damit nicht aus und bin vielen Anweisungen auf verschiedenen Seiten gefolgt, so kam es zu Stande, dass ich viele Liberies habe, sie aber nicht benutze.
Das Auslesen der Datei klappt ja, nur er plottet sie genauso rein wie er sie bekommt.
Später soll mit dem Graphen aber übersichtlich ausgelesen werden.
Ein Major tick für eine volle Stunde und 3 minor ticks für eine viertel stunde.
Später soll die Temperatur nur alle 15 Minuten gespeichert werden. Damit hätte ich theoretisch alle Daten die ich brauche.

Ich wollte den Graphen schön übersichtlich haben, dass man 24 Stunden auf einen Blick erkennt und wenn an dem Tag aber erst um 12:15 gemessen und gespeichert wird, dann wollte ich, dass auch erst an der Stelle die Linie beginnt.
In den Stunden davor ist der Graph dann leer.
Ähnlich wie bei einer Chart bei einer Börse, da gibt es auch Uhrzeit und einen Wert.
Ich muss mir womöglich was anderes einfallen lassen, weil es anscheinend ja so nicht geht.
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Hast du Sirius3 Bezugnahme auf die limit-Methode von matplotlib nicht gesehen? Damit kannst du die Begrenzungen des Plots selbst definieren, und die Daten werden dann da reingemalt, wo sie eben sind.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@JymaXx: Du bettelst so lange, bis man Dir alles vorkaut? Zum Programmieren gehört auch, dass man sich in der Dokumentation zurechtfindet. Und wenn man sich einfach nur die Hilfe von `data.plot` anschaut,

Code: Alles auswählen

 |      Parameters
 |      ----------
 |      data : DataFrame
 |      x : label or position, default None
 |      y : label, position or list of label, positions, default None
 |          Allows plotting of one column versus another
 |      kind : str
 |          - 'line' : line plot (default)
 |          - 'bar' : vertical bar plot
 |          - 'barh' : horizontal bar plot
 |          - 'hist' : histogram
 |          - 'box' : boxplot
 |          - 'kde' : Kernel Density Estimation plot
 |          - 'density' : same as 'kde'
 |          - 'area' : area plot
 |          - 'pie' : pie plot
 |          - 'scatter' : scatter plot
 |          - 'hexbin' : hexbin plot
 |      ax : matplotlib axes object, default None
 |      subplots : boolean, default False
 |          Make separate subplots for each column
 |      sharex : boolean, default True if ax is None else False
 |          In case subplots=True, share x axis and set some x axis labels to
 |          invisible; defaults to True if ax is None otherwise False if an ax
 |          is passed in; Be aware, that passing in both an ax and sharex=True
 |          will alter all x axis labels for all axis in a figure!
 |      sharey : boolean, default False
 |          In case subplots=True, share y axis and set some y axis labels to
 |          invisible
 |      layout : tuple (optional)
 |          (rows, columns) for the layout of subplots
 |      figsize : a tuple (width, height) in inches
 |      use_index : boolean, default True
 |          Use index as ticks for x axis
 |      title : string or list
 |          Title to use for the plot. If a string is passed, print the string at
 |          the top of the figure. If a list is passed and `subplots` is True,
 |          print each item in the list above the corresponding subplot.
 |      grid : boolean, default None (matlab style default)
 |          Axis grid lines
 |      legend : False/True/'reverse'
 |          Place legend on axis subplots
 |      style : list or dict
 |          matplotlib line style per column
 |      logx : boolean, default False
 |          Use log scaling on x axis
 |      logy : boolean, default False
 |          Use log scaling on y axis
 |      loglog : boolean, default False
 |          Use log scaling on both x and y axes
 |      xticks : sequence
 |          Values to use for the xticks
 |      yticks : sequence
 |          Values to use for the yticks
 |      xlim : 2-tuple/list
 |      ylim : 2-tuple/list
 |      rot : int, default None
 |          Rotation for ticks (xticks for vertical, yticks for horizontal plots)
 |      fontsize : int, default None
 |          Font size for xticks and yticks
 |      colormap : str or matplotlib colormap object, default None
 |          Colormap to select colors from. If string, load colormap with that name
 |          from matplotlib.
 |      colorbar : boolean, optional
 |          If True, plot colorbar (only relevant for 'scatter' and 'hexbin' plots)
 |      position : float
 |          Specify relative alignments for bar plot layout.
 |          From 0 (left/bottom-end) to 1 (right/top-end). Default is 0.5 (center)
 |      table : boolean, Series or DataFrame, default False
 |          If True, draw a table using the data in the DataFrame and the data will
 |          be transposed to meet matplotlib's default layout.
 |          If a Series or DataFrame is passed, use passed data to draw a table.
 |      yerr : DataFrame, Series, array-like, dict and str
 |          See :ref:`Plotting with Error Bars <visualization.errorbars>` for
 |          detail.
 |      xerr : same types as yerr.
 |      stacked : boolean, default False in line and
 |          bar plots, and True in area plot. If True, create stacked plot.
 |      sort_columns : boolean, default False
 |          Sort column names to determine plot ordering
 |      secondary_y : boolean or sequence, default False
 |          Whether to plot on the secondary y-axis
 |          If a list/tuple, which columns to plot on secondary y-axis
 |      mark_right : boolean, default True
 |          When using a secondary_y axis, automatically mark the column
 |          labels with "(right)" in the legend
 |      `**kwds` : keywords
 |          Options to pass to matplotlib plotting method
dann sieht man, dass es für fast alle Bedürfnisse schon einen passenden Parameter gibt:

Code: Alles auswählen

xmin = data.index.min().floor('1D')
xmax = data.index.max().ceil('1D')
data.plot(xlim=(xmin, xmax))
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@JymaXx: Dazu musst Du halt die Grenzen setzen von 0 bis 24 Uhr und nicht selbst Stundenangaben an Ticks setzen die am Ende vielleicht eine ganz andere Stunde anzeigen. Die Methode dazu wird beispielsweise im ersten Tutorial in der Matplotlib-Dokumentation erwähnt und verlinkt: Usage Guide.

Bevor man tatsächlich Label und Tickpositionen selbst erzeugt sollte man die Möglichkeiten von `Locator`- und `Formatter`-Objekten ausgeschöpft haben. Labels für Ticks braucht man in der Regel für Kategorien, also beispielsweise Parteinamen bei einem Balkendiagramm eines Wahlausgangs oder ähnlichem. Bei einer Zeitreihe basieren die X-Tick-Labels ja aber auf den Zeitdaten selbst und es ist nur eine Frage wie man die als Zeichenkette formatiert und eben in welchen Abständen man die setzen will.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten