Definition GPIO mittels zweiter Datei

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
Danieldz
User
Beiträge: 4
Registriert: Samstag 22. Juni 2019, 08:00

Guten Morgen,
Ich bin frischer Neuling in der Programmiersprache Python. Da ich gerne den Raspberry Pi in verschiedenen Projekten einsetzen möchte und mich sehr gerne mit Programmieren an sich beschäftige, habe ich mich auch an Python rangetraut. Dazu habe ich auch als Hilfestellung "das umfassende Handbuch - Raspberry Pi".
Ich habe mit Hilfe des Buches es geschafft, die GPIO Pins erfolgreich einzurichten und schon in einem kleinen Testprogramm zu benutzen.
Dann habe ich mir überlegt, dass ich gerne das Setup etwas vereinfachen möchte. Dazu habe ich mir eine zweite Datei ("GPIO_Hilfe.py") erstellt.
Mittels print - Befehlen kann ich überprüfen, ob die Datei auch abgearbeitet wird, was sie tut, aber ich bekomme trotzdem eine Fehlermeldung:
>>> %Run 'GPIO Basics_V2.py'
Setup OK
Pin 26 Setup OK
Pin 21 Setup OK
Traceback (most recent call last):
File "/home/pi/Python/Einstieg/GPIO Basics_V2.py", line 16, in <module>
gpio.add_event_detect(21, gpio.BOTH)
RuntimeError: You must setup() the GPIO channel as an input first
>>>
Mein Hauptprogramm:

Code: Alles auswählen

#! /usr/bin/python3
#noch in Bearbeitung

import RPi.GPIO as gpio #alle RPi.GPIO Befehle mit gpio ausführen
import time
import GPIO_Hilfe as IO

IO.Setup("Board", False) #Setup ausführen

IO.IO(26, "OUT", "HIGH") #als Ausgang definieren
IO.IO(21, "IN", "HIGH") # als Eingang definieren

def Taster(n):
    print('Pegelwechsel für Pin ', n)

gpio.add_event_detect(21, gpio.BOTH)
gpio.add_event_callback(21,Taster) #Funktion wird solange aufgerufen, wie das Programm auch läuft

time.sleep(10) #nach 10 Sekunden wird das Programm beendet
gpio.cleanup(n) # GPIO Pins wieder freigeben
meine zweite Datei "GPIO_Hilfe":

Code: Alles auswählen

#! /usr/bin/python3
import RPi.GPIO as gpio #alle RPi.GPIO Befehle mit gpio ausführen
def Setup(bez, warn):
    if bez == "Board":
        gpio.setmode(gpio.BOARD) #Zählweise entspricht dem J8 Header
    else:
        gpio.setmode(gpio.BCM) #Zählweise Broadcam-GPIO-Nummern
    if warn == True:
        gpio.setwarnings(True)
    else:
        gpio.setwarnings(False) #Warnungen unterdrücken
    print("Setup OK")


def IO(Pin, Dir, HL): #LOW, HIGH, NONE
    if Dir == "In":
        if HL == "LOW":
            gpio.setup(Pin, gpio.IN, pull_up_down=gpio.PUD_DOWN)
        elif HL == "HIGH":
            gpio.setup(Pin, gpio.IN, pull_up_down=gpio.PUD_UP)
        else:
            gpio.setup(Pin, gpio.IN)
    else:
        if HL == "LOW":
            gpio.setup(Pin, gpio.OUT)
            gpio.output(Pin, gpio.LOW)
        elif HL == "HIGH":
            gpio.setup(Pin, gpio.OUT)
            gpio.output(Pin, gpio.HIGH)
        else:
            gpio.setup(Pin, gpio.OUT)

    print("Pin ", Pin, " Setup OK")
Wie kann ich den Fehler beheben?

Vielen Dank für eure Hilfe.

Habt ein schönes Wochenende.

LG
Danieldz
Benutzeravatar
__blackjack__
User
Beiträge: 13110
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Danieldz: Irgendwie wird das einfach nur viel komplizierter ohne das es wirklich Vorteile bringt, dass Du Konstanten durch Zeichenketten ersetzen anhand derer dann die Konstanten ausgewählt werden. Was soll das?

`IO()` ist auch kein Name an dem ein Leser erkennen könnte das hier ein Pin aufgesetzt wird.

Warum importierst Du das Modul `GPIO_Hilfe` als `IO`? Wenn man die *eigenen* Module beim importieren umbenennt, stellt sich doch die Frage warum nicht gleich den Namen für das Modul verwendet unter dem man es dann benutzen will, denn man hat die Benennung des Moduls ja selbst in der Hand. Ich würde das aber auch nicht `IO` nennen, denn das ist viel zu generisch. Zudem gibt es in der Standardbibliothek bereits ein `io`-Modul.

Zur Namensschreibweise: Man schreibt Namen in Python klein_mit_unterstrichen. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (`MixedCase`).

Man sollte nichts abkürzen was nicht allgemein bekannt ist. Wenn man `bezeichnung` meint, sollte man nicht `bez` schreiben und `direction` statt `dir`. `HL`? Warum nennst Du ein Argument nach dem Spiel Half-Life?

Man vergleicht Wahrheitswerte nicht mit literalen Wahrheitswerten, denn da kommt ja nur wieder ein Wahrheitswert bei heraus. Entweder der, den man sowieso schon hatte, dann hätte man den auch gleich nehmen können, oder dessen Gegenteil, das man mit ``not`` bekommt. Also statt ``if warn == True:`` einfach nur ``if warn:``. Allerdings unterscheiden sich die beiden Zweige nur durch einen Wahrheitswert – und zwar genau den, den `warn` hat. Also kann man sich das ``if``/``else`` an der Stelle auch komplett sparen und einfach den Wert einsetzen:

Code: Alles auswählen

    if warn == True:
        gpio.setwarnings(True)
    else:
        gpio.setwarnings(False)
    
     # ->
     
     gpio.setwarnings(warn)
Da man die Warnungen aber sowieso nicht untersdrücken, sondern deren Ursache beheben sollte, ist dieser Teil der Funktion sowieso überflüssig.

`print()`-Ausgaben gehören nicht in solche Funktionen. Wenn man zur Fehlersuche Ausgaben braucht, dann gibt es dafür das `logging`-Modul, wo man dann auch entscheiden kann ob und was alles ausgegeben werden soll und wohin.

Die `setup()`-Funktion ist damit dann schon mal überflüssig, denn der Benutzer hat echt nix gewonnen wenn er statt ``gpio.setmode(gpio.BOARD)`` nun ``gpio_hilfe.setup('Board')`` schreibt. Zudem kann er auch ``gpio_hilfe.setup('Parrot')`` schreiben ohne dass das mit einem Fehler quittiert wird. Das ist also eher eine Verschlechterung.

Und das ist auch der Grund für den Fehler, denn bei der `io()`-Funktion gibst Du 'IN' an, was aber nicht 'In' ist und somit steht 'IN' für `gpio.OUT`, wie auch 'Parrot', 'Spam', 'Wombat', oder sonst was für `gpio.OUT` steht. Man sollte bei solchen Prüfungen immer explizit auf *alle* Werte prüfen, und nicht in einem ``else`` einfach annehmen, dass es schon der letzte verbleibende gültige Wert ist. In so ein ``else`` am Ende gehört Code der eine Ausnahme auslöst wenn kein gültiger Wert übergeben wurde.

Bei der `io`-Funktion verwendest Du das dritte Argument je nach dem was das zweite Argument war, für zwei unterschiedliche Dinge. Was es schwer bis unmöglich macht einen passenden Namen dafür zu finden. Und auch diese Funktion ersetzt eigentlich nur Zeichenketten durch Konstanten für den Aufruf. Die Funktion hat keinen Mehrwert, die macht einfach nur alles komplizierter und Fehleranfälliger. Um die tolle Verbesserung mal dem Original gegenüber zu stellen:

Code: Alles auswählen

gpio_hilfe.io(26, 'OUT', 'HIGH')
gpio_hilfe.io(21, 'IN', 'HIGH')

# ->

gpio.setup(26, gpio.OUT, initial=gpio.HIGH)
gpio.setup(21, gpio.IN, pull_up_down=gpio.PUD_UP)
Zum Programm: Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Die Kommentare sind allesamt überflüssig. Faustregel: Kommentare sollten nicht beschreiben *was* der Code tut, denn das steht da ja bereits als Code, sondern *warum* er das so tut – sofern das nicht offensichtlich ist.

Man sollte sicherstellen das die `cleanup()`-Funktion in jedem Fall aufgerufen wird. Und es wird dort ein nicht existierendes `n` übergeben.

Funktionen und Methoden werden üblicherweise nach der Tätigkeit benannt, die sie ausführen. `Taster` ist keine Tätigkeit. Von der Schreibweise des Namens würde der Leser hier keine Funktion sondern eine Klasse erwarten, die einen Taster repräsentiert. Da es hier keine gute/offensichtliche Beschreibung für die Tätigkeit gibt, kann man die Konvention `on_<ereignis>` verwenden, die für Rückruffunktionen nicht unüblich ist, also beispielsweise `on_button()`.

Magische Zahlen und insbesondere Wiederholungen davon vermeidet man mit Konstanten. Dann lässt sich am Code ablesen was die Zahl eigentlich bedeutet und wenn man sie Zahl an sich mal ändern muss, lässt sich das an *einer* Stelle im Code machen, statt Fehleranfällig alle Vorkommen suchen und ersetzen zu müssen.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import time

from RPi import GPIO as gpio

BUTTON_PIN = 21
# 
# TODO Find a better name that reflects what is driven by this pin.
# 
OUTPUT_PIN = 26


def on_button(pin):
    print('Pegelwechsel für Pin', pin)


def main():
    gpio.setmode(gpio.BOARD)
    try:
        gpio.setup(OUTPUT_PIN, gpio.OUT, initial=gpio.HIGH)
        gpio.setup(BUTTON_PIN, gpio.IN, pull_up_down=gpio.PUD_UP)

        gpio.add_event_detect(BUTTON_PIN, gpio.BOTH)
        gpio.add_event_callback(BUTTON_PIN, on_button)

        time.sleep(10)
    finally:
        gpio.cleanup()


if __name__ == '__main__':
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Danieldz
User
Beiträge: 4
Registriert: Samstag 22. Juni 2019, 08:00

Danke __blackjack__!
Das war eine sehr hilfreiche Antwort. Das ging leider aus dem Handbuch nicht so schön hervor.
Vielen Dank auch für die Hinweise zwecks Groß- und Kleinschreibung. Ich werde das versuchen in zukünftigen Projekten anzuwenden.

Die Grundidee dahinter war tatsächlich, dass ich die Initialisierung der I/Os etwas vereinfachen kann, wenn ich mal mehrere I/Os zu definieren habe. Aber klar, Copy and Paste ist da vermutlich schneller getippt, als mein Spaß.

Dein Programm funktioniert übrigens.

Habt eine schöne Woche.

LG
Danieldz
Antworten