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

Montag 23. September 2019, 11:41

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: 11625
Registriert: Sonntag 21. Oktober 2012, 17:20

Montag 23. September 2019, 12:18

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

Montag 23. September 2019, 12:32

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: 5547
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Montag 23. September 2019, 12:46

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.
long long ago; /* in a galaxy far far away */
Frank R.
User
Beiträge: 38
Registriert: Montag 23. September 2019, 10:10

Montag 23. September 2019, 13:43

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: 11625
Registriert: Sonntag 21. Oktober 2012, 17:20

Montag 23. September 2019, 13:59

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: 5547
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Montag 23. September 2019, 14:36

@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.
long long ago; /* in a galaxy far far away */
Frank R.
User
Beiträge: 38
Registriert: Montag 23. September 2019, 10:10

Montag 23. September 2019, 14:50

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: 11625
Registriert: Sonntag 21. Oktober 2012, 17:20

Montag 23. September 2019, 15:09

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: 5547
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Montag 23. September 2019, 15:26

@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.
long long ago; /* in a galaxy far far away */
Frank R.
User
Beiträge: 38
Registriert: Montag 23. September 2019, 10:10

Montag 23. September 2019, 18:54

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: 7712
Registriert: Mittwoch 14. Oktober 2015, 14:29

Montag 23. September 2019, 19:03

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

Montag 23. September 2019, 20:13

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

Montag 23. September 2019, 20:48

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: 11625
Registriert: Sonntag 21. Oktober 2012, 17:20

Montag 23. September 2019, 20:50

warum probierst Du es nicht einfach aus?
Antworten