How handle unexpected Key press - python

I have designed an application which have two buttons i.e CAL and SAV.
Accordingly that I have two functions but the issue is sometimes production line Operator by mistake presses SAV button. So that attribute error arises and program stuck.
How to overcome this issue? Please guide me.
Here is my code:
class ADS1x15:
"""Base functionality for ADS1x15 analog to digital converters."""
class ADS1115(ADS1x15):
"""Class for the ADS1115 16 bit ADC."""
class AnalogIn:
"""AnalogIn Mock Implementation for ADC Reads."""
import RPi.GPIO as GPIO
import tkinter as tk
GPIO.setmode(GPIO.BCM)
GPIO.setup(12,GPIO.IN) #Save Button
GPIO.setup(5,GPIO.IN) #Cal Button
root=tk.Tk()
root.geometry("1000x600")
file = open("/home/pi/data_log.txt", "r")
f = file.read().split(',')
rangeh = int(f[3])
offset = int(f[4])
fullScale = int(f[5])
chan=AnalogIn(ads,P0,P1)
def cal(channel):
global Dsel,cal_c,rangeh,offset,fullScale,chan
cal_c = cal_c + 1
if cal_c == 1:
root.L1 = tk.Label(root,text="Put Zero Weight and Press CAL btn",fg="brown",font="bold")
root.L1.pack()
root.L1.place(x=1,y=1)
elif cal_c == 2:
root.L1.destroy()
offset = chan.value
file = open("/home/pi/data_log.txt", "w")
if os.stat("/home/pi/data_log.txt").st_size == 0:
file.write("rangeh,offset,Full_Scale,\n")
file.write(str(rangeh)+","+str(offset)+","+str(fullScale))
file.flush()
root.L2 = tk.Label(root,text="Put Full Weight and Press SAV btn",fg="brown",font="bold")
root.L2.pack()
root.L2.place(x=1,y=1)
def sav(channel):
global rangeh,offset,fullScale
file = open("/home/pi/data_log.txt", "w")
if os.stat("/home/pi/data_log.txt").st_size == 0:
file.write("rangeh,offset,Full_Scale,\n")
file.write(str(rangeh)+","+str(offset)+","+str(fullScale))
file.flush()
root.L2.destroy()
def update():
""" function for continuous show value in every 500ms in tkinter window"""
GPIO.add_event_detect(5,GPIO.RISING,callback=cal,bouncetime=1000)
GPIO.add_event_detect(12,GPIO.RISING,callback=sav,bouncetime=1000)
root.after(500,update)
root.mainloop()
This error generated due to root.L2.destroy() this line.
Can I block or disable this sav function, so that without call of cal function, it shouldn't execute?

A brute force solution would be to check whether root has an L2 attribute or not
from tkinter import messagebox
def sav(channel):
if hasattr(root, 'L2'):
global rangeh, offset, fullScale
file = open("/home/pi/data_log.txt", "w")
if os.stat("/home/pi/data_log.txt").st_size == 0:
file.write("rangeh,offset,Full_Scale,\n")
file.write(str(rangeh) + "," + str(offset) + "," + str(fullScale))
file.flush()
root.L2.destroy()
else:
messagebox.showinfo('Unable to save', 'No data was generated yet')
A more elegant approach would be to disable the save button on startup and only enable it after the cal function has been executed.
I am not very familiar with Raspberry Pi implementations, so this is only a rough sketch on how to achieve the button disabling:
By the looks of it, the buttons are "wired in" via the GPIO.add_event_detect functions.
So i would remove the sav-callback from the main script and dynamically add it after the cal script, something like that:
# [...] beginning of your script [...]
def cal(channel):
# [...] original body of cal function [...]
activate_save_button()
def activate_save_button():
GPIO.add_event_detect(12, GPIO.RISING, callback=sav, bouncetime=1000)
def deactivate_save_button():
GPIO.remove_event_detect(12)
def sav(channel):
# [...] original body of sav function [...]
# remove save button functionality after saving
deactivate_save_button()
def update():
""" function for continuous show value in every 500ms in tkinter window"""
GPIO.add_event_detect(5, GPIO.RISING, callback=cal, bouncetime=1000)
# line with callback=sav is deleted here
root.after(500, update)
root.mainloop()

Related

Python Tkinter Label not responding

I am trying to make a loading and a GIF would be a lot helpful if it was supported in python tkinter. But since it is not supported, so I put all the frame-by-frame pictures in the list that makes a loading when played continuously (using assign_pic function) and then I created a label (called lab_loading) of which I change the picture after 200ms by calling the start_anim function. I am calling the assign_pic function in a loop which I think causes this error. See my source code below 👇 and the video I provided to understand this problem clearly.
Video: https://drive.google.com/file/d/1WHwZqvd8vXz-ehXbQ_fRtrKPEyFLKrVe/view?usp=sharing
Source code:
from time import sleep
from tkinter import Tk, Label
from PIL import ImageTk, Image
class Loading(Tk):
def __init__(self):
super().__init__()
self.title('Loading')
self.geometry('250x217')
self.address = getcwd()
self.imgs_list = []
self.loadingImgsList(self.address)
# This method Puts all the images in the list
def loadingImgsList(self, curAddress):
address = f'{curAddress}\\loading'
self.imgs_list = [(ImageTk.PhotoImage(Image.open(
f"{address}\\{i}.png"))) for i in range(1, 4)]
# This mehtod assigns the picture from the list via index (ind) number from the imgs_list list and
# updates the GUI when called.
def assign_pic(self, ind):
lab_loading.config(image=self.imgs_list[ind])
self.update_idletasks()
sleep(0.2)
def start_anim(self):
ind = 0
b = 0
while (b < 300):
if ind == 2:
ind = 0
else:
ind += 1
self.after(200, self.assign_pic, ind)
b += 1
if __name__ == "__main__":
root = Loading()
lab_loading = Label(root, image='')
lab_loading.pack()
root.start_anim()
root.mainloop()
I Tried to make start_anime function recursive but it was still the same. I don't know why this is happening. I also made the loop finite but it was still not working. So a solution to this problem or even a better suggestion would highly be appreciated.
you shouldn't be using sleep inside tk, as it blocks python from handling user actions.
the way you do animation in tk is by using the after method, to call a function that would update the canvas, this function will call after again, until the animation is complete.
# everything before this function should be here
self.ind = 0 #somewhere in __init__
def assign_pic(self):
if self.ind < len(imgs_list):
lab_loading.config(image=self.imgs_list[self.ind])
self.ind += 1
root.after(500,self.assign_pic) # time in milliseconds
else:
print("done") # do what you want after animation is done
if __name__ == "__main__":
root = Loading()
lab_loading = Label(root, image='')
lab_loading.pack()
root.after(100,root.assign_pic)
root.mainloop()
the after function schedules the given function after a certain delay, during which the GUI is free to respond to any action.
Edit: after method takes argument in milliseconds not in seconds, i had the input in it in seconds instead of milliseconds, it's now fixed.

Why does running mainloop() cause my whole computer to freeze?

I'm building a large, complicated program, one half of which involves a GUI, which I'm building using Tkinter.
Previous iterations of this GUI seemed to work as intended. However, in the latest version, when I try to run a demonstration (see the demo() function in the code below), my whole computer freezes, and my only option is to carry out a hard reset.
Does anyone have any ideas as to why this might be happening? Some points which might be useful:
If I run the code below with the line self.gui.mainloop() commented out, the desired window appears on the screen for long enough for the toaster message to be displayed, and then closes without any freezing.
The ArrivalsManagerDaughter and DeparturesManagerDaughter objects transmit data wirelessly to another device, but they shouldn't be doing anything, other than being initialised, in the code which is causing the freezing. I don't believe that these are the cause of the problem, although I could well be wrong.
Here's the whole Python file which I'm trying to run. I'm happy to post more code if requested.
"""
This code holds a class which manages transitions between the
"recommendations" and "custom placement" windows, and also oversees their
interactions with Erebus.
"""
# GUI imports.
from tkinter import *
# Non-standard imports.
import ptoaster
# Custom imports.
from erebus.arrivals_manager_daughter import ArrivalsManagerDaughter
from erebus.departures_manager_daughter import DeparturesManagerDaughter
# Local imports.
from charon.custom_placement_window import Custom_Placement_Window
from charon.recommendations_window import Recommendations_Window
# Local constants.
REFRESH_INTERVAL = 1000
# ^^^ in miliseconds ^^^
##############
# MAIN CLASS #
##############
class Comptroller:
""" The class in question. """
def __init__(self, welcome=False, delete_existing_ledger=False,
internal=False, diagnostics=False, path_to_icon=None):
self.close_requested = False
self.path_to_icon = path_to_icon
self.recommendations = dict()
self.arrivals_manager = ArrivalsManagerDaughter(self,
diagnostics=diagnostics)
self.departures_manager = DeparturesManagerDaughter(
delete_existing=delete_existing_ledger, internal=internal,
diagnostics=diagnostics)
self.gui = Tk()
self.top = Frame(self.gui)
self.window = Recommendations_Window(self)
self.is_on_recommendations_window = True
self.arrange()
if welcome:
print_welcome()
def add_recommendation(self, ticket, epc, column, row):
""" Add a recommendation to the dictionary. """
recommendation = dict()
recommendation["ticket"] = ticket
recommendation["epc"] = epc
recommendation["column"] = column
recommendation["row"] = row
self.recommendations[ticket] = recommendation
def remove_recommendation(self, ticket):
""" Delete a recommendation from the dictionary. """
del self.recommendations[ticket]
def get_top(self):
""" Return the top-level GUI object. """
return self.top
def arrange(self):
""" Arrange the widgets. """
self.window.get_top().pack()
self.top.pack()
def switch_to_custom_placement(self, ticket, epc):
""" Switch window from "Recommendations" to "Custom Placement". """
columns = self.arrivals_manager.columns
rows = self.arrivals_manager.rows
self.window.get_top().pack_forget()
self.window = Custom_Placement_Window(self, ticket, epc, columns,
rows)
self.window.get_top().pack()
self.is_on_recommendations_window = False
def switch_to_recommendations(self):
""" Switch window from "Custom Placement" to "Recommendations". """
self.window.get_top().pack_forget()
self.window = Recommendations_Window(self)
self.window.get_top().pack()
self.is_on_recommendations_window = True
def refresh(self):
""" Refresh the "recommendations" window, as necessary. """
if (self.is_on_recommendations_window and
self.arrivals_manager.clear_quanta()):
self.window.refresh_rec_table()
self.departures_manager.clear_backlog()
if self.close_requested:
self.kill_me()
else:
self.gui.after(REFRESH_INTERVAL, self.refresh)
def simulate_recommendation(self, ticket, epc, column, row):
""" Simulate receiving a transmission from the Pi. """
self.add_recommendation(ticket, epc, column, row)
self.window.refresh_rec_table()
def request_close(self):
self.close_requested = True
def run_me(self):
""" Run the "mainloop" method on the GUI object. """
self.gui.after(REFRESH_INTERVAL, self.refresh)
self.gui.title("Charon")
if self.path_to_icon:
self.gui.iconphoto(True, PhotoImage(file=self.path_to_icon))
self.gui.protocol("WM_DELETE_WINDOW", self.request_close)
self.gui.mainloop()
def kill_me(self):
""" Kill the mainloop process, and shut the window. """
self.gui.destroy()
####################
# HELPER FUNCTIONS #
####################
def print_welcome():
""" Print a welcome "toaster" message. """
message = ("Notifications about boxes leaving the coldstore will be "+
"posted here.")
ptoaster.notify("Welcome to Charon", message,
display_duration_in_ms=REFRESH_INTERVAL)
def print_exit(epc):
""" Print an exit "toaster" message. """
message = "Box with EPC "+epc+" has left the coldstore."
ptoaster.notify("Exit", message)
###########
# TESTING #
###########
def demo():
""" Run a demonstration. """
comptroller = Comptroller(welcome=True, delete_existing_ledger=True,
internal=True, diagnostics=True)
comptroller.simulate_recommendation(1, "rumpelstiltskin", 0, 0)
comptroller.simulate_recommendation(2, "beetlejuice", 0, 0)
comptroller.run_me()
###################
# RUN AND WRAP UP #
###################
def run():
demo()
if __name__ == "__main__":
run()
FOUND THE PROBLEM: the culprit was actually calling a ptoaster function from within a Tkinter GUI. Which leads me on to my next question: Is it possible to combine ptoaster and Tkinter in an elegant fashion, and, if so, how?
The problem ocurred when calling print_welcome(), which in turn calls one of the ptoaster functions. It seems that Tkinter and ptoaster do not play together nicely. Removing the reference to print_welcome() from the Comptroller class put a stop to any freezing.
(On a side note: I'd be very grateful to anyone who could suggest an elegant method of combing ptoaster with Tkinter.)
Try changing
def demo():
""" Run a demonstration. """
comptroller = Comptroller(welcome=True, delete_existing_ledger=True,
internal=True, diagnostics=True)
comptroller.simulate_recommendation(1, "rumpelstiltskin", 0, 0)
comptroller.simulate_recommendation(2, "beetlejuice", 0, 0)
comptroller.run_me()
###################
# RUN AND WRAP UP #
###################
def run():
demo()
if __name__ == "__main__":
run()
To simply
if __name__ == "__main__":
""" Run a demonstration. """
comptroller = Comptroller(welcome=True, delete_existing_ledger=True,
internal=True, diagnostics=True)
comptroller.simulate_recommendation(1, "rumpelstiltskin", 0, 0)
comptroller.simulate_recommendation(2, "beetlejuice", 0, 0)
comptroller.run_me()
As to why this might happening, since you're creating an object by instantiating Comptroller inside a regular function demo, the object is not being "retained" after the demo exits.
EDIT
If you would like to still maintain demo and run you could create a class Demo and store the instance globally.
Or maybe a simple global variable inside demo to retain a "reference" to the instance Comptroller.

Python main script not waiting for user response in Tkinter GUI

I am brand new to using the Tkinter GUI and stuck while trying to make an interactive program that 1) solicits feedback from the user in the GUI, 2) waits for the user to respond and hit enter and 3) then uses the input to inform the next steps in the main script.
However, I am unable to make step 2 execute properly. At the function call waitforinput(), I would expect the main script to wait before moving on to the next lines which are test printouts. Instead, it prints the main script test lines with '' for result and then places an entry box that works. Why is this program moving to the next line before the waitforinput function is completed? Thanks!
import tkinter as tk
from tkinter import *
import threading, time
# assignments for input thread
WAIT_DELAY = 250 #milliseconds
lock = threading.Lock() # Lock for shared resources.
finished = False
result = ''
# Set up the graphical interface
root = tk.Tk(className='My Flashcards')
def main():
# request and wait for input from user
waitforinput()
Test = tk.Label(root, text = "result = " + result)
Test.pack()
Test.config(font = ('verdana', 24), bg ='#BE9CCA')
# sets background thread for getinput from user
def waitforinput():
global finished
with lock:
finished = False
t = threading.Thread(target=getinput)
t.daemon = True
root.after(WAIT_DELAY, check_status) # start waiting
t.start()
# checks to see if user has inputted
def check_status():
with lock:
if not finished:
root.after(WAIT_DELAY, check_status) # keep waiting
# solicits and returns a string from the user
def getinput():
# declaring string variable for storing name and password
answer_var = tk.StringVar()
# define a function that will get the answer and return it
def user_response(event = None):
answer = answer_entry.get()
global result
result = answer
global finished
finished = True # to break out of loop
# creating an entry for inputting answer using widget Entry
answer_entry = tk.Entry(root, width = 1, borderwidth = 5, bg ='#BE9CCA', textvariable = answer_var) ## could be global with args
# making it so that enter calls function
answer_entry.bind('<Return>', user_response)
# placing the entry
answer_entry.pack()
answer_entry.focus()
main()
root.mainloop()
'''
You don't need to use threads to wait for a response. Tkinter has methods specifically for waiting: wait_window and wait_variable.
wait_window waits for a window to be destroyed. This is typically used when creating a modal dialog. wait_variable can be used to wait until one of the special tkinter variable objects has been modified.
If you want your code to wait until a user has pressed the enter key in an entry, you can set a binding on that key to set a variable, and then wait for that variable to be set. Note: you normally don't want to use wait_variable for a variable used as the value of textvariable since the wait will stop as soon as a single character has been entered.
Your getinput function could look something like this, which can be called directly without the use of threads:
def getinput():
answer_var = tk.StringVar()
def user_response(event):
answer_var.set(answer_entry.get())
return
answer_entry = tk.Entry(root, width = 1, borderwidth = 5, bg ='#BE9CCA')
answer_entry.bind('<Return>', user_response)
answer_entry.pack()
answer_entry.focus()
answer_entry.wait_variable(answer_var)
return answer_var.get()
Once the statement answer_entry.wait_variable(answer_var) executes, tkinter will enter an event loop and won't return until answer_var has been set. You can then call get on the variable and return the value.
Here is how you can modify your main function to call this function:
def main():
result = getinput()
Test = tk.Label(root, text = "result = " + result)
Test.pack()
Test.config(font = ('verdana', 24), bg ='#BE9CCA')

How to update the tkinter text label every half a second? How to avoid .mainloop so that rest of program can execute?

I'm using a Kvaser Leaf Professional and trying to read the can-bus data from a motorcycle through Python coding to be able to use the data to see where values change and which values stays constant.
At the moment I'm struggling to display the data on tkinter and to update it after each time the data is read/updated again, because it changes every few microseconds. Normal ways of using .after() doesn't seem to work 100%, or I'm just doing something wrong. Or is there a better way to display updated data than tkinter?
The other problem I have is when I display the tkinter it uses a root.mailoop() to run the window and when that happens it enters that main loop and doesn't exit it and run the rest of the program and also won't be able to update the existing data displayed on the window. Is there a way to bypass the root.mainloop() maybe?
Any insight will be helpful and/or small errors I've made in my code. I started using Python only 2 days ago and I'm still learning a lot and don't know all the in's and out's yet.
Thank you in advance.
Here is my whole program so far:
import keyboard
import time
import sys
import tkinter as tk
import binascii
from canlib import canlib, Frame
from canlib.canlib import ChannelData
root = tk.Tk()
def setUpChannel(channel,
openFlags=canlib.canOPEN_ACCEPT_VIRTUAL,
bitrate=canlib.canBITRATE_500K,
bitrateFlags=canlib.canDRIVER_NORMAL):
ch = canlib.openChannel(channel, openFlags)
print("Using channel: %s, EAN: %s" % (ChannelData(channel).channel_name,
ChannelData(channel).card_upc_no)
)
ch.setBusOutputControl(bitrateFlags)
ch.setBusParams(bitrate)
ch.busOn()
return ch
def tearDownChannel(ch):
ch.busOff()
ch.close()
def text(t):
tx = binascii.hexlify(t).decode('utf-8')
n = 2
txt = [tx[i:i+n] for i in range(0, len(tx), n)]
return txt
def counter():
while True:
try:
cnt = 1
frame = ch0.read()
firstID = frame.id
while True:
frame = ch0.read()
cnt += 1
if frame.id == firstID:
break
pass
except (canlib.canNoMsg):
break
except (canlib.canError):
print("Rerun")
pass
return cnt
print("canlib version:", canlib.dllversion())
ch0 = setUpChannel(channel=0)
ch1 = setUpChannel(channel=1)
frame = Frame(id_=100, data=[1, 2, 3, 4], flags=canlib.canMSG_EXT)
ch1.write(frame)
print(frame)
cnt = counter()
print("Counter: %d" %(cnt))
time.sleep(1)
while True:
while True:
try:
show = ""
i = 1
while i <= cnt:
frame = ch0.read()
show = show +("%s\t%s\n" %(frame.id, text(frame.data)))
i += 1
print(show)
T = tk.Text(root, height=6, width=80)
T.config(state="normal")
T.insert(tk.INSERT, show)
T.pack()
root.mainloop()
break
except (canlib.canNoMsg) as ex:
pass
except (canlib.canError) as ex:
print(ex)
pass
tearDownChannel(ch0)
tearDownChannel(ch1)
You can edited my code as you want, it will help a lot if I can see how to implement the code better.
You dont need to use multi-threading or anything convoluted. Just use the widget.after() method that calls another function/method of your choosing after the desired time. In the case below, the time is 1 second -> 1000 milliseconds. Just call the class before the mainloop and it will start working.
class UI:
def __init__(self, parent):
# variable storing time
self.seconds = 0
# label displaying time
self.label.configure(text="%i s" % self.seconds)
# start the timer
self.label.after(1000, self.refresh_label)
def refresh_label(self):
#refresh the content of the label every second
# increment the time
self.seconds += 1
# display the new time
self.label.configure(text="%i s" % self.seconds)
# request tkinter to call self.refresh after 1s (the delay is given in ms)
self.label.after(1000, self.refresh_label)

Tracing a list with Tkinter

Tkinter has these variable classes: BooleanVar, DoubleVar, IntVar, StringVar. All of them have a trace() method that allows you to attach a callback that is called when the variable changes.
Is it possible (or is there a workaround) to trace a list? Specifically, I'm looking for a way to monitor a list so that I can change the elements of a Treeview.
The code below includes a test function (forceChange) that can be deleted, but demonstrates the rest of the code traces a python list variable. Since you're already using the tk event loop, I used that, but I've also tested it using the sched and time modules to schedule events when there is no GUI.
from tkinter import * import sys
class ListManager:
def __init__(self, root, listvar):
self.root = root
self.listvar = listvar
self.previous_value = listvar.copy()
# Create an event to watch for changes to the list.
self.watch_event = root.after(20, self.watchList)
# Create an event to change the list. This is for test purposes only.
self.change_event = root.after(200, self.forceChange)
def watchList(self):
''' Compare the previous list to the current list.
If they differ, print a message (or do something else).
'''
try:
if self.previous_value != self.listvar:
print("Changed! Was:", self.previous_value, " Is:", self.listvar)
self.previous_value = self.listvar.copy()
# Reschedule this function to continue to check.
self.root.after(20, self.watchList)
except Exception:
print("Variable has been destroyed")
self.root.after_cancel(self.change_event)
def forceChange(self):
try:
next = self.listvar[-1:][0]
except:
# Variable was destroyed.
return
if next == 11:
sys.exit()
next += 1
self.listvar.append(next)
self.root.after(500, self.forceChange)
if __name__ == '__main__':
root = Tk()
# This is the list we'll watch.
mylist = [1, 2, 3]
# Create a list manager
vlist = ListManager(root, mylist)
root.mainloop()

Categories

Resources