Using tKinter to read serial data - Python

82 views Asked by At

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

0

There are 0 answers