4
votes

I'm writing a Python programme which listens for RFID input and only runs if a valid token is presented. The programme also has a GUI which I'm wanting to build using TkInter.

Both parts of the puzzle work fine in isolation, however as it stands I seem to be able to choose one or the other - but not both! I can draw my TkInter window fine, however if I call the function to start listening for the RFID input then whilst that bit runs OK and works... there's no GUI.

Code is below. You can see my debugging efforts so far with my printouts to the terminal...

    #!/usr/bin/env python3
    import sys
    import MySQLdb

    if sys.version_info[0] == 2:
        from Tkinter import *
        import Tkinter as ttk
    else:
        from tkinter import *
        import tkinter as ttk

    class Fullscreen_Window:
        def __init__(self):
            self.tk = Tk()
            self.frame = Frame(self.tk)
            self.frame.pack()
            ttk.Button(self.tk, text="hello world").pack()

            self.tk.attributes('-zoomed', True)
            self.state = False
            self.tk.bind("<F11>", self.toggle_fullscreen)
            self.tk.bind("<Escape>", self.end_fullscreen)

            print("init running")
            self.listen_rfid() # Commenting this out makes the GUI appear, uncommenting means no GUI :(

        def toggle_fullscreen(self, event=None):
            self.state = not self.state  # Just toggling the boolean
            self.tk.attributes("-fullscreen", self.state)
            print("Toggling")
            return "break"

        def end_fullscreen(self, event=None):
            self.state = False
            self.tk.attributes("-fullscreen", False)
            return "break"

        def listen_rfid(self):
            print("Main loop running")
            dbHost = 'localhost'
            dbName = 'python'
            dbUser = 'python'
            dbPass = 'PASSWORD'

            dbConnection = MySQLdb.connect(host=dbHost, user=dbUser, passwd=dbPass, db=dbName)
            cur = dbConnection.cursor(MySQLdb.cursors.DictCursor)

            with open('/dev/stdin', 'r') as tty:
                while True:
                    RFID_input = tty.readline().rstrip()
                    cur.execute("SELECT * FROM access_list WHERE rfid_code = '%s'" % (RFID_input))

                    if cur.rowcount != 1:
                        print("ACCESS DENIED")
                    else:
                        user_info = cur.fetchone()
                        print("Welcome %s!!" % (user_info['name']))


            tty.close()
            listen_rfid()

    if __name__ == '__main__':
        w = Fullscreen_Window()
        w.tk.mainloop()

I'm sure it's something really simple but as I'm a Python/TkInter n00b it's beaten me and I'm all done Googling. Any help gratefully received :)

2
Where is the function listen_rfid that Fulscreen_Window.listen_rfid calls defined? Should that be self.listen_rfid?FamousJameous
It's defined on line 38, after the "end_fullscreen" function is defined.Paul Freeman-Powell
So then you do mean self.listen_rfid?FamousJameous

2 Answers

4
votes

Tkinter (and all GUIs) has an infinite loop called the mainloop that keeps the GUI active and responsive. When you make another infinite loop (while True) you block Tkinter's mainloop; and the GUI fails. You need to either put your loop in a separate thread or use Tkinter's mainloop to do your work. Since you are using a blocking readline, the thread is the best way to go. As a guess, replace your call with this:

from threading import Thread
t = Thread(target=self.listen_rfid)
t.daemon = True # this line tells the thread to quit if the GUI (master thread) quits.
t.start()

Edit: BTW, your imports are very bad. "ttk" is a subset of tkinter, not an alias, the alias "tk" is usually used for tkinter, and wildcard imports are bad and should be avoided. This is how your tkinter imports should look:

try:
    # python 2
    import Tkinter as tk
    import ttk
except ImportError:
    # python 3
    import tkinter as tk
    from tkinter import ttk

And then you use the appropriate prefix:

self.tk = tk.Tk()
self.frame = tk.Frame(self.tk)
3
votes

You should run listen_rfid using after. The problem is that listen_rfid as you have written it will run forever meaning that mainloop never starts. If you do this:

#!/usr/bin/env python3
import sys
import select
import MySQLdb

if sys.version_info[0] == 2:
    from Tkinter import *
    import Tkinter as ttk
else:
    from tkinter import *
    import tkinter as ttk

class Fullscreen_Window:
    def __init__(self):
        self.tk = Tk()
        self.frame = Frame(self.tk)
        self.frame.pack()
        ttk.Button(self.tk, text="hello world").pack()

        self.tk.attributes('-zoomed', True)
        self.state = False
        self.tk.bind("<F11>", self.toggle_fullscreen)
        self.tk.bind("<Escape>", self.end_fullscreen)

        print("init running")
        # Schedule self.listen_rfid to run after the mainloop starts
        self.tk.after(0, self.listen_rfid)     

    def toggle_fullscreen(self, event=None):
        self.state = not self.state  # Just toggling the boolean
        self.tk.attributes("-fullscreen", self.state)
        print("Toggling")
        return "break"

    def end_fullscreen(self, event=None):
        self.state = False
        self.tk.attributes("-fullscreen", False)
        return "break"

    def listen_rfid(self):
        print("Main loop running")
        dbHost = 'localhost'
        dbName = 'python'
        dbUser = 'python'
        dbPass = 'PASSWORD'

        dbConnection = MySQLdb.connect(host=dbHost, user=dbUser, passwd=dbPass, db=dbName)
        cur = dbConnection.cursor(MySQLdb.cursors.DictCursor)

        # readline is blocking so check that there is input
        # before attempting to read it.
        r, w, x = select.select([sys.stdin], [], [], 0)
        if r:
            # There is available input, so read a line.
            RFID_input = sys.stdin.readline().rstrip()
            cur.execute("SELECT * FROM access_list WHERE rfid_code = '%s'" % (RFID_input))

            if cur.rowcount != 1:
                print("ACCESS DENIED")
            else:
                user_info = cur.fetchone()
                print("Welcome %s!!" % (user_info['name']))

        # keep running every 500 milliseconds for as long as
        # the mainloop is active.
        self.tk.after(500, self.listen_rfid)

if __name__ == '__main__':
    w = Fullscreen_Window()
    w.tk.mainloop()

it will check every half second whether there is some input on the command line and process it.