Orange Pi - Led ansteuern

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Kampfgummibaerlie
User
Beiträge: 27
Registriert: Freitag 18. Juni 2021, 14:44

Habe eine plausible Lösung online gefunden:
Auf der Seite wird Schritt für Schritt erklärt, was wann, wo, wie gemacht wird und bewirkt.
https://buyzero.de/blogs/news/alles-uber-gpio-pins

Da es bereits spät ist habe ich mir den Code simpel herauskopiert und werde mir den jetzt nochmal Schritt für Schritt durchlesen.

Danke und LG
Kampfgummibaerlie
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Oh mein Gott. Der Code ist ja die totale Horrorshitshow. Mit os System calls, um Dateien anzulegen…. es geht wirklich nicht schlechter und weniger performant, selbst mit aller Mühe der Welt.

Die ganzen Shell Befehle erzeugen Dateien und beschreiben die mit werten für zb das Output Level. Schrieb den Code dazu einfach selbst. Das ist nun wirklich keine Raketenwissenschaft. In dem C code später macht diese Quelle der Hölle das sogar selbst. Warum sie da kein Popen oder System benutzen, wenn das doch in Python opportun war, bleibt ihr dunkles Geheimnis 🤫
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Hier nochmal der Code in allen Einzelheiten auseinander genommen:
In __init__ wird `num` geprüft, und im Fehlerfall einfach weiter gemacht, als ob nichts wäre, was dann fünf Zeilen später in einem AttributeError mündet. Zum Glück ist die if-Abfrage immer wahr, weil eine Zahl entweder größer 0 ist oder kleiner 40.
num = 0 ist anscheinend ein gültiger Wert, wird aber bei if self.num dann ausgeschlossen? Die if-Zeile macht, mit der "Fehlerbehandlung" eh keinen Sinn.
Es werden f-Strings verwendet, die Argumente aber trotzdem mit str in Strings explizit umgewandelt.
os.system sollte man nicht verwenden, und das nur dazu zu nutzen, um Dateien zu schreiben ist echt skuril.
__del__-Methoden sollte man vermeiden, ein explizites cleanup wäre da besser.
set_mode wäre wohl besser ein Property.

Code: Alles auswählen

from pathlib import Path
from time import sleep

class GPIO_Pin:
    GPIO_PATH = Path('/sys/class/gpio')

    def __init__(self, num, mode):
        if not 0 <= num <= 40:
            raise ValueError('Invalid Pin Number')
        self.num = num
        (self.GPIO_PATH / 'export').write_text(str(self.num))
        sleep(0.05)
        self.mode = mode

    def _read(self, type):
        return (self.GPIO_PATH / f"gpio(self.num)" / type).read_text()

    def _write(self, type, value):
        (self.GPIO_PATH / f"gpio(self.num)" / type).write_text(value)

    def reset(self):
        self.write(0)
        self.mode = 'in'

    @property
    def mode(self):
        return self._read('direction')
        
    @mode.setter
    def mode(self, value):
        try:
            mode = {
                "out": "out",
                "write": "out",
                "in": "in",
                "read": "in",
            }[value.lower()]
        except KeyError:
            raise ValueError('Invalid Mode')
        self._write('direction', mode)

    @property
    def value(self):
        return int(self._read('value'))
        
    @mode.setter
    def value(self, value):
        try:
            value = {
                "0": "0",
                "LOW": "0",
                0: "0",
                "1": "1",
                "HIGH": "1",
                1: "1",
            }[value]
        except KeyError:
            raise ValueError('Invalid Value')
        self._write('value', value)

    def cleanup(self):
        self.reset()
        (self.GPIO_PATH / 'unexport').write_text(str(self.num))


def main():
    pin = GPIO_Pin(13, 'out')
    try:
        number_of_blinks = 10
        for i in range(number_of_blinks):
            pin.value = 1
            sleep(0.5)
            pin.value = 0
            sleep(0.5)
    finally:
        pin.cleanup()

if __name__ == '__main__':
    main()
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

ich hätte Fragen. Ich gehe davon aus, dass der Code von @Sirius3 ein ungetesteter Zwischenstand ist, weil mir ist da ein paar Kleinigkeiten aufgefallen sind. Aber erst meine Frage, die Funktion `mode` gibt es zwei mal, einmal mit `@property` und ein mal mit `@mode.setter`. Ist das `setter` dazu da, damit man nicht einen weiteren Namen einführen muss und Python selbstständig merkt, dass wenn `mode = ...` geschrieben wird, dann wird die Funktion mit `setter` benutzt und ansonsten die andere? Ich "weis" bzw. ich lese es öfters, das andere Sprachen so was wie `setter` und `getter` brauchen, aber hier könnte ich die Funktion einfach `set_mode` nennen und den Wert "normal" als Argument übergeben. Gibt es einen weiteren Vorteil?

Was mir aufgefallen ist, `number_of_blinks` könnte eine Konstante sein. `i` wird in der for-Schleife gar nicht genutzt. Der Dekorator `mode.setter` über der Funktion `value` sollte `value.setter` heißen. Das Wörterbuch könnte auch eine Konstante sein und ich würde nicht alles 'value' nennen.
In `mode` würde ich auch wieder eine Konstante für das Wörterbuch nehmen.
In `_write` ist man bei Wahl der Klammer für den 'f'-String verrutscht und in `_read` auch. An der Stelle würde ich dann auch `num` in `pin_number` umbenennen. Und, da hat mir aber PyCharm geholfen, 'type` ist kein guter Namen, weil so schon eine Python-Funktion heißt.
In `reset` fehlt ein Unterstrich beim Aufruf von `_write` und ein Argument.
Ich habe das noch nie gemacht, aber ich habe die Konstanten jetzt in der Klasse definiert, weil die Klasse die zwingend braucht. Ist das richtig?

Ungetestet:

Code: Alles auswählen

from pathlib import Path
from time import sleep


NUMBER_OF_BLINK = 10


class GpioPin:
    GPIO_PATH = Path('/sys/class/gpio')
    COMMAND_TO_GPIO_STATE = {
        "0": "0",
        "LOW": "0",
        0: "0",
        "1": "1",
        "HIGH": "1",
        1: "1",
    }
    COMMAND_TO_GPIO_MODE = {
                "out": "out",
                "write": "out",
                "in": "in",
                "read": "in",
            }

    def __init__(self, pin_number, mode):
        if not 0 <= pin_number <= 40:
            raise ValueError('Invalid Pin Number')
        self.pin_number = pin_number
        (self.GPIO_PATH / 'export').write_text(str(self.pin_number))
        sleep(0.05)
        self.mode = mode

    def _read(self, file_type):
        return (self.GPIO_PATH / f"gpio{self.pin_number})" / file_type).read_text()

    def _write(self, file_type, value):
        (self.GPIO_PATH / f"gpio{self.pin_number})" / file_type).write_text(value)

    def reset(self):
        self._write('value', 0)
        self.mode = 'in'

    @property
    def mode(self):
        return self._read('direction')

    @mode.setter
    def mode(self, command):
        try:
            mode = self.COMMAND_TO_GPIO_MODE[command.lower()]
        except KeyError:
            raise ValueError('Invalid Mode')
        self._write('direction', mode)

    @property
    def value(self):
        return int(self._read('value'))

    @value.setter
    def value(self, command):
        try:
            gpio_state = self.COMMAND_TO_GPIO_STATE[command]
        except KeyError:
            raise ValueError('Invalid Value')
        self._write('value', gpio_state)

    def cleanup(self):
        self.reset()
        (self.GPIO_PATH / 'unexport').write_text(str(self.pin_number))


def main():
    pin = GpioPin(13, 'out')
    try:
        for _ in range(NUMBER_OF_BLINK):
            pin.value = 1
            sleep(0.5)
            pin.value = 0
            sleep(0.5)
    finally:
        pin.cleanup()


if __name__ == '__main__':
    main()
Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
narpfel
User
Beiträge: 645
Registriert: Freitag 20. Oktober 2017, 16:10

@Dennis89: Zu deiner Frage zu `property`: Das hast du so richtig verstanden. Die Funktion, die mit `@property` dekoriert ist, wird bei einem lesenden Zugriff auf das Attribut aufgerufen; die Funktion, die mit `@<name>.setter` dekoriert ist, bei einem schreibenden Zugriff (Dokumentation).

Der Vorteil von Properties ist, dass man eben nicht `get_something()` und `set_something(value)` schreiben muss, sondern ganz normalen Attributzugriff benutzen kann. Was ja offensichtlich kürzer ist und die Intention besser ausdrückt.

Ein weiterer Vorteil ist, dass man normale Attribute transparent durch Properties austauschen kann, ohne dass man Code, der das Attribut benutzt, ändern muss. In Sprachen, die das nicht können (looking at you, Java), ist es aus dem Grund üblich, gar keine öffentlichen Attribute zu haben, sondern für alles in vorausschauendem Gehorsam Getter und Setter zu schreiben. Was den Code im Allgemeinen nicht verständlicher macht.
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

Vielen Dank für die Erklärung 🙂


Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Antworten