TkInter crashing without error

I’ve been working on a minor python project recently, and went back to my old friend TkInter. TkInter is the standard GUI library for Python. I had used it previously in another small project, so when I wanted to quickly get a GUI going for a project, it made sense to use TkInter. There are many other GUI libraries, but TkInter is supposedly one of the quicker and easier to get going.

TkInter has a number of shortcomings. It’s clunky and not completely intuitive. But the biggest issues that I experience with it is trying to get the interface to update from an outside perspective. My specific implementation required me to update about 20 values on the GUI by reading data from a serial port.

The easiest way to implement this is to make use of the TkInter Label widget. The Label is used for displaying any kind of text. A label can be linked to a TkInter variable, so that if you update the variable, the label automatically gets updated. The difficulty is that you need an event to trigger an update of the variables.

The way to get around this is threads. Python has built in support for threads, via the Threading class. And so you create a thread, and pass it the Tk variables and update them from the thread. This allows you to continue doing other things in the GUI, and still have your thread run. Or does it.

The immediate issue is with how TkInter queues up tasks. Because the label uses Tk variables, you can only update them by calling a set method. Not just by declaring the variable equal to a value. As such this set gets added to a queue. I found that when I clicked a drop down menu (Tk.OptionMenu), my updates would pause. My thread would effectively pause as it waited to be allowed to call the variable set method.

This was not the end of the world, and I probably would have left it like this if I didn’t continue to have the program crash unexpectedly. It would happen often, but at different times, and only when I was interacting with another aspect of the GUI. For example, when I clicked a button or selected a menu item. The program would freeze, causing the python itself to crash, and not reporting any error.

I battled to find much info on this topic. All I’ve been able to come across is the repeating statement that “TkInter is not thread safe” and that you shouldn’t try to access TkInter widgets, except from within the main thread.

There are a couple ways around this. What I ended up using is the TkInter after method. It’s janky. But it works, and has proven reliable. Since I made the change, I have not had my app crashing any more. Now I have a global variable to store the data from serial in. I have a function within my GUI object that updates the Tk variables from the global array. It then calls itself using the after method.

The after method adds a note to TK to do something after a specific amount of time has lapsed, and appears to operate much like an interrupt, ignoring whatever else I am doing and updating on schedule. I first came across the after method as described by Furas on StackOverflow.

Below is a summary of what I was doing before, and what I am doing now.

Before:

class SerialThread(threading.Thread):
  def __init__(self, gui_object):
    threading.Thread.__init__(self)
    self.gui = gui_object

  def run(self):
    #Read serial info
    self.gui.variable1.set(SERIALDATA)

class GUI():
  def __init__(self):
    self.root = tk.Tk()

    self.variable1 = tk.StringVariable(self.root)
    label1 = tk.Label(self.root, textvariable=self.variable1)

    label1.grid(row=1,column=1)

    thread1 = SerialThread(self)
    thread1.start()

 self.root.mainloop()

if __name__ == "__main__":
  GUI();

And after:

TempVariable = "XXXX"
 
class SerialThread(threading.Thread):
  def __init__(self):
    threading.Thread.__init__(self)

  def run(self):
    global TempVariable
    #Read serial info
    TempVariable = SERIALDATA
 
class GUI():
  def __init__(self):
    self.root = tk.Tk()
 
    self.variable1 = tk.StringVariable(self.root)
    label1 = tk.Label(self.root, textvariable=self.variable1)
    label1.grid(row=1,column=1)
 
    self.updateLabels()
 
    thread1 = SerialThread()
    thread1.start()
 
    self.root.mainloop()
 
  def updateLabels(self):
    global TempVariable
    self.variable1.set(TempVariable)
    self.root.after(10,self.updateLabels)
 
if __name__ == "__main__":
  GUI();

UPDATE – below code skips the global variable and uses a thread lock:

class SerialThread(threading.Thread):
  def __init__(self):
    threading.Thread.__init__(self)
    self.serial_data_to_display = ""
    self.lock = threading.Lock()

  def run(self):
    while True:
        with self.lock:
            # serial read stuff
            self.serial_data_to_display = "SERIAL DATA"
        sleep(0.1)

class GUI():
  def __init__(self):
    self.root = tk.Tk()
    self.variable1 = tk.StringVariable(self.root)

    label1 = tk.Label(self.root,  textvariable=self.variable1)
    label1.grid(row=1,column=1)

    self.thread1 = SerialThread()
    self.thread1.start()

    self.root.mainloop()
    self.updateLabels()

  def updateLabels(self):
    with self.thread1.lock:
        self.variable1.set(self.thread1.serial_data_to_display)

    self.root.after(10,self.updateLabels)

if __name__ == "__main__":
  GUI()

Design of a 2-axis, Continuous Rotation, Camera Control Platform

This was the title of my final year project for my BEng (Mechatroics) degree at the University of Stellenbosch. It’s been a year, a loooong year, but at the same time it’s passed so quickly. I’ve probably spent more time on varsity work this year than in any other previous year, a combination of this skripsie, mechatronics and electrical design projects, interspersed between the year’s class requirements.

You can see a summary poster of the project here. And the full report here.

Skripsie is something very different to what we’ve done previously. We’re given a year to complete the project, which is a fairly long time. What I’ve appreciated is the fact that it’s the only major project we’ve been given to do individually. It’s not that I don’t like other people, it’s just that it’s sometimes nice to be able to do things my way. Most of the projects are put forth by lecturers, and they act as supervisors for the projects. I’ve been very fortunate with my supervisor and his continued support and enthusiasm for my project.

Final Product

Final Product

So what is it? Well it’s basically a turret that is capable of continuous rotation. You get a bunch of pan/tilt cameras on the market, but they all stop after 360textdegree or less. The department I did my project with had purchased several Basler a311fc cameras to play with and desired a platform they could use for tracking. It’s a very nice camera, good quality and capable of fairly high capture rates (50fps @ 640×480, 132fps @ 320×240) and comes with some nifty software (Basler Pylon Driver) to control it. So the major issue was to transfer data and power to the camera. I looked at a couple of wireless solutions but for simplicities sake eventually went with slip rings. Picked up 2 slip rings (at quite a cost, well I was surprised at the expense) from Moog.

a slipring

a slipring

Next issue was control. My control systems has never been the strongest, so decided to stick with some open loop control in the form of stepper motors. Picked up a 220Nmm and 440Nmm stepper motor to control the tilt and pan respectively. They’re bi-polar hybrid stepper motors with a 0.9textdegree step size. I drove them both in half-step mode effectively giving me 0.45textdegree accuracy. To drive them I made use of a combination of L297 and L298 ICs from ST.

The idea was to be able to control this all from a PC, so some software development and integration was also required. To bring it all together I made use of an Arduino Uno. I developed a GUI in Python which then communicated via a serial connection with the Arduino. I was originally going to use Java for this, but couldn’t get a serial connection running. Chatted to some friends who suggested Python and found this post with a nice example. For testing I also got hold of two AS5040 hall effect sensors from Austria Microsystems. These rotary encoders give a 1024bit resolution, effectively 0.35/textdegree. I managed to find some nice code for the Arduino to read the data via SSI over at RepRap.

CAD Model

CAD Model

This was also the first time I’ve had the opportunity to develop CAD models of something and have it built. We’ve done several machine design projects over the years, but they’ve all been conceptual only. I didn’t machine the stuff myself, but it was pretty cool when I built the thing, and compared it to my model, and it looked the same.

screenshot of the UI

screenshot of the UI

So I handed in the final report on the project today. Unfortunately it’s not working 100% at the moment, and one of the motor driver circuits got damaged, so I need to repair that before my presentation in a few weeks time.

But until then, it’s 3 exams in 3 weeks, so ought to be pretty chilled. And I’m almost an engineer o/