Posts: 31
Threads: 7
Joined: Oct 2021
Hello All... I am new to this forum and quite new to Python, please forgive my ignorance. I am using this code for a basic Tkinter GUI and need to figure out how to properly exit the code if my entry widget is blank. I am using 2 GPIO inputs on a Raspberry Pi along with this GUI. In my "def update_clock" I am checking if the entry is there or not and simply changing the label based on that. What I want to do is, "IF" the entry widget is blank and GPIO.input (5) == 1, I want to override everything and still keep a message "TESTER NOT READY". Getting the status of the GPIO is a physical pushbutton. Not sure how to handle this. Please let me know if I am making no sense at all here.
#!/usr/bin/env python3
import tkinter as tk
import tkinter.ttk as ttk
import RPi.GPIO as GPIO
from tkinter import *
from tkinter import messagebox
import time
from datetime import datetime
GPIO.setmode(GPIO.BCM)
GPIO.setup(5, GPIO.IN)
GPIO.setup(6, GPIO.IN)
class App(Frame):
def __init__(self,master=None):
Frame.__init__(self, master)
root.attributes('-fullscreen', True)
button = Button (master, text = "EXIT ESD", command = quit)
button.place(x=375, y=280)
tk.Label(master, text="Badge #",font=("Helvetica", 30)).grid(row=0)
tk.Entry(root).place(x=170,y=10, width=200, height=40)
self.master = master
self.label = Label(text="", fg="blue", font=("Helvetica", 30))
self.label.place(x=40,y=150)
self.entry_widget = tk.Entry(root, font=("Helvetica", 26))
self.entry_widget.focus_set()
self.entry_widget.place(x=170,y=10, width=200, height=40)
self.update_clock()
def update_clock(self):
d = datetime.now().strftime("%m-%d-%Y %H:%M:%S")
newT = datetime.strptime(d, "%m-%d-%Y %H:%M:%S").strftime("%m-%d-%Y %I:%M:%S %p")
now = time.strftime("%H:%M:%S")
if not self.entry_widget.get():
self.label.configure(text=newT + '\n' + '\n'" NOT READY! ", fg="red", font=("Helvetica", 24))
self.label.place(x=60,y=100)
self.after(1000, self.update_clock)
else:
self.label.configure(text=newT + '\n' + '\n'" TESTER READY! ", fg="blue", font=("Helvetica", 24))
self.label.place(x=60,y=100)
self.after(1000, self.update_clock)
if GPIO.input(5) == 1:
text_entered = self.entry_widget.get()
self.label.configure(text="PASS",fg="green", font=('Helvetica 110 bold'))
self.label.place(x=48,y=100)
self.after(40000, self.update_text)
FileName = str("/home/pi/esd.txt")
with open(FileName, "a") as f: # open file
f.write("Badge# " + text_entered + ", Test PASSED ON: " + newT + '\n')
elif GPIO.input(6) == 1:
text_entered = self.entry_widget.get()
self.label.configure(text="FAIL",fg="red", font=('Helvetica 110 bold'))
self.label.place(x=80,y=100)
self.after(50000, self.update_text)
FileName = str("/home/pi/esd.txt")
with open(FileName, "a") as f: # open file
f.write("Badge# " + text_entered + ", Test FAILED ON: " + newT + '\n')
def update_text(self):
self.entry_widget.delete(0, 'end')
def quit(self):
root.destroy()
root = Tk()
app=App(root)
root.wm_title("ESD TESTER")
root.geometry("520x480")
root.after(1000, app.update_clock)
root.mainloop()
Posts: 6,780
Threads: 20
Joined: Feb 2020
I don't understand the logic.
When should it display "Tester NOT Ready"?
When should it display "Tester Ready"?
When should it display "Not Ready"?
When should it display "Test Passed"?
When should it display "Test Failed"?
Posts: 31
Threads: 7
Joined: Oct 2021
(Oct-20-2021, 03:56 PM)deanhystad Wrote: I don't understand the logic.
When should it display "Tester NOT Ready"?
When should it display "Tester Ready"?
When should it display "Not Ready"?
When should it display "Test Passed"?
When should it display "Test Failed"?
Sorry for the confusion... It should say "Tester Ready" when there is something in "tk.entry" widget, which is there now but only visually on the GUI until the button is pressed. At that point, it doesn't care right now if the entry is blank. It can PASS or FAIL either way. If the entry is blank, they press physical button and pass the test, the screen says "PASS". If they press the button and fail the test, the screen says "FAIL". I am writing to a text file if they pass or fail, so I need to try and prevent writing to text unless there the entry has something in it. I hope this makes sense.... basically kill everything unless you have entered something into the entry field.
Posts: 6,780
Threads: 20
Joined: Feb 2020
Oct-21-2021, 03:47 AM
(This post was last modified: Oct-21-2021, 03:47 AM by deanhystad.)
Based on these assumptions:
input 5 starts the test
input 6 determines if the test fails or passes
You could do something like this:
import enum
class TestState(enum.Enum):
'''States for testing state machine'''
Unknown = 0, # Figure out what the new state should be
NotReady = 1, # Need to enter badge number before testing
Ready = 2, # Badge entered. Waiting for test button press
Testing = 3, # Running test.
Completed = 4 # Test completed. Wait for test button release
class App(Frame):
def __init__(self,master=None):
...
self.tester = ''
self.time = ''
self.set_state(TestState.Unknown)
self.update_clock()
def set_state(self, new_state):
'''Testing state machine. See states above'''
if new_state == TestState.NotReady:
# Report we are not ready for testing
self.label.configure('Test Not Ready')
if new_state == TestState.Ready:
# Report we are ready for testing
self.label.configure('Test Ready')
if new_state == TestState.Testing:
# Perform the test and report results
result = ('FAILED', 'PASSED)[GPIO.input(6)]
self.label.configure(f'blah blah blah Test {result} blah blah')
with open(self.test_file, "a") as f: # open file
f.write(f"Badge#{self.teste}, Test {result} ON: {self.time}n')
# Erase test results after 4 seconds
self.after(4000, lambda: self.set_state(TestState.Completed))
self.state = new_state
def update_clock(self):
'''Runs periodically'''
...
self.tester = self.entry_widget.get()
self.time = some date-time string
...
if self.state == TestState.Unknown:
if len(self.tester) == 0:
self.set_state(TestState.NotReady)
else:
self.set_state(TestState.Ready)
if self.state == TestState.NotReady:
# Waiting for tester to enter badge number
if len(self.tester) > 0:
self.set_state(TestState.Ready)
if self.state == TestState.Ready:
# Waiting for test button. Can also go to not-ready if
if len(self.tester) == 0:
self.set_state(TestState.NotReady)
elif GPIO.input(5) == 1:
self.set_state(TestState.Testing)
if self.state == TestState.Completed:
# Begin new test sequence when test button is released
if GPIO.input(5) == 0:
self.set_state(TestState.Unknown)
# Update frequently to be responsive to button press
self.after(100, lambda: self.update_clock) This implements a "state machine" that has the states:
Unknown: Current state is not known
NotReady: Waiting for badge number to be entered
Ready: Badge number entered and waiting for test button
Testing: Performing a test
Completed: Test is finished
We transition from one state to another when events occur. The transitions are (Transiton: Event):
Unknown->NotReady: Badge number is not entered
Unknown->Ready: Badge number is entered
NotReady->Ready: Badge number is entered
Ready->NotReady: Badge number is not entered
Ready->Testing: Test button is pressed
Testing->Completed: Test finished and 4 seconds passed
Completed->Unknown: Automatic
When we transition to a new state we perform some actions. The actions are:
Ready: Update status label
NotReady: Update status label
Testing: Run test. Record results. Update status label. Schedule transition to Unknown after 4 seconds
Since actions are only performed when the state changes you don't have to worry about recording the same results multiple times for one test or writing the test results over the Test Ready message. Most of the time your software will be do nothing but wait for an event.
Posts: 31
Threads: 7
Joined: Oct 2021
Thanks deanhystad for the extensive help, greatly appreciated. After seeing my code re-written, I need to study this for sure. I added some of the items I thought would be needed and ran the code I posted here. I had to clean up some lines that had syntax errors, so I hope I did that correctly. Now when I run the code, I get no errors but the code just ends without doing anything.
#!/usr/bin/env python3
import enum
import time
import tkinter as tk
import tkinter.ttk as ttk
import RPi.GPIO as GPIO
from tkinter import *
GPIO.setmode(GPIO.BCM)
GPIO.setup(5, GPIO.IN)
GPIO.setup(6, GPIO.IN)
now = time.strftime("%H:%M:%S")
class TestState(enum.Enum):
'''States for testing state machine'''
Unknown = 0, # Figure out what the new state should be
NotReady = 1, # Need to enter badge number before testing
Ready = 2, # Badge entered. Waiting for test button press
Testing = 3, # Running test.
Completed = 4 # Test completed. Wait for test button release
class App(Frame):
def __init__(self,master=None):
self.tester = ''
self.time = ''
self.label.configure('Test Not Ready')
self.set_state(TestState.Unknown)
self.update_clock()
def set_state(self, new_state):
'''Testing state machine. See states above'''
if new_state == TestState.NotReady:
# Report we are not ready for testing
self.label.configure('Test Not Ready')
if new_state == TestState.Ready:
# Report we are ready for testing
self.label.configure('Test Ready')
if new_state == TestState.Testing:
# Perform the test and report results
result = ('FAILED', 'PASSED')[GPIO.input(6)]
self.label.configure(f="blah Test" + {result})
with open(self.test_file, "a") as f: # open file
f.write(f='Badge#' + {self.test})
# Erase test results after 4 seconds
self.after(4000, lambda: self.set_state(TestState.Completed))
self.state = new_state
def update_clock(self):
'''Runs periodically'''
self.tester = self.entry_widget.get()
self.time = now
if self.state == TestState.Unknown:
if len(self.tester) == 0:
self.set_state(TestState.NotReady)
else:
self.set_state(TestState.Ready)
if self.state == TestState.NotReady:
# Waiting for tester to enter badge number
if len(self.tester) > 0:
self.set_state(TestState.Ready)
if self.state == TestState.Ready:
# Waiting for test button. Can also go to not-ready if
if len(self.tester) == 0:
self.set_state(TestState.NotReady)
elif GPIO.input(5) == 1:
self.set_state(TestState.Testing)
if self.state == TestState.Completed:
# Begin new test sequence when test button is released
if GPIO.input(5) == 0:
self.set_state(TestState.Unknown)
# Update frequently to be responsive to button press
self.after(100, lambda: self.update_clock)
Posts: 6,780
Threads: 20
Joined: Feb 2020
That was not complete code. It was meant more as a pseudo-code to help talk about state machines and how they could be applied to your problem. If it was real code it would've created windows and had a main. Something more like this:
#!/usr/bin/env python3
import enum
import datetime
import tkinter as tk
import RPi_GPIO as GPIO # My fake RPi package
import random
GPIO.setmode(GPIO.BCM)
GPIO.setup(5, GPIO.IN)
GPIO.setup(6, GPIO.IN)
class TestState(enum.Enum):
'''States for testing state machine'''
Unknown = 0, # Figure out what the new state should be
NotReady = 1, # Need to enter badge number before testing
Ready = 2, # Badge entered. Waiting for test button press
Testing = 3, # Running test.
Completed = 4 # Test completed. Wait for test button release
class App(tk.Frame):
def __init__(self, root, *args, **kwargs):
super().__init__(root, *args, **kwargs)
self.root = root
self.filename = 'results.txt'
self.tester = ''
self.time = ''
self.time_label = tk.Label(self, text="", font=("Helvetica", 22))
self.time_label.grid(row=0, column=0, columnspan=2, pady=10)
tk.Label(self, text="Badge #", font=("Helvetica", 22)) \
.grid(row=1, column=0, padx=10, pady=10, sticky='E')
self.entry_widget = tk.Entry(self, font=("Helvetica", 26), width=10)
self.entry_widget.focus_set()
self.entry_widget.grid(row=1, column=1, sticky='W')
self.status_label = tk.Label(self, text="", fg="blue", font=("Helvetica", 30), width=16)
self.status_label.grid(row=2, column=0, columnspan=2, pady=10)
# button = tk.Button(self, text = "EXIT ESD", command=lambda: root.destroy())
button = tk.Button(self, text = "EXIT ESD", command=self.fake_test)
button.grid(row=3, column=0, columnspan=2, pady=10)
self.set_state(TestState.Unknown)
def fake_test(self):
'''Run a fake test. I don't have a Raspberry Pi'''
GPIO.output(6, random.randint(0, 1))
GPIO.output(5, 1)
self.root.after(1000, lambda: GPIO.output(5, 0))
def set_state(self, new_state):
'''Testing state machine. See states above'''
if new_state == TestState.NotReady:
# Report we are not ready for testing
self.status_label.configure(text='TEST NOT READY', fg='red')
if new_state == TestState.Ready:
# Report we are ready for testing
self.status_label.configure(text='Test Ready', fg='blue')
if new_state == TestState.Testing:
# Perform the test and report results
if GPIO.input(6) == 1:
self.status_label.configure(text='PASS', fg='green')
with open(self.filename, "a") as f:
f.write(f'Badge# {self.tester}, Test PASSED ON: {self.time}\n')
else:
self.status_label.configure(text='FAIL', fg='red')
with open(self.filename, "a") as f:
f.write(f'Badge# {self.tester}, Test FAILED ON: {self.time}\n')
# Erase test results after 4 seconds
self.root.after(4000, lambda: self.set_state(TestState.Completed))
self.state = new_state
def update_clock(self):
'''Runs periodically'''
self.time = datetime.datetime.now().strftime("%m-%d-%Y %I:%M:%S %p")
self.time_label.configure(text=self.time)
self.tester = self.entry_widget.get()
if self.state == TestState.Unknown:
if len(self.tester) == 0:
self.set_state(TestState.NotReady)
else:
self.set_state(TestState.Ready)
if self.state == TestState.NotReady:
# Waiting for tester to enter badge number
if len(self.tester) > 0:
self.set_state(TestState.Ready)
if self.state == TestState.Ready:
# Waiting for test button. Can also go to not-ready if
if len(self.tester) == 0:
self.set_state(TestState.NotReady)
elif GPIO.input(5) == 1:
self.set_state(TestState.Testing)
if self.state == TestState.Completed:
# Begin new test sequence when test button is released
if GPIO.input(5) == 0:
self.set_state(TestState.Unknown)
# Update frequently to be responsive to button press
self.root.after(100, self.update_clock)
def main():
root = tk.Tk()
root.wm_title("ESD TESTER")
root.geometry("520x480")
# root.attributes('-fullscreen', True)
app = App(root)
app.pack()
#app.filename = "/home/pi/esd.txt"
app.update_clock()
root.mainloop()
if __name__ == '__main__':
main() I do not have a Raspberry Pi, so I faked up an RPi library. Be leery about anything GPIO related. But I do think the state machine is working pretty well. The test results file contained this after 3 tests.
Output: Badge# 1234, Test FAILED ON: 10-21-2021 09:46:41 AM
Badge# 1234, Test PASSED ON: 10-21-2021 09:46:47 AM
Badge# 1234, Test FAILED ON: 10-21-2021 09:46:52 AM
Posts: 31
Threads: 7
Joined: Oct 2021
[quote="deanhystad" pid='148977' dateline='1634827617']
That was not complete code. It was meant more as a pseudo-code to help talk about state machines and how they could be applied to your problem. If it was real code it would've created windows and had a main. Something more like this:
[python]#!/usr/bin/env python3
This is great... I am going to try this and let you know how it goes. I "REALLY" appreciate you taking time out of your day to help me out with this. Its going to be a great learning experience for me coming from a Visual Basic world.
|