The plan is to have several IO routines running "concurrently" (specifically on a Raspberry Pi, manipulating IO pins and running an SPI interface at the same time). I try to use asyncio to make this happen. However, my simple try-out refuses to run.
This is a reduced version of the code, leaving out the IO pin details:
"""\
Reduced problem representation:
this won't run because GPIO details have been left out
"""
import RPi.GPIO as gpio
import asyncio
GPIO_PB = 12 # Define pushbutton channel
async def payload():
""" Provides some payload sequence using asyncio.sleep() """
#Payload action
await asyncio.sleep(1)
#Payload action
await asyncio.sleep(1)
class IOEvent(asyncio.locks.Event):
"""\
Create an Event for asyncio, fired by a callback from GPIO
The callback must take a single parameter: a gpio channel number
"""
def __init__(self, ioChannel, loop):
super().__init__(loop = loop)
self.io = ioChannel
def get_callback(self):
"The callback is a closure that knows self when called"
def callback( ch ):
print("callback for channel {}".format(ch))
if ch == self.io and not self.is_set():
print(repr(self))
self.set()
print(repr(self))
return callback
async def Worker(loop, event):
print("Entering Worker: {}".format(repr(loop)))
while loop.is_running():
print("Worker waiting for {}".format(repr(event)))
await event.wait()
print("Worker has event")
event.clear()
await payload()
print("payload ended")
loop = asyncio.get_event_loop()
# Create an event for the button
pb_event = IOEvent( GPIO_PB, loop)
# register the pushbutton's callback
# Pushing the button calls this callback function
gpio.add_event_callback( GPIO_PB, pb_event.get_callback() )
try:
asyncio.ensure_future(Worker(loop, pb_event))
loop.run_forever()
except KeyboardInterrupt:
pass
finally:
print("Closing Loop")
loop.stop()
loop.close()
The output I get is like this:
Entering Worker: <_UnixSelectorEventLoop running=True closed=False debug=False>
Worker waiting for <__main__.IOEvent object at 0x76a2a950 [unset]>
callback for channel 12
<__main__.IOEvent object at 0x76a2a950 [unset,waiters:1]>
<__main__.IOEvent object at 0x76a2a950 [set,waiters:1]>
callback for channel 12
These lines show the pushbutton repeatedly and correctly firing its callback routine. The first time it calls the set()
funtion as expected. The event used for the wait()
call and the set()
call are the same. But the message "Worker has event", after the await event.wait()
call never appears.
I looked at PyQt5 and asyncio: yield from never finishes, but I do not see any other loops than the default loop.
Why does wait()
never return? How could I find out?
An asyncio event can be used to notify multiple asyncio tasks that some event has happened. An Event object manages an internal flag that can be set to true with the set () method and reset to false with the clear () method. The wait () method blocks until the flag is set to true.
When the first task completes asyncio.wait returns two lists: done and pending. We can iterate over each of these lists: pending contains tasks that have not been completed.
def wait_for_reaction_remove(bot, emoji=None, *, user=None, timeout=None, message=None, check=None): """Waits for a reaction to be removed by a user from a message within a time period. Made to act like other discord.py wait_for_* functions but is not fully implemented.
For the majority of asyncio examples, where the demonstration code is waiting on a single event, the issue of retaining a task handle until the task completes is not an issue. In these cases, the code blocks until the task complete, in which case the task has already been removed from the event loop when the coroutine returns a value.
Callbacks set by add_event_callback
are called from a different thread, as indicated by them being called automatically "in the background". This means that you can't call set
on an asyncio.Event
directly from a gpio callback, since asyncio classes are not thread-safe.
To wake up an asyncio.Event
from a different thread, you can pass event.set
to loop.call_soon_threadsafe
. In your case, you would change:
self.set()
to:
self._loop.call_soon_threadsafe(self.set)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With