I can not show arduino sensor reading in my interface, it blocks

3

I'm trying to create a graphical interface on tkinter to show the reading of a sensor on an Arduino one board.

The error is that when I run the program it stays in the cycle While and the window in white, when I give it interrupt it shows me in the label sensor a this error PY_VARO()

from tkinter import *
from time import sleep
import serial 
import sys
import numpy as np
import pylab as ventana


serie = serial.Serial('COM3', 9600) # Lectura del puerto serie de arduino con la lib serial

def sensor_tem(): # funcion para leer el sensor


    while 1: # inicio el Bucle

        #print(serie.readline())

        dato = serie.readline() # Lectura del sensor

        senso = dato[0:1]           
        readingt.set(senso)
        #ventana.sensor_tem()
        sleep(1)

#readingt = StringVar()

ventana = Tk()# inicia la ventana
ventana.geometry("600x300+0+0")  # Tamaño de ventana

ventana.title("Sensor Temperatura") # Titulo de ventana

tit_sensor = Label(ventana, text = "Lectura Sensor").place(x = 100, y = 70)
readingt = StringVar() # convierto la variable a String
 #muestro la lectura del sensor en el Label.
sensor = Label(ventana, text= readingt).place(x= 100, y = 100)
btn_salir = Button(ventana, text = " Salir", command = exit).place(x = 100, y = 130)

ventana.after(1, sensor_tem)#actualizo la ventana
ventana.mainloop()
    
asked by JJ Restrepo 05.12.2016 в 01:37
source

1 answer

1

You have several problems in the code that I will try to explain:

❶ The main problem that causes the GUI to crash is the use of an infinite cycle ( while 1 ). When you call the function for the first time (and last) you enter the cycle taking control of the main thread and no longer leaves it so the GUI is not given the opportunity to update and freezes. Also, you should never use sleep() in the main thread of an application Tkinter since it is also blocking.

The solution is to use after() and call the function recursively. On the other hand, if you only need to update the label , you can apply the after() on it and not on the entire window. The general idea is something like this:

def funcion():
    #hacer algo
    label.after(1000, funcion) 

sensor.after(1000, funcion)

In this way the same effect of an infinite cycle is created but in this case it is not blocking.

❷ On the other hand, the correct way to get the content of StringVar is using the get() method. However, it is much easier to tell the label to take the text of a variable by means of the textvariable attribute. In this way each time the variable changes, automatically (since it is actually an event) the content of Label changes:

readingt = StringVar()
sensor = Label(ventana, textvariable = readingt)

❸ It is very common to do this:

sensor = Label(ventana, textvariable = readingt).place(x= 100, y = 100)

Which works but then we find errors when trying to do for example:

label.after(1000, funcion)

The problem of using the place() method on the same line is that now sensor is not a Label object as we might think, it is actually the return of place() , ie None . For this reason it is always appropriate to apply place on a different line:

sensor = Label(ventana, textvariable = readingt)
sensor.place(x= 100, y = 100)

❹ You are importing pylab as ventana and then you create an application with Tkinter calling it also ventana , which overwrites the above. This really does not cause problems for now but if you are going to use pylab later you will have problems. For example, if you try to use the method linspace() of pylabel , you would call it like this:

t = ventana.linspace(-pl.pi, pl.pi, 10000)

this causes an error:

  

AttributeError: '_tkinter.tkapp' object has no attribute 'linspace'

because window is an instance of tkinter.Tk actually. Change the import to something like:

import pylab as pl

❺ Finally, the readline() method is blocking, it does not return anything until there is something to read, which can never be. This blocks the GUI equally, so that this does not happen we specify that you do not wait for a reading to exist:

serie = serial.Serial('COM3', 9600, timeout=0, writeTimeout=0)

The code should look like this:

from tkinter import *
import serial 
import sys
import numpy as np
import pylab as pl



serie = serial.Serial('COM3', 9600, timeout=0, writeTimeout=0)

def sensor_tem():
    dato = serie.readline()
    senso = dato[0:1]   
    readingt.set(senso)
    sensor.after(1000, sensor_tem)



ventana = Tk()
ventana.geometry("600x300+0+0")
ventana.title("Sensor Temperatura")

tit_sensor = Label(ventana, text = "Lectura Sensor")
tit_sensor.place(x = 100, y = 70)

readingt = StringVar()

sensor = Label(ventana, textvariable = readingt)
sensor.place(x= 100, y = 100)

btn_salir = Button(ventana, text = " Salir", command = exit)
btn_salir.place(x = 100, y = 130)

sensor.after(1000, sensor_tem)
ventana.mainloop()

A note, after() receives the wait time in milliseconds , I have put 1000 that would be a second. You can change it but keep in mind that 1 millisecond as you have it can be an important burden for the processor and may not provide much useful information.

There are other ways to address these problems such as the use of threads and queues but for your case this works correctly and is simpler.

EDIT:

I add example using threads and a tail:

import serial
import threading
import time
import queue
import tkinter as tk


class readerThread(threading.Thread):
    def __init__(self, queue):
        threading.Thread.__init__(self)
        self.queue = queue

    def run(self):
        serie = serial.Serial('COM3', 9600, timeout=0, writeTimeout=0)

        while True:
            datos = serie.readline()
            senso = datos[0:1]
            if senso:
                self.queue.put(senso)
            time.sleep(1)

class sensorGUI(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.geometry("600x300+0+0")
        self.title("Sensor Temperatura")

        self.tit_sensor = tk.Label(self, text = "Lectura Sensor")
        self.tit_sensor.place(x = 100, y = 70)

        self.readingt = tk.StringVar()

        self.sensor = tk.Label(self, textvariable = self.readingt)
        self.sensor.place(x= 100, y = 100)

        self.btn_salir = tk.Button(self, text = " Salir", command = self.salir)
        self.btn_salir.place(x = 100, y = 130)

        self.queue = queue.Queue()
        thread = readerThread(self.queue)
        thread.start()
        self.actualizar_datos()

    def actualizar_datos(self):
        while self.queue.qsize():
            try:
                self.readingt.set(self.queue.get())
            except Queue.Empty:
                pass
        self.after(100, self.actualizar_datos)

    def salir(self):
        self.destroy()

if __name__ =='__main__':
    app = sensorGUI()
    app.mainloop() 
    
answered by 05.12.2016 / 13:07
source