Pure Verzweiflung zur Syntax

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
BlinkyBill
User
Beiträge: 2
Registriert: Montag 3. Januar 2022, 14:43

Hallo Zusammen,

nachdem ich nun schon gefühlte Ewigkeiten in ein Skript investiert habe (das auch nur von einer Seite auf einem Tutorial stammt, aber meiner Meinung nach dort so nichts verloren hat), muss ich nun einen Hilferuf starten.

Das Original sieht so aus:

Code: Alles auswählen

#!/bin/sh
 
ACCESSKEY="DER-ALARM-ACCESSKEY"
API_URL="https://www.xxxxxxxx?accesskey=${ACCESSKEY}"
IS_MONITOR_ACTIVE=true
 
while true; do
    HAS_ALARM=`curl -s ${API_URL} | jq -r -j '.success'`
 
    if [ $HAS_ALARM = true ] && [ $IS_MONITOR_ACTIVE = false ]; then
        echo "Turn display on"
        xscreensaver-command -deactivate
        IS_MONITOR_ACTIVE=true
    elif [ $HAS_ALARM = false ] && [ $IS_MONITOR_ACTIVE = true ]; then
        echo "Turn display off"
        xscreensaver-command -activate
        IS_MONITOR_ACTIVE=false
    fi
 
    sleep 20
    
    done
    
Nach all meinen Versuchen, das auf einem Raspberry so zu laufen zu bekommen und nach und nach die syntaxfehler auszumerzen, bin ich nun auf folgendem Stand angekommen:

Code: Alles auswählen

#!/bin/sh
 
ACCESSKEY="DER-ALARM-ACCESSKEY"
API_URL="https://wwwxxxxxxxxxxxxxxxxx.accesskey=${ACCESSKEY}"
IS_MONITOR_ACTIVE=True
 
while True:
#    HAS_ALARM=`curl -s ${API_URL} | jq -r -j '.success'`
    HAS_ALARM='curl -s "https://xxxxxxxxxxaccesskey=yyyyyyyyyyyyyyy" | jq -r -j'.success
    if [ HAS_ALARM == True ] and [ IS_MONITOR_ACTIVE == False ]: 
#    if  [ IS_MONITOR_ACTIVE == false] :
#        echo "Turn display on"
        xscreensaver-command -deactivate
        IS_MONITOR_ACTIVE=True
    elif [ HAS_ALARM == False ] and [ IS_MONITOR_ACTIVE == True ]:
#       echo "Turn display off"
        xscreensaver-command -activate
        IS_MONITOR_ACTIVE=False
    fi
 
    time.sleep(20)
done
Hier scheitere ich nun aber an der Zeile mit dem curl endgültig.

curl -s "https://www.xxxxxxxxxxxx?accesskey=yyyyyyyyy" | jq -r -j ergibt auf der Konsole korrekt ein "success": false - funktioniert also prinzipiell.

Ich vermute, dass in dem Skript noch etliche Syntaxfehler stecken, aber ich gebe schlicht und ergreifend auf.

Ist das Ursprüngliche Programm irgendwie lauffähig und für eine andere Version ? Bin ich nur zu doof? Oder ist das einfach tatsächlich Müll ? :-(

Ich würde mich freuen, wenn da einer von Euch geübten mit ein paar Handgriffen die übelsten Fehler ausmerzen kann, oder mir den entscheidenden Tip geben. Die Doku zu curl scheint extrem Umfangreich und für Einsteiger eher ungeeignet :-)

im Voraus besten Dank
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das ist ein shell-Skript, und hat mit Python nix zu tun. Du hast es jetzt mit Python-Syntax "angereichert", aber das macht es immer noch nicht zu Python, und auch nicht mehr zu Shellskript. Du solltest dich also fuer eine der beiden Sprachen entscheiden, und es dann in der Implementieren. Da es schon shellscript ist, und du sonst alles von vorne schreiben muesstest (und zB sowas wie curl benutzt man in Python nicht, das macht man mit urllib oder requests), ist es wohl eher Shellscript.
BlinkyBill
User
Beiträge: 2
Registriert: Montag 3. Januar 2022, 14:43

Hau mir mal bitte einen Hammer auf den Kopf...

Vielen Dank!!! Ich war von den letzten Tagen noch so drauf, dass ich es immer mit python gestartet hab.

Wie war das mit dem Wald und den Bäumen ?

Dank Dir!
Benutzeravatar
__blackjack__
User
Beiträge: 13061
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@BlinkyBill: Anmerkungen zum Shell-Skript: Die Backtick-Syntax würde ich nicht mehr verwenden. ``$(…)`` ist lesbarer, man kann es verschachteln, und die Klammern werden von den meisten Editoren auch deutlich besser unterstützt, beispielsweise durch visualles hervorheben der jeweils anderen Klammer wenn man mit dem Cursor auf einer Klammer steht, oder Short-Cuts zum Springen zur anderen Klammer oder auswählen von allem zwischen den Klammern.

Bei ``jq`` ist die ``-r``-Option redundant, denn das ist in ``-j`` schon enthalten.

In Skripten verwende ich oft die Langform von Optionen, das ist lesbarer und man kann oft leichter in der Dokumentation danach suchen.

Die betroffene Zeile sähe dann so aus:

Code: Alles auswählen

    HAS_ALARM=$(curl --silent "${API_URL}" | jq --join-output '.success')
Und nun zum On-Topic-Teil: Die Portierung nach Python. 🤓

Initiale nahezu 1:1 Übersetzung (die davon ausgeht, das JSON enthält tatsächlich einen Wahrheitswert und nicht die Zeichenketten "true" und "false"):

Code: Alles auswählen

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

import requests

ACCESS_KEY = "DER-ALARM-ACCESSKEY"
API_URL = "https://www.xxxxxxxx"


def main():
    is_monitor_active = True
    while True:
        has_alarm = requests.get(
            API_URL, params={"accesskey": ACCESS_KEY}
        ).json()["success"]

        if has_alarm and not is_monitor_active:
            print("Turn display on")
            run(["xscreensaver-command", "-deactivate"])
            is_monitor_active = True
        elif not has_alarm and is_monitor_active:
            print("Turn display off")
            run(["xscreensaver-command", "-activate"])
            is_monitor_active = False

        sleep(20)


if __name__ == "__main__":
    main()
Da Python hier mit echten Wahrheitswerten arbeitet und nicht mit Zeichenketten "true" und "false" — oder eben auch was anderes wenn der Webserver was anderes sendet, lässt sich das mit etwas weniger Code-Wiederholung ausdrücken:

Code: Alles auswählen

        if has_alarm and not is_monitor_active:
            print("Turn display on")
            run(["xscreensaver-command", "-deactivate"])
            is_monitor_active = True
        elif not has_alarm and is_monitor_active:
            print("Turn display off")
            run(["xscreensaver-command", "-activate"])
            is_monitor_active = False

        # =>
        
        if has_alarm != is_monitor_active:
            print("Turn display", "on" if has_alarm else "off")
            run(
                [
                    "xscreensaver-command",
                    "-deactivate" if has_alarm else "-activate",
                ]
            )
            is_monitor_active = has_alarm
Der Code wäre insgesamt etwas lesbarer wenn man die beiden Aktionen zum prüfen des Alarms und zum setzen des Bildschirmschonerzustands jeweils in eine Funktion auslagert. Bei der Gelegenheit könnte man auch gleich eine Funktion zur Abfrage des Zustands vom Bildschirmschoner hinzufügen und nicht bedingungslos mit der Annahme der Monitor sein an starten.

Code: Alles auswählen

#!/usr/bin/env python3
from subprocess import PIPE, run
from time import sleep

import requests

ACCESS_KEY = "DER-ALARM-ACCESSKEY"
API_URL = "https://www.xxxxxxxx"
XSCREENSAVER_COMMAND = "xscreensaver-command"


def check_alarm():
    response = requests.get(API_URL, params={"accesskey": ACCESS_KEY})
    response.raise_for_status()
    return response.json()["success"]


def get_display_state():
    return (
        b"non-blanked"
        in run([XSCREENSAVER_COMMAND, "-time"], stdout=PIPE, check=True).stdout
    )


def set_display_state(state):
    run(
        [XSCREENSAVER_COMMAND, "-deactivate" if state else "-activate"],
        check=True,
    )


def main():
    is_display_active = get_display_state()
    while True:
        has_alarm = check_alarm()
        if has_alarm != is_display_active:
            print("Turn display", "on" if has_alarm else "off")
            set_display_state(has_alarm)
            is_display_active = has_alarm

        sleep(20)


if __name__ == "__main__":
    main()
Jetzt haben wir noch den recht grossen Unterschied zwischen dem Shell-Skript und dem Python-Programm im Umgang mit Problemen: Das Shell-Skript macht immer einfach weiter als wäre nichts passiert. Das Python-Programm bricht mit einer Ausnahme ab. Um das möglichst leicht zu lösen, kann man `loguru` benutzen um Ausnahmen in einem ``with``-Block einfach zu protokollieren. Da kann man dann auch gleich von `print()` auf Logging umstellen.

Und um Strg+C zum Abbrechen des Programms könnte man sich noch explizit kümmern.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
from subprocess import PIPE, run
from time import sleep

import requests
from loguru import logger

ACCESS_KEY = "DER-ALARM-ACCESSKEY"
API_URL = "https://www.xxxxxxxx"
XSCREENSAVER_COMMAND = "xscreensaver-command"


def check_alarm():
    response = requests.get(API_URL, params={"accesskey": ACCESS_KEY})
    response.raise_for_status()
    result = response.json()["success"]
    if not isinstance(result, bool):
        raise ValueError(
            f"expected boolean value, got {result!r} of type {type(result)}"
        )
    return result


def get_display_state():
    return (
        b"non-blanked"
        in run([XSCREENSAVER_COMMAND, "-time"], stdout=PIPE, check=True).stdout
    )


def set_display_state(state):
    run(
        [XSCREENSAVER_COMMAND, "-deactivate" if state else "-activate"],
        check=True,
    )


def main():
    try:
        is_display_active = get_display_state()
        while True:
            with logger.catch():
                has_alarm = check_alarm()
                if has_alarm != is_display_active:
                    logger.info(
                        "Turn display {}.", "on" if has_alarm else "off"
                    )
                    set_display_state(has_alarm)
                    is_display_active = has_alarm

            sleep(20)

    except KeyboardInterrupt:
        logger.info("Strg+C / SIGINT caught.")


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
Dennis89
User
Beiträge: 1152
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

@__blackjack__ kann es nicht sein, das ich von dir gelernt habe das man anstatt '!=' in Python lieber 'not' verwendet?


Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
ThomasL
User
Beiträge: 1366
Registriert: Montag 14. Mai 2018, 14:44
Wohnort: Kreis Unna NRW

Wie willst du denn das elegant mit not realisieren?
Ich bin Pazifist und greife niemanden an, auch nicht mit Worten.
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
Benutzeravatar
Dennis89
User
Beiträge: 1152
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

Wäre

Code: Alles auswählen

if not has_alarm == is_display_active:
falsch? Wenn ja, was übersehe ich da?

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

"falsch" nicht, nur deutlich umständlicher als `has_alarm != is_display_active`.
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Na „falsch“ ist es nicht. Aber schlecht. Denn eine solche Negierung bedeutet immer mehr kognitive Last, wenn man einen Ausdruck verstehen will. Du verwechselst das wahrscheinlich gerade mit dem „is not“, das aber eben auch nur für den is-Operator gilt & das ja auch innen steht. Nicht davor.
Benutzeravatar
Dennis89
User
Beiträge: 1152
Registriert: Freitag 11. Dezember 2020, 15:13

Okay ich verstehe was ihr meint. Danke für die Erklärung. 👍🏼

Verwechselt habe ich das nicht, aber irgendwie hatte ich mir da wohl was falsch gemerkt oder es aus dem Zusammenhang gerissen.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
nezzcarth
User
Beiträge: 1633
Registriert: Samstag 16. April 2011, 12:47

__blackjack__ hat geschrieben: Freitag 10. Juni 2022, 12:33

Code: Alles auswählen

    HAS_ALARM=$(curl --silent "${API_URL}" | jq --join-output '.success')
Nebenbemerkung: Wenn man sich nicht 150% sicher ist, dass da kein Whitespace vorkommen kann, oder das wirklich ist, was man will, würde ich auch die Substitution als Ganzes noch mal in doppelte Anführungszeichen packen. Das ist nach meinem Eindruck der Stil, der heute oft empfohlen wird.
Benutzeravatar
__blackjack__
User
Beiträge: 13061
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@nezzcarth: Da wird kein „word splitting“ mit dem Ergebnis betrieben und führende/folgende Whitespaces werden auch nicht entfernt:

Code: Alles auswählen

$ A=$(echo "   a   b\n   c   d   ")
$ echo ">$A<"
>   a   b
   c   d   <
Das war die Dash; bei der Bash muss man für das \n noch ``-e`` als Option an ``echo`` übergeben.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten