I am learning how to use Tkinter for a GUI to show the reading of serial data.
I have built a GUI with several functions: The basic idea is to be able to select the port, the baurate and then press the connect button.
When the connect button is pressed, several variables are initialised, and the figure for the display of the data is created:
plt.ion()
fig = plt.figure()
and a thread is initiated for the function to read the serial data.
t1 = threading.Thread(target=readSerial)
t1.deamon = True
t1.start()
Which I believe is also working. The problem arises when I add the line drawnow(dist_figure())
in the while loop inside the readSerial function.
The error it throws back is "main thread is not in the main loop." For my understanding, this happens because the main_window is created not in the main thread but inside a function ( connect_menu_init() ). But I'm not sure how to fix this issue. Another thing makes me think is why the error appears when the drawnow function is called, and not always. As I mentioned before, I am new to tkinter and reading data from serial ports. If someone has a better idea on how to stream live the data read from the serial port into a plot, I am all ears.
There is the full code I am using:
from tkinter import *
from tkinter import messagebox
import threading
from datetime import datetime
import matplotlib.pyplot as plt
from drawnow import drawnow
import serial
import serial.tools.list_ports
def connect_menu_init():
global main_window, connect_btn, refresh_btn
main_window = Tk()
main_window.title("Serial communication")
main_window.geometry("500x150")
port_lable = Label(main_window, text="Available Port(s): ", bg="white")
port_lable.grid(column=1, row=2, pady=20, padx=10)
port_bd = Label(main_window, text="Baude Rate: ", bg="white")
port_bd.grid(column=1, row=3, pady=20, padx=10)
refresh_btn = Button(main_window, text="Refresh", height=2,
width=10, command=update_coms)
refresh_btn.grid(column=3, row=2)
connect_btn = Button(main_window, text="Connect", height=2,
width=10, state="disabled", command=connexion)
connect_btn.grid(column=3, row=3)
baud_select()
update_coms()
def connect_check(args):
if "-" in clicked_com.get() or "-" in clicked_bd.get():
connect_btn["state"] = "disable"
else:
connect_btn["state"] = "active"
def baud_select():
global clicked_bd, drop_bd
clicked_bd = StringVar()
bds = ["-",
"4800",
"9600",
"14400",
]
clicked_bd.set(bds[0])
drop_bd = OptionMenu(main_window, clicked_bd, *bds, command=connect_check)
drop_bd.config(width=20)
drop_bd.grid(column=2, row=3, padx=50)
def update_coms():
global clicked_com, drop_COM
ports = serial.tools.list_ports.comports()
coms = [com[0] for com in ports]
coms.insert(0, "-")
clicked_com = StringVar()
clicked_com.set(coms[0])
drop_COM = OptionMenu(main_window, clicked_com, *
coms, command=connect_check)
drop_COM.config(width=20)
drop_COM.grid(column=2, row=2, padx=50)
def readSerial():
print("thread started")
global serialData, ArduinoData
global time_data, dist_data, distance, past_time, seconds
while serialData:
recep = ArduinoData.read(1)
match recep:
case b'\xAA':
if ArduinoData.read(1) == b'\xDD':
distance = int.from_bytes(ArduinoData.read(2), 'big')
tiempo_actual = datetime.now()
deltat = (tiempo_actual - past_time).total_seconds()
past_time = tiempo_actual
seconds += deltat
time_data.append(seconds)
dist_data.append(distance)
drawnow(dist_figure())
def connexion():
global ArduinoData, serialData, fig
global time_data, dist_data, distance, past_time, seconds
if connect_btn["text"] in "Disconnect":
serialData = False
connect_btn["text"] = "Connect"
refresh_btn["state"] = "active"
drop_bd["state"] = "active"
drop_COM["state"] = "active"
else:
serialData = True
connect_btn["text"] = "Disconnect"
refresh_btn["state"] = "disable"
drop_bd["state"] = "disable"
drop_COM["state"] = "disable"
port = clicked_com.get()
baud = clicked_bd.get()
try:
# Inicializa el serial
ArduinoData = serial.Serial(port, baud, bytesize=8,
parity='N', stopbits=1, timeout=1.5)
# verfica que la instancancia serial esté cerrada
ArduinoData.close()
# Abrir la instancia serial
ArduinoData.open()
# Flush: wait until all data is written
ArduinoData.flush()
except Exception as e:
print(f"No se pudo conectar al puerto {port}")
# crea e inicializa la variable a almacenar la distancia
distance = 0
# Se graba el instante de tiempo actual
past_time = datetime.now()
# crea e inicializa la variable a almacenar el tiempo transcurrido
seconds = 0
# Crea la lista vacía para almacenar el tiempo y la distancia
time_data = []
dist_data = []
# Crear la canvas para la figura
plt.ion() # tell matplotlib you want interactive mode to plot data
fig = plt.figure()
t1 = threading.Thread(target=readSerial)
t1.deamon = True
t1.start()
def dist_figure():
global time_data, dist_data
# Función para "imprimir" los últimos 100 datas de distancia
ax1 = plt.subplot()
plt.plot(time_data[-100:], dist_data[-100:])
ax1.set(xlabel='time (s)', ylabel='distance (cm)',
title='VL53LOX measurements')
def close_window():
global main_window, serialData
serialData = False
main_window.destroy()
connect_menu_init()
main_window.mainloop()
In summary, I have a GUI to select the port and baurate to read the data from a serial port (reading distance).
Expecting: use an interactive figure from matplotlib to stream the data that is being read from the serial port