Serial, Threading, Tkinter : RuntimeError - python

I've been trying to build a simple serial monitor based on this code:
https://aboesch.wordpress.com/2013/04/28/python-code-gui-for-controlling-serial-port-and-live-monitoring/
It's utilizing threading which i've head does not always play nice with Tkinter. I'm new to this and trying to do something pretty basic (send serial back and forth communicate between an RPi and an Arduino). Yesterday this exact code was running with no errors
Exception in thread Updating:
Traceback (most recent call last):
File "/usr/lib/python2.7/threading.py", line 810, in __bootstrap_inner
self.run()
File "/home/pi/sendstring_NEWTEST.py", line 21, in run
UpdateC.set(self.update_count)
File "/home/pi/sendstring_NEWTEST.py", line 37, in run
c_var.set(self.request_dict["c"])
RuntimeError: main thread is not in main loop
I'll paste my code below, it still sends properly, but it will only read once or twice if at all. (And for whatever confusing reason, the same code ran all day yesteday with no error). I understand that there's an issue with running your GUI outside of your main thread, but i'm not knowledgeable enough to understand how to fix it. I'm looking for some direction, preferably some assistance in modifying this current code, but open to new directions. I've looked into queue's a bit, i'm just starting out using serial and am looking for a stable way to read and write strings. Thanks for reading
import Tkinter as tk # for the GUI
import ttk # for nicer GUI widgets
import tkMessageBox # for GUI testbox
import serial # for communication with serial port
import time # for time stuff
import threading # for parallel computing
class myThread (threading.Thread):
def __init__(self, name, ser):
threading.Thread.__init__(self)
self.name = name
self.ser = ser
self.request_dict = {"c": ''}
# gets called when thread is started with .start()
def run(self):
self.update_count = 0
while self.ser.isOpen():
self.update_count += 1
# UpdateC.set(self.update_count)
for request in self.request_dict:
try:
# self.ser.write(request)
time.sleep(0.1)
# create string for the answer
self.request_dict[request] = ''
# as long as an answer byte is waiting, read the byte
while self.ser.inWaiting() > 0:
self.request_dict[request] += self.ser.read(1)
except:
# do nothing if command could not be send
pass
# set the label variables with the answeres receiced
c_var.set(self.request_dict["c"])
time.sleep(1)
# sending commands to the MWG
def mSend(command):
try:
ser.write(command)
except:
print "Could not send command. Port closed?"
# clear all entry widgets on the GUI
C_sendvar.set('')
return
# ===========================
# Begin of the main program
# ===========================
ser = serial.Serial()
ser.port = "/dev/ttyUSB0"
ser.baudrate = 9600
ser.timeout = 1
if ser.isOpen() == False:
ser.open()
root = tk.Tk()
root.geometry("600x500")
root.title("SERIAL MONITOR")
c_var = tk.StringVar()
C_sendvar = tk.StringVar()
#UpdateC = tk.StringVar()
mHeader = ttk.Label(root, text = "").grid(row=0, column=2)
mStatus = ttk.Label(root, text = "Serial port open: "+str(ser.isOpen())).grid(row=1, column=2)
mSeperate = ttk.Label(root, text = "").grid(row=2, column=1)
mSubhead_l = ttk.Label(root, text = "Receive Protocol:").grid(row=6, column=1)
mSeperate2 = ttk.Label(root, text = "").grid(row=5, column=1)
c_Show = ttk.Label(root, textvariable = c_var).grid(row=6, column=2)
c_Entry = ttk.Entry(root, textvariable = C_sendvar).grid(row=4, column=2)
c_Button = ttk.Button(root, text ="Send Protocol", command = lambda: mSend(C_sendvar.get())).grid(row=4, column=1)
time.sleep(1)
# call and start update-thread
thread1 = myThread("Updating", ser)
thread1.start()
# start GUI
root.mainloop()

Related

Python Tkinter GUI Input

i'm currently need help of my python system. i made a simple GUI that insist of a simple GUI with buttons that calls out other python scripts and print it to the geometry. however, i have an issue where i could not get the system to print anything out of the GUI or input anything on the tkinter geometry when the python script needed of any input from the User. the GUI fully works when there are no input needed from the users.
from the code below, i shown my full GUI system that calls out the 2 python scripts whenever button is pressed. i would like to make the tkinter geometry to accept inputs directly to the tkinter geometry and send its input to the python script and display its output back again to the tkinter geometry. How do i improve this system or address this problem?
GUI.py
from tkinter import *
from subprocess import *
from threading import *
import time
import subprocess
#text1.insert(END, your stuff here) to put output to GUI
#GUI
root = Tk()
root.title('System 1')
root.geometry('1000x900')
root.configure(background='black')
text1 = Text(root, width= 100, height = 40)
text1.pack()
#command list
def command():
try:
child = Popen(['python', '-u', 'CMDping.py'], stdout=PIPE)
text1.delete("1.0", END)
for line in iter(child.stdout.readline, ''):
text1.insert(INSERT, line)
text1.see(END)
text1.update_idletasks()
child.stdout.close()
child.wait()
except CalledProcessError:
text1.insert(END, "File Not Found!")
def command1():
try:
bttn1.destroy()
pythonfile1 = 'python NetworkCommands.py'
p1 = subprocess.Popen(pythonfile1, shell=True)
out, err = p1.communicate()
except CalledProcessError:
text1.insert(END, "File Not Found!")
#Threading for command Function
def Threading():
t1=Thread(target=command)
t1.start()
root.update()
def Threading1():
t1=Thread(target=command1)
t1.start()
root.update()
#buttons
bttn= Button(root,bg="black",fg="white",highlightcolor="white", text="Diagnose", command=Threading)
bttn.pack(side=LEFT, padx=5,pady=0)
bttn1= Button(root,bg="black",fg="white",highlightcolor="white", text="Flush_DNS", command=Threading1)
bttn1.pack(side=LEFT, padx=5,pady=0)
#Clock
class Clock:
def __init__(self):
self.time1 = ''
self.time2 = time.strftime('%H:%M:%S')
self.mFrame = Frame()
self.mFrame.pack(side=TOP,expand=YES,fill=X)
self.watch = Label(self.mFrame, text=self.time2, font=('times',12,'bold'))
self.watch.pack()
self.changeLabel() #first call it manually
def changeLabel(self):
self.time2 = time.strftime('%H:%M:%S')
self.watch.configure(text=self.time2)
self.mFrame.after(200, self.changeLabel) #it'll call itself continuously
obj1 = Clock()
root.mainloop()
CMDping.py
import subprocess
def ping():
command = input("Enter IP Address to ping: ")
time.sleep(2)
os.system("ping " + command)
ping()

Live Tkinter Labels

I have a question regarding tkinter labels and pymodbus. The scenario is, I'm attempting to build a GUI whereby the program connects to a "serial client" or "slave" device and essentially polls the serial clients registers. I am attempting to read these registers and display them on a tkinter label which I have been able to do! However, I would like to take the concept further and have the labels update every second. The registers in question are sensors so I'd like to capture them as they vary, and display them on the GUI. So far this is a simplified version of what's been completed so far.
from tkinter import *
from tkinter import ttk
from tkinter import messagebox
from pymodbus.client.sync import ModbusSerialClient
client = ModbusSerialClient(
port="COM14",
startbit=1,
databits=8,
parity="N",
stopbits=2,
errorcheck="crc",
baudrate=38400,
method="RTU",
timeout=3)
root = Tk()
root.geometry("500x350")
res = client.read_holding_registers(address=50, count=1, unit=1)
value_1 = DoubleVar()
value_1.set(res.registers)
value_label = ttk.Label(root, textvariable = value_1, font = ("Arial", 25, "bold"))
value_label.place(x = 50, y = 50)
root.mainloop()
At the moment, the program connects to the sensor in question and takes the value from the register when program is loaded, is there a way where it polls for the value every second, and updates?
Thanks in advance.
You can use .after() to execute a function to poll the register every second:
...
value_1 = DoubleVar()
value_label = ttk.Label(root, textvariable=value_1, font=("Arial", 25, "bold"))
value_label.place(x=50, y=50)
def poll_register():
res = client.read_holding_registers(address=50, count=1, unit=1)
value_1.set(res.registers)
# call poll_register() again one second later
root.after(1000, poll_register)
poll_register() # start polling register
...

How do I implement a stop button with Tkinter for a stepper motor system?

I have a question regarding the use of a stop button in Tkinter.
For an experiment, I have to set up and X/Y stage that works by using two stepper motors. The arduino program works perfectly. The only problem is that when I activate the start function, which drives the stage to various coordinates, it freezes. Now the problem is that it has to run for weeks on end and it needs a stop button for emergencies and stopping the stepper motor in general. The stop button has to do two things: it has to stop the stepper driver motors, and it has to break the tkinter.after loop. However, due to the freezing, it is impossible to click on the button.
Here is my code:
import tkinter as tk
import serial
ser = serial.Serial('COM5', 115200)
running = False
def quit():
"""Function that closes the serial port and destroys the root of the GUI"""
global root
ser.close()
root.destroy()
def route():
"""Writes coordinates to the arduino, which in return drives the stepper motors"""
if running == True:
# The g line stands for go to!
ser.write(b'g115000\r\n')
root.after(50)
ser.write(b'g225000\r\n')
root.after(30000)
ser.write(b'g1400\r\n')
root.after(50)
ser.write(b'g2500\r\n')
root.after(12000,route())
def zeroing():
"""Zeros the program, this is necessary for the stage to
calibrate it's boundary conditions"""
#zeros the stage so that it is ready to use!
varLabel.set("zeroing, please move away from the stage")
#the z command zeros the motors for boundary business
ser.write(b'z\r\n')
def run_program():
"""Runs the function Route and sets running to True (not a good start/stop system)"""
#starts the program, but only after you zero the stage
global running
running = True
varLabel.set("Program running")
route()
def stop_program():
"""Sets the running flag to False and sends a stop command to the arduino"""
#stops the program immediately
global running
running = False
varLabel.set("Program stopped,please zero before continuing")
#the s byte is a command that stops the stepper motors
ser.write(b's\r\n')
if __name__== "__main__":
root = tk.Tk()
canvas1 = tk.Canvas(root, width=800, height=400)
canvas1.pack()
root.title('XY-stage controller')
#instructions
instructions = tk.Label(root,text='Enter the amount of hours you want your measurements to last in the text box.'
'\n Click on run program to start a measurement session.'
'\n Click on stop incase of an emergency or if it is wanted to stop the program.',
font = "Raleway")
instructions.pack(side='bottom')
# initialize active labels
varLabel = tk.IntVar()
tkLabel = tk.Label(textvariable=varLabel,)
tkLabel.pack(side='top')
# Buttons for initializing a bunch of good functions
zerobutton = tk.IntVar()
tkrunprogram= tk.Button(
root,
text='Zero',
command = zeroing,
height = 4,
fg = "black",
width = 10,
bg = 'gray',
bd = 5,
activebackground = 'green'
)
tkrunprogram.pack(side='top')
runprogbutton = tk.IntVar()
tkrunprogram= tk.Button(
root,
text='Run Program',
command = run_program,
height = 4,
fg = "black",
width = 10,
bg = 'gray',
bd = 5,
activebackground = 'green'
)
tkrunprogram.pack(side='top')
stopbutton = tk.IntVar()
tkstopprog= tk.Button(
root,
text='Stop Program',
command = stop_program,
height = 4,
fg = "black",
width = 10,
bg = 'gray',
bd = 5,
activebackground = 'red'
)
tkstopprog.pack(side='top')
Buttonquit = tk.IntVar()
tkButtonQuit = tk.Button(
root,
text='Quit',
command = quit,
height = 4,
fg = "black",
width = 10,
bg = 'yellow',
bd = 5
)
# initialize an entry box
entry1 = tk.Entry(root)
durbox = canvas1.create_window(400, 200, window=entry1)
tkButtonQuit.pack(side='top')
root.mainloop()
The after commands in the end will introduce pauses of 60 minutes, which would make the program freeze for 60 minutes. Hopefully there is an easy solution to interrupting the function!
Thank you in advance!
You can make use of multithreading. Make all the communication in a separate thread and also make sure you don't update the GUI components in the child thread.
Here is a minimal example:
import serial
import tkinter as tk
from threading import Thread
import time
def start():
global running
stop()
btn.config(text="Stop", command=stop)
running = True
info_label["text"] = "Starting..."
thread = Thread(target=run, daemon=True)
thread.start()
def run():
ser = serial.Serial("COM5", 115200, timeout=2)
while running:
ser.write(b'g115000\r\n')
time.sleep(50)
ser.write(b'g225000\r\n')
time.sleep(30000)
ser.write(b'g1400\r\n')
time.sleep(50)
ser.write(b'g2500\r\n')
ser.write(b's\r\n')
ser.close()
def stop():
global running
running = False
info_label["text"] = "Stopped"
btn.config(text="Start", command=start)
root = tk.Tk()
running = False
info_label = tk.Label(root, text="INFO:")
info_label.pack()
btn = tk.Button(root, text="Start", command=start)
btn.pack()
root.mainloop()
after(x000) is effectively the same as time.sleep(x) - it puts the whole app to sleep. As a general rule of thumb, you should never do this in the same thread as the GUI. That doesn't mean you need to use threads, however.
tkinter's after method lets you schedule commands to run in the future. If the commands you are running are fast such as sending a few bytes down a serial connection, this is really all you need. It is less complex and has less overhead than using threads.
For example, your route function can probably be written something like this:
def route():
if running == True:
# immediately write this:
ser.write(b'g115000\r\n')
# after 50ms, write this:
root.after(50, ser.write, b'g225000')
# after 30 more seconds, write this
root.after(50+30000, ser.write, b'g1400\r\n')
# and then after 50ms more, write this
root.after(50+30000+50, ser.write, b'g2500\r\n')
# and finally, after 12 seconds, do it all again
root.after(50+30000+50+12000,route)
Once you call this once, you don't need to call it again, and you don't need to call it in a thread. It simply places some work on a queue that gets picked up some time in the future.
Since each call to root.after returns an id, you can save these ids so that in the case of wanting to stop everything, you can call after_cancel on each saved id.
Another way is to define a job as a series of delays and then bytes to write. For example:
job = (
(0, b'g115000\r\n'),
(50, b'g225000'),
(30000, b'g1400\r\n'),
(50, b'g2500\r\n'),
)
Then, your route function can look something like this (untested, but this is pretty close)
def route(job):
global after_id
delay = 0
for (delta, data) in job:
delay += delta
root.after(delay, ser.write, data)
delay += 12000
root.after(delay, route, job)
There are many variations of that theme. For example, you could create a Job class that implements this logic, or the job could contain the commands rather than the data. The point being, you can define a data structure that defines work to be done, and then use after to schedule that work.

Python tkinter GUI freezing/crashing

from Tkinter import *
import tkFileDialog
import tkMessageBox
import os
import ttk
import serial
import timeit
import time
######################################################################################
class MyApp:
def __init__(self, parent):
########################################################
#Setup Frames
self.MiddleFrame = Frame(parent) #Middle Frame
self.MiddleFrame.pack()
#GLOBAL VARIABLES
self.chip_number = 0 #number of chip testing
###########################################
#Middle Frame setup
Label(self.MiddleFrame, text='Done').grid(row=8, column=1, sticky = E)
self.Done = Canvas(self.MiddleFrame, bg="yellow", width=10, height=10)
self.Done.grid(row=8, column=2)
Label(self.MiddleFrame, text='Chip Number:').grid(row=9, column=1, sticky = E)
#start button
self.button1 = Button(self.MiddleFrame,state=NORMAL, command= self.start_pre)
self.button1["text"]= "START"
self.button1.grid(row=1, column=2, sticky = E)
###########################################
#Action of Start Button
def start_pre(self):
x = 0
while x<10000:
self.start_button()
x=x+1
#Talking to Board
def start_button(self):
#increase chip count number and update
self.chip_number += 1
Label(self.MiddleFrame, text=str(self.chip_number)).grid(row=9, column=2, sticky = E)
#reset-yellow
self.reset_color()
print "Still Working", self.chip_number
self.Done.configure(background="green")
self.Done.update_idletasks()
###############################################################
#Color Boxes
#Reset
def reset_color(self):
self.Done.configure(background="yellow")
self.Done.update_idletasks()
###############################################################################################################
#Start Programs
root = Tk() #makes window
root.title("Interface")
myapp = MyApp(root) #this really runs program
root.mainloop() #keep window open
With my program, i first push the start button.
I will print "still working" and the GUi will update chip number and blink done light over and over. The start button go to function that will execute 10000 times. However after 3000 iterations, the gui freeze, but the program is still print "still working". How do I keep the gui from crashing?
There are many problems with your code. For one, this is fundamentally flawed:
while self.stop == True:
self.start_button()
time.sleep(0.5)
You simply can't expect a GUI to behave properly with code like that. As a general rule of thumb you should never have the main thread of a GUI call sleep. Causing sleep prevents the event loop from processing any events, including low level events such as requests to refresh the screen.
The use of sleep has been asked and answered many times on stackoverflow. You might find some of those questions useful. For example,
windows thinks tkinter is not responding
Python Tkinter coords function not moving canvas objects inside loop
How do widgets update in Tkinter?
Tkinter multiple operations
Python Tkinter Stopwatch Error
You have another problem that falls into the category of a memory leak. From that while loop, you call self.start_button() indefinitely. This happens about once a second, due to sleep being called for half a second in the loop, and another half a second in start_button.
Each time you call start_button, you create another label widget that you stack on top of all previous widgets in row 9, column 2. Eventually this will cause your program to crash. I'm surprised that it causes your program to fail so quickly, but that's beside the point.
My recommendation is to start over with a simple example that does nothing but update a label every second. Get that working so that you understand the basic mechanism. Then, once it's working, you can add in your code that reads from the serial port.
May I suggest that you start over with the following code? You can port in back to Python 2 if needed, but your program has been rewritten to use Python 3 and has been designed to use tkinter's ability to schedule future events with the after methods. Hopefully, you will find the code easier to follow.
import collections
import timeit
import tkinter
def main():
root = Application()
root.setup()
root.mainloop()
class Application(tkinter.Tk):
def setup(self):
mf = self.__middle_frame = tkinter.Frame(self)
self.__middle_frame.grid()
bf = self.__bot_frame = tkinter.Frame(self)
self.__bot_frame.grid()
self.__port_set = False
self.__chip_number = 0
self.__chip_pass_num = 0
self.__chip_fail_num = 0
self.__chip_yield_num = 0
self.__stop = True
self.__widgets = collections.OrderedDict((
('COT', 'Continuity Test'), ('CHE', 'Chip Erase'),
('ERT', 'Erase Test'), ('WRT', 'Write Test'),
('WIRT', 'Wire Reading Test'), ('WIT', 'Wire Reading Test'),
('WRAT', 'Write All Test'), ('DO', 'Done')))
for row, (key, value) in enumerate(self.__widgets.items()):
label = tkinter.Label(mf, text=value+':')
label.grid(row=row, column=0, sticky=tkinter.E)
canvas = tkinter.Canvas(mf, bg='yellow', width=10, height=10)
canvas.grid(row=row, column=1)
self.__widgets[key] = label, canvas
self.__cn = tkinter.Label(mf, text='Chip Number:')
self.__cn.grid(row=8, column=0, sticky=tkinter.E)
self.__display = tkinter.Label(mf)
self.__display.grid(row=8, column=1, sticky=tkinter.E)
self.__button = tkinter.Button(bf, text='START',
command=self.__start_pre)
self.__button.grid(sticky=tkinter.E)
def __start_pre(self):
self.__button['state'] = tkinter.DISABLED
self.__start_button(0)
def __start_button(self, count):
if count < 100:
self.__chip_number += 1
self.__display['text'] = str(self.__chip_number)
self.__widgets['DO'][1]['bg'] = 'yellow'
start_time = timeit.default_timer()
print('Still Working:', self.__chip_number)
self.after(500, self.__end_button, count)
else:
self.__button['state'] = tkinter.NORMAL
def __end_button(self, count):
self.__widgets['DO'][1]['bg'] = 'green'
self.after(500, self.__start_button, count + 1)
if __name__ == '__main__':
main()

Intermittent Python thread error, "main thread is not in main loop"

Middle aged dad (electrical engineer not programmer by trade) trying to teach my 13 year old daughter electronics and programming. So far, I love Python. I am building a program to display temperatures throughout our house with tkinter GUI and DS18B20 sensors.
We've pieced together the program below from reading books, online research and using Stack Overflow for trouble-shooting bugs (this site rocks!).
Now we're stumped, we keep getting an intermittent error, when we run the program the first time after loading idle on our Raspberry, it works fine.
The second time, and all subsequent times, we get this error message:
Traceback (most recent call last):
File "/home/pi/Code-working-library/stackoverflow-paste.py", line 140, in <module>
app.equipTemp.set(tempread)
File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 203, in set
return self._tk.globalsetvar(self._name, value)
RuntimeError: main thread is not in main loop
Note, our understanding is that in order to have a static window and update labels updated temps read off our sensor (DS18B20) we needed to use a thread. The sample code we started with has the _init_ statements with only one underscore preceding and trailing - not sure why, if I add a second underscore, I get error messages. The updating window code we used as our basis came from Raspberry Pi forum
Here is our code:
from Tkinter import *
import tkFont
import os
import glob
import time
import subprocess
import re
import sys
import time
import threading
import Image
import ImageTk
os.system('modprobe w1-gpio')
os.system('modprobe w1-therm')
#28-000005c6ba08
sensors = ['28-000005c6ba08']
sensors1 = ['28-000005c70f69']
def read_temp_raw():
catdata = subprocess.Popen(['cat',device_file], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out,err = catdata.communicate()
out_decode = out.decode('utf-8')
lines = out_decode.split('\n')
return lines
def read_temp():
lines = read_temp_raw()
while lines[0].strip()[-3:] != 'YES':
time.sleep(0.2)
lines = read_temp_raw()
equals_pos = lines[1].find('t=')
if equals_pos != -1:
temp_string = lines[1][equals_pos+2:]
temp_c = float(temp_string) / 1000.0
temp_f = temp_c * 9.0 / 5.0 + 32.0
return temp_f
########### build window ###################
bground="grey"
class App(threading.Thread):
def _init_(self):
threading.Thread._init_(self)
self.start()
def callback(self):
self.root.quit()
def run(self):
#Make the window
self.root = Tk()
self.root.wm_title("Home Management System")
self.root.minsize(1440,1000)
self.equipTemp = StringVar()
self.equipTemp1 = StringVar()
self.equipTemp2 = StringVar()
self.customFont = tkFont.Font(family="Helvetica", size=16)
# 1st floor Image
img = Image.open("HOUSE-PLANS-01.png")
photo = ImageTk.PhotoImage(img)
Label1=Label(self.root, image=photo)
Label1.place(x=100, y=100)
# 2nd floor
img2 = Image.open("HOUSE-PLANS-02.png")
photo2 = ImageTk.PhotoImage(img2)
Label1=Label(self.root, image=photo2)
Label1.place(x=600, y=100)
# Basement image
img3 = Image.open("HOUSE-PLANS-03.png")
photo3 = ImageTk.PhotoImage(img3)
Label1=Label(self.root, image=photo3)
Label1.place(x=100, y=500)
# Attic Image
img4 = Image.open("HOUSE-PLANS-04.png")
photo4 = ImageTk.PhotoImage(img4)
Label1=Label(self.root, image=photo4)
Label1.place(x=600, y=500)
# House Isometric Image
img5 = Image.open("house-iso.png")
photo5 = ImageTk.PhotoImage(img5)
Label1=Label(self.root, image=photo5)
Label1.place(x=1080, y=130)
#Garage Temp Label
Label2=Label(self.root, textvariable=self.equipTemp, width=6, justify=RIGHT, font=self.customFont)
Label2.place(x=315, y=265)
print "start monitoring and updating the GUI"
self.root.mainloop() #start monitoring and updating the GUI
########### Start Loop ###################
print "starting app"
app = App()
app.start()
print "app started"
################### Begin ds18b20 function ##############
while True:
# 28-000005c6ba08
i = "28-000005c6ba08"
base_dir = '/sys/bus/w1/devices/'
device_folder = glob.glob(base_dir + i)[0]
device_file = device_folder + '/w1_slave'
tempread=round(read_temp(),1)
app.equipTemp.set(tempread)
time.sleep(5)
##################### END ds18b20 Function ######
You need to run the GUI code in the main thread, and your temperature reading code needs to be in the background thread. It's only safe to update the GUI in the main thread, so you can pass the temperature data you're reading from the background thread back to the main thread via a Queue, and have the main thread periodically check for data in the queue using self.root.after():
from Tkinter import *
import tkFont
import os
import glob
import time
import threading
import Image
import Queue
def update_temp(queue):
""" Read the temp data. This runs in a background thread. """
while True:
# 28-000005c6ba08
i = "28-000005c6ba08"
base_dir = '/sys/bus/w1/devices/'
device_folder = glob.glob(base_dir + i)[0]
device_file = device_folder + '/w1_slave'
tempread=round(read_temp(),1)
# Pass the temp back to the main thread.
queue.put(tempread)
time.sleep(5)
class Gui(object):
def __init__(self, queue):
self.queue = queue
#Make the window
self.root = Tk()
self.root.wm_title("Home Management System")
self.root.minsize(1440,1000)
self.equipTemp = StringVar()
self.equipTemp1 = StringVar()
self.equipTemp2 = StringVar()
self.customFont = tkFont.Font(family="Helvetica", size=16)
# 1st floor Image
img = Image.open("HOUSE-PLANS-01.png")
photo = ImageTk.PhotoImage(img)
Label1=Label(self.root, image=photo)
Label1.place(x=100, y=100)
# 2nd floor
img2 = Image.open("HOUSE-PLANS-02.png")
photo2 = ImageTk.PhotoImage(img2)
Label1=Label(self.root, image=photo2)
Label1.place(x=600, y=100)
# Basement image
img3 = Image.open("HOUSE-PLANS-03.png")
photo3 = ImageTk.PhotoImage(img3)
Label1=Label(self.root, image=photo3)
Label1.place(x=100, y=500)
# Attic Image
img4 = Image.open("HOUSE-PLANS-04.png")
photo4 = ImageTk.PhotoImage(img4)
Label1=Label(self.root, image=photo4)
Label1.place(x=600, y=500)
# House Isometric Image
img5 = Image.open("house-iso.png")
photo5 = ImageTk.PhotoImage(img5)
Label1=Label(self.root, image=photo5)
Label1.place(x=1080, y=130)
#Garage Temp Label
Label2=Label(self.root, textvariable=self.equipTemp, width=6, justify=RIGHT, font=self.customFont)
Label2.place(x=315, y=265)
print "start monitoring and updating the GUI"
# Schedule read_queue to run in the main thread in one second.
self.root.after(1000, self.read_queue)
def read_queue(self):
""" Check for updated temp data"""
try:
temp = self.queue.get_nowait()
self.equipTemp.set(temp)
except Queue.Empty:
# It's ok if there's no data to read.
# We'll just check again later.
pass
# Schedule read_queue again in one second.
self.root.after(1000, self.read_queue)
if __name__ == "__main__":
queue = Queue.Queue()
# Start background thread to get temp data
t = threading.Thread(target=update_temp, args=(queue,))
t.start()
print "starting app"
# Build GUI object
gui = Gui(queue)
# Start mainloop
gui.root.mainloop()
Edit:
After actually taking a look at the tkinter source code, as well as the Python bug tracker, it appears that unlike almost every other GUI library out there, tkinter is intended to be thread-safe, as long you run the mainloop in the main thread of the application. See the answer I added here for more info, or go straight to the resolved issue about tkinter's thread safety on the Python bug tracker here. If the tkinter source and Python's bug tracker are correct, that would mean that as long as you run the mainloop in the main thread, you can happily call gui.equipTemp.set() directly from your temperature reading thread - no Queue required. And in my testing, that did indeed work just fine.
GUI toolkits are not threadsafe. You can only built and change your GUI from the main thread.
Since reading the temperature does not take that long, you can remove all the threading code and use the after-method from Tk.
Your read_temp_raw function is very complicated:
def read_temp_raw():
with open(device_file) as temp:
return temp.read().split('\n')

Categories

Resources