0

I am building a gui to control Rasp Pi 4. I have a main script pin_gui1.py.
I have all of my control functions in pin_control_stub1.py, which I am importing. Can successfully run those functions on button presses. But error comes when the function tries to change text property of a Label widget in the root script.

Problem: This works fine of I define the function in main script, but get NameError if in imported script. You can see the functions run successfully, and I can even pass them values from the main script, and print to console. But get error 'lbl_command' not defined when I try to change that label's text.
To see this error, click the "All On" or "All Off" buttons.

My guess is that the functions in imported scriot are compiled on import, so other widgets in main script are not defined yet. But I can't figure out how to delay this (if that is the problem). Appreciate any suggestions.

Imported script is only a stub right now: the final will have full board control commands, so the desired result is to be able to show the commands sent to the board in the label at the bottom of the gui named 'lbl_command'.

pin_gui1.py:

#GUI to control Raspberry Pi 4
import tkinter as tk
import pin_control_stub1 as p

# If i define functions here, they can alter text in lbl_command
def randflash():
    print("Random Flash 5 sec")
    lbl_command["text"] = "Random Flash 5 sec"

m = tk.Tk()
m.title("CONTROL GUI")
m.geometry('500x400')

# This is outside other frames
banner = tk.Label(text="Control Panel for pin_control", fg='cyan', bg='black')
banner.pack()

frame1 = tk.Frame(master=m, width=300, height=200, bg='#888')
frame1.pack(fill='both', expand=True)

btn1 = tk.Button(frame1, width=10, text='Random Flash 5 sec', command=randflash) #command=lambda: p.randflash(5)
btn2 = tk.Button(frame1, width=10,  text='All On', command=p.all_on, fg='green')
btn3 = tk.Button(frame1, width=10,  text='All Off', command=p.all_off, fg='red')
btn1.pack(side='left',padx=5,ipadx=20,ipady=10)
btn2.pack(side='left',padx=5,ipadx=20,ipady=10)
btn3.pack(side='left',padx=5,ipadx=20,ipady=10)

# 2nd frame holds pin/color buttons
frame2 = tk.Frame(master=m, width=300, height=200, relief=tk.SUNKEN, bg='#ccc')
frame2.pack(expand=True, fill="both")

for i in range(len(p.pins)):
        txt = 'Pin ' + str(p.pins[i]) + ': ' + p.colors[i]
        tk.Button(frame2, width=10,text=txt, command=lambda i=i: p.do_pin(p.pins[i])).grid(pady=2)

frame3 = tk.Frame(master=m, width=300, height=100, bg='#888')
frame3.pack(expand=True, fill='both')
lbl_command = tk.Label(frame3, text=' -- commands --', relief=tk.SUNKEN, bg='#ccc')
lbl_command.pack(ipadx=30)


m.mainloop()

pin_control_stub1.py:

# This stub exists only to test pin_gui1.py on laptop
# The real pin_control.py runs only on Raspberry Pi 4

pins = [12,13,16,21,22,23,24,25]
colors = ['red','green','blue','red','green','blue','yellow','yellow']

# def randflash():
#     print("Random Flash 5 sec")
#     lbl_command["text"] = "Random Flash 5 sec"

def all_on():
    print("Turns all on")
    lbl_command["text"] = "All ON"
    # NOTE: This causes NameError: name 'lbl_command' is not defined.
    # Works only if I move this function to pin_gui.py

def all_off():
    print("Turns all off")
    p.lbl_command["text"] = "All OFF"
    # NOTE: This causes NameError: name 'p' is not defined.
    # Works only if I move this function to pin_gui.py

def do_pin(x):
    print("turns on pin#" + str(x))
sailor
  • 1
  • Read up on [Tutorial - 9.2. Python Scopes and Namespaces](https://docs.python.org/3/tutorial/classes.html#python-scopes-and-namespaces) – stovfl Apr 12 '20 at 19:44
  • I read that page, and a lot of other info on variable scopes, but still can't figure out a fix. I did try defining 'global lbl_command' - same result. I even defined it before importing the module. However this doesn't explain why the function works if it is in the main script, especially this quote from above: "The local namespace for a function is created when the function is called". - ie; not when the function is loaded, just when it's called. – sailor Apr 12 '20 at 20:00
  • I should have specified both scripts are in the same directory, so path should not be an issue. – sailor Apr 12 '20 at 20:04
  • ***doesn't explain why the function works if it is in the main script***: You define `lbl_command` global in the `__main__` namespace. The `pin_control_stub1.py` doesn't belong to the `__main__` namespace. – stovfl Apr 12 '20 at 20:16
  • I've searched lots of sources on this, but still not finding a solution that works. I did try changing the import statement to: **from pin_control_stub1 import all_on, all_off**. According to some posts I saw, this should place those functions in the same namespace. But still same error. – sailor Apr 12 '20 at 20:34
  • What did you not understand from my comment? That's how it is designed to work! You can redesign your code using `OOP` syntax or pass a reference to `def all_on(lbl_command):` – stovfl Apr 12 '20 at 21:21

1 Answers1

0

For anyone interested, here's how I solved this issue. I finally found the hint I needed here

In main script (pin_gui1.py) include this line, but must be at end of script:

p.addglobals(globals())

Then, in imported script (pin_control_stub1.py), include this line:

addglobals = lambda x: globals().update(x)

Much better explanation of how this works in the link above, but it has solved my problem and works perfectly.

sailor
  • 1