Abfrage der Laufzeit entfernen und weiteres

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Frank R.
User
Beiträge: 38
Registriert: Montag 23. September 2019, 10:10

Hallo Forum,

ich habe bezüglich des folgendes Codes eine Frage:

Code: Alles auswählen

import gpiozero
import time
import random

LEDs = gpiozero.LEDBoard(17, 18, 27, 22, 23, 24, 25, 4, 12)
T1 = gpiozero.Button(16)

start = False
while (start == False):
    if T1.is_pressed:
        start = True
    else:
        start = False

sekunden = int(input("Dauer der Zufallswiedergabe: "))

t0 = time.perf_counter()

while (start == True):
    ZZahl = random.randint(0,8)
    LEDs[ZZahl].on()
    time.sleep(0.1)
    LEDs[ZZahl].off()
    t1 = time.perf_counter()
    if (t1-t0) > sekunden:
        start = False
    else:
        start = True

LEDs.off()
Wie aus dem Code ersichtlich, wird nach Tasterdruck die Frage "Dauer der Zufallswiedergabe:" gestartet. Nach Eingabe einer Zahl, z.B. 10, und dem Drücken der Eingabetaste, werden alle LEDs zufällig ein- und ausgeschaltet und nach Ablauf von 10 Sekunden beendet.Der Code soll nun so aussehen, dass das Blinken sofort nach Tasterdruck startet, eine vorher FEST in den Code geschriebene Wiederholung durchläuft, z.B. 10 mal statt 10 Sekunden, dann endet, und die ZULETZT eingeschaltete LED für einige Sekunden eingeschaltet bleibt bevor die Schleife beendet wird. Nach erneutem Tasterdruck soll die Zufallswiedergabe dann wieder starten.

Folgenden Code hatte ich dazu in einer früheren Version des Codes geschrieben:

Code: Alles auswählen

NumLED = len(LED)  # Anzahl aktiver LEDs
Repeat = 5  # Muster wird Repeat mal wiederholt
Ontime = 0.1  # Leuchtdauer der LED

            for i in range(Repeat * NumLED):
                j = random.randint(0, NumLED - 1)
                GPIO.output(LED[j], True)
                time.sleep(Ontime)
                GPIO.output(LED[j], False)
Die restlichen Codezeilen habe ich oben entfernt, der Code war ohnehin fehlerhaft. Wie kann ich nun die Funktion, so wie sie im zweiten Codeschnipsel angezeigt wird, ohne Schleifenprobleme in den Code ganz oben einbinden?

Für eure Hilfe wäre ich dankbar!
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Die Klammern um die while-Bedingungen sind überflüssig und können weg. Explizit auf True oder False prüft man nicht, sondern nutzt `start` bzw. `not start`; wobei sich das ein bißchen holprig liest, `not started` wäre besser. In Wirklichkeit ist `started` ganz überflüssig.

Statt mit einer busy-Loop die Welt zu heizen gibt es `button.wait_for_press`. Warum heißt der Knopf eigentlich `T1` und was hat das mit `t1` zu tun?
Apropos kryptische Namen. Abkürzungen vermeiden, `ZZahl` ist eigentlich eine `zufallszahl`. Variablennamen werden in Python nach Konvention klein_mit_unterstrich geschrieben.

Kommt also das dabei raus:

Code: Alles auswählen

import gpiozero
import time
import random

def main():
    leds = gpiozero.LEDBoard(17, 18, 27, 22, 23, 24, 25, 4, 12)
    button = gpiozero.Button(16)
    button.wait_for_press()

    sekunden = int(input("Dauer der Zufallswiedergabe: "))
    t0 = t1 = time.perf_counter()
    while t1 - t0 < sekunden:
        zufallszahl = random.randint(0,8)
        leds[zufallszahl].on()
        time.sleep(0.1)
        leds[zufallszahl].off()
        t1 = time.perf_counter()

    leds.off()

if __name__ == '__main__':
    main()

Warum wechselst Du in Deinem zweiten Code auf RPi.GPIO? Das passt nicht zusammen. Wo ist das Problem, die while-Schleife im ersten Beispiel durch eine for-Schleife zu ersetzen? Was für ein konkretes Problem hast Du noch?
Frank R.
User
Beiträge: 38
Registriert: Montag 23. September 2019, 10:10

Erstmal vielen Dank für Deine Antwort. Der zweite Code ist wie gesagt veraltet, ich hatte erst danach auf gpiozero gewechselt. Und ich hatte vergessen zu erwähnen, dass ich Anfänger bin. Also bitte nicht zuviel Vorkenntnisse erwarten. Ich habe zwar bereits etliche Anleitungen angeschaut, aber das was ich suche nicht gefunden.

Der von Dir gepostete Code ist also, wenn ich das richtige lese, der selbe Ablauf wie mein Code, aber optimiert?
Benutzeravatar
__blackjack__
User
Beiträge: 13113
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Erst einmal zum ersten Code. Das Hauptprogramm sollte in einer Funktion verschwinden.

`T1` sollte einen besseren Namen bekommen, an dem der Leser erkennen kann was der Wert bedeutet.

Um die Bedingung bei ``while`` gehören keine unnötigen Klammern. Man vergleicht auch nicht mit literalen ``True`` und ``False`` Werten. Da kommt ja eh nur wieder ``True`` oder ``False`` bei heraus, das heisst entweder der Wert, den man sowieso schon hatte, oder das Gegenteil davon — dafür gibt es dann ``not``. Also stannt ``while (start == False):`` schreibt man ``while not start:``.

Dann gibt `Button.is_pressed` einen Wahrheitswert zurück. Es ist also unnötig umständlich das als Bedingung in einem ``if``/``else`` zu nehmen um dann in den beiden Zweigen einem Namen ``True`` oder ``False`` zuzuweisen wenn `is_pressed` doch bereits der Wert ist, den man zuweisen möchte:

Code: Alles auswählen

    start = False
    while not start:
        if button.is_pressed:
            start = True
        else:
            start = False
    # ->
    
    start = False
    while not start:
        start = button.is_pressed
Aber auch das ist noch nicht gut, denn man sollte den Prozessor nicht 100% damit beschäftigen dauernd den Zustand von dem Button zu testen. Das Objekt hat dafür entsprechende `wait_*()`-Methoden die das intern effizienter lösen können:

Das `start`-Flag braucht man auch bei der zweiten Schleife nicht aus der man einfach eine ”Endlosschleife” machen kann, die bei Eintreten der Endbedingung per ``break`` verlassen wird. Der Name `start` ist ein etwas komisch für ein Flag das für's *beenden* zuständig ist.

Statt selbst eine Zufallszahl mit ”magischen” literalen Grenzwerten zu erzeugen, sollte man sich den Indexzugriff sparen und einfach mit `random.choice()` eine LED auswählen.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import random
import time

import gpiozero


def main():
    leds = gpiozero.LEDBoard(17, 18, 27, 22, 23, 24, 25, 4, 12)
    button = gpiozero.Button(16)

    button.wait_for_release()
    sekunden = int(input("Dauer der Zufallswiedergabe: "))

    start_time = time.monotonic()
    while True:
        led = random.choice(leds)
        led.on()
        time.sleep(0.1)
        led.off()
        if (time.monotonic() - start_time) >= sekunden:
            break

    leds.off()


if __name__ == "__main__":
    main()
Ich verstehe jetzt nicht wo Dein konkretes Problem ist? Du beschreibst was Du erreichen willst, und zeigst dann Code der für `RPi.GPIO` geschrieben ist, aber nicht das macht was Du erreichen willst. Ansonsten ist das eigentlich recht einfach. Du musst Dir halt klar machen was der vorhandene Code in welcher Reihenfolge macht, und wenn Du das ändern willst, eben entsprechend abändern. Wenn die zuletzt ausgewählte Zufalls-LED zum Beispiel an bleiben soll, dann darf sie halt vor dem verlassen der Schleife nicht ausgeschaltet werden. Das ist doch eigentlich logisch‽

Und das da keine Frage kommen soll nach dem Knopfdruck und eine feste Anzahl Wiederholungen statt einer bestimmten Zeit, ist doch auch trivial. Das hast Du anscheinend mit `RPi.GPIO` schon gemacht, und das betrifft ja den Programmablauf und nicht die API mit der die LEDs angesteuert werden. Das ist also unabhängig davon ob Du `RPi.GPIO` oder `gpiozero` dafür verwendest.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Frank R.
User
Beiträge: 38
Registriert: Montag 23. September 2019, 10:10

Mein konkretes Problem ist, dass ich nicht weiß, wie ich den Code schreiben muss. Ich bin wie gesagt Anfänger, ich besitze den Raspi erst seit knapp 3 Wochen und hatte mit Python vorher überhaupt nichts zu tun. Was das Programm können soll ist mir klar, das hilft mir aber nichts wenn ich die entsprechenden Befehle etc. nicht kenne bzw. verstehe. Genau deswegen frage ich ja hier.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Du weißt, wie man Variablen setzt; Du weißt, wie man eine for-Schleife macht? Wenn Du dann noch Deinen eigenen Code verstanden hast, sollte es kein Problem sein, das von Dir gewünschte umzusetzen. Falls Du doch irgendwo nicht weiter kommst, dann beschreibe bitte genau, an welcher Stelle es hakt, sonst können wir Dir nicht helfen.
Benutzeravatar
__blackjack__
User
Beiträge: 13113
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Frank R.: Was genau kennst oder verstehst Du denn nicht? Wo genau hakt es? Soweit ich das sehe ist an Zutaten eigentlich alles da, und das ist auch alles auf dem Niveau eines Grundlagentutorials zum Programmieren allgemein, also auch gar nicht mal Raspi- oder GPIO-spezifisch. Ein Grundlagentutorial findet sich beispielsweise in der Python-Dokumentation.

Ansonsten gilt allgemein das man Probleme in Teilprobleme zerlegt und die dann einzeln löst. Du hast da ja beispielsweise drei Stück, die unabhängig voneinander sind: 1. Keine Abfrage der Zeit. 2. Feste Anzahl von Wiederholungen statt bestimmte Laufzeit. 3. Die letzte LED soll nach der Schleife eine Weile an bleiben. Alle drei Sachen kann man für sich isoliert, nacheinander lösen. Wobei die ersten beiden sehr einfach sind. 1. ist wirklich trivial, und 2. müsstest Du können, denn so etwas hast Du ja bereits gemacht. Bei 3. muss man dann ein kleines bisschen überlegen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Frank R.
User
Beiträge: 38
Registriert: Montag 23. September 2019, 10:10

Nur mal zum Verständnis, so sah der ursprüngliche Code aus, noch mit RPi.GPIO statt gpiozero.

Code: Alles auswählen

import RPi.GPIO as GPIO
import time  # für sleep()
import random  # für random()

# BCM-Bezeichnung der Pins verwenden
GPIO.setmode(GPIO.BCM)

LED = [17, 18, 27, 22, 23, 24, 25, 4, 12]  # GPIOs für die LEDs

# GPIOs auf Ausgang setzen und LED ausschalten
for i in LED:
    GPIO.setup(i, GPIO.OUT, initial=0)

NumLED = len(LED)  # Anzahl aktiver LEDs
Repeat = 5  # Muster wird Repeat mal wiederholt
Ontime = 0.1  # Leuchtdauer der LED

GPIO.setup(16, GPIO.IN, pull_up_down=GPIO.PUD_UP)  # Push Button an GPIO16

print("Taster drücken, um Programm zu starten")
print("Strg+C beendet das Programm")

try:
    while True:
        button_state = GPIO.input(16)
        if button_state == False:
            for i in range(Repeat * NumLED):
                j = random.randint(0, NumLED - 1)
                GPIO.output(LED[j], True)
                time.sleep(Ontime)
                GPIO.output(LED[j], False)

except KeyboardInterrupt:
    print("Programm beendet")
    GPIO.cleanup()
 
Der gesamte Code wurde aus diversen Beispielen die ich gefunden hatte zusammengesetzt, und ist natürlich fehlerhaft. Aber zumindest lief das Blinken fast genauso ab, wie gewünscht. Taste drücken, 5 Durchläufe, LEDs aus. Dass die Schleife so nicht geschrieben werden sollte hatte ich schon von jemandem erfahren. Ist das Problem jetzt ersichtlich?
Nur weil ich ein paar Zeilen Code zusammengeschrieben habe, bedeutet das noch lange nicht, dass ich ihn komplett verstehe. Und dass ich mit RPi.GPIO angefangen habe, statt mit gpiozero, liegt einfach daran, dass die meisten Bespiele darauf basieren.

Ich werde hier noch einmal ganz genau formulieren, was der Code bewerkstelligen soll (wenn er denn richtig geschrieben ist):
  1. Programm wird gestartet
  2. Druck auf den Taster startet eine fest vorgegebene Anzahl an Durchläufen (Repeat = 5)
  3. alle LEDs werden zufällig nacheinander für 0.1 Sekunden (Ontime) eingeschaltet
  4. nach Ablauf der bei Repeat angegebenen Zahl, in dem Fall 5 mal, soll die zuletzt eingeschaltete LED für einige Sekunden beleuchtet bleiben (fehlt im Code)
  5. nach Ausschalten der zuletzt eingeschalteten LED, bspw. nach 10 Sekunden, wird die Schleife beendet, bzw. soll das Programm auf Ausgangszustand
  6. Programm wartet auf erneutes Betätigen des Tasters
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Und nochmal die Frage: wo hängst Du konkret fest?

Du hast bereits gezeigt, dass Du for-Schleifen beherrschst, Punkt 2 sollte damit klar sein.
Punkt 3 ist eh schon umgesetzt.
Punkt 4: Frage: was ist die zuletzt leuchtende LED? Wie kann man sie nochmal einschalten?
Punkt 6: Wenn Du wieder mit Punkt 2 starten willst, brauchst Du nochmal eine Schleife um den ganzen Block.
Benutzeravatar
__blackjack__
User
Beiträge: 13113
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Frank R.: Wenn Du Code nicht komplett verstanden hast, den Du Dir irgendwoher zusammenkopiert hast, bedeutet das Du ihn verstehen musst. Wie gesagt, da ist soweit ich das sehe nichts was nicht in einem Grundlagentutorial behandelt würde. Arbeite so eines durch — in der Python-Dokumentation gibt es beispielsweise eines — und sage dann ganz konkret was Du nicht verstehst, wo es Probleme gibt. Das kann Dir niemand abnehmen, das musst Du schon selbst machen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Frank R.
User
Beiträge: 38
Registriert: Montag 23. September 2019, 10:10

Also das wird jetzt vermutlich falsch sein, aber würde das funktionieren?

Code: Alles auswählen

    Durchgänge = 5
    t0 = t1 = time.perf_counter()
    while t1 - t0 < Durchgänge:
also "sekunden = int(input("Dauer der Zufallswiedergabe: "))" entfernen, und stattdessen in der Zeile "Durchgänge = 5"? Vorher musste ich ja einen Wert eingeben und bestätigen, aber mit dem neuen Code wäre der Wert doch fest eingetragen. Oder bin ich hier auf dem Holzweg?
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du bist auf dem Holzweg. Du mischst Zeiten (und dabei auch noch die gleichen zwei Zeitstempel, womit t1 - t0 gleich 0 ist) mit Anzahl von Durchgaengen. 0 ist aber immer kleiner als 5, da passiert also nix.

Wenn du etwas x mal machen willst, dann kannst du das mit

Code: Alles auswählen

for i in range(x):
    ...
tun.
Frank R.
User
Beiträge: 38
Registriert: Montag 23. September 2019, 10:10

Wenn ich das richtig verstanden habe, ist der Code für RPi.GPIO

Code: Alles auswählen

NumLED = len(LED)  # Anzahl aktiver LEDs
Repeat = 5  # Muster wird Repeat mal wiederholt
Ontime = 0.1  # Leuchtdauer der LED

            for i in range(Repeat * NumLED):
                j = random.randint(0, NumLED - 1)
                GPIO.output(LED[j], True)
                time.sleep(Ontime)
                GPIO.output(LED[j], False)
also an für sich richtig, muss aber für gpiozero umgeschrieben werden? Sprich die Zeilen "GPIO.output(LED[j], True)" und "GPIO.output(LED[j], False)" sind falsch?
Frank R.
User
Beiträge: 38
Registriert: Montag 23. September 2019, 10:10

so z.B.?

Code: Alles auswählen

for i in range(Repeat * NumLED):
    j = random.randint(0, NumLED - 1)
    LED[j].on()
    time.sleep(Ontime)
    LED[j].off()
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

warum probierst Du es nicht einfach aus?
Frank R.
User
Beiträge: 38
Registriert: Montag 23. September 2019, 10:10

Wollte ich morgen machen, heute Abend werfe ich den Raspi nicht mehr an. Mir geht es erstmal darum, überhaupt den Code zu verstehen. Und das ist gar nicht so einfach, denn obwohl ich schon unzählige Anleitungen durchwühlt habe, fehlt irgendwie immer genau das, was ich suche. Klar, für Fortgeschrittene sieht das anders aus, aber als Anfänger kann man z.B. nicht immer sehen, ob ein Beispiel auch wirklich korrekt ist. So habe ich z.B. ein Codebeispiel abgetippt, und beim debuggen über Thonny wurde sofort eine Fehlermeldung angezeigt. Da war eine eine einzige Codezeile drin, welche das gesamte Beispiel funktionsunfähig gemacht hat. Genau deswegen mag ich es auch nicht sonderlich, wenn ich auf Tutorials verwiesen werde, die ggf. fehlerhaft sind oder die mich noch mehr verwirren.
Am sinnvollsten ist immer, wenn man kommentierten Code hat, bei dem ich nachvollziehen kann, was da eigentlich passiert. Dann wird´s mit dem Lernen auch einfacher.
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das verwiesene Tutorial ist das offizielle von Python. Nicht das es perfekt wäre - aber fehlerhaft ist es jedenfalls nicht.
Benutzeravatar
__blackjack__
User
Beiträge: 13113
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Frank R.: Ich verweise dann noch mal auf das Tutorial in der Python-Dokumentation. Das ist von den Leuten die auch Python entwickeln, die kennen sich also wirklich gut mit dem Thema aus. Die Codebeispiele dort sind getestet, und falls die doch mal Fehler machen, dann werden diese in der Regel auch gemeldet und behoben. Wobei die Codebeispiele in der Python-Dokumentation, die Schnippsel aus einer Python-Shell, sind auch automatisiert getestet werden. Das heisst jedes mal wenn die Dokumentation gebaut wird, dann läuft da auch ein Programm drüber und führt diese Code-Schnippsel aus und vergleicht die Ausgabe mit dem was in der Dokumentation steht. Dazu gibt es in der Python-Standardbibliothek das `doctest`-Modul: https://docs.python.org/3.6/library/doctest.html

Programmieren zu lernen oder Code zu schreiben ohne den ausführen und testen zu können, ist so sinnvoll wie ohne Wasser schwimmen lernen zu wollen. Klar kann man bestimmte Bewegungsabläufe auf dem trockenen am Boden durchführen, aber ob das tatsächlich auch im Wasser klappt, merkt man erst wenn man im Wasser ist. Und wenn es nicht klappt, kann man auch nur dort Verbesserungen vornehmen und prüfen ob die das ganze tatsächlich besser oder schlechter machen.

Man programmiert nicht in dem man ganze Programme runterschreibt und die dann laufen lässt wenn man sie fertig geschrieben hat, sondern man entwickelt Programme Stück für Stück. In dem man immer ein bisschen Code schreibt der dem Ziel näher kommt und den dann testet. Erst wenn der Code das macht was er soll, schreibt man weiteren Code dazu. Und testet den dann auch wieder. Bis man am Ende ein komplettes Programm entwickelt hat.

Ich habe immer noch das Gefühl Du drückst Dich so ein bisschen darum selbst zu lernen und willst *alles* von anderen erklärt bekommen. Denn Du hast immer noch keinen einzigen *konkreten* Punkt genannt an dem Du etwas nicht verstehst. Andererseits aber Code gezeigt der im Grunde alle Zutaten enthält um Dein Ziel zu erreichen, und der sich auf Grundlagen beschränkt die in Tutorials und Bücher beschrieben werden. Warum sollte man Dir hier Schleifen erklären, wo das doch bereits in diversen öffentlich zugänglichen Büchern und Tutorials erklärt wird? Da steht nicht exakt der Code den Du haben willst, aber den musst *Du* ja auch entwickeln. Genau das ist ja programmieren.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Frank R.
User
Beiträge: 38
Registriert: Montag 23. September 2019, 10:10

Habe den Raspi eben doch noch mal befeuert, und den folgenden Code anhand eurer Beispiele angepasst:

Code: Alles auswählen

import gpiozero
import time
import random


def main():
    leds = gpiozero.LEDBoard(17, 18, 27, 22, 23, 24, 25, 4, 12)
    button = gpiozero.Button(16)
    numled = len(LED)  # Anzahl aktiver LEDs
    repeat = 5  # Muster wird Repeat mal wiederholt
    ontime = 0.1  # Leuchtdauer der LED
    button.wait_for_press()

    for i in range(repeat * numled):
        j = random.randint(0, numled - 1)
        LED[j].on()
        time.sleep(ontime)
        LED[j].off()
    
    leds.off()


if __name__ == '__main__':
    main()

Beim debuggen in Thonny werden mir nun zwei Fehler angezeigt:
  • in Zeile 9 > Python does not know what LED stands for
  • in Zeile 14 > Unused variable 'i'
Frank R.
User
Beiträge: 38
Registriert: Montag 23. September 2019, 10:10

ah, habe schon was gefunden. Natürlich geht das so nicht, denn im alten Code für RPi.GPIO war ja noch die Zeile "for i in LED: GPIO.setup(i, GPIO.OUT, initial=0)" vorhanden, deswegen meckert der wegen fehlender variable 'i'
Antworten