pythontkintergpio

how to change label text in Tkinter with callback function out of window.mainloop


im triggering callback function witch reads encoder output with GPIO.add_event_detect so the global counter has current value, but i cant pass it to tkinter like i did with listbox.insert(END,counter)

import RPi.GPIO as GPIO
from tkinter import ttk
import tkinter as tk

counter = 0

# GPIO Pins #
A = 17  
B = 27
#-----------#

# GPIO settings
GPIO.setwarnings(True)
GPIO.setmode(GPIO.BCM)
#
GPIO.setup(A, GPIO.IN)
GPIO.setup(B, GPIO.IN)

def Enc(A):
    global counter
    sleep(0.002)
    input_A = GPIO.input(A)
    input_B = GPIO.input(B)
    if (input_A == 1) and (input_B == 0):
        counter += 1
        print  (counter)

        #listbox.insert(END,counter)

    elif (input_A == 1) and (input_B == 1):
        counter -= 1
        print  (counter)

        #listbox.insert(END,counter)
    else:
        return

# GPIO output detection
GPIO.add_event_detect(A, GPIO.RISING, callback=Enc, bouncetime=10)

# window settings
window = tk.Tk()
window.title("encoder_value")

# string value?
window.Enc_counter_str = tk.StringVar(value=counter)

# label
tk.Label(window, text='Enc:',fg = "medium violet red", bg = "light grey", font = "Helvetica 16 bold italic").grid(column=0, row=0, **padding)

# output label
window.Enc_label = tk.Label(window)
window.Enc_label.grid(column=0, row=2, columnspan=1, **padding)
window.Enc_label.config(text=window.Enc_counter_str.get(),fg = "medium violet red", bg = "light grey", font = "Helvetica 16 bold italic")

# tk window loop
window.mainloop()

Solution

  • Improved version:

    import RPi.GPIO as GPIO
    import tkinter as tk
    from time import sleep
    
    ENCODER_PINS = [
        (17, 27),  # Encoder 1
        (22, 23),  # Encoder 2
        (24, 25),  # Encoder 3
        (5, 6)     # Encoder 4
    ]
    
    class EncoderApp:
    
        def __init__(self, master):
            self.master = master
            self.master.title("Encoder Values")
            GPIO.setwarnings(True)
            GPIO.setmode(GPIO.BCM)
    
            # Initialize counters and UI elements
            self.counters = [0, 0, 0, 0]
            self.setup_ui()
    
            # Setup GPIOs and event detection for each encoder
            for idx, (A, B) in enumerate(ENCODER_PINS):
                GPIO.setup(A, GPIO.IN)
                GPIO.setup(B, GPIO.IN)
                GPIO.add_event_detect(A, GPIO.BOTH, callback=lambda ch, idx=idx: self.enc_handler(ch, idx), bouncetime=10)
                GPIO.add_event_detect(B, GPIO.BOTH, callback=lambda ch, idx=idx: self.enc_handler(ch, idx), bouncetime=10)
    
        def setup_ui(self):
            padding = {"padx": 10, "pady": 10}
            self.enc_value_strs = [tk.StringVar(value=counter) for counter in self.counters]
            
            for idx, enc_str in enumerate(self.enc_value_strs):
                tk.Label(self.master, text=f'Enc {idx + 1}:', fg="medium violet red", bg="light grey", 
                         font="Helvetica 16 bold italic").grid(column=0, row=idx, **padding)
                
                enc_label = tk.Label(self.master, textvariable=enc_str, fg="medium violet red", 
                                     bg="light grey", font="Helvetica 16 bold italic")
                enc_label.grid(column=1, row=idx, **padding)
    
            self.master.protocol("WM_DELETE_WINDOW", self.close_app)
    
        def enc_handler(self, channel, idx):
            sleep(0.002)
            A, B = ENCODER_PINS[idx]
            input_A = GPIO.input(A)
            input_B = GPIO.input(B)
            
            # Logic for counting all cases (4 transitions) for rotary encoders
            if (input_A == 1) and (input_B == 0):
                self.counters[idx] += 1
            elif (input_A == 1) and (input_B == 1):
                self.counters[idx] -= 1
            elif (input_A == 0) and (input_B == 1):
                self.counters[idx] += 1
            elif (input_A == 0) and (input_B == 0):
                self.counters[idx] -= 1
    
            self.enc_value_strs[idx].set(self.counters[idx])
    
        def close_app(self):
            GPIO.cleanup()
            self.master.destroy()
    
    if __name__ == "__main__":
        root = tk.Tk()
        app = EncoderApp(root)
        root.mainloop()
    

    and version without GUI:

    import RPi.GPIO as GPIO
    from time import sleep
    
    ENCODER_PINS = [
        (17, 27),  # Encoder 1
        (22, 23),  # Encoder 2
        (24, 25),  # Encoder 3
        (5, 6)     # Encoder 4
    ]
    
    class EncoderReader:
    
        def __init__(self):
            GPIO.setwarnings(True)
            GPIO.setmode(GPIO.BCM)
    
            # Initialize counters
            self.counters = [0, 0, 0, 0]
    
            # Setup GPIOs and event detection for each encoder
            for idx, (A, B) in enumerate(ENCODER_PINS):
                GPIO.setup(A, GPIO.IN)
                GPIO.setup(B, GPIO.IN)
                GPIO.add_event_detect(A, GPIO.BOTH, callback=lambda ch, idx=idx: self.enc_handler(ch, idx), bouncetime=10)
                GPIO.add_event_detect(B, GPIO.BOTH, callback=lambda ch, idx=idx: self.enc_handler(ch, idx), bouncetime=10)
    
        def enc_handler(self, channel, idx):
            sleep(0.002)
            A, B = ENCODER_PINS[idx]
            input_A = GPIO.input(A)
            input_B = GPIO.input(B)
            
            # Logic for counting all cases (4 transitions) for rotary encoders
            if (input_A == 1) and (input_B == 0):
                self.counters[idx] += 1
                direction = "increment"
            elif (input_A == 1) and (input_B == 1):
                self.counters[idx] -= 1
                direction = "decrement"
            elif (input_A == 0) and (input_B == 1):
                self.counters[idx] += 1
                direction = "increment"
            elif (input_A == 0) and (input_B == 0):
                self.counters[idx] -= 1
                direction = "decrement"
            else:
                return
    
            print(f"Encoder {idx + 1} {direction} to {self.counters[idx]}")
    
        def run(self):
            try:
                while True:
                    sleep(0.1)
            except KeyboardInterrupt:
                GPIO.cleanup()
    
    if __name__ == "__main__":
        reader = EncoderReader()
        reader.run()
    

    also keep in mind that, because of Python's Global Interpreter Lock (GIL), even though the GPIO callbacks use multiple threads, they don't execute concurrently. This could lead to minor update lags if all encoders generate events at the same time and very quickly.