Heizungssteuerung mit Nachlauf

  • Hallo,


    das ist mein erstes Python Programm daher kann es etwas holprig Programmiert sein. Das wird auch die beiden kleinen Fehler verursachen.

    1. Den Taster muss man lange drücken bis der wert gesetzt wird

    2. time.sleep am ende macht keinen unterschied ob da 10 oder 1 steht


    Zeil ist es nach Betätigung des Tasters bis zur soll Temperatur auf zu heizen danach 20 Minuten weiter heizen und dann aus bis zum nächsten Tasten Betätigung

    das ein drücken des Tasters während der Nach Heitz Phase die Phase verlängert ist mir bewusst und gewollt.

    z soll mal eine Restlaufzeit für die Nach Heitz Phase werden.


    wenn das alles flüssig läuft würde ich die Temperatur, Restlaufzeit und die Statusmeldungen gerne über i2C auf ein Display ausgeben hat da jemand einen Tipp?

    Temperatur.txt

    Edited once, last by benbei ().

  • Go to Best Answer
  • benbei: Wie lange man *nach* einer Endlosschleife schläft ist in der Tat völlig egal, weil das was nach einer Endlosschleife steht, niemals ausgeführt wird. Das `time.sleep()` sollte wohl besser *in* der Schleife stehen.


    Wobei das keine besonders genaue Art ist zwei Minuten zu warten in dem man 120 mal eine Sekunde schläft. Zum einen ist `sleep()` nicht genau und diese Ungenauigkeit summiert sich, und zum anderen verbraucht der Code der da in der Schleife läuft ja auch Zeit die da noch oben drauf kommt.


    ``as`` beim Importieren ist zum umbenennen da. `GPIO` wird aber gar nicht umbenannt.


    Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst. Code auf Modulebene und Funktiondefinitionen zu mischen, macht das ganze noch einmal eine Ecke unübersichtlicher. Die initiale Definition von `z` und der Teil wo das dann verwendet wird, sind beispielsweise sehr weit auseinander.


    `z` ist kein guter Name. Namen sollten dem Leser vermitteln was der Wert der dahinter steckt, im Programm bedeutet.


    Man sollte dafür sorgen, dass `GPIO.cleanup()` am Ende des Programmablaufs aufgerufen wird. Auch wenn eine Ausnahme auftritt, durch Fehler oder weil der Benutzer beispielsweise Strg+C gedrückt hat, um das Programm zu beenden.


    Die Pin-Nummer des Tasters steht als magischer Zahlwert direkt im Quelltext, statt wie beim Relais als Konstante definiert zu sein.


    In der Dokumentation zu `os.system()` wird auf das `subprocess`-Modul verwiesen.


    Pfadteile setzt man nicht mit ``+`` zusammen sondern mit `os.path.join()` beziehungsweise in neuem Code sollte man `pathlib` verwenden.


    `glob()` gibt keine Garantien über die Reihenfolge der Ergebnisse. Wenn da also mehrere Sensoren vorhanden sind, ist nicht garantiert *welcher* davon genommen wird.


    Die Temperatur braucht man in der Schleife pro Durchlauf nur *einmal* lesen.


    Python hat keine Syntax für eine nachprüfende Schleife, das wird deshalb idiomatisch als Endlosschleife ausgedrückt, die bei entsprechender Bedingung mit ``break`` abgebrochen wird, damit man den Code für die Endbedingung nicht wiederholt schreiben muss.


    `equals_pos` mit `find()` zu ermitteln ist umständlicher als `index()` zu verwenden. Bei `index()` muss man nicht selbst prüfen ob da ein spezieller Fehlerrückgabewert bei heraus kommt, und vor allem ist die Ausnahme deutlicher als den `UnboundLocalError` den man momentan als Folgefehler bekommen würde.


    Ungetestet:

    “Es ist schon über so viele Dinge Gras gewachsen, dass man bald keiner Wiese mehr trauen kann.”

  • Danke für die Hinweise :)

    alles habe ich noch nicht verstanden.

    Hinweis as gpio habe ich verstanden und verändert

    Hinweis z könnte ich besser später definieren

    Hinweis gpio Definition ist sauberer das gleich zu machen verstanden

    Hinweis os verstehe ich nicht

    Hinweis Pfade nicht mit + zusammensetzen verstehe ich auch nicht bzw. warum nicht?

    Hinweis `glob()` Da gibt es nur den einen Sensor mehr sind auch nicht geplant.

    Hinweis Nachprüfende Schleife verstanden

    Hinweis `equals_pos` mit `find()... Verstehe ich nicht


    ich habe deine Version direkt mal getestet. Sie funktioniert bis seconds einen negativen wert erreicht dann fängt das Relais an zu blinken (1sec an 1sec aus)

    Edited once, last by benbei ().

  • Hallo,

    Hinweis os verstehe ich nicht

    'os' ist nicht mehr das aktuelle Modul um solche Operationen auszuführen. Das es dafür etwas neueres und besseres gibt wird sogar in der Dokumentation von 'os' beschrieben. Zum Beispiel findet man dort:

    Note that the subprocess module provides more powerful facilities for

    spawning new processes and retrieving their results; using that module is
    preferable to using these functions.

    oder das:


    we advise you to use the subprocess module instead.

    Um 'subprocess' zu verwenden/verstehen hier die Dokumentation dazu.


    Hinweis Pfade nicht mit + zusammensetzen verstehe ich auch nicht bzw. warum nicht?

    Es ist zum Beispiel nicht schön zu lesen. Es ist nicht so, das jeder String so einfach zusammen gestückelt wird. Es kann vorkommen, dass du Variablen in einen String einbringen musst und diese auch noch formatieren, also zum Beispiel einen vorhandenen Wert mit nur einer Nachkommastelle ausgeben. Da bietet sich dann die 'format'-Methode oder eben die 'f'-Strings wesentlich besser an.

    Zu f-Strings

    Zur 'format'-Methode

    Hinweis `glob()` Da gibt es nur den einen Sensor mehr sind auch nicht geplant.

    Dazu möchte ich aus Erfahrung sagen, wenn du jetzt 'glob' verwendest und bei deinem nächsten Programm wieder und den Hinweis von __blackjack__ vergessen hast, könnte es aber zu Problemen kommen. Ich arbeite mich auch immer wenn ich Zeit hab in Python ein und versuche, alle Tipps, auch in Hinblick auf die Zukunft, umzusetzen, damit es zu keinen Problemen kommt wenn ich mal in alten Codes nachschaue.


    Hinweis `equals_pos` mit `find()... Verstehe ich nicht

    Was genau verstehst du daran nicht? Hilft eine Erklärung von blackjack-Codes?


    Grüße

    Dennis

    🎧 Mein Herz pumpt nur Adrenalin, ein Feuer tobt tief in mir drin und du, du löscht es mit Benzin 🎧

  • benbei: Pfade/Pfadteile sind nicht einfache/beliebige Zeichenketten sondern müssen bestimmten Regeln folgen damit sie gültig sind. Die Regeln können sich von Betriebssystem zu Betriebssystem auch subtil unterscheiden. Und bei ``+`` ist nicht garantiert, dass da am Ende auch ein gültiger Pfad bei heraus kommt, oder der Pfad den man bekommen würde, wenn man zwei Pfade nach den Regeln verbindet.


    `equals_pos` wird ermittelt und nur wenn das nicht -1 ist, wird das Ergebnis ermittelt und an den Namen `temp_c` gebunden. Wenn es aber den Wert -1 hat, dann wird versucht `temp_c` zurückzugeben was in dem Fall aber überhaupt gar nicht definiert wurde → `UnboundLocalError`.

    “Es ist schon über so viele Dinge Gras gewachsen, dass man bald keiner Wiese mehr trauen kann.”

  • @__blackjack__

    Danke,

    das meiste habe ich verstanden und konnte es umsetzen.

    Das Problem im deinem Programm, das nach Ablauf der Zeit ins negative das Relais wider angezogen hat, muss an bool liegen das gibt scheinbar auch bei negativen werten true zurück. Ich habe die if Verknüpfungen mit GPIO LOW und HIGH wieder reingeschrieben dann läft es wie es soll.

    Der nächste schritt wird dann die Ausgabe der Temperatur und Statusmeldungen über Display lcd i2c aus zu geben.

  • Ich habe von das Programm von __blackjack__ weiter geschrieben und bin jetzt wider an einem Punkt an dem ich nicht so recht weiter weiß.

    Ich finde nicht die Befehle oder das Format den Temperaturwert an LCD aus zu geben unter wenn ich suche wird meist nicht der I2C Benutzt. An stelle von "lcd.text(" ",1) soll die Temperatur angezeigt werden eventuell auch die Sekunden.


    Der Taster muss immer noch lange (ca.3sec.) gedrückt werden damit kann ich aber leben.

    Ansonsten funktioniert es wie es soll

    gibt es eine Möglichkeit ein Python Programm Exclusiv auszuführen oder beim Start eines Programms andere zu beenden?

    da ich immer nur ein Programm die gpio schreiben lassen möchte.


  • Hallo,

    An stelle von "lcd.text(" ",1) soll die Temperatur angezeigt werden eventuell auch die Sekunden.

    die Temperatur bindest du in Zeile 50 an den Namen 'temperature'. Du kannst diese Variable nutzen, ebenso wie 'seconds':

    lcd.text(f'{temperature} °C und {seconds} s', 1)


    Importiert wird immer zu Beginn des Programms, zwischen den Importen steht kein anderer Code. Zeile 6 gehört auch eher in die 'main'-Funktion. Wenn du 'subprocess' importiert hast, dann würde ich entweder alles mit subprocess.FUNKTIONSNAME aufrufen oder eben from subprocess import run, call verwenden und kein Mischmasch. Benötigst du 'call'? Wieso nutzt du für den Shutdown-Befehl nicht 'run'? Benutze dazu kein 'shell=True'. Das kann eine Sicherheitsrisiko sein, da dadurch beliebige Programme/Befehle ausgeführt werden können.

    'pause' importierst du, nutzt es aber gar nicht.

    Du rufst die Funktion 'safe_exit' auf, diese erwartet zwei Argumente, nämlich 'signum' und 'frame'. Die gibst du beim Aufruf aber gar keine Argumente an.

    Das was bei dir 'seconds' heißt sind ja an sich keine Sekunden, da nicht im Sekundentakt runter gezählt wird oder wenn doch, dann ist es Glück. Und wenn du während des runterzählens den Taster drückst, werden die 'seconds' wieder zu 120. Es wird auch in einem Dauerbrenner die Temperatur gelesen und immer wieder der gleiche Text auf das Display geschrieben, wenn kein Taster gedrückt wird. Das würde ich etwas separater aufbauen. Eventuell klappt der Tastendruck dann auch besser.
    Durch dein "Netzstecker ziehen"-Befehl wird 'GPIO-cleanup' nicht ausgeführt, mag vielleicht nicht schlimm sein, da der Pi ausgeschaltet wird, aber schön ists nicht unbedingt.


    In meinem folgenden ungetesteten Zwischenstandsvorschlag, habe ich deine 'seconds' unverändert drin, da ich denke es spielt hier keine große Rolle. Ich habe noch versucht, deine 'if'-Abfragen etwas zusammen zu fassen(Kann also noch Logikfehler enthalten). Vielleicht nützt es dir ja was, verfeinern kann/muss man es auf jeden Fall noch:


    Viel Spass und Grüße

    Dennis

    🎧 Mein Herz pumpt nur Adrenalin, ein Feuer tobt tief in mir drin und du, du löscht es mit Benzin 🎧

  • Danke Dennis89 du hast mir sehr weiter geholfen.

    Dein Programm funktioniert allerdings wenn der Relais Ausgang schon high ist wird der nicht beim Programmstart zurückgesetzt.

    Mein Display hat 2*16 Zeichen da musste ich den Text noch kürzen.

    Die unbenutzten Funktionen habe ich aus meiner Version raus genommen ich dachte das die zum LCD gehören.

    das "Sekunden" ein Loop ist ist mir bewusst ich habe den sleep 0.5 am Ende des Loop herausgenommen dadurch muss auch der Taster nicht mehr so lange gedrückt werden. Auf meiner Hardware dauert eine schleife jetzt ca.1,45 Sekunden im Späteren Einsatz soll die Zeit größer 20 Minuten sein da spielt es keine Rolle ob 21 oder 22 Minuten.

    Ein erneuter Tastendruck soll die Zeit von vorne anfangen lassen sonst würde ich das mit einer weiteren if abfrage verhindern.

    viele Grüße

    Benjamin

  • jetzt Funktioniert alles soweit das ich mit der richtigen Laufzeit und Temperatur mal getestet habe. Manchmal erhalte ich den Fehler:

    ........ if lines[0].strip().endswith("YES"): IndexError: list index out of range .

    Das bedeutet soviel wie diese Position gibt es in der liste (noch?)nicht. Ich denke das das mit dem W1 Bus zusammen hängt kann jemand mehr dazu sagen?

    so richtig kann ich den Fehler auch nicht reproduzieren.

  • Hallo,


    deine Datei w1slave dürfte diesen oder ähnlichen Inhalt haben?

    Code
    18 01 4b 46 7f ff 08 10 b0 : crc=b0 YES
    18 01 4b 46 7f ff 08 10 b0 t=17500

    mit:

    Code
    with TEMPERATURE_SENSOR_PATH.open("r", encoding="utf-8") as file:
            return list(file)

    wird der Inhalt der Datei in einer Liste gespeichert, dass sieht dann so aus:

    Code
    ['18 01 4b 46 7f ff 08 10 b0 : crc=b0 YES\n', '18 01 4b 46 7f ff 08 10 b0 t=17500\n']

    Index 0 ist immer der "erste Teil" der Liste. Bleiben wir bei den Namen aus deinem Pythonprogramm, also heißt die Liste 'lines'.

    Mit print(lines[0]) greifst du auf den Index 0 zu, in dem Fall gibst du ihn dir einfach aus. Das Ergebnis wäre:

    Code
    18 01 4b 46 7f ff 08 10 b0 : crc=b0 YES

    Wenn du den Fehler 'out of Range' bekommst, dann kann er auf den Index, in dem Fall Index 0, nicht zugreifen und in dem Fall müsste meinem Verständnis nach, die Datei leer sein. Du kannst dir die Datei ja mal anschauen, nach dem du den Fehler wieder erhalten hast. Wenn sie dann leer ist, dann kannst du Python den Inhalt der Datei prüfen lassen, bevor auf einen Index zugegriffen wird.


    Zur Vollständigkeit meiner Erklärung und das wird auch der Grund sein warum __blackjack__ vor dem Prüfen auf "YES" noch ein 'strip()' eingebaut hat ist, das es vorkommen kann, dass in dem Fall 'lines[0]' mit einem Leerzeichen endet. Dann gibt 'endswith' ein 'False' zurück, weil die Zeile nicht mit "YES" sonder mit " " endet. 'strip()' entfernt also Leerzeichen vor und nach einem String.


    Weis nicht ob du die Erklärungen überhaupt benötigst, aber ein "Ja" fand ich auf deine erste Frage zu unhöfflich und zu den anderen Fragen über den Bus kann ich mangels Erfahrung nicht mal was sagen ^^


    Grüße

    Dennis


    Zu der Bus-Ver

    🎧 Mein Herz pumpt nur Adrenalin, ein Feuer tobt tief in mir drin und du, du löscht es mit Benzin 🎧

  • Im Moment läuft dieser Zwischenstand außerhalb der Thonny Umgebung Stabil. Ein loop dauert auch tatsächlich ziemlich genau eine Sekunde. Der Index Error Taucht nicht auf bisher.


    Bei der Temperatur brauche ich eigentlich keine Nachkomastellen daher runde nach einer Stelle. Das Spart platz auf dem Display.

    Die Rundungsfunktion habe ich bei den Sekunden reingebracht damit ich bei bedarf die Loop-Zeit einer Tatsächlichen Sekunde Anpassen kann ohne dann Fließkomma Zahlen durch digitale Brüche zu bekommen.

    Alternativ könnte man sicherlich auch mit Hundertstel Sekunden Sauber Arbeiten.


    Ich hätte jetzt gerne die Möglichkeit das Ganze Alternativ über eine GUI (tinker) zu Steuern allerdings finde ich in den Tutorials zwar den Hinweis das Variablen nicht einfach übergeben werden können aber nicht so recht eine Lösung wie es dann doch geht außerdem soll mein Programm ja laufen unabhängig ob auf dem GUI etwas eingegeben wird oder nicht.

    Ist tinker dafür das richtige Modul oder soll ich mir lieber easy gui ansehen?



  • Hallo,


    'tkinter' kannst du dafür verwenden, ist auch geschickt da es nicht erst nachinstalliert werden muss.

    Programmiertechnisch geht es jetzt aber einen Schritt weiter. Du musst objektorientierte Programmierung lernen. Neben den Funktionen kommen jetzt noch Klassen in dein Programm. Dafür würde ich keine Tutorials die irgendwo im Netz benutzen sondern das offizielle Python-Tutorial.

    Für die Programmierung von GUI's ist dass die Grundlage.


    Danach gehts dann mit 'tkinter' weiter.


    P.S. die Grundlagen der objektorientierten Programmierung gibts auch auf deutsch, beachte aber, dass das für Python 3.3 gilt. Aktuell sind wir bei 3.9. Der Umgang mit Klassen kannst du daraus trotzdem lernen:

    https://readthedocs.org/projec…downloads/pdf/python-3.3/


    Grüße

    Dennis

    🎧 Mein Herz pumpt nur Adrenalin, ein Feuer tobt tief in mir drin und du, du löscht es mit Benzin 🎧

  • Ich habe noch weiter Programmiert und eine Lauffähige Version mit GUI hinbekommen.

    Die GUI Startet leider nur wenn man das Programm händisch startet. Wenn es beim Systemstart startet wird sie nicht angezeigt.

    hat jemand eine Lösung dafür? Probiert habe ich das Programm erst nach 90s starten zu lassen ohne erfolg.


    Ich überlege den GPIO Input in den Fenster threat zu verlagern dann müsste ein kurzes Tippen ausreichen.

    Schön wäre wenn der Status im Display auch im Eingabefenster Dargestellt würde. Das wird der nächste Schritt.


    Den Index Fehler kann ich mittlerweile Provozieren indem ich viel auf die SD Karte zugreife.

    kann man den mit except index error irgendwie auffangen?


    Ich freue mich auf eure Anmerkungen und Verbesserungsvorschläge.


    Edited 2 times, last by benbei ().

  • Hallo,


    wie startest du dein Programm automatisch? Also mit Cronjob, systemd, *.desktop-Datei? Wenn letzteres, dann zeig uns bitte mehr Details, wenn nicht, dann nimm letzteres :)


    Grüße

    Dennis

    🎧 Mein Herz pumpt nur Adrenalin, ein Feuer tobt tief in mir drin und du, du löscht es mit Benzin 🎧

  • Es Startet mit einem Eintrag in /etc/rc.local. Ich weiß das eine *.desktop z.B. in /etc/xdg/autostart besser wäre aber dort bekomme ich auch als root keine Schreibrechte. Eine Funktionierende *.desktop Datei habe ich auf dem Desktop.

    • Best Answer

    Es Startet mit einem Eintrag in /etc/rc.local.

    Das wird damit nichts. ;)


    Eine Funktionierende *.desktop Datei habe ich auf dem Desktop.

    Zeig die mal bitte! Oder / und erstelle das Verzeichnis /home/pi/.config/autostart falls noch nicht vorhanden und kopiere diese Datei mal dort rein. Danach zum Test einmal vom Desktop abmelden und als User pi wieder anmelden.

  • Zu deinem Code, es ist doof dass das sogar in der tkinter-Dokumentation so vor gemacht wird, aber wie hyle schon schrieb: Keine *-Importe verwenden. Das Problem dabei ist, dass du dir durch den Stern deinen Namensraum mit allen in tkinter definierten Namen füllst, dazu gehören auch die, die tkinter selbst importiert. Das kann zu Namenkollisionen und Programmfehler führen. Das Ganze wird außerdem auch unübersichtlich, weil man gar nicht nach vollziehen kann, wo welcher Name denn her kommt.

    Da von tkinter doch ein paar Funktionen benötigt werden bietet es sich an tkinter zu importieren und als 'tk' abzukürzen. Dann kann man auf jede Funktion aus tkinter mit tk.funktionsname zugreifen. Also den Sternimport am besten durch import tkinter as tk ersetzen.


    Jetzt wirds etwas schwieriger, verbessere mich wenn ich mich täusche, aber ich habe das Gefühl du hast dir gedacht du nimmst deinen vorhanden Code, schreibst eine zusätzliche Funktion in der du wieder Funktionen definierst und dann hast du ein GUI.

    Das ist, so weit ich das beurteilen kann, der falsche Weg.

    'EingabeFenster' hätte vermutlich eine Klasse werden sollen, in der du dann Funktionen definierst.

    Allgemein solltest du dir für dieses Projekt und für die meistens GUI's als erstes die objektorientierte Programmierung aneignen.

    So baust du dir irgendwie einen eine klapprige Hängebrücke um ans Ziel zu kommen, dabei ist die A81 gar nicht so weit weg.


    Ich würde dir empfehlen das offizielle Python Tutorial durch zu arbeiten.

    Und dann nochmals mit dem GUI anzufangen.


    Habe jetzt auch überlegt was ich dir für ein Beispiel noch zeigen könnte, eins dass dein Problem schon zur Hälfte löst wollte ich bewusst nicht, da ich das Gefühl habe, du möchtest das hier lernen. Von dem her zeige ich dir ein Code mit tkinter, der auch etwas völlig automatisch macht, aber mit deinem Problem nichts zu tun hat. So siehst du mal, wie so ein GUI ungefähr aufgebaut werden kann und was da auf dich zu kommt.


    Kämpfe dich mal durch das Tutorial.


    Viel Erfolg und Grüße

    Dennis

    🎧 Mein Herz pumpt nur Adrenalin, ein Feuer tobt tief in mir drin und du, du löscht es mit Benzin 🎧

    Edited 2 times, last by Dennis89: Fipptehler verbessert, danke hyle ().

  • benbei

    Selected a post as the best answer.