0

I'm using trying to use python with an ultrasonic sensor to measure distance, and then update a tkinter label with the distance value every second. However, I'm having problems; it will run for a while, anything from a couple of seconds up to a few minutes, then freeze.

Here is my code:

from tkinter import *
import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)
GPIO_TRIGGER_X = 4
GPIO_ECHO_X = 27
GPIO.setup(GPIO_TRIGGER_X, GPIO.OUT)
GPIO.setup(GPIO_ECHO_X, GPIO.IN)

def distanceX():

    GPIO.output(GPIO_TRIGGER_X, True)
    time.sleep(0.0001)
    GPIO.output(GPIO_TRIGGER_X, False)

    StartTime = time.time()
    StopTime = time.time()

    while GPIO.input(GPIO_ECHO_X) == 0:
        StartTime = time.time()

    while GPIO.input(GPIO_ECHO_X) == 1:
        StopTime = time.time()

    TimeElapsed = StopTime - StartTime
    distance = (TimeElapsed * 34300) / 2

    return distance

def updateDistance():
        dX = distanceX()
        print(dX)
        lengthValue.configure(text=dX)
        root.after(1000, updateDistance)

root = Tk()

root.geometry("200x100")
root.tk_setPalette(background="white", foreground="black")

lengthName = Label(root, text = "Length:")
lengthValue = Label(root, text="start")

lengthName.grid(row=1, column=1)
lengthValue.grid(row=1, column=2)

updateDistance() 
root.mainloop()

I have tried running distanceX() alone in a separate script just printing out the values, that works fine. I've also tried the running the script without distanceX() like this:

dX = 0

def updateDistance():
        global dX
        print(dX)
        lengthValue.configure(text=dX)
        dX += 1
        root.after(1000, updateDistance)

..and that also works fine.

Any ideas? Apologies in advance if I've left any needed info out, this is my first go at python and tkinter...

fillefrans
  • 33
  • 1
  • 4
  • 2
    Try using `threading.Thread()`. – acw1668 Feb 11 '19 at 13:09
  • Do not use sleep in tkinter. `sleep()` will block the mainloop. That said if you use threading you can use sleep in a separate thread. You need to take care on how you build your loops because tkinter runs in a single thread. – Mike - SMT Feb 11 '19 at 13:23

2 Answers2

0

Tkinter is single threaded. Your while loop in function distanceX blocks the main thread until it receives a True value and continues with the rest of the function. That's why you are experiencing freezes.

Try run the below:

from tkinter import *
import time

root = Tk()
flag = True

def something():
    global flag
    while flag:
        print ("Hello World")
        time.sleep(1)

def set_flag():
    global flag
    flag = False

something()
root.after(2000,set_flag)
root.mainloop()

And you will see your Tk window won't even pop up due to While loop blocking the main thread.

To solve this, you need to thread your distanceX() function. Something like:

from tkinter import *
import threading, time

root = Tk()
flag = True

def something():
    global flag
    while flag:
        print ("Hello world")
        time.sleep(1)

def set_flag():
    global flag
    flag = False

t = threading.Thread(target=something)
t.start()
root.after(2000,set_flag)
root.mainloop()

You can read more about threading in here.

Henry Yik
  • 22,275
  • 4
  • 18
  • 40
  • Using `threading.Event` object instead of global `flag` is more preferable. – acw1668 Feb 11 '19 at 14:07
  • I added `lengthValue.configure(text=distance)` to `distanceX()` and threaded the function by adding `t = threading.Thread(target=distanceX)` and `t.start()` as you suggested. I also added a button to the GUI that will simply print "All good" just so I could have a sure way of checking if the GUI was still responsive. When I run this, the label will update fine at first but then stop updating after a while (like before), but the button prints "All good" as it's supposed to... – fillefrans Feb 12 '19 at 07:52
  • Because your function only got executed once in the thread. You will need to wrap your `distanceX()` inside a loop function and thread the loop. – Henry Yik Feb 12 '19 at 09:32
  • I did, I included `while True` to the beginning of `distanceX()`. It works perfectly fine, I can see the label updating with the correct values, but it will suddenly stop updating, testrun I just did took 15 min before it stopped updating. But like I said, the main thread & GUI seems to be fine also after that point, the button I added that prints "All good" works fine even then. – fillefrans Feb 12 '19 at 15:08
  • I'm new to stackoverflow so I'll check first; am I ok to add the full, rewritten code as an answer so I can show all the changes? – fillefrans Feb 12 '19 at 15:19
0

Turns out the problem was in fact the two while loops in distanceX(). Added a timeout to both and all is well. Working code:

from tkinter import *
import RPi.GPIO as GPIO
import threading, time

GPIO.setmode(GPIO.BCM)
GPIO_TRIGGER_X = 4
GPIO_ECHO_X = 27
GPIO.setup(GPIO_TRIGGER_X, GPIO.OUT)
GPIO.setup(GPIO_ECHO_X, GPIO.IN)

def distanceX():

    while True:
         timeout = time.time() + 0.1
         GPIO.output(GPIO_TRIGGER_X, True)
         time.sleep(0.0001)
         GPIO.output(GPIO_TRIGGER_X, False)

         StartTime = time.time()
         StopTime = time.time()

         while GPIO.input(GPIO_ECHO_X) == 0:
            StartTime = time.time()
            if time.time() > timeout:
                break

         while GPIO.input(GPIO_ECHO_X) == 1:
            StopTime = time.time()
            if time.time() > timeout:
                break

         TimeElapsed = StopTime - StartTime
         distance = (TimeElapsed * 34300) / 2

         print(distance)
         lengthValue.configure(text=distance)

         time.sleep(1)

def check():
    print("All good")


root = Tk()

root.geometry("200x100")
root.tk_setPalette(background="white", foreground="black")

lengthName = Label(root, text = "Length:")
lengthValue = Label(root, text="start")
button = Button(root, text="Check", command=check)

lengthName.grid(row=1, column=1)
lengthValue.grid(row=1, column=2)
button.grid(row=2, column=1)

t1 = threading.Thread(target=distanceX)
t1.start()
root.mainloop()
fillefrans
  • 33
  • 1
  • 4