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
...
Related
I am trying to create a python script that will trigger a tkinter window every time a certain event happens. The python script will have a while true loop and during the loop the tkinter event may or may not happen (if-else block). Right now the actual loop part isn't done, so I am currently testing the tkinter part but I can't seem to open more than tkinter window.
Below is the test script I am using.
from tkinter import *
from sys import exit
import os
onetwo = "C:/Users/I/Downloads/Transfer_Out_1016_Outlook.txt"
def popupError(s):
popupRoot = Tk()
##popupRoot.after(20000, exit)
popupButton = Button(popupRoot, text = s, font = ("Verdana", 12), bg = "yellow", command = lambda: os.system(onetwo))
popupButton.pack()
popupRoot.geometry('400x50+700+500')
popupRoot.mainloop()
popupError("HelloWORLD")
def popupTwo(s):
popupRoot = Tk()
##popupRoot.after(20000, exit)
popupButton = Button(popupRoot, text = s, font = ("Verdana", 12), bg = "yellow", command = lambda: os.system(onetwo))
popupButton.pack()
popupRoot.geometry('400x50+700+500')
popupRoot.mainloop()
popupTwo("HEWWWWWEWEWKOO")
I apologize for the lack of an actual piece of code but this is the best I can do right now given the dev status of the other parts of the overall python script.
Note that the tkinter window may be triggered more than once in a single loop session.
If any other details are needed, I'll try my best to add more in.
Here’s what you can do:
from tkinter import *
def popup(winName):
newWin = Toplevel()
btn2 = Button(newWin, text=winName)
btn2.pack()
root = Tk()
btn = Button(root, text=“Popup”, command=lambda: popup(“text”))
btn.pack()
root.mainloop()
I am using NI instrument to read data and display it on GUI. Have used tkinter. But could not find a way to update the data using while loop.
import nidaqmx
import time
from tkinter import *
master = Tk()
while True:
with nidaqmx.Task() as task:
task.ai_channels.add_ai_voltage_chan("Dev1/ai0")
print('1 Channel 1 Sample Read: ')
data = task.read()
sensor_value ='%.2f' % data #said sensor value
master.minsize(width=400, height=400)
w = Label(master, text=sensor_value) #shows as text in the window
w.pack() #organizes widgets in blocks before placing them in the parent.
time.sleep(5)
mainloop()
When working with Tkinter we should avoid Threading, while loop with root.update() it is not like we can't use them but not advisable instead use after(delay_ms, callback=None, *args) method provided by Tkinter itself for a reason.
Now to your code, there are few issues in your code.
In while loop you are creating a Label every 5 secs instead create one label and update the value of that label inside the loop with w['text'] = '...' or w.configure(text='...').
Don't put mainloop inside the loop, instead call it in the last line with the instance of the main window in your case master (master.mainloop()).
Same with master.minsize(width=400, height=400), you don't have to tell the master window every 5 sec to set the minimum size to 400x400 it should be called once if not decide to change the minimum size to different geomentry.
Your code should look like this.
import nidaqmx
from tkinter import *
master = Tk()
master.minsize(width=400, height=400)
w = Label(master) #shows as text in the window
w.pack() #organizes widgets in blocks before placing them in the parent.
def run():
with nidaqmx.Task() as task:
task.ai_channels.add_ai_voltage_chan("Dev1/ai0")
print('1 Channel 1 Sample Read: ')
data = task.read()
w['text'] = '%.2f' % data #said sensor value
master.after(5000, run)
run() # run the function once.
master.mainloop()
This should be the right way of doing this and as i couldn't run your code if anything doesn't work, let me know otherwise.
try this code:
import time
from tkinter import *
import nidaqmx
master = Tk()
def test():
while True:
with nidaqmx.Task() as task:
task.ai_channels.add_ai_voltage_chan("Dev1/ai0")
print('1 Channel 1 Sample Read: ')
data = task.read()
sensor_value = '%.2f' % data # said sensor value
w['text'] = sensor_value
master.update()
time.sleep(2)
w = Label(master)
w.pack()
btn = Button(text="Start read from sensor", command=test)
btn.pack()
mainloop()
You'll likely need a second thread to poll the sensor.
import nidaqmx
import time
import threading
from tkinter import *
stop_signal = threading.Event()
def read_loop():
with nidaqmx.Task() as task:
task.ai_channels.add_ai_voltage_chan("Dev1/ai0")
while True:
data = task.read()
label["text"] = "%.2f" % data
# Wait for the signal, or 5 seconds
if stop_signal.wait(timeout=5):
break # If the signal was set, break
# Build window
master = Tk()
master.minsize(width=400, height=400)
label = Label(master, text="")
label.pack()
# Set up & start reading thread
threading.Thread(target=read_loop).start()
try:
# Enter Tk main loop
mainloop()
finally:
# Clean up afterwards
stop_signal.set()
Keep in mind that mainloop() is blocking, so time.sleep() is useless as you won't get back to the task.read() line.
Consider using the Thread module from the threading lib importing it like this:
from threading import Thread
or using a button to refresh the value as someone else suggested you.
The purpose is that input fn takes string input and pass it into GUI fn which runs the condition and ammend tkinter window accordingly.
#*********************************** IMPORTING MODULES*****************
import tkinter
from tkinter import*
import tkinter.messagebox
import sqlite3
import os
import threading
from time import sleep
from input import*
conn = sqlite3.connect('portal.db')
c = conn.cursor()
global a
#*************** TKINTER GUI CODE******************
def gui(a):
window = tkinter.Tk()
window.title("Smart Notice Board")
#********************** FRAMES OF MAIN WINDOW(HOME)******************
top = Canvas(window,width=1024,height=184)
top.pack(fill=X)
middle = Canvas(window, width=1024, height=450, bg='steelblue')
middle.pack(fill=X)
main_left = Canvas(middle, width=275, height=450, bg='lightgreen')
main_left.pack(side=LEFT)
main_right = Canvas(middle, width=800, height=450, bg='steelblue')
main_right.pack(side=RIGHT)
bottom = Canvas(window, width=1024, height=70, bg='black')
bottom.pack(fill=X)
#************************** IMAGES********************
i_top = tkinter.PhotoImage(file='F:\\C_backup\\fyp\\5 jan 2k19\\BG.png')
top.create_image(0,10, anchor=tkinter.NW,image = i_top)
i_right = tkinter.PhotoImage(file='F:\\C_backup\\fyp\\5 jan 2k19\\aus1.png')
main_right.create_image(0,0, anchor=tkinter.NW,image = i_right)
#i_left = tkinter.PhotoImage(file='F:\\C_backup\\fyp\\5 jan 2k19\\widget1.png')
#main_left.create_image(0,0, anchor=tkinter.NW,image = i_left)
t1 = tkinter.PhotoImage(file='F:\\C_backup\\fyp\\5 jan 2k19\\first.png')
t2 = tkinter.PhotoImage(file='F:\\C_backup\\fyp\\5 jan 2k19\\BG.png')
#***************** TIMETABLE IMAGE VIEWING FN***********************
def home():
main_right.create_image(0,0, anchor=tkinter.NW,image = t2)
#*********************** TIMETABLE BUTTON PRESS FN*************************
def timetable():
main_right.create_image(0,0, anchor=tkinter.NW,image = t1)
#******************************* CONDITIONS**********************
if a == "NULL":
timetable()
if a == "HOME":
home()
#*********************** MAIN MENU BUTTONS****************
button_1 = Button(text = ' HOME', anchor = 'w', height = 2, width = 8,activebackground = '#33B5e5',bg = 'brown',fg = 'white',command = home)
top.create_window(2,150,anchor = 'nw', window = button_1)
button_2 = Button(text='TIMETABLE', height = 2, width=12, activebackground = '#33B5e5', bg = 'brown', fg = 'white',command = timetable)
top.create_window(75,150, anchor='nw', window = button_2)
window.mainloop()
#************************** MAIN LOOP************************
if __name__ == "__main__":
#print(valuea())
a=valuea()
gui(a)
Now what I want is continuously run that thing and update Tkinter window; but the 2btn fn only takes it one time and pass it into GUI fn which runs tkinter and it stucks on window.mainloop as tkinter is infinity loop.
Please suggest me a solution also u can run this code by only setting pictures from your computer
The standard method to run code regularly in the mainloop is to register a timeout function using the after method of the root window.
But, whatever you do in such a timeout function (and indeed in all other callbacks), it should not block, because that would lock up the mainloop! So you cannot use input. But you could read from sys.stdin, which is a io.TextIOWrapper instance.
You could use input in a second thread. But since Tkinter isn't thread-safe, that second thread should not use Tkinter functions or methods. So you should not simply update a label from the second thread. You could save/append the input to a global variable, but you'd have to protect that with a lock or mutex. And you'd need to use a timeout function in the main Tkinter thread to test if the lock or mutex is released by the second thread so the Tkinter thread can claim it and access the data. As you can see this is really complicated. So mixing Tkinter and threads is generally not recommended.
In order to ammend tkinter window and show the text and open picture.I did slight change in the input.py file;and coded as below
def vala():
a=speech()
if a == "HOME":
home()
if a == "NULL":
timetable()
window.after(1000,vala)
what I have done is I convert it into recursive fn that call itself after 1000ms. In this way, it can be done.
Basically, I have done it with Pocketsphinx as Input (i.e. my project takes speech input and open file/image in tkinter screen and it runs continuously)
I apologize if this is a redundant post, but I have spent a few hours sniffing out solutions around StackOverflow and other sources, this is where I ended up. AFAIK it should work.
Put simply, I can't fathom why my variable doesn't update more than once. When I run this program, it starts at '0.0' and updates to a realistic value (after time.sleep(5)), but only once. Theoretically it should update in real-time...
I realize you can't recreate my dev environment with my exact Modbus slave device, etc etc, but flow_unpack is a "good" variable because it prints to the Tkinter window once. If it was bad, it would return a ValueError or something. The problem is: why isn't it updating?
import tkinter as tk
import time
import minimalmodbus
import serial
import struct
root = tk.Tk()
root.resizable(width=False, height=False)
root.geometry('{}x{}'.format(500, 500))
i = minimalmodbus.Instrument('/dev/ttyUSB0', 1)
i.serial.baudrate = 9600
i.serial.bytesize = 8
i.serial.parity = serial.PARITY_ODD
i.serial.stopbits = 1
i.serial.timeout = 1
i.debug = False
var = tk.DoubleVar()
label = tk.Label(root, textvariable=var)
label.pack()
mass_flow_rate = i.read_registers(registeraddress=246, numberOfRegisters=2,
functioncode=3)
flow_ = [mass_flow_rate[0], mass_flow_rate[1]]
flow_pack = struct.pack('HH', flow_[0], flow_[1])
flow_unpack = round(struct.unpack('f', flow_pack)[0], 4)
def function():
global flow_unpack
var.set(flow_unpack)
root.update()
time.sleep(5)
root.after(2000, function)
root.mainloop()
Change this in your code
def function():
global flow_unpack
var.set(flow_unpack)
root.after(2000, function) # just add this for further calls
# root.update() -> not needed, see Brian comment.
# time.sleep(5) -> do you really need this?
# you will block the tkinter mainloop and your UI.
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()