Python: Can't pop from an empty list - python

I am creating a python program to detect and enable usb to usb data transfer between usb storage drives. However I am having an issue with updating the dev_label (device name of the drive) and passing it to Exchange. Here is the code :
serial_list=[]
context = Context()
monitor = Monitor.from_netlink(context)
monitor.filter_by(subsystem='block',device_type='partition')
observer = GUDevMonitorObserver(monitor)
def device_connected(observer, device):
Welcome.device_count+=1
flag =False
for iden in serial_list :
if iden == device.__getitem__('ID_SERIAL_SHORT'):
flag=True
if flag ==False:
serial_list.append(device.__getitem__('ID_SERIAL_SHORT'))
Welcome.dev_label.append(str(device.__getitem__('ID_FS_LABEL')))
size = len(Welcome.dev_label)
label = gtk.Label('Device connected :: {0!r}'.format(Welcome.dev_label[size-1]))
Welcome.vbox.pack_start(label)
Welcome.window.show_all()
if Welcome.device_count<2:
label = gtk.Label('Connect the second device')
Welcome.vbox.pack_start(label)
Welcome.window.show_all()
else :
Exchange()
observer.connect("device-added",device_connected)
monitor.start()
class Welcome:
device_count = 0
window = gtk.Window()
vbox= gtk.VBox(False, 5)
dev_label = []
def __init__(self):
self.window.set_default_size(300, 300)
self.window.set_title("Welcome")
label = gtk.Label("Connect the desired device")
self.vbox.pack_start(label)
self.window.add(self.vbox)
self.window.connect("destroy", lambda q: gtk.main_quit())
self.window.show_all()
class Exchange:
window1 = gtk.Window(Welcome.dev_label.pop())
window2 = gtk.Window(Welcome.dev_label.pop())
def __init__(self):
width = gtk.gdk.screen_get_default().get_width()
height = gtk.gdk.screen_get_default().get_height()
self.window1.resize(width/2,height)
self.window2.resize(width/2,height)
self.window2.move(self.window1.get_position()[0]+width/2, self.window1.get_position()[1])
label = gtk.Label("Hello")
self.window1.add(label)
self.window1.connect("destroy" , lambda q : gtk.main_quit())
self.window1.show_all()
label = gtk.Label("World")
self.window2.add(label)
self.window2.connect("destroy",lambda q : gtk.main_quit())
self.window2.show_all()
Welcome()
gtk.main()
The error shown in the trace back is :
Traceback (most recent call last):
File "project.py", line 70, in <module>
class Exchange:
File "project.py", line 71, in Exchange
window1 = gtk.Window(Welcome.dev_label.pop())
IndexError: pop from empty list
I can't figure out how to synchronize all these event so that the compiler doesn't throw an error. Values are being popped from Welcome.dev_label only after they've been updated in device_connected so why does the compiler have a problem? I am a python newbie so please be gentle.

This is not the compiler givin errors but the program.
You can change your class to this:
import time
class Exchange:
while not Welcome.dev_label: time.sleep(0.001)
window1 = gtk.Window(Welcome.dev_label.pop()) # line 4
while not Welcome.dev_label: time.sleep(0.001)
window2 = gtk.Window(Welcome.dev_label.pop())
This would be kind of a synchronization primitive given that only line 4 and 6 remove content.
In general you would use a queue for this.
import queue # Python3 # import Queue Python 2
Welcome.dev_label # = queue.Queue()
Welcome.dev_label.put(...) # instead of append
Welcome.dev_label.get(...) # instead of pop
But I do not know wether your code uses threads and runs in parallel. If the time.sleep example works then you can switch to a queue.

Related

I can't pass data from thread to GUI JLabel

if name == 'main':
demo = DesktopFrame()
demo.setLocation(30, 30)
demo.show()
comm = "COM4"
global comPort
comPort = MyCommPort()
comPort.set_port(comm)
poll = Polling(comPort)
poll.start()
poll.join()
This code runs (as def), My GUI comes up and allows me to change the frame content from the menu while the thread POLL sends and receives data from my serial port (fixed test loop of 20 times right now)
**GOAL:
My goal is to have the thread update a JLabel (make it change node address) as it goes through the polling sequence.**
I've been following the jthon-swingutils2.1.1 documentation
https://pythonhosted.org/jython-swingutils/threads.html
-- Running GUI code from background threads.
from swingutils.threads.swing import callSwing
def fillInExchangeRate():
rate = fetchExchangeRate('USD', 'EUR')
callSwing(rateField.setValue, rate)
PROBLEM:
Method pollUpdate(self, address)
I can't discover the correct way for callSwing() to find my GUI JLabel [pollText] I get the following error message.
Exception in thread Thread-1:Traceback (most recent call last):
File "C:\jython2.7.0\Lib\threading.py", line 222, in _Thread__bootstrap self.run()
File "C:\Users\jwkel\Documents\NetBeansProjects\JythonProject\src\mycommport.py", line 95, in run self.xmit_data(self.toOutput)
File "C:\Users\jwkel\Documents\NetBeansProjects\JythonProject\src\mycommport.py", line 71, in xmit_data self.pollUpdate("Polling A")
File "C:\Users\jwkel\Documents\NetBeansProjects\JythonProject\src\mycommport.py", line 66, in pollUpdate callSwing(pollText.setValue, address)
NameError: global name 'pollText' is not defined
now for some general information about my GUI
demo = JFrame
self.menubar = JMenuBar() + 5 JMenu() + 15 JMenuItem() // all working correctly
self.add(self.mainPanel, BorderLayout.EAST)
contains:
self.mainPanel = JPanel()
self.mainText = JTextArea()
self.mainScroll = JScrollPane(self.mainText) +
self.mainScroll.getViewport().setView((self.mainText))
self.mainPanel = JPanel() self.mainPanel.add(self.mainScroll)
self.add(self.modePanel, BorderLayout.NORTH)
contains:
self.modePanel = JPanel()
self.modeText = JLabel("Current Mode Setting : Stand Alone")
self.pollText = JLabel("Polling Reader - ?") ******* MY TARGET ********
self.add(self.panel, BorderLayout.WEST)
contains:
self.panel = JPanel()
self.panel.add(JLabel(ImageIcon(self.showImage)))
problem code
from swingutils.threads.swing import callSwing
class Polling(Thread):
def __init__(self, pollPort):
Thread.__init__(self)
self._pollPort= pollPort
def pollUpdate(self, address):
callSwing(pollText.setValue, address)
def xmit_data(self,sendThis):
'''commands and data sent to readers'''
print 'send this ',sendThis
self.pollUpdate("Polling A")
self._pollPort.outStream.write(sendThis)
time.sleep(0.3)
self._pollPort.outStream.flush()
def recv_data(self):
'''relies and requests from readers'''
print 'recev data'
self.pollUpdate("Polling #")
self.s =''
self.text = ''
for num in range(100):
self.s = self._pollPort.inStream.read()
if self.s == 10:
break
else:
self.text = self.text + chr(self.s)
print 'text = ', self.text
return self.text
def run(self):
for i in range(20):
time.sleep(.100)
self.toOutput="*ATest\n"
self.xmit_data(self.toOutput)
time.sleep(.300)
self.myText = self.recv_data()
print self.myText
I found a better example to follow and rewrote the code to use a java Class SwingWorker. All is well!

Reading higher frequency data in thread and plotting graph in real-time with Tkinter

In the last couple of weeks, I've been trying to make an application that can read EEG data from OpenBCI Cyton (#250Hz) and plot a graph in 'real-time'. What seems to work better here are threads. I applied the tips I found here 1 to communicate the thread with Tkinter, but the application still doesn't work (gives me the error RecursionError: maximum recursion depth exceeded while calling a Python object). Maybe I'm doing something wrong because I'm trying to use multiple .py files? See below the main parts of my code and a few more comments in context:
###FILE main.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from AppWindow import *
window = AppWindow()
window.start()
###FILE AppWindow.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import tkinter as tk
from tkinter import ttk
from tkinter.scrolledtext import ScrolledText
import scroller as scrl
import logging
import requests
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import random
from pandas import DataFrame
import stream_lsl_eeg as leeg
#Definitions
H = 720
W = 1280
#Color palette>> https://www.color-hex.com/color-palette/92077
bg_color = "#c4ac93"
sc_color = "#bba58e"
tx_color = "#313843"
dt_color = "#987f62"
wn_color = "#6b553b"
class AppWindow:
#Other Functions
def plotGraph(self, x, y):
self.ax.clear()
self.ax.plot(x,y, color = tx_color)
plt.subplots_adjust(bottom=0.31, left=0.136, top=0.9, right=0.99)
plt.ylabel('Magnitude', fontsize = 9, color = tx_color)
plt.xlabel('Freq', fontsize = 9, color = tx_color)
self.figure.canvas.draw()
def __init__(self):
self.root = tk.Tk() #start of application
self.root.wm_title("Hybrid BCI - SSVEP and Eye Tracker")
#Other Graphical Elements
#Button that calls function
self.btn_ReceiveEEG = tk.Button(self.EEG_frame, text = "Receive EEG signal", bg = bg_color, fg = tx_color, state = tk.DISABLED, command = lambda: leeg.getEEGstream(self))
self.btn_ReceiveEEG.place(anchor = 'nw', relx = 0.52, rely = 0.5, width = 196, height = 40)
#Other Graphical Elements
def start(self):
self.root.mainloop() #end of application
### FILE stream_lsl_eeg.py
from pylsl import StreamInlet, resolve_stream
import tkinter as tk
import AppWindow as app
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import threading
import queue
import time
class myThread(threading.Thread):
def __init__(self, name, q, f):
threading.Thread.__init__(self)
self.name = name
self.q = q
self.f = f
def run(self):
print("Starting ", self.name)
pullSamples(self.q, self.f) #place where function is called
def getInlet(app): #this is triggered by another button and it's working fine
global inlet
app.logger.warn('Looking for an EEG strean...')
streams = resolve_stream('type', 'EEG')
inlet = StreamInlet(streams[0])
app.logger.warn('Connected')
app.btn_ReceiveEEG.config(state = tk.NORMAL)
def pullSamples(q):
i = 0
while i<1000:
sample, timestamp = inlet.pull_sample()
threadLock.acquire() #thread locks to put info in the queue
q.put([sample,timestamp]) #data is put in the queue for other threads to access
threadLock.release() #thread unlocks after info is in
i += 1
stopcollecting = 1
print("Exit flag on")
def plotSamples(app, kounter): #Outside Thread
if not stopcollecting: #testing if stream reception stopped
while dataqueue.qsize( ):
try:
kounter += 1
sample, timestamp = dataqueue.get(0)
samples.append(sample[0]) #getting just channel 1 (0)
timestamps.append(timestamp)
show_samples = samples[-250:]
show_timestamps = timestamps[-250:]
app.plotGraph(show_timestamps,show_samples)
print(counter) #this was just a control to count if the right amount of samples was coming out of the queue
except dataqueue.Empty:
pass #still not implemented, but will return to the main application
app.root.after(60, plotSamples(flag,app,kounter)) #60 chosen because plot should update every 15 samples (15/250 = 0,06s)
def getEEGstream(app): #function called by button
app.logger.warn('Starting thread...')
#
kounter = 0
start = time.perf_counter()
thread1.start()
##
plotSamples(flag, app, kounter)
##
thread1.join() #I don't know if I need this...
finish = time.perf_counter()
#
print(f'Sizes: Samples [{len(samples)}, {len(samples[0])}], {len(timestamps)} timestamps')
print(f'Sucessfully streamed in {round(finish-start,3)}s!')
###
threadLock = threading.Lock()
dataqueue = queue.Queue()
stopcollecting = 0
kounter = []
flag = queue.Queue() #secondary queue for flags not used at the moment
flag.put(0)
thread1 = myThread("Thread-1", dataqueue,flag)
samples,timestamps = [],[]
show_samples, show_timestamps = [],[]
As I found here 2, a function should not call itself, but it's basically what here 1 does. Also, I don't think I'm calling root.mainloop() multiple times like done in here 3.
After executing, python gives me the following error/output:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\robotics\AppData\Local\Continuum\anaconda3\envs\psychopy\lib\tkinter\__init__.py", line 1705, in __call__
return self.func(*args)
File "C:\Users\robotics\Documents\gitDocuments\SSVEP_EyeGaze_py\AppWindow.py", line 109, in <lambda>
self.btn_ReceiveEEG = tk.Button(self.EEG_frame, text = "Receive EEG signal", bg = bg_color, fg = tx_color, state = tk.DISABLED, command = lambda: leeg.getEEGstream(self))
File "C:\Users\robotics\Documents\gitDocuments\SSVEP_EyeGaze_py\stream_lsl_eeg.py", line 118, in getEEGstream
plotSamples(flag, app, kounter)
File "C:\Users\robotics\Documents\gitDocuments\SSVEP_EyeGaze_py\stream_lsl_eeg.py", line 104, in plotSamples
app.root.after(60, plotSamples(flag,app,kounter))
File "C:\Users\robotics\Documents\gitDocuments\SSVEP_EyeGaze_py\stream_lsl_eeg.py", line 104, in plotSamples
app.root.after(60, plotSamples(flag,app,kounter))
File "C:\Users\robotics\Documents\gitDocuments\SSVEP_EyeGaze_py\stream_lsl_eeg.py", line 104, in plotSamples
app.root.after(60, plotSamples(flag,app,kounter))
[Previous line repeated 986 more times]
File "C:\Users\robotics\Documents\gitDocuments\SSVEP_EyeGaze_py\stream_lsl_eeg.py", line 92, in plotSamples
while dataqueue.qsize( ): # if not dataqueue.empty():
File "C:\Users\robotics\AppData\Local\Continuum\anaconda3\envs\psychopy\lib\queue.py", line 87, in qsize
with self.mutex:
RecursionError: maximum recursion depth exceeded while calling a Python object
Exit flag on
This means the thread is being successfully executed, apparently, but the plotSamples() is crashing.
Any advice??
after() (similar to button's command= and bind()) needs function's name without () and without argument - it is called callback - and after sends it to mainloop and mainloop later uses () to run it.
You use function with ()
app.root.after(60, plotSamples(flag,app,kounter))
so it runs it at once (it doesn't send it to mainloop) and this function runs at once again the same function which runs at once the same function, etc. - so you create recursion.
It works like
result = plotSamples(flag,app,kounter) # run at once
app.root.after(60, result)
If you have to use function with arguments then you can do
app.root.after(60, plotSamples, flag, app, kounter)
Eventually you can use lambda to create function without argument
app.root.after(60, lambda:plotSamples(flag,app,kounter) )

Open printer selection window in Tkinter

I am having a problem possibly due to my lack of knowledge. I want to open the windows dialog to choose printer and send to print (to a printer) using Tkinter.
Currently, I use a code to relate Tkinter to Wxpython and make it asynchronous creating a separate process.
This is the code mentioned above:
from tkinter import *
from threading import Thread
import wx
def f_imprimir(ventana, entry):
class TextDocPrintout(wx.Printout):
def __init__(self):
wx.Printout.__init__(self)
def OnPrintPage(self, page):
dc = self.GetDC()
ppiPrinterX, ppiPrinterY = self.GetPPIPrinter()
ppiScreenX, ppiScreenY = self.GetPPIScreen()
logScale = float(ppiPrinterX)/float(ppiScreenX)
pw, ph = self.GetPageSizePixels()
dw, dh = dc.GetSize()
scale = logScale * float(dw)/float(pw)
dc.SetUserScale(scale, scale)
logUnitsMM = float(ppiPrinterX)/(logScale*25.4)
### Print code ###
return True
class PrintFrameworkSample(wx.Frame):
def OnPrint(self):
pdata = wx.PrintData()
pdata.SetPaperId(wx.PAPER_A4)
pdata.SetOrientation(wx.LANDSCAPE)
data = wx.PrintDialogData(pdata)
printer = wx.Printer(data)
printout = TextDocPrintout()
useSetupDialog = True
if not printer.Print(self, printout, useSetupDialog) and printer.GetLastError() ==
wx.PRINTER_ERROR:
wx.MessageBox(
"There was a problem printing.\n"
"Perhaps your current printer is not set correctly?",
"Printing Error", wx.OK)
else:
data = printer.GetPrintDialogData()
pdata = wx.PrintData(data.GetPrintData()) # force a copy
printout.Destroy()
self.Destroy()
app=wx.App(False)
PrintFrameworkSample().OnPrint()
entry.config(state="normal")
def process(ventana, entry):
entry.config(state="disable")
t = Thread(target=f_imprimir, args=(ventana,entry))
t.start()
v = Tk()
entry = Entry(v)
entry.pack()
v.bind("a", lambda a:process(v,entry))
when wx.app finishes, which can happen when the Printer Selector closes, I plan to change the status of the entry to "normal".
But it throws an error when changing the state of the entry to "normal", which I suppose is because the window and the order I send are in separate processes. The error would be:
Exception in thread Thread-1:
Traceback (most recent call last):
File "C:\Python38-32\lib\threading.py", line 932, in _bootstrap_inner
self.run()
File "C:\Python38-32\lib\threading.py", line 870, in run
self._target(*self._args, **self._kwargs)
File "C:\Users\DANTE\Google Drive\JNAAB\DESARROLLO\pruebas\pedasito.py", line 65, in f_imprimir
entry.config(state="normal")
File "C:\Python38-32\lib\tkinter\__init__.py", line 1637, in configure
return self._configure('configure', cnf, kw)
File "C:\Python38-32\lib\tkinter\__init__.py", line 1627, in _configure
self.tk.call(_flatten((self._w, cmd)) + self._options(cnf))
RuntimeError: main thread is not in main loop
Does anyone have a solution for this problem or an alternative way to create the print window without blocking the TCL window and being able to block the entry? If there is a way to do it or send to print using Tkinter and avoid this mess it would be even better. Thank you.
I dont use wxPython however your error is due to a threading issue related to tkinter. Tkinter likes to be in the main thread and trying to pass widgets to a seperate thread can cause problems. However your entry field is in the global namespace already so you do not need to pass it.
Simply updated from your thread once it needs to be.
I would do this in your if/else condition so it only happens at the correct time.
Something like this would work:
Note you will need to actually do something with the value passed. As it is now none of your code actually prints anything other than a blank page.
import tkinter as tk
from threading import Thread
import wx
def f_imprimir(value):
# here you can see the value of entry was passed as a string so we can avoid any issues with the widget
print(value)
class TextDocPrintout(wx.Printout):
def __init__(self):
wx.Printout.__init__(self)
def OnPrintPage(self, page):
dc = self.GetDC()
ppiPrinterX, ppiPrinterY = self.GetPPIPrinter()
ppiScreenX, ppiScreenY = self.GetPPIScreen()
logScale = float(ppiPrinterX)/float(ppiScreenX)
pw, ph = self.GetPageSizePixels()
dw, dh = dc.GetSize()
scale = logScale * float(dw)/float(pw)
dc.SetUserScale(scale, scale)
logUnitsMM = float(ppiPrinterX)/(logScale*25.4)
return True
class PrintFrameworkSample(wx.Frame):
def OnPrint(self):
pdata = wx.PrintData()
pdata.SetPaperId(wx.PAPER_A4)
pdata.SetOrientation(wx.LANDSCAPE)
data = wx.PrintDialogData(pdata)
printer = wx.Printer(data)
printout = TextDocPrintout()
useSetupDialog = True
if not printer.Print(self, printout, useSetupDialog) and printer.GetLastError() == wx.PRINTER_ERROR:
wx.MessageBox("There was a problem printing.\n\n"
"Perhaps your current printer is not set correctly?\n\n"
"Printing Error", wx.OK)
entry.config(state="normal")
else:
data = printer.GetPrintDialogData()
pdata = wx.PrintData(data.GetPrintData()) # force a copy
entry.config(state="normal")
printout.Destroy()
self.Destroy()
app = wx.App(False)
PrintFrameworkSample().OnPrint()
def process(_=None):
entry.config(state="disable")
t = Thread(target=f_imprimir, args=(entry.get(),))
t.start()
v = tk.Tk()
entry = tk.Entry(v)
entry.pack()
v.bind("<Return>", process)
v.mainloop()

RuntimeError: deleteUI: Object 'Animation_Copy_Tool' not found

Getting this error when pressing the "Close Window" button in my UI. This button should delete the UI window but isn't. Full traceback:
Error: deleteUI: Object 'Animation_Copy_Tool' not found.
Traceback (most recent call last):
File "", line 36, in closeBtnCmd
RuntimeError: deleteUI: Object 'Animation_Copy_Tool' not found. #
# Animation Copy Tool
# Bakari Holmes 5/7/2015
# This is designed to copy and existing animation
# from one rig to another and make the process easier
# with a simple UI
import maya.cmds as mc
import functools
import maya.mel as mm
import pprint
class AnimCopyWindow(object):
##classmethod
def showUI(cls):
win = cls()
win.create()
return win
def __init__(self):
self.window = "Animation Copy Tool"
self.title = "Animation Copier"
self.size = (546,350)
def pasteTheseKeys(self, *args):
self.offsetVal = mc.intFieldGrp(self.int_offset, q=True, value1=True)
self.selObj_pasteKeys = mc.ls(sl=True)
for objectQuant in self.selObj_pasteKeys:
print objectQuant
self.ct = mc.currentTime(query = True)
self.t = self.ct + self.offsetVal
mc.currentTime(self.t)
# mc.selectKey(selObj_pasteKeys[objectQuant])
mc.pasteKey(time=(self.t,self.t), f=(1.0,1.0), option="merge", copies=1, to=0, fo=0, vo=0)
def closeBtnCmd(self,*args):
mc.deleteUI(self.window,window=True)
def create(self):
# check to see if window exists already
if mc.window(self.window,exists=True):
mc.deleteUI(self.window,window=True)
self.window = mc.window(self.window, title=self.title,widthHeight=self.size,menuBar=True)
self.copyAnim = mc.window(title="Transfer Animation Tool", backgroundColor=[0.3,0.3,0.3],sizeable=False,resizeToFitChildren=True)
#set the layout for UI
mc.columnLayout(adjustableColumn=True)
self.tx_src = mc.textFieldGrp(label="Source Object", editable=False, text=sel[0])
self.int_offset = mc.intFieldGrp(label="Frame Offset Amount", value1=0)
#add paste animation button
self.btn1 = mc.button(label="PASTE ANIMATION", command=self.pasteTheseKeys, bgc=[0.1,0.1,0.5])
#add close button window
self.btn2 = mc.button(label="CLOSE WINDOW", command=self.closeBtnCmd, bgc=[0.2,0.2,0.2])
mc.showWindow()
#################################
#####end of class definition#####
#################################
def keys_as_dictionary(channel):
"""return a dictionay of times:values for <channel>"""
keys = mc.keyframe(channel, q=True, tc=True) or []
values = mc.keyframe(channel, q=True, vc=True) or []
return dict(zip(keys, values))
def channels():
"""return a dictionary of <plug>:<channel_dict> for each animated plug selected"""
keys = mc.keyframe(sl=True, n=True, q=True)
result = {}
for k in keys:
plugs = mc.listConnections(k, p=True)[0]
result[plugs]= keys_as_dictionary(k)
return result
#store selected object info
sel = mc.ls(selection=True)
if (len(sel) != 1):
mm.eval("warning Must select one animated object;")
else:
mc.copyKey()
win = AnimCopyWindow()
win.create()
pprint.pprint(channels())
This error almost always means your UI element is not named what you think it is: Maya will automatically rename the items to make sure that no two siblings have the same name -- you can ask for "my_window" and get back "my_window123" . So you need to capture the actual name that is returned from cmds.window() or whatever ui command you use and delete that. Hard coded names are never reliable

Tkinter window not playing well with threads

I've got a program that will eventually receive data from an external source over serial, but I'm trying to develop the display-side first.
I've got this "main" module that has the simulated data send and receive. It updates a global that is used by a Matplotlib stripchart. All of this works.
#-------------------------------------------------------------------------------
# Name: BBQData
# Purpose: Gets the data from the Arduino, and runs the threads.
#-------------------------------------------------------------------------------
import time
import math
import random
from threading import Thread
import my_globals as bbq
import sys
import BBQStripChart as sc
import serial
import BBQControl as control
ser = serial.serial_for_url('loop://', timeout=10)
def simData():
newTime = time.time()
if not hasattr(simData, "lastUpdate"):
simData.lastUpdate = newTime # it doesn't exist yet, so initialize it
simData.firstTime = newTime # it doesn't exist yet, so initialize it
if newTime > simData.lastUpdate:
simData.lastUpdate = newTime
return (140 + 0.05*(simData.lastUpdate - simData.firstTime), \
145 + 0.022*(simData.lastUpdate - simData.firstTime), \
210 + random.randrange(-10, 10))
else:
return None
def serialDataPump():
testCtr = 0;
while not bbq.closing and testCtr<100:
newData = simData()
if newData != None:
reportStr = "D " + "".join(['{:3.0f} ' for x in newData]) + '\n'
reportStr = reportStr.format(*newData)
ser.write(bytes(reportStr, 'ascii'))
testCtr+=1
time.sleep(1)
bbq.closing = True
def serialDataRcv():
while not bbq.closing:
line = ser.readline()
rcvdTime = time.time()
temps = str(line, 'ascii').split(" ")
temps = temps[1:-1]
for j, x in enumerate(temps):
bbq.temps[j].append(float(x))
bbq.plotTimes.append(rcvdTime)
def main():
sendThread = Thread(target = serialDataPump)
receiveThread = Thread(target = serialDataRcv)
sendThread.start()
receiveThread.start()
# sc.runUI()
control.runControl() #blocks until user closes window
bbq.closing = True
time.sleep(2)
exit()
if __name__ == '__main__':
main()
## testSerMain()
However, I'd like to add a SEPARATE tkinter window that just has the most recent data on it, a close button, etc. I can get that window to come up, and show data initially, but none of the other threads run. (and nothing works when I try to run the window and the plot at the same time.)
#-------------------------------------------------------------------------------
# Name: BBQ Display/Control
# Purpose: displays current temp data, and control options
#-------------------------------------------------------------------------------
import tkinter as tk
import tkinter.font
import my_globals as bbq
import threading
fontSize = 78
class BBQControl(tk.Tk):
def __init__(self,parent):
tk.Tk.__init__(self,parent)
self.parent = parent
self.labelFont = tkinter.font.Font(family='Helvetica', size=int(fontSize*0.8))
self.dataFont = tkinter.font.Font(family='Helvetica', size=fontSize, weight = 'bold')
self.makeWindow()
def makeWindow(self):
self.grid()
btnClose = tk.Button(self,text=u"Close")
btnClose.grid(column=1,row=5)
lblFood = tk.Label(self,anchor=tk.CENTER, text="Food Temps", \
font = self.labelFont)
lblFood.grid(column=0,row=0)
lblPit = tk.Label(self,anchor=tk.CENTER, text="Pit Temps", \
font = self.labelFont)
lblPit.grid(column=1,row=0)
self.food1Temp = tk.StringVar()
lblFoodTemp1 = tk.Label(self,anchor=tk.E, \
textvariable=self.food1Temp, font = self.dataFont)
lblFoodTemp1.grid(column=0,row=1)
#spawn thread to update temps
updateThread = threading.Thread(target = self.updateLoop)
updateThread.start()
def updateLoop(self):
self.food1Temp.set(str(bbq.temps[1][-1]))
def runControl():
app = BBQControl(None)
app.title('BBQ Display')
app.after(0, app.updateLoop)
app.mainloop()
bbq.closing = True
if __name__ == '__main__':
runControl()
Your title sums up the problem nicely: Tkinter doesn't play well with threads. That's not a question, that's the answer.
You can only access tkinter widgets from the same thread that created the widgets. If you want to use threads, you'll need your non-gui threads to put data on a queue and have the gui thread poll the queue periodically.
One way of getting tkinter to play well with threads is to modify the library so all method calls run on a single thread. Two other questions deal with this same problem: Updating a TKinter GUI from a multiprocessing calculation and Python GUI is not responding while thread is executing. In turn, the given answers point to several modules that help to solve the problem you are facing. Whenever I work with tkinter, I always use the safetkinter module in case threads appear to be helpful in the program.

Categories