Skript bricht immer nach etwa 4 Stunden ab?!

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Chrissili
User
Beiträge: 42
Registriert: Sonntag 22. Januar 2023, 10:09

Hallo zusammen,
mittlerweile habe ich es ja dank eurer Hilfe geschafft, diverse Sensorwerte abzugreifen und diese in einer while True Schleife fortlaufend auf einem LCD2004 auszugeben. Leider bricht dieses Skript aber immer nach etwa 4 Stunden ab... im Assistant steht "unused import datetime, unused pause imported from signal, unused argument signum und unused argument frame"...
Liegt das daran? Und wenn ja, wie kann man das umgehen?
Wäre euch über Tipps sehr dankbar
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Nein, liegt es nicht. Das ist nur angemeckerte Schlampigkeit.
Chrissili
User
Beiträge: 42
Registriert: Sonntag 22. Januar 2023, 10:09

Woran kann das dann sonst liegen? Darf ich meinen Code posten oder gibt es eine Art timeout? Ich dachte ich könnte das Skript quasi in einer Endlosschleife auf dem raspberry laufen lassen...
Benutzeravatar
Kebap
User
Beiträge: 687
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Du darfst gerne deinen Code posten, aber es wäre gut, wenn du ihn vorher so verkleinerst, dass er bei uns läuft und auch immer noch den Fehler zeigt, aber möglichst wenig anderen Code enthält, der dafür nicht nötig ist.
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
Chrissili
User
Beiträge: 42
Registriert: Sonntag 22. Januar 2023, 10:09

gelöscht
Zuletzt geändert von Chrissili am Donnerstag 16. März 2023, 09:21, insgesamt 1-mal geändert.
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Sage mal, nach etwa 30 Posts ist es dir immer noch nicht zuzumuten, deinen Code in die Code Tags zu setzen? Damit das Python verständlich ist?
Chrissili
User
Beiträge: 42
Registriert: Sonntag 22. Januar 2023, 10:09

Entschuldigung

Code: Alles auswählen

import urllib.request
import requests
import datetime
from signal import signal, SIGTERM, SIGHUP, pause
from rpi_lcd import LCD
from time import sleep
session = requests.Session()
response = session.post("https://server.growatt.com/login", data={
"account": "abc",
"password": "abc",
"validateCode": "",
"isReadPact": "0",
})
lcd = LCD()

def safe_exit(signum, frame):
exit(1)
try:
while True:
signal(SIGTERM, safe_exit)
signal(SIGHUP, safe_exit)
TempGartenhaus = 'http://192.168.178.22:8181/alchy.exe?sa ... 2).Value()'
FeuchtGartenhaus = 'http://192.168.178.22:8181/alchy.exe?sa ... 2).Value()'
with urllib.request.urlopen(TempGartenhaus) as f1:
g1 = (f1.read().decode('utf-8'))
h1 = (g1[g1.find("sagt")+5:g1.find("sagt")+9])+" Grad "
response = session.post("https://server.growatt.com/panel/getDevicesByPlantList", data={"currPage": "1","plantId": "abc"})
g = response.json()
response = session.post("https://server.growatt.com/panel/storag ... StatusData", data={"typ": "STORAGE", "plantId": "abc", "storageSn": "abc"})
l = response.json()
lcd.text(g.get('obj').get('datas')[0].get('lastUpdateTime'), 1)
lcd.text("PV " + l.get('obj').get('ppv1') + "W + Netz " + l.get('obj').get('gridPower') + "W", 2)
lcd.text("Bat " + l.get('obj').get('batPower') + "W + VB " + l.get('obj').get('loadPower') + "W", 3)
lcd.text("Batterie noch " + l.get('obj').get('capacity') + "%", 4)
sleep(10)
lcd.text("Gartenhaus", 1)
lcd.text(h1 + h2, 2)
sleep(3)
except KeyboardInterrupt:
pass
finally:
lcd.clear()
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das kann nicht der Code sein. Da fehlen alle Einrückungen (der Grund, warum es Code Tags gibt), und so läuft der nicht.
Chrissili
User
Beiträge: 42
Registriert: Sonntag 22. Januar 2023, 10:09

sorry, ich bereite das nochmal sauber auf...
darf ich fragen, wie man denn urllib.error.URLError: <urlopen error [Errno 101] Network is unreachable> umgeht?
denke das ist der Fehler, also quasi wenn er einen Netzwerkfehler hat / also quasi WLAN kurzfristig unterbrochen ist...
reicht es da, vor jedem url-Aufruf quasi ein "Try: " zu setzen?
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Jein. Es muss schon etwas mehr sein, denn wenn etwas *nicht* geklappt hat, kann das Programm ja nicht weiter rechnen, weil es von den Ergebnissen abhaengt. Also muss man retries einplanen.

Alternativ kann der ganze Kram auch in eine systemd-Unit, damit er wieder gestartet wird, wenn er, aus welchen Gruenden auch immer, abbricht. Das ist im Zweifel stabile, weil deine Faehigkeiten, robust zu programmieren, wenig ausgepraegt sind. Und es einfach woanders krachen kann.
Chrissili
User
Beiträge: 42
Registriert: Sonntag 22. Januar 2023, 10:09

Hallo __deets__ danke für deine Geduld. Kannst Du mir bitte nur kurz noch helfen, wie ich das in eine systemd-Unit packen kann? Was muss ich da abändern?
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Da muss nichts geaendert werden. Du musst gegebene Stichworte mal suchen, um eine Idee zu bekommen, was die bedeuten, und es dann probieren. Und dich natuerlich auch melden, wenn's nicht geht, oder du Fragen hast. Aber Kiefer auf, Kiefer zu, und schlucken - das sind schon deine Aufgaben, ich bin nicht deine Vogelmutti, die dir alles vorkaut.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Auch mit Einrückungen wäre der Code nicht lesbar.
Man benutzt keine einbuchstabigen Variablennamen. Was soll gl, hl, g oder l denn bedeuten? Was ist ein temporäres Gartenhaus?
Warum verwendest Du mal requests und dann auch urllib?
Warum denkst Du, dass Du einen eigenen signal-Handler brauchst?
Mit find und Indexgahampel geht man normalerweise nicht auf Text los. Wie sieht denn die Rückgabe der Gartenhaus-URL aus?
Man hat nicht irgendwo im Code literale Strings mit IDs oder Passwörtern, sondern definiert die als Konstanten am Anfang des Programms, um sie leicht finden zu können.
Auf Werte in Wörterbüchern greift man über Eckige Klammern zu, nicht per get.

Code: Alles auswählen

import requests
from time import sleep
from rpi_lcd import LCD

ACCOUNT = "abc"
PASSWORD = "abc"
PLANT_ID = "abc"
STORAGE_SN = "abc"
URL_GOWATT = "https://server.growatt.com"
URL_GOWATT_PLANT_LIST = URL_GOWATT + "/panel/getDevicesByPlantList"
URL_GOWATT_STORAGE_STATUS = URL_GOWATT + "/panel/storag ... StatusData"

URL_TEMPERATURE_SUMMER_HOUSE = 'http://192.168.178.22:8181/alchy.exe?sa ... 2).Value()'
URL_HUMIDITY_SUMMER_HOUSE = 'http://192.168.178.22:8181/alchy.exe?sa ... 2).Value()'

def main_loop(session, lcd):
    while True:
        response = session.get(URL_TEMPERATURE_SUMMER_HOUSE):
        _, _, text = response.content.partition("sagt")
        temperature_summer_house = text.split()[0]

        response = session.post(URL_GOWATT_PLANT_LIST, data={"currPage": "1","plantId": PLANT_ID})
        plant_list = response.json()
        plant = plant_list['obj']['datas'][0]

        response = session.post(URL_GOWATT_STORAGE_STATUS, data={"typ": "STORAGE", "plantId": PLANT_ID, "storageSn": STORAGE_SN})
        data = response.json()['obj']

        lcd.text(plant['lastUpdateTime'], 1)
        lcd.text(f"PV {data['ppvl']}W + Netz {data['gridPower']}W", 2)
        lcd.text(f"Bat {data['batPower']}W + VB {data['loadPower']}W", 3)
        lcd.text(f"Batterie noch {data['capacity']}%", 4)
        sleep(10)
        lcd.text("Gartenhaus", 1)
        lcd.text(f"{temperature_summer_house} Grad", 2)
        sleep(3)


def main():
    session = requests.Session()
    response = session.post(URL_GOWATT + "/login", data={
        "account": ACCOUNT,
        "password": PASSWORD,
        "validateCode": "",
        "isReadPact": "0",
    })
    lcd = LCD()
    try:
        main_loop(session, lcd)
    except KeyboardInterrupt:
        pass
    finally:
        lcd.clear()

if __name__ == "__main__":
    main()
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Chrissili: Egal wie die tatsächliche Einrückung aussieht: Da wird ein `h2` benutzt das nirgends definiert wird, also ist das sicher nicht der Code der da tatsächlich läuft.

`datetime` und `pause` werden importiert, aber nirgends verwendet. Das mit dem `signal()`-Modul ist aber auch unsinnig. An sich schon, aber in jedem Schleifendurchlauf immer wieder die gleiche Funktion zu registrieren kommt da noch mal oben drauf.

`FeuchtGartenhaus` wird definiert, aber nirgends verwendet.

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

`requests.Session`-Objekte sind Kontextmanager. Die sollte man mit ``with`` verwenden, wo das geht.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).

Namen sollten keine kryptischen Abkürzungen enthalten oder gar nur daraus bestehen. Der Name soll dem Leser verraten was der Wert dahinter bedeutet. Einbuchstabige Namen sind nur selten sinnvolle Namen. Und man nummeriert keine Namen.

Im gleichen Programm mal `urllib.request` und mal `requests` zu verwenden macht wenig Sinn.

`str.find()` hat das Problem, dass es *immer* eine Zahl liefert, die als Index verwendet werden kann, auch in dem Fall wenn die Teilzeichenkette gar nicht gefunden wird. Dann macht das Programm etwas sehr wahrscheinlich nicht sinnvolles. Darum ist es besser `str.index()` zu verwenden, was den Fehler nicht so einfach unbemerkt durchrutschen lässt.

Die meisten `get()`-Aufrufe auf Wörterbüchern in dem Code machen keinen Sinn. Zum einen wird ``.get("obj")`` verdammt oft wiederholt, zum anderen kann man damit zwar einen `KeyError` vermeiden, in sehr vielen Fällen wird dann aber vom `None` wieder versucht etwas mit `get()` abzufragen, womit man den Fehler einfach nur verschoben hat.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
from time import sleep

import requests
from rpi_lcd import LCD


def main():
    try:
        with requests.Session() as session:
            session.post(
                "https://server.growatt.com/login",
                data={
                    "account": "abc",
                    "password": "abc",
                    "validateCode": "",
                    "isReadPact": "0",
                },
            )
            lcd = LCD()
            while True:
                response_text = session.get(
                    "http://192.168.178.22:8181/alchy.exe?sa ... 2).Value()"
                ).text
                index = response_text.index("sagt")
                temperature_text = (
                    response_text[index + 5 : index + 9] + " Grad"
                )

                devices_data = session.post(
                    "https://server.growatt.com/panel/getDevicesByPlantList",
                    data={"currPage": "1", "plantId": "abc"},
                ).json()["obj"]
                status_data = session.post(
                    "https://server.growatt.com/panel/storag ... StatusData",
                    data={
                        "typ": "STORAGE",
                        "plantId": "abc",
                        "storageSn": "abc",
                    },
                ).json()["obj"]

                lcd.text(devices_data["datas"][0]["lastUpdateTime"], 1)
                lcd.text(
                    f"PV {status_data['ppv1']}W + Netz {status_data['gridPower']}W",
                    2,
                )
                lcd.text(
                    f"Bat {status_data['batPower']}W + VB {status_data['loadPower']}W",
                    3,
                )
                lcd.text(f"Batterie noch {status_data['capacity']}%", 4)
                sleep(10)

                lcd.text("Gartenhaus", 1)
                lcd.text(f"{temperature_text} {h2}", 2)
                sleep(3)

    except KeyboardInterrupt:
        pass
    finally:
        lcd.clear()


if __name__ == "__main__":
    main()
Wobei das für meinen Geschmack schon zu viel Code in der einen Funktion ist. Und wenn da jetzt auch noch mehr Fehlerbehandlung dazu kommt, gibt es noch tiefere Einrückung und es wird dann langsam unübersichtlich.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Chrissili
User
Beiträge: 42
Registriert: Sonntag 22. Januar 2023, 10:09

Ist es denn so dass sich der raspberry pi irgendwann selbständig abschaltet?
Benutzeravatar
sparrow
User
Beiträge: 4187
Registriert: Freitag 17. April 2009, 10:28

Nein*.

* = der Raspi läuft mit einem vollständigern Betriebssystem. Theoretisch könnte man auch so ein Verhalten konfigurieren.
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

All sowas sollte auch im syslog stehen. Aber Netzwerkfehler zb können sporadisch auftreten, und dann muss man damit eben umgehen.
Chrissili
User
Beiträge: 42
Registriert: Sonntag 22. Januar 2023, 10:09

Wie würdest du im Skript damit umgehen wenn das Netzwerk kurzfristig nicht erreichbar ist?
Benutzeravatar
Dennis89
User
Beiträge: 1153
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

vielleicht so, wie du es mit 'KeyboardInterrupt' machst:
https://requests.readthedocs.io/en/late ... exceptions

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
DeaD_EyE
User
Beiträge: 1017
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

So z.B.:

Code: Alles auswählen

import time


import requests

OK = "https://google.de"  # Verbindung ist möglich
NOK = "https://0.0.0.0"  # Nullroute, keine Verbindung

while True:
    time.sleep(1)
    try:
        response = requests.post(NOK)
    except requests.ConnectionError:
        print("Netzwerkproblem...")
        continue

    print(
        "Dieser Code wird bei einem Fehler nicht ausgeführt, da zuvor `continue` verwendet worden ist."
    )

Grundsätzlich sollte man auch bei der Abfrage bzw. beim Post-Request auch einen Timeout setzen, damit er bei Nichterreichbarkeit im Programm weiter macht und nicht unendlich lange wartet.

Code: Alles auswählen

import time
import logging

import requests

OK = "https://google.de"  # Verbindung ist möglich
logging.basicConfig(format="%(asctime)s %(name)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)

while True:
    time.sleep(1)
    try:
        # hier ist nun ein Timeout gesetzt
        # wenn der Verbindungsaufbau und die Abfrage länger dauert,
        # dann wird auch ein ConnectionError ausgelöst,
        # den man abfangen muss
        response = requests.post(OK, timeout=0.001)
        # 1 ms, so schnell antwortet  kein realer  Server
    except requests.ConnectionError:
        # mit Logging ist besser
        logger.warning("Keine Netzwerkverbindung")
        continue

    print(
        "Dieser Code wird bei einem Fehler nicht ausgeführt, da zuvor `continue` verwendet worden ist."
    )
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Antworten