简体   繁体   中英

Creating a Knight Rider style streaming LED with a row of images in Python

I'm learning python with the raspberry pi and a pi-face expansion board. Using Tkinter I've created a Gui with buttons to operate the pi-face LED's. In one part of the code I open a new window which shows a button and a row of images of a LED in the 'off' state. I'm trying to add some code to make the row of LED images stream an LED image in the 'on' state left to right along the row of images, like the Knight Rider car's front lights. I've tried a few things in the while loop but can't quite see how to achieve it without a lot of lines of code. I think there must be a way to do it in the same way that the digital write is incremented to create the streaming LED on the piface expansion board. Here is my code...

class App2:

    def __init__(self, master):
            self.signal = False    #added to stop thread
            print('self.signal', self.signal)

            self.master=master    # I added this line to make the exit button work
            frame = Frame(master)
            frame.pack()
            Label(frame, text='Turn LED ON').grid(row=0, column=0)
            Label(frame, text='Turn LED OFF').grid(row=0, column=1)

            self.button0 = Button(frame, text='Knight Rider OFF', command=self.convert0)
            self.button0.grid(row=2, column=0)
            self.LED0 = Label(frame, image=logo2)   #added to create a row of images
            self.LED1 = Label(frame, image=logo2)
            self.LED2 = Label(frame, image=logo2)
            self.LED3 = Label(frame, image=logo2)
            self.LED4 = Label(frame, image=logo2)
            self.LED5 = Label(frame, image=logo2)
            self.LED6 = Label(frame, image=logo2)
            self.LED7 = Label(frame, image=logo2)
            self.LED0.grid(row=2, column=1)
            self.LED1.grid(row=2, column=2)
            self.LED2.grid(row=2, column=3)
            self.LED3.grid(row=2, column=4)
            self.LED4.grid(row=2, column=5)
            self.LED5.grid(row=2, column=6)
            self.LED6.grid(row=2, column=7)
            self.LED7.grid(row=2, column=8)

            self.button9 = Button(frame, text='Exit', command=self.close_window)
            self.button9.grid(row=3, column=0)


    def convert0(self, tog=[0]):

        tog[0] = not tog[0]

        if tog[0]:
            print('Knight Rider ON')
            self.button0.config(text='Knight Rider ON')
            t=threading.Thread(target=self.LED)
            t.start()
            self.signal = True    #added to stop thread
            print('self.signal', self.signal)
            print('tog[0]', tog[0])
            self.LED0.config(image = logo)
        else:
            print('Knight Rider OFF')
            self.button0.config(text='Knight Rider OFF')
            self.signal = False   #added to stop thread
            print('self.signal', self.signal)
            print('tog[0]', tog[0])
            self.LED0.config(image = logo2)


    def LED(self):
            while self.signal:   #added to stop thread

                a=0

                while self.signal:   #added to stop thread
                        pfio.digital_write(a,1) #turn on
                        sleep(0.05)
                        pfio.digital_write(a,0) #turn off
                        sleep(0.05)
                        a=a+1

                        if a==7:
                                break

                while self.signal:   #added to stop thread

                        pfio.digital_write(a,1) #turn on
                        sleep(0.05)
                        pfio.digital_write(a,0) #turn off
                        sleep(0.05)
                        a=a-1

                        if a==0:
                                break

    def close_window(self):
            print('Knight Rider OFF')
            print('self.signal', self.signal)
            self.button0.config(text='Knight Rider OFF')
            self.LED0.config(image = logo2)
            self.signal = False   #added to stop thread
            print('self.signal', self.signal)


            sleep(1)
            print('Close Child window')
            self.master.destroy()   # I added this line to make the exit button work

root = Tk()
logo2 = PhotoImage(file="/home/pi/Off LED.gif")
logo = PhotoImage(file="/home/pi/Red LED.gif")

root.wm_title('LED on & off program')
app = App(root)

root.mainloop()

You don't need threads for such a simple task. It's very easy to set up a persistent repeating task in tkinter, if the task doesn't take more than a couple hundred milliseconds (if it takes much longer, your UI will start to lag).

The basic pattern is to write a function that does some work, and then have that function cause itself to be called again using after . For example:

def animate():
    # do something, such as turn an led on or off
    <some code here to turn one led on or off>

    # run this again in 100 ms
    root.after(100, animate)

The above will create an infinite loop that runs inside tkinter's mainloop. As long as <some code here... > doesn't take too long, the animation will appear fluid and your UI won't be laggy.

Example

Here's a simple working example of the technique. It uses a simple iterator to cycle through the leds, but you can use any algorithm you want to pick the next led to light up. You could also turn on or off both the on screen and hardware led at the same time, or turn on or off multiple leds, etc.

To make this code copy/paste-able, it uses colored frames rather than images, but you can use images if you want.

import tkinter as tk  # Tkinter for python 2
from itertools import cycle

class LEDStrip(tk.Frame):
    segments = 16
    speed = 100 # ms

    def __init__(self, parent):
        tk.Frame.__init__(self, parent)

        leds = []
        for i in range(self.segments):
            led = tk.Frame(self, width=12, height=8, borderwidth=1, 
                           relief="raised", background="black")
            led.pack(side="left", fill="both", expand=True)
            leds.append(led)

        self.current_segment = None
        self.iterator = cycle(leds)

    def animate(self):
        # turn off the current segment
        if self.current_segment:
            self.current_segment.configure(background="black")

        # turn on the next segment
        self.current_segment = next(self.iterator)  # self.iterator.next() for python 2
        self.current_segment.configure(background="red")

        # run again in the future
        self.after(self.speed, self.animate)


root = tk.Tk()
strip = LEDStrip(root)
strip.pack(side="top", fill="x")

# start the loop
strip.animate()

root.mainloop()

Might not be exactly what you are looking for, but you can get some inspiration to set up this "cylon" algorithm on the terminal first. LEDs don't have intermediate color values, but I guess the remnant perception of light should do the trick.

import sys,time
shift = lambda l, n=1: l[n:]+l[:n]
c = u' ▁▂▃▄▅▆▇' # possible color values
L = 8           # number of items
B = L*[0]       # indices of items
A = [0] + list(range(7)) + list(range(7,0,-1)) + 6*[0]  # light sequence
C = L*[' ']     # start blank

while 1:
   B[A[0]]=L            # set the most brilliant light 
   for x in range(L):
      B[x]-= 1           # decrease all lights values
      B[x] = max(0,B[x]) # not under 0
      C[x] = c[B[x]]     # get the corresponding 'color'
   A = shift(A,-1)       # shift the array to the right
   sys.stdout.write(('%s%s%s')%(' ',''.join(C[1:]),'\r'))
   time.sleep(0.1)

Or try that one https://www.trinket.io/python/79b8a588aa

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM