Can't set python event flag
Clash Royale CLAN TAG#URR8PPP
Can't set python event flag
I have been struggling the whole day with this, I'm pretty certain it's a thread problem, but I just can't figure out what's wrong. Basically, My main.py creates an instance of "Neopixel". That neopixel starts a thread that runs a led ring. Some ring animations are running for as long as a condition is met, unlike simple led blinking that don't cause problems.
To handle those conditions, well, I use an event flag. It's set to true when it's a long animation, and when another led state kicks in, it's unset so it should theoretically stop the animation as it is in a while event.is_set() loop. But... It is never cleared, even though I do clear it.
Some code:
def __init__(self):
self._logger = logging.getLogger('ProjectAlice')
self._logger.info('Initializing Project Alice')
self._leds = NeoPixels()
self._leds.onStart()
....
self._logger.info('Project Alice started')
self._leds.onConnecting()
self.greetAlice()
....
elif message.topic == self._SUB_GREETING_BACK:
self._state = State.REGISTERED
self._logger.info('- Alice greeted back, module registered')
self._leds.onConnected()
I've remove irrelevant parts but left the led part. As we see, it starts, creates an instance of Neopixels, calls onStart() for the leds, then calls onConnect(), then tries to reach the main server through mqtt. When the main server replies, I call onConnected(). But the leds always stay in "onConnect()" and never go to the "onConnected()" state. There's a print('Done') in there, that never shows, but when I ctrl-c the program, which also then executes the "onConnected" animation
Somehow, the
self._animation.clear()
in
def onConnected(self):
does not register, the animation loop never ends but this
self._logger.info('- Alice greeted back, module registered')
prints, meaning onConnected() is called
class NeoPixels(object):
def __init__(self):
self._running = True
self._ring = Adafruit_NeoPixel(num=config.settings['ringLedCount'], pin=config.settings['ringLedPin'], brightness=125, strip_type=ws.SK6812_STRIP_RGBW)
self._ring.begin()
self._queue = Queue.Queue()
self._animation = threading.Event()
threading.Thread(target=self._run).start()
def onStart(self):
self._running = True
self._animation.clear()
self._queue.put(self._start)
def onConnecting(self):
self._animation.clear()
self._queue.put(self._connecting)
def onConnected(self):
self._animation.clear()
self._queue.put(self._connected)
def _run(self):
while self._running:
func = self._queue.get()
func()
def _start(self):
for i in range(self._ring.numPixels()):
self._setPixelColorRGB(i, 255, 0, 0)
self._ring.show()
time.sleep(10 / 1000.0)
for i in range(self._ring.numPixels()):
self._setPixelColorRGB(i, 0, 0, 0)
self._ring.show()
time.sleep(10 / 1000.0)
time.sleep(0.25)
for i in range(self._ring.numPixels()):
self._setPixelColorRGB(i, 255, 0, 0)
self._ring.show()
time.sleep(1 / 1000.0)
def _connecting(self):
self._animation.set()
while self._animation.is_set():
for i in range(self._ring.numPixels()):
self._setPixelColorRGB(i, 255, 0, 0)
self._ring.show()
time.sleep(20 / 1000.0)
threading.Timer(interval=20 / 1000.0, function=self._setPixelColorRGB, args=[i, 0, 0, 0]).start()
print('done')
def _connected(self):
for i in range(self._ring.numPixels()):
self._setPixelColorRGB(i, 0, 128, 0)
self._ring.show()
time.sleep(1)
self._clear()
1 Answer
1
This very much looks like a timing issue with your queue. If the call to onConnected
happens too quickly after onConnecting
has been called, onConnected
clears the event before onConnecting
-> _connecting
sets it.
onConnected
onConnecting
onConnected
onConnecting
_connecting
This is how I stripped down your class to run and test it (I removed all the LED ring stuff and added print statements to onConnected
and _run
)
onConnected
_run
import time, threading, queue
class NeoPixels:
def __init__(self):
self._running = True
self._queue = queue.Queue()
self._animation = threading.Event()
threading.Thread(target=self._run)
def onStart(self):
self._running = True
self._animation.clear()
self._queue.put(self._start)
def onConnecting(self):
self._animation.clear()
self._queue.put(self._connecting)
def onConnected(self):
print("called onConnected")
self._animation.clear()
self._queue.put(self._connected)
def _run(self):
while self._running:
func = self._queue.get()
print("now running ".format(func.__qualname__))
func()
def _start(self):
time.sleep(.5)
def _connecting(self):
self._animation.set()
while self._animation.is_set():
for i in range(5):
time.sleep(20 / 1000.0)
print('done')
def _connected(self):
for i in range(1):
print("connected")
time.sleep(1)
And here's how I run it:
leds = NeoPixels()
leds.onStart()
leds.onConnecting()
leds.onConnected()
Here's the output:
called onConnected
now running NeoPixels._start
now running NeoPixels._connecting
As you can see, onConnected
is called and clears the event before _run
even fetched _start
from the queue and processed it. So when onConnecting
is executed, _connecting
sets the event and since nothing clears it after that, it keeps running indefinitely.
onConnected
_run
_start
onConnecting
_connecting
So, changing onConnected
to wait for the queue to clear, like so:
onConnected
def onConnected(self):
while not self._queue.empty():
time.sleep(.1)
print("called onConnected")
self._animation.clear()
self._queue.put(self._connected)
will lead to the following output:
now running NeoPixels._start
now running NeoPixels._connecting
called onConnected
done
now running NeoPixels._connected
connected
Out of curiosity, how do you actually terminate/join the thread? You create it anonymously, so I don't see how you would get a handle on it to close it once done.
Ah, now that makes sense. I constantly had to kill the process manually following the implementation you presented here and was wondering about the witchcraft you must have come up with to grab and terminate that thread :D
– shmee
Aug 8 at 13:13
Dude! Really? I did not see that? I was printing lines and lines to see what was hanging and I did not notice anything! Many many thanks for pointing it out!
– Psychokiller1888
Aug 8 at 22:08
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
Thank you for your answer! I will try that as soon as I get back home, could it be that easy? But wanted to answer the question of yours: I made a light version to test separately and striped down the whole thing to post here. On the project, this is run through a ThreadManager that handles threads terminating when needed.
– Psychokiller1888
Aug 8 at 10:43