Skip to content
Advertisement

How to run periodic serial communication in a separate thread in python

I am programming a GUI with tkinter that controls the communication to multiple motor controllers that are connected serially via RS485. I have a function (get_status()) that requests the status (motor running, motor position, position reached, …) of the controllers via pySerial and displays the uptated status on the UI. I want to run this funktion every second.

The problem is, while the serial communication is running, the UI freezes, and that happening every second makes the program very laggy to use.

I already tried to run the get_status funktion in its own thread but it didn’t make any difference.

I am relatively new to python and not sure if I really understood threading in the first place.

What I tried was this:

import serial, threading, tkinter as tk, serial.tools.list_ports, time, os, tkinter.filedialog
from tkinter import ttk

class myThread (threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
    def run(self):
        print("Starting " + self.name)
        get_status()
        print("Exiting " + self.name)

ser = serial.Serial()
thread = myThread()

def get_status():
    status = ask_status()
    update_status_leds(status)
    window.after(1000, thread.run)

com_port = chs_com_port.get().split(" ")
ser.port = com_port[0]
ser.baudrate = 19200
ser.bytesize = serial.SEVENBITS
ser.parity = serial.PARITY_EVEN
ser.timeout = 0.2
ser.open()
thread.run()

For this question I heaviliy simplified the get_status funktion but this is basically how it works.

As I said, even now that get_status() supposedly runs in a new thread, it is still blocking the UI mainloop from updating resulting in a freeze while the serial communication is running.

Any help is greatly appreciated and sorry to any veteran who has to scratch his head over my shitty code.

EDIT: I found a semi good way that kind of works. I made another function status_loop() that creates a new thread object and starts the thread. This funktion gets called by window.after(1000,status_loop) in the get_status() funktion

The new code looks like this:

import serial, threading, tkinter as tk, serial.tools.list_ports, time, os, tkinter.filedialog
from tkinter import ttk

class myThread (threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
    def run(self):
        print("Starting " + self.name)
        get_status()
        print("Exiting " + self.name)

ser = serial.Serial()

def status_loop():
    thread = myThread()
    thread.start()

def get_status():
    status = ask_status()
    update_status_leds(status)
    window.after(1000, status_loop)

com_port = chs_com_port.get().split(" ")
ser.port = com_port[0]
ser.baudrate = 19200
ser.bytesize = serial.SEVENBITS
ser.parity = serial.PARITY_EVEN
ser.timeout = 0.2
ser.open()
status_loop()

Advertisement

Answer

You use thread in a wrong way:

  • start() should be used instead of run() to start the thread
  • should not call thread.run() inside get_status(). Use while loop instead
import time
...

class myThread (threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
    def run(self):
        print("Starting " + self.name)
        get_status()
        print("Exiting " + self.name)

...

def get_status():
    while True:
       status = ask_status()
       update_status_leds(status)
       time.sleep(1)

...

thread = myThread()
thread.daemon = True
thread.start() # start the thread

If the tasks inside get_status() are not time-consuming tasks, then simply use after() instead of thread:

def get_status():
    status = ask_status()
    update_status_leds(status)
    window.after(1000, get_status)

...

get_status() # start the after loop
User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement