use*_*752 6 python multithreading raspberry-pi
中年爸爸(电气工程师不是贸易程序员)试图教我13岁的女儿电子和编程.到目前为止,我喜欢Python.我正在构建一个程序,通过tkinter GUI和DS18B20传感器显示整个房屋的温度.
我们将下面的程序拼凑起来,从阅读书籍,在线研究和使用Stack Overflow来解决问题(这个网站摇滚!).
现在我们感到难过,我们不断出现间歇性错误,当我们在Raspberry上加载空闲后第一次运行程序时,它运行正常.
第二次,以及随后的所有时间,我们收到此错误消息:
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
Run Code Online (Sandbox Code Playgroud)
注意,我们的理解是,为了获得一个静态窗口并更新标签更新的温度读取我们的传感器(DS18B20),我们需要使用一个线程.我们开始的示例代码的_init_语句只有一个下划线在前面和后面 - 不知道为什么,如果我添加第二个下划线,我会收到错误消息.我们用作基础的更新窗口代码来自Raspberry Pi论坛
这是我们的代码:
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 ######
Run Code Online (Sandbox Code Playgroud)
您需要在主线程中运行GUI代码,并且您的温度读取代码需要在后台线程中。只有在主线程中更新 GUI 才是安全的,因此您可以通过 a 将从后台线程读取的温度数据传递回主线程Queue,并让主线程使用以下命令定期检查队列中的数据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()
Run Code Online (Sandbox Code Playgroud)
编辑:
在实际查看了 tkinter 源代码以及 Python bug 跟踪器之后,似乎与几乎所有其他 GUI 库不同,tkinter旨在是线程安全的,只要您在主线程中运行主循环的应用程序。请参阅我在此处添加的答案以获取更多信息,或者直接转到此处的Python 错误跟踪器上有关 tkinter 线程安全性的已解决问题。如果 tkinter 源代码和 Python 的错误跟踪器是正确的,这意味着只要您在主线程中运行主循环,您就可以愉快地gui.equipTemp.set()直接从温度读取线程调用 - 不需要Queue。在我的测试中,这确实工作得很好。