2012-05-13 14 views
5

Sto provando a scrivere un programma che ottiene i dati da una porta seriale e aggiorna automaticamente la finestra di Tkinter in tempo reale sulla base di tali dati.Aggiornamento dinamico della finestra di Tkinter basata sui dati seriali

Ho cercato di creare un thread separato per la finestra che ottiene periodicamente i dati correnti dal thread principale e aggiorna la finestra, in questo modo:

serialdata = [] 
data = True 

class SensorThread(threading.Thread): 
    def run(self): 
     serial = serial.Serial('dev/tty.usbmodem1d11', 9600) 
     try: 
      while True: 
       serialdata.append(serial.readline()) 
     except KeyboardInterrupt: 
      serial.close() 
      exit() 

class GuiThread(threading.Thread): 
    def __init__(self): 
     threading.Thread.__init__(self) 
     self.root = Tk() 
     self.lbl = Label(self.root, text="") 

    def run(self): 
     self.lbl(pack) 
     self.lbl.after(1000, self.updateGUI) 
     self.root.mainloop() 

    def updateGUI(self): 
     msg = "Data is True" if data else "Data is False" 
     self.lbl["text"] = msg 
     self.root.update() 
     self.lbl.after(1000, self.updateGUI) 

if __name == "__main__": 
    SensorThread().start() 
    GuiThread().start() 

    try: 
     while True: 
      # A bunch of analysis that sets either data = True or data = False based on serialdata 
    except KeyboardInterrupt: 
     exit() 

esecuzione mi dà questo errore:

Exception in thread Thread-2: Traceback (most recent call last): File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/threading.py", line 522, in __bootstrap_inner self.run() File "analysis.py", line 52, in run self.lbl1.pack() File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/lib-tk/Tkinter.py", line 1764, in pack_configure + self._options(cnf, kw)) RuntimeError: main thread is not in main loop

Quando provo questo errore, per lo più ottengo post in cui le persone cercano di interagire con la finestra da due thread diversi, ma non credo che lo stia facendo. Qualche idea? Grazie mille!

+1

Hai provato a eseguire la parte TK non in una discussione? Cioè, eseguo la porta seriale in una discussione e la roba di TK può rimanere nel processo principale. Sospetto che potrebbe funzionare ... –

+0

Come un thread per ottenere i dati della porta seriale e un altro thread per il ciclo di analisi dei dati? Darò un colpo. – user1363445

risposta

6

Non eseguire il GUI TK da un thread: eseguirlo dal processo principale. I purè tuo esempio in qualcosa che dimostra il principio

from time import sleep 
import threading 
from Tkinter import * 

serialdata = [] 
data = True 

class SensorThread(threading.Thread): 
    def run(self): 
     try: 
      i = 0 
      while True: 
       serialdata.append("Hello %d" % i) 
       i += 1 
       sleep(1) 
     except KeyboardInterrupt: 
      exit() 

class Gui(object): 
    def __init__(self): 
     self.root = Tk() 
     self.lbl = Label(self.root, text="") 
     self.updateGUI() 
     self.readSensor() 

    def run(self): 
     self.lbl.pack() 
     self.lbl.after(1000, self.updateGUI) 
     self.root.mainloop() 

    def updateGUI(self): 
     msg = "Data is True" if data else "Data is False" 
     self.lbl["text"] = msg 
     self.root.update() 
     self.lbl.after(1000, self.updateGUI) 

    def readSensor(self): 
     self.lbl["text"] = serialdata[-1] 
     self.root.update() 
     self.root.after(527, self.readSensor) 

if __name__ == "__main__": 
    SensorThread().start() 
    Gui().run() 
+1

si dovrebbe usare un oggetto 'Queue' thread-safe per comunicare tra i thread invece di usare una semplice lista di variabili. –

+1

Sarebbe meglio sì, ma il mio obiettivo era mostrare all'OP la soluzione al problema, non insegnare loro i pitoni meccanismi IPC ;-) –

1

Hai bisogno di mettere la GUI nel thread principale, ed usare un thread separato per richiedere la porta seriale. Quando si leggono i dati dalla porta seriale, è possibile trasferirli su un oggetto Queue.

Nella thread GUI principale è possibile impostare il polling per controllare periodicamente la coda, utilizzando after per pianificare il polling. Chiama una funzione che prosciuga la coda e quindi chiama se stessa con after per emulare efficacemente un ciclo infinito.

Se i dati provenienti dal sensore arrivano a una velocità piuttosto lenta e si può eseguire il polling della porta seriale senza bloccare, è possibile farlo tutto nel thread principale, invece di premere e tirare dalla coda, il proprio thread principale può solo vedere se ci sono dati disponibili e leggerli se c'è. Puoi farlo solo se è possibile leggere senza bloccare, altrimenti la GUI si bloccherà mentre attende i dati.

Ad esempio, è possibile farlo funzionare in questo modo:

def poll_serial_port(self): 
    if serial.has_data(): 
     data = serial.readline() 
     self.lbl.configure(text=data) 
    self.after(100, self.poll_serial_port) 

È possibile che questo controllerà la porta seriale 10 volte al secondo, tirando una voce off alla volta. Dovrai regolarlo naturalmente per le tue condizioni di dati reali. Ciò presuppone che tu abbia un metodo come has_data che può restituire True se e solo se una lettura non bloccherà.

Problemi correlati