Liebe Insider vom Forum
Ich bin Lehrer und arbeite mit einem Schüler an einem "Torzähler", den wir in einen Tischkicker einbauen wollen. Wir arbeiten...
- mit einem Raspberry Pi
- einem LED-Display von Adafruit
- zwei Druckschaltern (für beide Kickertore)
- und programmieren mit Python
Wir sind Python-Anfänger und versuchen, das nötige Wissen im Selbststudium zu erlangen. Bei folgendem Programmier-Problem kommen wir derzeit nicht weiter:
Der Kern unseres Programms sind Abfragen der beiden Druckschalter, welche ständig laufen sollen. Wird der eine Druckschalter betätigt (bei einem Tor), so soll die eine Variable (Torstand der einen Mannschaft) hochgezählt werden; beim anderen Druckschalter die andere Variable. Die beiden Variabeln werden ständig aktuell im Display angezeigt. Gemäss unserer Logik würden sich dafür zwei parallel laufende while-Schlaufen eignen. Allerdings finden wir keine funktionierende Syntax, wie wir das hinkriegen könnten. Hat jemand Tipps oder hilfreiche Links?
Besten Dank im Voraus!
Swi_Mo
Zwei while-Schlaufen parallel laufen lassen in Python?
-
BlackJack
@Swi_Mo: Das einfachste wäre in *einer* ``while``-Schleife beide Schalter zu prüfen. Ansonsten muss man entweder selber anfangen etwas Nebenläufiges zu bauen (`threading`-Modul), oder das verwenden was die Bibliothek bietet, mit der die GPIOs angesprochen werden.
Ein Mittelding wäre selber für die Nebenläufigkeit zu sorgen, aber in den beiden Threads keine ``while``-Schleife zu verwenden, sondern `Button.wait_for_press()` beim `gpiozero`-Package oder `wait_for_edge()` bei `RPi.GPIO`. Das hat den Vorteil, dass diese Bibliotheken das intern effizienter lösen können als tatsächlich CPU Zeit mit warten zu verbrennen („busy waiting“) falls die Hardware Interrupts dafür bietet.
Wenn man die Nebenläufigkeit nicht selber programmieren möchte, bieten sowohl `gpiozero` als auch `RPi.GPIO` die Möglichkeit eine Rückruffunktion für das drücken, loslassen, oder beides zu registrieren. `when_pressed` und `when_released` bei `Button`-Objekten bzw. `add_event_detect()`.
Wenn man die Threads selbst schreibt und mit einer `Queue.Queue` mit dem Hauptthread kommuniziert, kommt man mit Funktionen aus, bei den Rückruffunktionen muss man sich wohl entweder Closures oder objektorientierte Programmierung anschauen wenn das sauber werden soll.
Das hat übrigens alles nichts mit Syntax zu tun.
Ein Mittelding wäre selber für die Nebenläufigkeit zu sorgen, aber in den beiden Threads keine ``while``-Schleife zu verwenden, sondern `Button.wait_for_press()` beim `gpiozero`-Package oder `wait_for_edge()` bei `RPi.GPIO`. Das hat den Vorteil, dass diese Bibliotheken das intern effizienter lösen können als tatsächlich CPU Zeit mit warten zu verbrennen („busy waiting“) falls die Hardware Interrupts dafür bietet.
Wenn man die Nebenläufigkeit nicht selber programmieren möchte, bieten sowohl `gpiozero` als auch `RPi.GPIO` die Möglichkeit eine Rückruffunktion für das drücken, loslassen, oder beides zu registrieren. `when_pressed` und `when_released` bei `Button`-Objekten bzw. `add_event_detect()`.
Wenn man die Threads selbst schreibt und mit einer `Queue.Queue` mit dem Hauptthread kommuniziert, kommt man mit Funktionen aus, bei den Rückruffunktionen muss man sich wohl entweder Closures oder objektorientierte Programmierung anschauen wenn das sauber werden soll.
Das hat übrigens alles nichts mit Syntax zu tun.
Hier mal eine funktionierende Lösung auf die Schnelle. Kann sicherlich optimiert werden 
Code: Alles auswählen
#!/usr/bin/env python
# coding: utf-8
from __future__ import print_function
from RPi import GPIO
import signal
from itertools import count
from functools import partial
PINS = [20, 21]
def setup_gpio():
GPIO.setmode(GPIO.BCM)
GPIO.setup(PINS, GPIO.IN)
def print_score(counter_team_red, counter_team_blue, pin):
if pin == 20:
print('Team Red: {}'.format(counting_goals_team_red(counter_team_red)))
else:
print('Team Blue: {}'.format(counting_goals_team_blue(counter_team_blue)))
def counting_goals_team_red(counter_team_red):
return counter_team_red.next()
def counting_goals_team_blue(counter_team_blue):
return counter_team_blue.next()
def main():
setup_gpio()
counter_team_red = count()
counter_team_blue = count()
try:
for pin in PINS:
GPIO.add_event_detect(pin, GPIO.RISING, callback=partial(print_score, counter_team_red, counter_team_blue), bouncetime=500)
signal.pause()
except KeyboardInterrupt:
GPIO.cleanup()
if __name__ == '__main__':
main()
-
BlackJack
@lackschuh: Zwei Verbesserungsvorschläge: Das Aufräumen würde ich in einen ``finally``-Zweig verlegen, denn sonst wird bei anderen Ausnahmen nicht aufgeräumt. Und der Vergleich mit 20 ist sehr magisch, vor allem steht diese literale 20 zweimal im Quelltext.
-
BlackJack
Eine konzeptionell recht einfache Variante bei der der Spielstand im üblichen A:B-Format ausgegeben werden kann, die `gpiozero` und Rückrufe mit einer Queue verwendet (ungetestet):
Man sieht hier an den Namenspräfixen `team_x_*` ganz gut, dass sich selbst hier schon OOP anbieten würde, weil so ein Team offensichtlich aus mehreren Einzelteilen besteht, die man zu einem Verbundtyp zusammenfassen kann/sollte.
Code: Alles auswählen
#!/usr/bin/env python
# coding: utf-8
from __future__ import absolute_import, division, print_function
from functools import partial
from Queue import Queue
from gpiozero import Button
TEAM_A_PIN = 20
TEAM_B_BIN = 21
def main():
goals = Queue()
team_a_button = Button(TEAM_A_PIN)
team_a_button.when_pressed = partial(goals.put, TEAM_A_PIN)
team_b_button = Button(TEAM_B_BIN)
team_b_button.when_pressed = partial(goals.put, TEAM_B_BIN)
try:
team_a_score = team_b_score = 0
while True:
team_pin = goals.get()
if team_pin == TEAM_A_PIN:
team_a_score += 1
elif team_pin == TEAM_B_BIN:
team_b_score += 1
else:
assert False, 'unknown team {0!r}'.format(team_pin)
print('A:B = {0}:{1}').format(team_a_score, team_b_score)
except KeyboardInterrupt:
pass
finally:
for device in [team_a_button, team_b_button]:
device.close()
if __name__ == '__main__':
main()@BlackJack
Ich kannte das ``gpiozero`` Modul noch gar nicht. Sieht aber auf den ersten Blick interessant aus...
Ich kannte das ``gpiozero`` Modul noch gar nicht. Sieht aber auf den ersten Blick interessant aus...
Code: Alles auswählen
Traceback (most recent call last):
File "taster.py", line 42, in <module>
main()
File "taster.py", line 16, in main
team_a_button.when_pressed = partial(goals.put, TEAM_A_PIN)
File "/usr/local/lib/python2.7/dist-packages/gpiozero/devices.py", line 159, in __setattr__
return super(GPIOBase, self).__setattr__(name, value)
File "/usr/local/lib/python2.7/dist-packages/gpiozero/mixins.py", line 209, in when_activated
self._when_activated = self._wrap_callback(value)
File "/usr/local/lib/python2.7/dist-packages/gpiozero/mixins.py", line 283, in _wrap_callback
'value must be a callable which accepts up to one '
gpiozero.exc.BadEventHandler: value must be a callable which accepts up to one mandatory parameter-
BlackJack
@lackschuh: Nachdem ich da in den Quelltext geschaut habe, würde ich sagen das ist die Schuld von `gpiozero`. Die machen da zu viel (indirekte) Typprüfung und schliessen somit `partial`-Objekte aus. Also im Grunde *alle* aufrufbaren Objekte die in C geschrieben sind deren Funktionssignatur man nicht mit `inspect`-Mitteln anschauen kann. Da ist `partial` ja nicht das einzige. Sie haben eine spezielle Prüfung für in Python eingebaute Objekte, denn da geht das auch nicht. Das geht irgendwie gegen „duck typing“. 
Edit: Ich hab mal ein Issue dazu aufgemacht: https://github.com/RPi-Distro/python-gp ... issues/436
Edit: Ich hab mal ein Issue dazu aufgemacht: https://github.com/RPi-Distro/python-gp ... issues/436
-
BlackJack
Die machen diese Magie ja unter anderen um zu sehen ob sie die Rückruffunktion ohne Argumente oder mit einem, nämlich dem Objekt auf dem sie gesetzt wird, aufgerufen werden soll. Dann kommt man ohne `partial()` aus wenn man die Team-Buttons als Vergleich wer das Tor gemacht hat, verwendet (ungetestet und in der Hoffnung das das niemand mit einem Python ausprobiert wo `Queue.put()` in C implementiert ist…):
Code: Alles auswählen
#!/usr/bin/env python
# coding: utf-8
from __future__ import absolute_import, division, print_function
from Queue import Queue
from gpiozero import Button
TEAM_A_PIN = 20
TEAM_B_BIN = 21
def make_team_button(queue, pin):
button = Button(pin)
button.when_pressed = queue.put
return button
def main():
goals = Queue()
team_a_button, team_b_button = buttons = [
make_team_button(goals, p) for p in [TEAM_A_PIN, TEAM_B_BIN]
]
try:
team_a_score = team_b_score = 0
while True:
team_button = goals.get()
if team_button == team_a_button:
team_a_score += 1
elif team_button == team_b_button:
team_b_score += 1
else:
assert False, 'unknown team {0!r}'.format(team_button)
print('A:B = {0}:{1}').format(team_a_score, team_b_score)
except KeyboardInterrupt:
pass
finally:
for button in buttons:
button.close()
if __name__ == '__main__':
main()
Solangsam lohnt sich eine Team-Klasse
Code: Alles auswählen
#!/usr/bin/env python
# coding: utf-8
from __future__ import absolute_import, division, print_function
from Queue import Queue
from gpiozero import Button
TEAM_PINS = [20, 21]
class Team(Button):
def __init__(self, pin, queue):
Button.__init__(self, pin)
self.when_pressed = queue.put
self.score = 0
def main():
goals = Queue()
teams = [Team(pin, goals) for pin in TEAM_PINS]
try:
while True:
team = goals.get()
team.score += 1
print('A:B = {teams[0].score}:{teams[1].score}').format(teams=teams)
except KeyboardInterrupt:
pass
finally:
for team in teams:
team.close()
if __name__ == '__main__':
main()
Guten Tag allerseits
Unsere zwei Schlaufen laufen inzwischen. Hier ist das aktuelle ganze Programm:
Unsere zwei Schlaufen laufen inzwischen. Hier ist das aktuelle ganze Programm:
Code: Alles auswählen
#!/usr/bin/python
from Adafruit_7Segment import SevenSegment # Anschluss des Displays gemaess Dokumentation auf der Adafruit Webseite
import os
import time
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.cleanup()
GPIO.setwarnings(False)
GPIO.setup(27,GPIO.IN,pull_up_down=GPIO.PUD_UP) # Dieses Setup ist von Youtube-Video
GPIO.setup(24,GPIO.IN,pull_up_down=GPIO.PUD_UP) # Connecting a Push Switch with Raspberry Pi
print"------------------"
print"Programm fuer Tischkicker mit zwei Druckschaltern. Torstands-Ausgabe an Adafruit 7-Segment Display"
print"------------------"
segment = SevenSegment(address=0x70)
Rot = 0
Blau = 0
# Die folgenden 7 Zeilen definieren, wie die beiden Torstaende am Display anzezeigt werden
def aktualisiereAnzeige (Team, Punktestand):
if Team == "rot":
segment.writeDigit(0, int(Punktestand / 10))
segment.writeDigit(1, Punktestand % 10)
if Team == "blau":
segment.writeDigit(3, int(Punktestand / 10))
segment.writeDigit(4, Punktestand % 10)
aktualisiereAnzeige("rot",Rot) # Diese 2 Zeilen, damit von Anfang an der Punktestand gezeigt wird.
aktualisiereAnzeige("blau",Blau)
# Hier sind die zwei parallel laufenden Schlaufen
while True:
if ( GPIO.input(27)!=True ): # !=True kehrt den Ausgabewert des Schalterzustands um. Ohne dieses Element zaehlen die Torstaende bei nicht gedruecktem Schalter hoch. Druecken der Schalter unterbricht das Hochzaehlen
Rot = Rot + 1
aktualisiereAnzeige("rot",Rot)
if ( GPIO.input(24)!=True ):
Blau = Blau + 1
aktualisiereAnzeige("blau",Blau)
time.sleep(0.10)
Zuletzt geändert von Anonymous am Montag 12. Dezember 2016, 16:00, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
-
BlackJack
@Swi_Mo: Eine Einrücktiefe von *einem* Leerzeichen pro Ebene ist arg wenig. Konvention sind vier Leerzeichen. Und auch welche nach Kommata und um binäre Operatoren, damit es leichter lesbar ist.
Das `os`-Modul wird importiert, aber nicht verwendet.
`GPIO.cleanup()` also Aufräumen, gehört ans Ende des Programms und es sollte sichergestellt werden, dass das auch tatsächlich aufgerufen wird. Also zum Beispiel auch wenn das Programm durch eine Ausnahme endet. Dann kann man sich auch das ausstellen der Warnungen sparen. Bei Warnungen sollte man überlegen was falsch läuft und sie nicht ignorieren.
Hauptprogramm und Definitionen von Funktionen sollte man nicht vermischen und das Hauptprogramm sollte auch nicht direkt auf Modulebene stehen, sondern auch in einer Funktion. Wenn man das macht, dann fällt auf das die Funktion zur Aktualisierung der Anzeige einfach so auf `segment` zugreift ohne diesen Wert als Argument übergeben zu bekommen. Der Kommentar erscheint überflüssig. Die Angabe 7 Zeilen zu genau und man müsste das immer anpassen wenn man etwas an der Funktion ändert so das sich die Zeilenanzahl ändert. Wenn man schon beschreibt was eine Funktion macht, dann wäre das als Docstring besser aufgehoben als als Kommentar.
In der Funktion wird in den beiden ``if``-Zweigen fast das gleiche gemacht. Die unterscheiden sich nur durch den Index der Ziffern für die Anzeige. Die Bedigungen beeinflussen also eigentlich nur diesen Index. Die beiden Abfragen schliessen sich auch gegenseitig aus, also ist für den zweiten Zweig auch eher ein ``elif`` angebracht. Und dann würde ich noch ein ``else`` hinzufügen das den Fall behandelt das etwas anderes als `team` übergeben wurde.
Der `int()`-Aufruf ist überflüssig. Wenn man Python 3 berücksichtigen möchte, könnte man an der Stelle den Ganzahldivisionsoperator ``//`` verwenden.
Werte die man im Programm mehrfach wiederholt und/oder die man einfach anpassen können möchte, werden am Anfang als Konstanten definiert. Magische Zahlen haben so dann auch gleich einen Namen an dem der Leser erkennt wofür die gut sind.
`GPIO.setup()` kann man auch mehr als einen Pin übergeben.
`Rot` und `Blau` vermitteln nicht wirklich, dass es sich dabei um Punkte handelt.
Der Kommentar mit den parallelen Schlaufen (sagt das tatsächlich jemand so?) ist verwirrend weil da weder zwei Schleifen noch irgend etwas paralleles folgt. Das ist eine Schleife mit sequentieller Abfrage der beiden Taster (oder wirklich Schalter?).
Die Klammern gehören da nicht um die Bedingung und auf literale Wahrheitswerte vergleicht man nicht. Da kommt nur wieder ein Wahrheitswert bei heraus, also kann man entweder gleich den Ausgangswert nehmen, oder wie in diesem Fall seine Negation mit ``not``.
Beim Kommentar fand ich den letzten Satz ein wenig verwirrend. Den sollte man in Bezug zu dem davor geschriebenen setzen. Ausserdem würde ich das was die **input()**-Funktion liefert nicht als *Ausgabewert* bezeichnen.
Da nicht auf die Flanke reagiert wird, sondern wirklich die ganze Zeit mit Zehntelsekunden-Päuschen auf geschlossenen Kontakt geprüft wird, ist das ein bisschen fehleranfällig für zu langes Drücken IMHO.
Das `os`-Modul wird importiert, aber nicht verwendet.
`GPIO.cleanup()` also Aufräumen, gehört ans Ende des Programms und es sollte sichergestellt werden, dass das auch tatsächlich aufgerufen wird. Also zum Beispiel auch wenn das Programm durch eine Ausnahme endet. Dann kann man sich auch das ausstellen der Warnungen sparen. Bei Warnungen sollte man überlegen was falsch läuft und sie nicht ignorieren.
Hauptprogramm und Definitionen von Funktionen sollte man nicht vermischen und das Hauptprogramm sollte auch nicht direkt auf Modulebene stehen, sondern auch in einer Funktion. Wenn man das macht, dann fällt auf das die Funktion zur Aktualisierung der Anzeige einfach so auf `segment` zugreift ohne diesen Wert als Argument übergeben zu bekommen. Der Kommentar erscheint überflüssig. Die Angabe 7 Zeilen zu genau und man müsste das immer anpassen wenn man etwas an der Funktion ändert so das sich die Zeilenanzahl ändert. Wenn man schon beschreibt was eine Funktion macht, dann wäre das als Docstring besser aufgehoben als als Kommentar.
In der Funktion wird in den beiden ``if``-Zweigen fast das gleiche gemacht. Die unterscheiden sich nur durch den Index der Ziffern für die Anzeige. Die Bedigungen beeinflussen also eigentlich nur diesen Index. Die beiden Abfragen schliessen sich auch gegenseitig aus, also ist für den zweiten Zweig auch eher ein ``elif`` angebracht. Und dann würde ich noch ein ``else`` hinzufügen das den Fall behandelt das etwas anderes als `team` übergeben wurde.
Der `int()`-Aufruf ist überflüssig. Wenn man Python 3 berücksichtigen möchte, könnte man an der Stelle den Ganzahldivisionsoperator ``//`` verwenden.
Werte die man im Programm mehrfach wiederholt und/oder die man einfach anpassen können möchte, werden am Anfang als Konstanten definiert. Magische Zahlen haben so dann auch gleich einen Namen an dem der Leser erkennt wofür die gut sind.
`GPIO.setup()` kann man auch mehr als einen Pin übergeben.
`Rot` und `Blau` vermitteln nicht wirklich, dass es sich dabei um Punkte handelt.
Der Kommentar mit den parallelen Schlaufen (sagt das tatsächlich jemand so?) ist verwirrend weil da weder zwei Schleifen noch irgend etwas paralleles folgt. Das ist eine Schleife mit sequentieller Abfrage der beiden Taster (oder wirklich Schalter?).
Die Klammern gehören da nicht um die Bedingung und auf literale Wahrheitswerte vergleicht man nicht. Da kommt nur wieder ein Wahrheitswert bei heraus, also kann man entweder gleich den Ausgangswert nehmen, oder wie in diesem Fall seine Negation mit ``not``.
Beim Kommentar fand ich den letzten Satz ein wenig verwirrend. Den sollte man in Bezug zu dem davor geschriebenen setzen. Ausserdem würde ich das was die **input()**-Funktion liefert nicht als *Ausgabewert* bezeichnen.
Da nicht auf die Flanke reagiert wird, sondern wirklich die ganze Zeit mit Zehntelsekunden-Päuschen auf geschlossenen Kontakt geprüft wird, ist das ein bisschen fehleranfällig für zu langes Drücken IMHO.
Code: Alles auswählen
#!/usr/bin/env python
import time
from Adafruit_7Segment import SevenSegment
from RPi import GPIO
ROT, BLAU = 'rot', 'blau'
TEAM_PINS = ROTES_TEAM_PIN, BLAUES_TEAM_PIN = [27, 24]
def aktualisiere_punkte_anzeige(segment, team, punktestand):
if team == ROT:
index = 0
elif team == BLAU:
index = 3
else:
assert False, 'Falscher Wert fuer `team`: {}'.format(team)
segment.writeDigit(index, punktestand // 10)
segment.writeDigit(index + 1, punktestand % 10)
def main():
try:
GPIO.setmode(GPIO.BCM)
GPIO.setup(TEAM_PINS, GPIO.IN, pull_up_down=GPIO.PUD_UP)
segment = SevenSegment(address=0x70)
print '------------------'
print 'Programm fuer Tischkicker mit zwei Druckschaltern.'
print 'Torstands-Ausgabe an Adafruit 7-Segment Display'
print '------------------'
punkte_rot = 0
punkte_blau = 0
aktualisiere_punkte_anzeige(segment, ROT, punkte_rot)
aktualisiere_punkte_anzeige(segment, BLAU, punkte_blau)
while True:
# ``not`` kehrt den Eingabewert des Schalterzustands um.
# Ohne diese Operation zaehlen die Torstaende bei nicht
# gedruecktem Schalter hoch, sondern druecken der Schalter
# unterbricht das Hochzaehlen.
if not GPIO.input(ROTES_TEAM_PIN):
punkte_rot += 1
aktualisiere_punkte_anzeige(segment, ROT, punkte_rot)
if not GPIO.input(BLAUES_TEAM_PIN):
punkte_blau += 1
aktualisiere_punkte_anzeige(segment, BLAU, punkte_blau)
time.sleep(0.1)
finally:
GPIO.cleanup()
if __name__ == '__main__':
main()Oder mit einem Team-Objekt, um die restlichen Code-Wiederholungen zu eliminieren:
Code: Alles auswählen
#!/usr/bin/env python
import time
from Adafruit_7Segment import SevenSegment
from RPi import GPIO
ROTES_TEAM_PIN, BLAUES_TEAM_PIN = [27, 24]
class Team(object):
def __init__(self, segment, pin, index):
self.pin = pin
self.index = index
self.segment = segment
self.punkte = 0
self.aktualisiere_punkte_anzeige()
GPIO.setup(self.pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
def aktualisiere_anzeige(self):
for index, ziffer in enumerate(divmod(self.punkte, 10), self.index):
self.segment.writeDigit(index, ziffer)
def aktualisiere_punkte(self):
# ``not`` kehrt den Eingabewert des Schalterzustands um.
# Ohne diese Operation zaehlen die Torstaende bei nicht
# gedruecktem Schalter hoch, sondern druecken der Schalter
# unterbricht das Hochzaehlen.
if not GPIO.input(self.pin):
self.punkte += 1
self.aktualisiere_anzeige()
def main():
try:
GPIO.setmode(GPIO.BCM)
segment = SevenSegment(address=0x70)
print '------------------'
print 'Programm fuer Tischkicker mit zwei Druckschaltern.'
print 'Torstands-Ausgabe an Adafruit 7-Segment Display'
print '------------------'
teams = [
Team(segment, ROTES_TEAM_PIN, 0),
Team(segment, BLAUES_TEAM_PIN, 3),
]
while True:
for team in teams:
team.aktualisiere_punkte()
time.sleep(0.1)
finally:
GPIO.cleanup()
if __name__ == '__main__':
main()
Inzwischen ist unser RaspberryPi-Projekt zu einem Arduino-Projekt geworden und abgeschlossen.
Unser sehr beschränktes Python-Wissen liess sich problemlos in C++ vom Arduino transferieren. Die beiden Schlaufen laufen inzwischen anstandslos im Tischkicker-Zähler.

Am Ende der 1. Seite von folgendem Thread gibt es Infos des fertigen Projekts und den verwendeten Code.
http://www.forum-raspberrypi.de/Thread- ... t-geworden
Beste Grüsse!

Am Ende der 1. Seite von folgendem Thread gibt es Infos des fertigen Projekts und den verwendeten Code.
http://www.forum-raspberrypi.de/Thread- ... t-geworden
Beste Grüsse!
