2012-05-13 91 views
5

Estoy intentando escribir un programa que obtiene datos de una conexión de puerto serie y actualiza automáticamente la ventana de Tkinter en tiempo real en función de esos datos.Actualización dinámica de la ventana de Tkinter basada en datos en serie

Me trataron de crear un hilo separado para la ventana que recibe periódicamente los datos actuales desde el hilo principal y actualiza la ventana, como esto:

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() 

Correr me da este error:

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

Cuando googleo este error, la mayoría de las veces recibo publicaciones en las que las personas intentan interactuar con la ventana desde dos hilos diferentes, pero no creo que esté haciendo eso. ¿Algunas ideas? ¡Muchas gracias!

+1

¿Intentó ejecutar la parte TK no en un hilo? Simplemente ejecuto las cosas del puerto serie en un hilo y las cosas TK pueden permanecer en el proceso principal. Sospecho que podría funcionar ... –

+0

¿Como un hilo para obtener los datos del puerto serie y otro hilo para el ciclo de análisis de datos? Voy a dar una oportunidad. – user1363445

Respuesta

6

No ejecute el TK gui desde una secuencia: ejecútelo desde el proceso principal. Trituré tu ejemplo en algo que demuestra el 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

debe usar un objeto 'Queue' seguro para subprocesos para comunicarse entre los subprocesos en lugar de utilizar una variable de lista simple. –

+1

Eso sería mejor sí, pero mi objetivo era mostrar al OP la solución al problema, no enseñarles los mecanismos IPC de pitones ;-) –

1

Es necesario poner la interfaz gráfica de usuario en el hilo principal, y utilizar un hilo separado para sondear el puerto serie. Cuando lee datos del puerto serie, puede insertarlos en un objeto Queue.

En el hilo de la GUI principal puede configurar el sondeo para verificar la cola periódicamente, usando after para programar el sondeo. Llame a una función que agota la cola y luego se llama a sí mismo con after para emular efectivamente un bucle infinito.

Si los datos que provienen del sensor son bastante lentos, y puede sondear el puerto serie sin bloquear, puede hacerlo todo en el hilo principal, en lugar de empujar y tirar desde la cola, su El hilo principal puede ver si hay datos disponibles y leerlos si los hay. Solo puede hacer esto si es posible leer sin bloquear, de lo contrario su GUI se congelará mientras espera datos.

Por ejemplo, se puede hacer que funcione de esta manera:

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

Lo anterior comprobará el puerto serie 10 veces por segundo, tirando de un elemento a una hora. Tendrás que ajustar eso para tus condiciones de datos reales, por supuesto. Esto supone que tiene algún método como has_data que puede devolver verdadero si y solo si una lectura no se bloquea.

Cuestiones relacionadas