I am trying to implement virtual LEDs on a Python window. I turn the LED "on" by drawing a green oval. I then use root.after() to schedule another call that turns the LED "off" by drawing a dark green oval. I use a 250ms delay. There are 4 active LEDs on the display. When I allow them to run (I have a global boolean to indicate if I want LEDs to be active), the processor usage starts to climb, going up a percent or two a minute, steadily climbing. I know root.after() returns a handle and you can use that to delete the action, but I don't think I'm overloading the system with too many of these calls.

Location is a tuple with 2 pairs of X,Y values for bounding box. LED() draws the LED immediately. ServiceLEDs() is called at 8 Hrz, and checks the time for all active LEDs to see if the time has passed; this is my second attempt, as my first attempt did a simpler root.after() call for every LED that needed to be handled. I thought this would be faster. Commented out ScheduleLED implements setting an "after" for each LED. The active ScheduleLED sets an array entry with a True flag, the desired state and the time it should expire.

Is it the after() that is causing me time proplems? Is it the tuples? All I know, is if I set UseLEDs to False, the progam runs along at about 3 percent of CPU, and if True, it grows and grows...

Help. Please!

Kelly

Here's code snippets:

def LED(self, location, state):
        if (UseLEDs):
            self.LEDCanvas.create_oval( location, fill=LEDColors[state])
        
    def ServiceLEDs(self):
        if (not UseLEDs): return
        FLAG = 0
        STATE = 1
        TIME = 2
        theTime = getTime()
        for ii in range(0,4):
            if (LEDTimes[ii][FLAG]):
                if ( theTime > LEDTimes[ii][TIME] ):
                    self.LEDCanvas.create_oval( LEDTuples[ii], fill=LEDColors[LEDTimes[ii][STATE]])
                    LEDTimes[ii] = ( False, 0, 0 )
                    
        root.after(125, self.ServiceLEDs)
        
        
    # def ScheduleLED(self, delay, tuple, state):
        # if (UseLEDs):
            # print "DEBUG", "LED", delay, state, tuple
            # root.after( delay, lambda l=tuple, s=state : self.LED( l, s ))
            
    def ScheduleLED(self, delay, theLED, state):
        if (UseLEDs):
            LEDTimes[theLED] = (True, state, getTime()+(delay/1000))
            #root.after( delay, lambda l=tuple, s=state : self.LED( l, s ))

Recommended Answers

All 10 Replies

You are continually drawing one oval on top of another, so Tkinter has to keep track of more and more widgets, not to mention that you could reach the recursion limit. Change the color instead. This code is a little crude, but I don't have any time right now.

import time
from Tkinter import *

def change_color(canvas, ov, color):
    print "color =", color
    if color == 'blue':
        color = 'yellow'
    else:
        color = 'blue'
    canvas.itemconfig(ov, fill=color)

    return color

root = Tk()
root.title('Canvas')
canvas = Canvas(root, width=200, height=200)

color = 'black'
ov=canvas.create_oval(10,10,50,50, fill=color)
canvas.pack()

root.update_idletasks()
for x in range(5):
    time.sleep(1.5)
    color = change_color(canvas, ov, color)
    root.update_idletasks()

root.mainloop()

Woooee, your handle looks like a bad Scrabble rack of letters, but you are a genius my friend! I've only been writing Python for a few months now and I'm still learning how to do things efficiently. I didn't consider that the objects still live on, even though I wasn't tracking the specific handles of the objects I was creating. Changing the colors did the trick, I can now blink them all day long and my CPU usage stays nice and low. I'll also look elsewhere in my application to see if I'm doing something similar in other places. Thank you for taking the time to read and solve my problem.

Here for future version functioning version of Woooee's fast code using after-timing:

import time
from Tkinter import *

def change_color(canvas, ov, color):
    color = 'yellow' if  (color != 'yellow') else 'blue'
    print "color =", color
    canvas.itemconfig(ov, fill=color)
    canvas.update_idletasks()

    canvas.after(1500, change_color, canvas, ov, color)

root = Tk()
root.title('Canvas')
canvas = Canvas(root, width=200, height=200)

color = 'yellow'
ov=canvas.create_oval(10,10,50,50, fill=color)
canvas.pack()

change_color(canvas, ov, color)

root.mainloop()

Here for future version functioning version of Woooee's fast code using after-timing:

import time
from Tkinter import *

def change_color(canvas, ov, color):
    color = 'yellow' if  (color != 'yellow') else 'blue'
    print "color =", color
    canvas.itemconfig(ov, fill=color)
    canvas.update_idletasks()

    canvas.after(1500, change_color, canvas, ov, color)

root = Tk()
root.title('Canvas')
canvas = Canvas(root, width=200, height=200)

color = 'yellow'
ov=canvas.create_oval(10,10,50,50, fill=color)
canvas.pack()

change_color(canvas, ov, color)

root.mainloop()

That method still has a possible recursion limit problem. Good one. Do you know about these Tkinter links (and the Padres folded this year)
http://effbot.org/tkinterbook/
http://www.ferg.org/thinking_in_tkinter/index.html
http://pmw.sourceforge.net/doc/index.html

Those are good links. I've learned Python by the Google method, and I've been to each of those sites for at least a nugget of information. There is so much to this language, so many little things that can affect how programs work. I read through "Thinking in Tkinter", and as it talks about the pack'er, it says you can't really control it. However, another place I found pack_propagate(0) to inhibit the pack'er from going overboard on rearranging a frame. (Also works with grid()) I knew the create_ methods made an object, but I didn't think it through, that those objects are all still there, even if I'm not tracking them.

How would the examples above hit a recursion limit? I don't see the recursion; it repeats, but there is no 'go deeper or exit' logic, it just repeats itself. As I understand the construct, .after() just sets a timer. I suppose if the timer was reset before the function ended, it would reenter (which can be bad if you don't plan for it), this one looks pretty benign.

And yes, the Padres collapse was epic. They could do no wrong until August, when they could do no right. Having the Chargers trip and fall out of the gate has not made San Diego a very happy pro sports town. Of course, it's finally sunny again so who cares!

cheers

Recursion is when a function calls itself. It is easy to illustrate by adding a counter that increases by one each time the function is called

import time
from Tkinter import *
 
def change_color(canvas, ov, color, ctr):
    color = 'yellow' if  (color != 'yellow') else 'blue'
    print "color =", color, ctr
    canvas.itemconfig(ov, fill=color)
    canvas.update_idletasks()
 
    ctr += 1
    canvas.after(1500, change_color, canvas, ov, color, ctr)
 
root = Tk()
root.title('Canvas')
canvas = Canvas(root, width=200, height=200)
 
color = 'yellow'
ov=canvas.create_oval(10,10,50,50, fill=color)
canvas.pack()
 
change_color(canvas, ov, color, 0)
 
root.mainloop()

You must increase counter and decreace it when exiting. To reach the recursion limit is not possible as the function exits after setting timer.

Yeah, Tonyjv is correct. To call yourself, you have to do it directly; this is scheduling a call to itself, which is different. Once you call the timer, you exit the function, which you could do forever.

I didn't know you could add parameters to after(). Thanks for that nugget!

Thank you all, again, for taking the time to post on this. I was going to abandon my virtual LEDs until you gave me this solution. This makes my program much better and more useful.

commented: Nice maners +2
Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.