Script (Schleife) funktioniert nur nach Reboot

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
renner2000
User
Beiträge: 3
Registriert: Mittwoch 27. Oktober 2021, 20:25

Hallo zusammen,
ich bin neu hier im Forum und suche für meine Herausforderung DEN Lösungsansatz.
Mein Projekt: Ein Raspi 3 prüft via GPIO24 einen Taster innen an der Gartentür, bei Betätigung wird via Bash-Script ein Relais+Türöffner betätigt.
Dies klappt reibungslos, nur nicht die Funktion, dies in eine SQL-DB zu schreiben. Nach einem Reboot klappt das für ca. 1h, manchmal auch nur 30min.
Gleiches gilt für ein weiteres Script, welches via Astrisk ein DECT-Telefon anruft, wenn der Taster aussen gedrückt wird.
Auch dies funktionierte bis heute immer, nur der SQL-Teil nicht. Nach einem Reboot schon.
Heute nun das erste mal, dass auch neben der SQL-Funktion das Script garnicht mehr funktionierte.
Was mache ich wohl falsch?
Ich möchte hierbei anmerken, dass ich eher gut mit PHP unterwegs bin und ich mich eher noch in der Lernphase bei Python befinde.
Ich würde mich über Kommentare/Support sehr freuen. DANKE!

Hier mein Taster-Script innen:

Code: Alles auswählen

#!/usr/bin/python3.7
import mysql.connector
import time
import shutil
import RPi.GPIO as gpio
import os
import sys
import subprocess

#Einstellungen
klingel_gpio = 24 #GPIO, der mit der Schaltung verbunden ist

#Datenbank
mydb = mysql.connector.connect(
  host="192.168.2.xxx",
  port="3307",
  user="klingel",
  password="xxxxx",
  database="klingel_log"
)

gpio.setmode(gpio.BCM)
gpio.setup(klingel_gpio, gpio.IN, pull_up_down=gpio.PUD_UP)

while True:
        time.sleep(0.01)
        if not gpio.input(klingel_gpio):
                time.sleep(0.1)
                if not gpio.input(klingel_gpio):
                        # hier Bash-Script öffnen
                        subprocess.run(["oeffner.sh"])
                        # hier in DB schreiben
                        mycursor = mydb.cursor()
                        sql = "INSERT INTO klingel_liste (klingel, foto_url) VALUES (%s, %s)"
                        val = ("Öffner Knopf", "xxx.jpg")
                        mycursor.execute(sql, val)
                        mydb.commit()
                        mycursor.close()
                        time.sleep(6)
                        #6 Sekunden warten, bis neu in SQL geschrieben werden kann, Taster wird erst nach 7 Sek wieder geprüft, da 6 Sek Relais aktiv
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@renner2000: Eingerückt wird per Konvention vier Leerzeichen pro Ebene. Nicht 2 und nicht 8.

`os`, `shutil`, und `sys` werden importiert, aber nicht verwendet.

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

Konstanten werden KOMPLETT_GROSS geschrieben.

Kommentare sollen dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht *was* der Code macht, denn das steht da bereits als Code, sondern warum er das macht. Sofern das nicht offensichtlich ist. Da bleibt nicht mehr viel übrig. Eigentlich nur noch der zu dem 6 Sekunden `sleep()`, weil da Umstände beschrieben werden, die nicht aus dem Quelltext ersichtlich sind.

Nur weil das eine MySQL-Datenbank ist, macht es keinen Sinn `my` vor die Namen zu hängen. Müsste man ja dann ändern wenn man stattdessen eine SQLite3- oder PostgreSQL-Datenbank verwendet, die ja grundsätzlich die gleiche API haben, von den Platzhaltern im SQL mal abgesehen.

Die Datenbankverbindung wird nicht sauber wieder abgebaut und `GPIO.cleanup()` wird nicht aufgerufen. Beides sollte auch bei einer Endlosschleife sichergestellt werden, denn die wird ja doch verlassen bei Fehlern, oder wenn der Benutzer das mit Strg+C abbricht.

Bei `subprocess.run()` sollte man in der Regel ``check=True`` angeben, damit Fehlercodes vom externen Programm nicht einfach ignoriert werden.

Zwischenstand:

Code: Alles auswählen

#!/usr/bin/python3.7
import subprocess
import time
from contextlib import closing

import mysql.connector
from RPi import GPIO as gpio

KLINGEL_PIN = 24


def main():
    try:
        gpio.setmode(gpio.BCM)
        gpio.setup(KLINGEL_PIN, gpio.IN, pull_up_down=gpio.PUD_UP)
        with closing(
            mysql.connector.connect(
                host="192.168.2.xxx",
                port="3307",
                user="klingel",
                password="xxxxx",
                database="klingel_log",
            )
        ) as database:
            while True:
                time.sleep(0.01)
                if not gpio.input(KLINGEL_PIN):
                    time.sleep(0.1)
                    if not gpio.input(KLINGEL_PIN):
                        subprocess.run(["oeffner.sh"], check=True)

                        with closing(database.cursor()) as cursor:
                            cursor.execute(
                                "INSERT INTO klingel_liste (klingel, foto_url)"
                                " VALUES (%s, %s)",
                                ("Öffner Knopf", "xxx.jpg"),
                            )
                            database.commit()
                        #
                        # 6 Sekunden warten, bis neu in Datenbank geschrieben
                        # werden kann, Taster wird erst nach 7 Sekunden wieder
                        # geprüft, da 6 Sekunden Relais aktiv.
                        #
                        time.sleep(6)
    finally:
        gpio.cleanup()


if __name__ == "__main__":
    main()
Ich würde von dem „busy waiting“ abrücken und auf `gpiozero` umstellen.

Was heisst denn „gar nicht mehr funktioniert“ genau? Startet es nicht? Bricht es ab? Bleibt es hängen? Wenn es abbricht oder hängen bleibt: Wo tut es das? Bei Abbruch bitte den kompletten Traceback zeigen.

Ich hätte jetzt eher erwartet, dass es nach längerer Zeit kracht, weil MySQL Verbindungen nicht ewig offen hält, insbesondere wenn da nichts passiert. Falls da nur sehr selten was passiert, könnte man auch überlegen die Datenbankverbindung dafür auf- und abzubauen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
August1328
User
Beiträge: 65
Registriert: Samstag 27. Februar 2021, 12:18

Das ist nicht mein Thema ist, aber ich wollte an dieser Stelle Mal ein großes Dankeschön an die fleissigen und sehr geduldigen Profis hier los werden!

Auch aus diesem Kommentar und Code Beispiel von __blackjack__ habe ich wieder etwas für mein eigenes Projekt lernen können 8)

Bis gestern ist mein Program (Aufruf von inzwischen 11 Skripten durch subprocess.Popen() ) bei Abbruch in einem Sammelsurium von Fehlermeldungen und Tracebacks geendet und meine Versuche, das in den Griff zu kriegen, hat nicht vollständig geklappt.

Jetzt sitzen die 3 try & except, in 1 zusätzlichen finally werden die beiden API Verbindungen sauber beendet, alles meldet ordentlich Skript Ende und es gibt keine Fehlermeldung mehr :D :D :D

In diesem Sinne wünsche ich ein schönes & entspanntes Wochenenende allerseits. Ich werde ein paar Tage ohne Trading Python & PC verbringen.

Andy
renner2000
User
Beiträge: 3
Registriert: Mittwoch 27. Oktober 2021, 20:25

Hej hej,

super - vielen Dank für deinen Support.

Ich habe testweise deinen Code einmal probiert, habe jedoch den gleichen Fehler wie zuvor.
Ich habe die Datei stets per systemd im Autostart.
Zuerst funktioniert immer alles, jedoch nach ein paar Stunden nicht mehr.
Lese ich am Status korrekt heraus, dass es an der Datenbankverbindung liegt?
Hinzufügen sollte ich noch, dass die Datenbank ein Server im Netzwerk ist, also nicht lokal auf dem Raspberry.
Parallel laufen noch weitere Datenbankeinträge von anderen Raspberrys, diese laufen alle Problemfrei.
Du hattest angeregt, die Datenbank nur bei Bedarf zu öffnen oder zu schließen?
Sollte es an der Performance liegen, könnte es helfen, dass man dem Script beibringt zu warten bis die Verbindung steht?

Hier der Status von systemd

Code: Alles auswählen

lines 1-16/16 (END)...skipping...
● klingel.service - Oeffnerinnen
   Loaded: loaded (/lib/systemd/system/klingel.service; enabled; vendor preset: enabled)
   Active: failed (Result: exit-code) since Fri 2021-10-29 08:13:03 BST; 7h ago
  Process: 496 ExecStart=/usr/local/sbin/klingel.py & (code=exited, status=1/FAILURE)
 Main PID: 496 (code=exited, status=1/FAILURE)

Oct 29 08:13:03 tuerklingel klingel.py[496]: Traceback (most recent call last):
Oct 29 08:13:03 tuerklingel klingel.py[496]:   File "/usr/local/sbin/klingel.py", line 75, in <module>
Oct 29 08:13:03 tuerklingel klingel.py[496]:     main()
Oct 29 08:13:03 tuerklingel klingel.py[496]:   File "/usr/local/sbin/klingel.py", line 57, in main
Oct 29 08:13:03 tuerklingel klingel.py[496]:     with closing(database.cursor()) as cursor:
Oct 29 08:13:03 tuerklingel klingel.py[496]:   File "/usr/lib/python3/dist-packages/mysql/connector/connection.py", line 871, in cursor
Oct 29 08:13:03 tuerklingel klingel.py[496]:     raise errors.OperationalError("MySQL Connection not available.")
Oct 29 08:13:03 tuerklingel klingel.py[496]: mysql.connector.errors.OperationalError: MySQL Connection not available.
Oct 29 08:13:03 tuerklingel systemd[1]: klingel.service: Main process exited, code=exited, status=1/FAILURE
Oct 29 08:13:03 tuerklingel systemd[1]: klingel.service: Failed with result 'exit-code'.

Danke und viele Grüße
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Wie gesagt schliesst MySQL Verbindungen die länger nicht benutzt werden. Ich glaube Standardwert ist 8 Stunden. Keine Ahnung ob man das entsprechend konfigurieren kann. Ich würde das bei einem Programm das relativ selten etwas einträgt, einfach dadurch lösen, dass die Verbindung für das eintragen auf- und danach wieder abgebaut wird. Das umgeht auch Probleme die entstehen, wenn man den DB-Server neu startet und deshalb Verbindungen verloren gehen, und natürlich alle möglichen Netzprobleme, die zu einem Abbruch führen können.

Falls Auf-/Abbau jedes mal zu teuer sein sollte, könnte man vorher ein "SELECT 1" oder so absetzen, und an der Stelle dann schauen, dass man die Verbindung neu aufbaut wenn das nicht funktioniert.

Ich würde da wahrscheinlich nichts selbst programmieren, sondern SQLAlchemy verwenden, und da dann die `pool_pre_ping` und/oder `pool_recycle`-Optionen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
renner2000
User
Beiträge: 3
Registriert: Mittwoch 27. Oktober 2021, 20:25

Vielen Dank!

Dies Lösung ist, die SQL-Verbindung innerhalb der Schleife zu öffnen, anzusprechen und wieder zu schließen - also nur wenn der Knopf gedrückt wird.
Danke für den Tipp - ein tolles Forum hier! :D

Viele Grüße
renner2000
Antworten