Python Forum
Tkinter Exit Code based on Entry Widget
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Tkinter Exit Code based on Entry Widget
#1
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()
Reply
#2
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"?
Reply
#3
(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.
Reply
#4
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.
Reply
#5
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)
Reply
#6
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
Reply
#7
[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.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  ValueError: could not convert string to float: '' fron Entry Widget russellm44 5 494 Mar-06-2024, 08:42 PM
Last Post: russellm44
  TKinter Widget Attribute and Method Quick Reference zunebuggy 3 787 Oct-15-2023, 05:49 PM
Last Post: zunebuggy
  [Tkinter] entry widget DPaul 5 1,433 Jul-28-2023, 02:31 PM
Last Post: deanhystad
  [Tkinter] The Text in the Label widget Tkinter cuts off the Long text in the view malmustafa 4 4,668 Jun-26-2022, 06:26 PM
Last Post: menator01
  Can't get tkinter button to change color based on changes in data dford 4 3,363 Feb-13-2022, 01:57 PM
Last Post: dford
  [Tkinter] Making entry global in tkinter with multiprocessing luckyingermany 2 2,284 Jan-21-2022, 03:46 PM
Last Post: deanhystad
  [Tkinter] Update variable using tkinter entry methon drSlump 6 5,094 Oct-15-2021, 08:01 AM
Last Post: drSlump
  Tkinter | entry output. Sap2ch 1 1,950 Sep-25-2021, 12:38 AM
Last Post: Yoriz
  auto-generate code for Entry box location snakes 1 1,839 May-07-2021, 08:30 PM
Last Post: Yoriz
  .get() from generated Entry widgets in tkinter snakes 4 4,155 May-03-2021, 11:26 PM
Last Post: snakes

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020