pygames und Win10: Fenster friert ein trotz Aufruf von pygame.event.get()

Hier werden alle anderen GUI-Toolkits sowie Spezial-Toolkits wie Spiele-Engines behandelt.
Antworten
blbltheworm
User
Beiträge: 10
Registriert: Samstag 12. November 2011, 00:29

Hallo zusammen,

die Situation ist etwas verzwickt und ich bekomme den Knoten nicht gelöst, vielleicht hat ja jemand einen Rat.
Vor einiger Zeit habe ich einen Emulator geschrieben, der auf dem PC die Pins eines Raspberry Pi emuliert, sodass RasPi Code auf dem PC getestet werden kann (https://github.com/blbltheworm/yarpie/). Da es in der Zwischenzeit einige andere Ansätze zu dem Thema gibt, habe ich es nicht weiterverfolgt. Es gibt jedoch noch einen Bug der mich fuchst und da mittlerweile noch einmal danach gefragt wurde, würde ich ihn gerne Lösen. Das Tool läuft unter Linux einwandfrei, aber unter Windows friert das pygames Fenster ein, wenn man eine MainLoop programmiert.
Zum Aufbau des Emulator:

Beim Importieren des Pakets wird pygames gestartet und ein Fenster mit der grafischen Repräsentation des Emulators erstellt. Das ganze läuft als Thread

Code: Alles auswählen

class emugui(threading.Thread)
Die Klasse hat zudem eine Funktion in der die Mainloop des Emulators und das Eventhandling von Pygames läuft:

Code: Alles auswählen

def run(self): 
        """
            Mainloop of the emulated GPIO. Is called when the emulator class is initialized.
        """
        
        self._running = True
    
        # create clock to reduce framerate
        clock = pygame.time.Clock()
        
        while self._running:
            clock.tick(30) #reduce framerate to 30 fps
            # get all events and check them 
            events = pygame.event.get()
            
            for event in events:
                print(event.type)
                if event.type == pygame.QUIT:
                    self._running = False
                
                #Weiterer Code zum Event-Handling
Davon bekommt der Benutzer aber alles nichts mit. Er soll sein Programm schreiben und kann dann über ein spezielles Modul mit der emugui kommunizieren, als wäre es die Hardware eines Raspberry Pi. Das funktioniert solange, bis der Benutzer seine eigene Mainloop schreibt

Code: Alles auswählen

import time
while(1):
        #Code zur Interaktion mit der RasPi-Hardware
        time.sleep(0.1)
 
Unter Linux funktioniert das. Unter Windows friert das Fenster ein, wenn die zwei Schleifen (die der emugui und die des Hauptprogramms) gleichzeitig laufen.
Hat jemand eine Idee woran das liegt und an welcher Stelle hier der Eventaustausch zwischen pygames und Win10 verloren geht? Ich bin für jede Idee dankbar. Wichtig ist nur, dass in der Schleife des Hauptprogramms kein pygames Code auftaucht, denn der Emulator ist so geschrieben, dass er Code verarbeiten kann, der für den Raspberry geschrieben ist.
Benutzeravatar
__blackjack__
User
Beiträge: 13100
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@blbltheworm: Ich vermute mal das Problem fängt schon da an das Du PyGame nicht vom Hauptthread aus bedienst. Andere GPIO-Emulatoren lösen das mit `multiprocessing`.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
blbltheworm
User
Beiträge: 10
Registriert: Samstag 12. November 2011, 00:29

Hi und Danke für die Antwort.

Hm, "multiprocessing" ist ein großes Wort. Hast du zufällig ein Beispiel, das ich mir anschauen kann?
blbltheworm
User
Beiträge: 10
Registriert: Samstag 12. November 2011, 00:29

Hallo,

ich hab mal ein stark reduziertes Beispiel geschrieben. Wenn mir jemand helfen kann, das in eine multiprocessing-Variante zu überführen, wäre ich sehr dankbar. Ziel ist es, dass pygame in Hintergrund läuft und sich um die Darstellung der Daten kümmert, während das Hauptprogramm nur mit einer Klasse (hier bg_pygame) kommuniziert.

Code: Alles auswählen

import pygame
import threading
import time


class cbg_pygame(threading.Thread):
    """
        Main class emulating the GPIO and i2c and draws the main window.
    """
    def __init__(self, xoffset = 150, yoffset = 20):
        
        threading.Thread.__init__(self)
                
        # Initialise pygame and create screen, surfaces and Gadgets (Input/Buttons/Lists...)    
        pygame.init()
        
        # Define some colors
        self.pos=[0,0] #Position of the ball on the screen
        self.dimension=[800,600] #screen dimensions
        self.size=30 #size of the ball

        self._screen = pygame.display.set_mode((self.dimension[0], self.dimension[1]))
                
        pygame.display.set_caption('pygames-test')
        
        
        #load the background image
        pygame.mouse.set_visible(1)
        pygame.key.set_repeat(1, 30)
        
        
    def run(self): 
        """
            Mainloop of the emulated GPIO. Is called when the emulator class is initialized.
        """
        
        self.running = True
    
        # create clock to reduce framerate
        clock = pygame.time.Clock()
        
        while self.running:
            clock.tick(30) #reduce framerate to 30 fps
    
            # get all events and check them 
            events = pygame.event.get()
            for event in events:
                if event.type == pygame.QUIT:
                    self.running = False
                
            self._screen.fill((255, 255, 255))
            pygame.draw.ellipse(self._screen, (0, 0, 0), [self.pos[0], self.pos[1], self.size, self.size], 4)
            pygame.display.flip()

        pygame.quit()
        
    def stop(self):
        self.running = False
        
        

bgpg = cbg_pygame()
bgpg.start()

direction=[1,1]
while(bgpg.running): #Program is stopped if pygame window is closed
    for i in range(2):
        if direction[i]: #direction[0]=x, direction[1]=y, traveling with a velocity of 20 pixels/circle
            if bgpg.pos[i]+20 < bgpg.dimension[i]-bgpg.size:
                bgpg.pos[i]=bgpg.pos[i]+20
            else:
                bgpg.pos[i]=bgpg.dimension[i]-bgpg.size
                direction[i]=0
        else:
            if bgpg.pos[i]-20 > 0:
                bgpg.pos[i]=bgpg.pos[i]-20
            else:
                bgpg.pos[i]=0
                direction[i]=1
                
    time.sleep(0.1)
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

Du brauchst da keine Threads und kein multiprocessing und ich sehe auch nicht, wo da GPIOs emuliert werden. Das ist eine ganz normale Ereignisschleife die Du mit Deiner Updateschleife Kombinieren mußt.
blbltheworm
User
Beiträge: 10
Registriert: Samstag 12. November 2011, 00:29

Hi Sirius,

bitte den ganzen Thread lesen. Das das Beispiel keinen Sinn ergibt ist mir bewusst, es soll nur mein Vorgehen im Emulator demonstrieren. Das ist das einfachste Beispiel, das mir eingefallen ist. Die Frage ist, wie man das Beispiel unter Windows zum laufen bekommt, ohne, dass die Events in der Hauptschleife bearbeitet werden müssen. Wenn du dir die Mühe machen möchtest, dir den gesamten Code anzuschauen, bin ich dir natürlich auch für jeden Tipp dankbar. Der vollständige Code liegt unter https://github.com/blbltheworm/yarpie/.

Viele Grüße,
blbl
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

Wenn dein stark reduziertes Beispiel nicht das Problem hat, dass du lösen möchtest, dann ist es wohl zu stark reduziert. Die Antwort bleibt gleich: es darf nur eine Schleife geben, in der sowohl die pygame-Events als auch die gpio-Events abgearbeitet werden.
Benutzeravatar
__blackjack__
User
Beiträge: 13100
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Sirius3: Das Problem ist das da ein Benutzer Code schreiben können soll, der nichts von der GUI wissen muss. Das heisst der soll genau so frei sein Code zu schreiben als wenn er nur `RPi.GPIO` als Abhängigkeit hätte. Also *er* schreibt eine Endlosschleife im Hauptthread die *nichts* von einer GUI weiss. Die GUI-Hauptschleife muss aber parallel zu seinem Code laufen. Und das geht halt mit Multiprocessing, weil in diesem Szenario die GUI-Hauptschleife nur in einem anderen Prozess im Hauptthread laufen kann.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

Ah, ok, das Emulatorfenster soll unabhängig vom eigentlichen Programm laufen. Dann würde ich nicht zu Multiprocessing greifen, denn das ist unter Windows auch eklig, sondern den Emulator komplett als eigenständiges Skript laufen lassen und per IPC ans eigentliche Programm anbinden.
blbltheworm
User
Beiträge: 10
Registriert: Samstag 12. November 2011, 00:29

das Beispiel hat genau das Problem, das auch der Emulator hat. Es läuft unter Linux und hängt unter Windows. Aber es emuliert halt nichts :)

Danke für eure Vorschläge.
@Sirius Ist ein IPC-Ansatz nicht etwas überdimensioniert (nicht, dass ich Ahnung davon hätte).
Vom Verständnis her: IPC würde heißen, dass ich zwei unabhängige Skripte habe. (z.B. emulator.py und prog.py). Um den Emulator zu verwenden, müsste der User dann entweder emulator.py manuell starten und in seinem prog.py den richtigen Port angeben, sodass die beiden auf der 127.0.0.1 kommunizieren können, oder? Der Emulator wäre dann der Server und das prog.py der Client. Alternative wäre es auch möglich, dass prog.py eine neue Python-Instanz aufruft, die emulator.py ausführt. Ich hab mal angefangen nach IPC zu suchen, bin aber ehrlich gesagt von der Fülle an Lösungen erschlagen. Sirius, kannst du mir ein Paket vorschlagen? Letztendlich sollte ja ein simpler Austausch von Bytecodes in beide Richtungen reichen. Zudem sollte das ganze unter Linux und Windows laufen, sonst bin ich keinen Schritt weiter.

@blackjack: Ich hab leider noch nicht verstanden, wo der Unterschied zwischen threading und multiprocessing liegt. Wie würde das Beispiel denn mit multiprocessing aussehen?

Danke schon Mal für eure Hilfe.
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

Threading ist ein einem Prozess. Und der kann eben nur eine Ereignisbehandlung haben, speziell unter Windows. Du willst aber zwei, respektive deinem User keine Beschränkungen auferlegen. Also braucht’s 2 Prozesse.

Ein Weg für IPC wäre, das Netzwerk Protokoll von pigpio umzusetzen. Damit kann der Code dann 1:1 übernommen werden auf einem PI, der den pigpiod laufen lässt. Oder eben dein eigenes. Nimm zb HTTP, das kommt mit Python ja alles schon mit.

Und ich würde immer so vorgehen, dass der Emulator zuerst gestartet werden muss. Schlussendlich läuft ja auch ein Programm auf einem existierend PI, statt einen irgendwie her zu zaubern. .
blbltheworm
User
Beiträge: 10
Registriert: Samstag 12. November 2011, 00:29

Hi,
ich sehe schon, ich muss mir das Thema Threads auch nochmal genauer anschauen, hab es wohl doch noch nicht so recht verstanden.
pigpio kannte ich noch nicht, das ist auch ein Interessanter Ansatz, wenn auch nicht der, den ich eigentlich verfolgen wollte.
Meine ursprüngliche Intension war ein Paket anzubieten, dass analog zum guten alten RPi.GPIO eingebunden werden kann. Daher würde ich auch gerne auf einen zusätzlichen Deamon vermeiden.
Wie würde das simple Beispiel oben denn mit Multiprocessing aussehen? Kann mir da jemand weiterhelfen?
Die Frage ist, ob ich alternativ weiterkomme, wenn ich auf pygame für die Darstellung verzichte und z.B. über TK gehe. Da müsste ich mich dann halt entsprechend einarbeiten. Oder laufe ich dort in ein ähnliches Problem unter Windows?
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

Du kommst immer in die selben Probleme. Der einzig funktionierende Weg ist es, Emulator und individuelles Programm komplett zu trennen. Du kannst ja beim nutzen deines rpi.gpio per subprocess den Emulator automatisch starten. Aber nur so bist du und der Nutzer völlig frei, alles zu tun, was ihr wollt.
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich verstehe auch nicht, worin der Reiz eines implizit gestarteten emulators liegt. Nehmen wir mal an du kannst da fortgeschrittene Dinge wie periodische Signale oder grafische Darstellung der Ausgaben machen. Dann soll man jedes mal, wenn der Anfänger den Code Verbockt und das Skript abschmiert, alles wieder neu gestartet werden, eingerichtet, angeschaltet? Das ist doch öde. Du hast viel mehr freiheitsgrade und bessere Features, wenn ud das sauber trennst.
blbltheworm
User
Beiträge: 10
Registriert: Samstag 12. November 2011, 00:29

Hallo zusammen,

naja, der Reiz wäre gewesen, dass der Benutzer in seinem Code nur das import RPi.GPIO durch ein import RPIemu.GPIO austauschen muss und alles läuft. Aber OK. Könnt ihr ein IPC-Paket empfehlen? Ich habe mit dem Thema null Erfahrung.
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das hat ja damit nix zu tun. Der Code sieht ja immer noch so aus.

Und ich habe zb HTTP vorgeschlagen.
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

Genau das was Du schreibst, man startet erst den Emulator und kann dann das eigentliche Programm ohne Änderungen laufen lassen.
blbltheworm
User
Beiträge: 10
Registriert: Samstag 12. November 2011, 00:29

@__deets__ das mit dem HTTP hatte ich gelesen, dachte aber, dass du einfach das HTTP-Protokoll meinst. Mittlerweile habe ich gesehen, dass es auch ein gleichnamiges Pythonmodul gibt.
Ich werde mal mein Glück versuchen ;)
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wird schon. Python bringt da genug mit. Und Performance Erwägungen gilden erst, wenn’s knirscht ;)
Antworten