How to get Tkinter GUI working for serial data - python

Im trying to display serial data in a simple gui. The serial data is dynamic (temperature sensor).
This is the code which I wrote for the GUI in tkinter, but its throwing error for line number 9.
from time import sleep
import threading
import serial
from tkinter import *
serialdata = []
data = True
class SensorThread(threading.Thread):
def run(self):
seri_=serial.Serial('/dev/ttyACM0', 9600)
try:
i = 0
while True:
serialdata.append(seri_.readline())
i += 1
sleep(1)
except KeyboardInterrupt:
exit()
class Gui(object):
def __init__(self):
self.root = Tk()
self.lbl = Label(self.root, text="")
self.updateGUI()
self.readSensor()
def run(self):
self.lbl.pack()
self.lbl.after(1000, self.updateGUI)
self.root.mainloop()
def updateGUI(self):
msg = "Data is True" if data else "Data is False"
self.lbl["text"] = msg
self.root.update()
self.lbl.after(1000, self.updateGUI)
def readSensor(self):
self.lbl["text"] = serialdata[-1]
self.root.update()
self.root.after(527, self.readSensor)
if __name__ == "__main__":
SensorThread().start()
Gui().run()
While running this code Im getting this error. Can anyone please help resolve it :-
Traceback (most recent call last):
File "ts.py", line 39, in <module> Gui().run() File "ts.py", line 23, in __init__ self.readSensor() File "ts.py", line 34, in readSensor self.lbl["text"] = serialdata[0] IndexError: list index out of range

The following example will make is work. Just added a try except IndexError to make sure serialdata is not empty. I replaced the SensorThread with a function that adds a random int between 0 and 100 to the serialdata every one second.
import random
from time import sleep
import threading
from tkinter import *
serialdata = []
data = True
class SensorThread(threading.Thread):
def run(self):
while True:
sleep(1)
serialdata.append(random.randint(0, 100))
class Gui(object):
def __init__(self):
self.root = Tk()
self.lbl = Label(self.root, text="")
self.updateGUI()
self.readSensor()
def run(self):
self.lbl.pack()
self.lbl.after(1000, self.updateGUI)
self.root.mainloop()
def updateGUI(self):
msg = "Data is True" if data else "Data is False"
self.lbl["text"] = msg
self.root.update()
self.lbl.after(1000, self.updateGUI)
def readSensor(self):
try:
self.lbl["text"] = serialdata[-1]
except IndexError:
pass
self.root.update()
self.root.after(527, self.readSensor)
if __name__ == "__main__":
SensorThread().start()
Gui().run()

Related

python thread data manipulation

I am trying to manipulate some data in a thread from the main function. The issue I am facing about modifying some of the variables which are part of the function which is running on a thread.
So I am trying to run a Tkinter based GUI loop in a thread to ensure it is always running. And want to modify some of the label corresponding to the status in the main function execution. I am facing an issue where it is unable to locate the label variables in the main loop as it is part of the function running on the thread.
Below is a simplified psuedo code for that approach. Please suggest if this a correct way to the above task or is there is any better and efficient way.
import threading
def thread_func():
i = 0
while True:
print('i from thread: ', i)
if __name__ == '__main__':
t = threading.Thread(target=thread_func)
t.start()
while True:
i += 1
Actual scaled down simplified code
import threading
import tkinter as tk
def gui():
window = tk.Tk()
label = tk.Label(text='On')
label.pack()
window.mainloop()
if __name__ == '__main__':
t = threading.Thread(target=gui)
t.start()
while True:
label['text'] = 'Active'
Error:
Traceback (most recent call last):
File "test.py", line 17, in <module>
label['text'] = 'Active'
NameError: name 'label' is not defined
Is there a better way to keep the tkinter gui always on and perform some task in the loop?
Using class and threading:
import tkinter
class Test(tkinter.Label):
x = False
def __init__(self):
super().__init__()
self.pack()
self['text'] = 'On'
def gui(self):
if not self.x:
self['text'] = 'Active'
self.mainloop()
else:
self.mainloop()
if __name__ == '__main__':
label = Test()
while isinstance(label, Test):
t = threading.Thread(target=label.gui())
t.start()
When you write code of label there then you will get error because when program start it starts from creating thread, that thread will only end when tkinter window is close and same for previous thread_fuc code. And you wrote the label code after tkinter window is closed.
The above issue will be solved by doing this :
import threading
def thread_func():
while True:
print('i from thread: ', i)
def tt():
global i
while True:
i += 1
if __name__ == '__main__':
i=0
t = threading.Thread(target=thread_func)
t.start()
yt = threading.Thread(target=tt)
yt.start()
Making i global and running 2 function parallely. We have to global because we can't use the variable of one function to another. And additionally we are running 2 function in 2 thread.
And for your tkinter file as #TheLizzard suggest, you can use .after insted of using thread in tkinter if you want to change content constantly/want to use loop.
Here's the basic example how you can implement it:
import random
import tkinter as tk
app = tk.Tk()
app.geometry("200x220")
label = tk.Label(app, text="0")
label.pack()
def change(b=0):
if b < 30:
a = random.randrange(1, 7, 1)
label.config(text=a)
app.after(100, change, b+1)
b1 = tk.Button(app, text="Get New Number", command=change)
b1.pack()
app.mainloop()
For more explanation about it you may visit here.

Exit Python RFID Thread

I have started a small project and am currently stuck on a problem.
I have written a small GUI in Tkinter which opens another thread when the button is pressed in order to read the ID of an RFID chip. This is then returned to the GUI via a queue object and displayed there by the label. This works so far.
Now I would like to implement that the thread is terminated when no RFID chip has been detected for 10 seconds or the thread still exists after this time. I have tried to interrupt this with an event as follows but without success.
Does anyone have an idea how this could work?
Thanks already.
#!/usr/bin/env python3
import tkinter as tk
import queue
import threading
import time
import RPi.GPIO as GPIO
from mfrc522 import SimpleMFRC522
class GUI:
def __init__(self, master):
self.master = master
self.test_button = tk.Button(self.master, command=self.tb_click)
self.test_button.configure(text="Start", background="Grey", padx=50)
self.test_button.pack(side='top')
self.test_label = tk.Label(self.master, text="test")
self.test_label.pack(side='bottom')
def tb_click(self):
self.stop = threading.Event()
self.queue = queue.Queue( )
thread = Rfid(self.queue, self.stop)
thread.start()
self.master.after(100, self.process_queue)
self.master.after(10000, self.terminate)
def process_queue(self):
try:
msg = self.queue.get(0)
#print(str(msg))
self.test_label.configure(text=str(msg))
except queue.Empty:
self.master.after(100, self.process_queue)
def terminate(self):
if thread.is_alive():
self.stop.set()
class Rfid(threading.Thread):
def __init__(self, queue, stop):
threading.Thread.__init__(self)
self.queue = queue
self.stop = stop
def run(self):
while True:
if self.stop.isSet():
print("abgeschalten")
break
reader = SimpleMFRC522()
id, text = reader.read()
print(str(id))
self.queue.put(str(text))
GPIO.cleanup()
root = tk.Tk()
root.title("Test")
main_ui = GUI(root)
root.mainloop()

Thread doesn't log into tkinter window if it is in an imported file

I have a tkinter app running alongside two different threads that are logging into it using a queue.
One of the threads is in the same code file as the tkinter app. The other one is imported from another file, even thought their code is similar. What I verify is that only the thread defined in the same file manages to write into the UI. Do you know why this happens?
The code for the main file is:
import time
import queue
import threading
import tkinter as tk
from tkinter.scrolledtext import ScrolledText
from tkinter import ttk
import logging
from logging.handlers import QueueHandler
from foo import ImportedLoggerThread
logger = logging.getLogger(__name__)
class LoggerThread(threading.Thread):
def __init__(self):
super().__init__()
self._stop_event = threading.Event()
def run(self):
logger.debug('LoggerThread: running')
i = 0
while not self._stop_event.is_set():
logger.info("LoggerThread: iteration %d" % i)
i += 1
time.sleep(1)
def stop(self):
self._stop_event.set()
class LoggingWindow:
def __init__(self, frame):
self.frame = frame
self.scrolled_text = ScrolledText(frame, height=12)
self.scrolled_text.pack()
self.log_queue = queue.Queue()
self.queue_handler = QueueHandler(self.log_queue)
logger.addHandler(self.queue_handler)
# start polling
self.frame.after(100, self.poll_log_queue)
def write(self, record):
msg = self.queue_handler.format(record)
self.scrolled_text.insert(tk.END, msg + '\n')
# Scroll to the bottom
self.scrolled_text.yview(tk.END)
def poll_log_queue(self):
# Poll every 100ms
while True:
try:
record = self.log_queue.get(block=False)
except queue.Empty:
break
else:
self.write(record)
self.frame.after(100, self.poll_log_queue)
class App:
def __init__(self, root):
self.root = root
frame = ttk.Labelframe(text="Log")
frame.pack()
self.console = LoggingWindow(frame)
self.th = LoggerThread()
self.th.start()
self.imported = ImportedLoggerThread()
self.imported.start()
self.root.protocol('WM_DELETE_WINDOW', self.quit)
def quit(self):
self.th.stop()
self.imported.stop()
self.root.destroy()
def main():
logging.basicConfig(level=logging.DEBUG)
root = tk.Tk()
app = App(root)
app.root.mainloop()
if __name__ == '__main__':
main()
and for the second file foo.py:
import threading
import logging
import time
logger = logging.getLogger(__name__)
class ImportedLoggerThread(threading.Thread):
def __init__(self):
super().__init__()
self._stop_event = threading.Event()
def run(self):
logger.debug('Imported: running')
i = 0
while not self._stop_event.is_set():
logger.info("Imported: iteration %d" % i)
i += 1
time.sleep(2)
def stop(self):
self._stop_event.set()
Thanks in advance!
You define 2 logger instances in your files (logger = logging.getLogger(__name__)) and it causes your issue. If you use the same logger instance, it should work. It means in your case, you should pass the logger instance from your main file to the imported module (foo.py). Please see below the fixed foo.py and the fixed App class in the main file.
foo.py:
import threading
import time
class ImportedLoggerThread(threading.Thread):
def __init__(self, my_logger):
super().__init__()
self._stop_event = threading.Event()
self.my_logger = my_logger # Should be passed from caller side.
def run(self):
self.my_logger.debug('Imported: running')
i = 0
while not self._stop_event.is_set():
self.my_logger.info("Imported: iteration %d" % i)
i += 1
time.sleep(2)
def stop(self):
self._stop_event.set()
As you can see above the "imported" module uses a getting logger (It should comes from the "main" file)
App class:
class App:
def __init__(self, root):
self.root = root
frame = ttk.Labelframe(text="Log")
frame.pack()
self.console = LoggingWindow(frame)
self.th = LoggerThread()
self.th.start()
self.imported = ImportedLoggerThread(my_logger=logger) # Should be passed the defined logger instance.
self.imported.start()
self.root.protocol('WM_DELETE_WINDOW', self.quit)
def quit(self):
self.th.stop()
self.imported.stop()
self.root.destroy()
As you can see in the App class, the defined logger instance is passed to the imported ImportedLoggerThread class.
Output:
>>> python3 test.py
DEBUG:__main__:LoggerThread: running
DEBUG:__main__:Imported: running
INFO:__main__:LoggerThread: iteration 0
INFO:__main__:Imported: iteration 0
INFO:__main__:LoggerThread: iteration 1
INFO:__main__:Imported: iteration 1
INFO:__main__:LoggerThread: iteration 2
GUI:

Text widgets as List

I'm trying to create tabs with Text using tkinter Notebook widget and Text widget.
I have created list of tabs and Text widgets everything going as far as good except add_tabs method. Whenever I press control-n for adding new tabs only for first time i got this exception:
I have no idea how can i fix this problem thanks for helping me.
Thank you.
C:\Users\Imtiyaz\Desktop>python maintabtest.py
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Python34\lib\tkinter\__init__.py", line 1482, in __call__
return self.func(*args)
File "maintabtest.py", line 32, in add_tabs
self.nb.add(self._tabs[self.i],text="untitled")
IndexError: list index out of range
code:
import tkinter.ttk as ttks
from tkinter import BOTH,LEFT
class mainbody:
def __init__(self,master):
self.master = master
self._tabs = []
self._text = []
self.i = 1
self.body = ttks.Frame(self.master)
self.nb = ttks.Notebook(self.master)
self.nb.pack(fill=BOTH,expand=1)
self.body.pack(fill=BOTH,expand=1)
self.initial_tab = ttks.Frame(self.nb)
self.Inittext = ttks.tkinter.Text(self.initial_tab)
self.Inittext.pack(fill=BOTH,expand=1)
self.initial_tab.pack(fill=BOTH,expand=1)
self._text.append()
self.nb.add(self.initial_tab,text="first_tab")
self.File_name = ttks.Entry(self.master)
self.File_name.pack(side=LEFT)
self.sbtn = ttks.Button(self.master,text="save_btn",command=lambda:self.save_())
self.sbtn.pack(side=LEFT)
self.master.bind('<Control-n>',self.add_tabs)
def add_tabs(self,event):
self._tabs.append(ttks.Frame())
self.nb.add(self._tabs[self.i],text="untitled")
self._text.append(ttks.tkinter.Text(self._tabs[self.i]))
self._text[self.i].pack(fill=BOTH,expand=1)
self.i = self.i + 1
def save_(self):
self.fname = self.File_name.get()
self._txt_id = self.nb.index('current')
self.get_input = self._text[self._txt_id].get("1.0","end-1c")
with open(self.fname,'w') as f:
f.write(self.get_input)
if __name__ == "__main__":
root = ttks.tkinter.Tk()
mainbody(root)
root.mainloop()
Your problem is that self.i did not synchronize with the size of the 2 lists: self._tabs and self._text.
Basically you don't need the self.i to track the index to the last item of the 2 lists. Just use -1 instead of self.i in add_tabs() to refer the last item in the list as below:
import tkinter.ttk as ttks
from tkinter import BOTH,LEFT
from tkinter.messagebox import showinfo
class mainbody:
def __init__(self,master):
self.master = master
self._tabs = []
self._text = []
self.body = ttks.Frame(self.master)
self.nb = ttks.Notebook(self.master)
self.nb.pack(fill=BOTH, expand=1)
self.body.pack(fill=BOTH, expand=1)
self.add_tabs("first_tab") # add the initial tab
self.File_name = ttks.Entry(self.master)
self.File_name.pack(side=LEFT)
self.sbtn = ttks.Button(self.master, text="save_btn", command=self.save_file)
self.sbtn.pack(side=LEFT)
self.master.bind('<Control-n>', lambda e:self.add_tabs())
def add_tabs(self, name="untitled"):
self._tabs.append(ttks.Frame())
self.nb.add(self._tabs[-1], text=name)
self._text.append(ttks.tkinter.Text(self._tabs[-1]))
self._text[-1].pack(fill=BOTH, expand=1)
def save_file(self):
self.fname = self.File_name.get().strip()
if not self.fname == '':
self._txt_id = self.nb.index('current')
self.get_input = self._text[self._txt_id].get("1.0","end-1c")
with open(self.fname, 'w') as f:
f.write(self.get_input)
else:
showinfo('Warning', 'Please input filename')
if __name__ == "__main__":
root = ttks.tkinter.Tk()
mainbody(root)
root.mainloop()

Stop Threading when clossing the app

Can I close the DataCom thread before TKinter GUI exit? When I run the code into WING IDE closing the GUI the DataCom still running receiving packets from center. I tried to find something similar example none of them work.
from Tkinter import Tk, N, S, W, E, BOTH, Text, Frame,Label, Button,Checkbutton, IntVar,Entry
import socket
import sys
import os
import time
import datetime
from threading import Thread
data = ''
def timeStamped(fmt='%Y-%m-%d-%H-%M-%S_{fname}'):
return datetime.datetime.now().ctime()
def get_constants(prefix):
"""Create a dictionary mapping socket module constants to their names."""
return dict( (getattr(socket, n), n)
for n in dir(socket)
if n.startswith(prefix)
)
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.friend_check = IntVar()
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("Home.local")
self.PoolLabel = Label (text = "Pool Temperature:")
self.PoolLabel.grid(row=0,column=0,columnspan=2, sticky=W)
self.BasementLabel = Label (text = "Basement Conditions:")
self.BasementLabel.grid(row=3,column=0,columnspan=2, pady =10)
try:
datalist=data.split(':')
print datalist
self.cl_label=Label(text="")
self.cl_label.config(text=datalist[0].split(', ',1)[1])
self.cl_label.grid(row=1,column=0,columnspan=2,sticky='EW')
self.baselabelT=Label(text="")
self.baselabelT.config(text=datalist[1])
self.baselabelT.grid(row=4,column=0,columnspan=2,sticky='EW')
self.baselabelH=Label(text="")
self.baselabelH.config(text=datalist[2])
self.baselabelH.grid(row=5,column=0,columnspan=2,sticky='EW')
except IndexError:
datalist = 'null'
self.btn_Exit = Button(text="Exit", command = '')
self.btn_Exit.grid(row=10,column=2)
#self.update()
self.after(1000, self.initUI)
class DataCom(Thread):
def __init__(self, val):
Thread.__init__(self)
self.val = val
def run(self):
global data
families = get_constants('AF_')
types = get_constants('SOCK_')
protocols = get_constants('IPPROTO_')
# Create a TCP/IP socket
sock = socket.create_connection(('localhost', 10000))
print >>sys.stderr, 'Family :', families[sock.family]
print >>sys.stderr, 'Type :', types[sock.type]
print >>sys.stderr, 'Protocol:', protocols[sock.proto]
print >>sys.stderr
while True:
try:
# Send flag
message = 'INFO'
print >>sys.stderr, 'sending "%s" Length: %s' % (message,len(message))
sock.sendall(message)
amount_received = 0
amount_expected = len(message)
while amount_received < amount_expected:
data = sock.recv(1024)
amount_received += len(data)
if len(data) != 0:
print >>sys.stderr, 'Server received %s %s:Length %s' % (timeStamped(), data, len(data))
else:
sock.close()
print >>sys.stderr, 'No more data, closing socket'
break
if not data:
break
finally:
time.sleep(1)
def main():
myThread = DataCom(1)
myThread.setName('DataComThread')
myThread.start()
root = Tk()
root.geometry("600x450+900+300")
root.resizable(0,0)
app = Example(root)
root.config(background = "#FFFFFF") # ui debug
root.mainloop()
if __name__ == '__main__':
main()

Categories