-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
__await__ magic method and async/await #3540
Conversation
This adds the `async def` and `await` verbs to valid CircuitPython syntax using the Micropython implementation. Consider: ``` >>> class Awaitable: ... def __iter__(self): ... for i in range(3): ... print('awaiting', i) ... yield ... return 42 ... >>> async def wait_for_it(): ... a = Awaitable() ... result = await a ... return result ... >>> task = wait_for_it() >>> next(task) awaiting 0 >>> next(task) awaiting 1 >>> next(task) awaiting 2 >>> next(task) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration: 42 >>> ``` and more excitingly: ``` >>> async def it_awaits_a_subtask(): ... value = await wait_for_it() ... print('twice as good', value * 2) ... >>> task = it_awaits_a_subtask() >>> next(task) awaiting 0 >>> next(task) awaiting 1 >>> next(task) awaiting 2 >>> next(task) twice as good 84 Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration: ``` Note that this is just syntax plumbing, not an all-encompassing implementation of an asynchronous task scheduler or asynchronous hardware apis. uasyncio might be a good module to bring in, or something else - but the standard Python syntax does not _strictly require_ deeper hardware support. Micropython implements the await verb via the __iter__ function rather than __await__. It's okay. The syntax being present will enable users to write clean and expressive multi-step state machines that are written serially and interleaved according to the rules provided by those users. Given that this does not include an all-encompassing C scheduler, this is expected to be an advanced functionality until the community settles on the future of deep hardware support for async/await in CircuitPython. Users will implement yield-based schedulers and tasks wrapping synchronous hardware APIs with polling to avoid blocking, while their application business logic gets simple `await` statements.
Some examples of improved compliance with CPython that currently have divergent behavior in CircuitPython are listed below: * yield from is not allowed in async methods ``` >>> async def f(): ... yield from 'abc' ... Traceback (most recent call last): File "<stdin>", line 2, in f SyntaxError: 'yield from' inside async function ``` * await only works on awaitable expressions ``` >>> async def f(): ... await 'not awaitable' ... >>> f().send(None) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in f AttributeError: 'str' object has no attribute '__await__' ``` * only __await__()able expressions are awaitable Okay this one actually does not work in circuitpython at all today. This is how CPython works though and pretending __await__ does not exist will only bite users who write both. ``` >>> class c: ... pass ... >>> def f(self): ... yield ... yield ... return 'f to pay respects' ... >>> c.__await__ = f # could just as easily have put it on the class but this shows how it's wired >>> async def g(): ... awaitable_thing = c() ... partial = await awaitable_thing ... return 'press ' + partial ... >>> q = g() >>> q.send(None) >>> q.send(None) >>> q.send(None) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration: press f to pay respects ```
Firmware too large
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this, I'm very excited. Particularly I'm happy to see the identical code that can be written in Python3 and in CircuitPython with these changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, this looks much better. Requesting Scott's review too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very cool! Thank you for all of this work.
Have you thought about what hardware and library APIs could benefit from this?
Yes, I can't wait wait to make a context manager for communication resources, e.g.,
Adding library support for this pattern allows synchronization to happen at the point of demand rather than at a top level workflow. Users of this api can do like In my own user interface application I see frequent sleeps for 5.8 milliseconds as I have a 100hz loop checking my rotary button for input. In the future I would like to see interrupt-based sleep state in addition to this polling model - though the polling model is already a huge improvement in ergonomics for complex applications.
The event loop would watch for the pin to be interrupted and add an instance of |
@WarriorOfWire Thanks! This is great to see. I'm excited to start using it :) That said, any pointers on reading material on how to use it would be appreciated as while I'm somewhat familiar with the concepts, I'm not quite sure where to start. |
@siddacious yeah, if you just want to write async/await code you should take a look at tasko https://github.com/WarriorOfWire/tasko If you want to implement your own event loop from scratch then grab a coffee and follow along with David Beazley https://youtu.be/Y4Gt3Xjd7G8 In fact, watching that video might be the best recommendation on the topic I have in general. |
@WarriorOfWire perfect, thanks :) |
I am on a steep learning curve on CircuitPython and now I need to understand how to get async/await to work. First obstacle is I get: no module named 'asyncio'. |
@ErlendFj |
Thanks for the swift answer. I'll try with yield or asynccp and see if I
can sort it out.
…On Sun, 26 Sep 2021, 21:28 Dan Halbert, ***@***.***> wrote:
@ErlendFj <https://github.com/ErlendFj> asyncio is not yet available,
though we are looking at event and concurrency support in more depth for
8.0.0. However, async/await is turned on for most boards, and there are
several libraries already available. See this forum post:
https://forums.adafruit.com/viewtopic.php?f=60&t=183355
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#3540 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ADF6CNPWFZPQLOOGJZVZIX3UD5X4RANCNFSM4SMDKUAA>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
|
Hi, #=================================================================
# Asynccp functions
#=================================================================
class App:
def __init__(self):
self.text_ID = '' #result from task
self.now_audio = 'Hei.wav' #input to task How to make these 'global'
self.now_colour = BLACK #input to task
async def scan_NFC(self):
uid = pn532.read_passive_target(timeout=0.1)
print(".", end="")
if uid is not None:
text_ID = ''
for e in uid:
text_ID += '' + str(e)
print(text_ID)
async def play_audio(self):
print("s", end="")
if self.now_audio != '':
audio.play(audiocore.WaveFile(open(now_audio, "rb")))
async def toggle_colour(self):
print("t", end="")
if self.now_colour is BLACK:
pixels[0] = BLUE
pixels.show()
self.now_colour = BLUE
elif self.now_colour is BLUE:
pixels[0] = BLACK
pixels.show()
self.now_colour = BLACK
def run():
app = App()
asynccp.schedule(frequency=1, coroutine_function=app.scan_NFC)
asynccp.schedule(frequency=0.2, coroutine_function=app.play_audio)
asynccp.schedule(frequency=0.1, coroutine_function=app.toggle_colour)
asynccp.run()
if __name__ == '__main__':
run() |
sorry - the insert code did not work as wanted |
I corrected the code embed. However, a better place to seek help using CircuitPython is our Discord in #help-with-circuitpython or the Adafruit Forum. Since you're using a third party library (asynccp) you may also wish to inquire in whatever support forum they suggest. |
I'd suggest you should separate your app data from your app actions. Something like this: class AppState:
def __init__(self):
self.now_audio = 'Hei.wav'
self.now_colour = BLACK
async def scan_NFC():
# This is an unfortunate expression. Is there not a start_read_passive_target() and is_read_passive_target_done()? Because then you could actually read from this and not block for up to 100 milliseconds like you're doing here.
uid = pn532.read_passive_target(timeout=0.1)
print(".", end="")
if uid is not None:
text_ID = '' # This wasn't storing to app_state so I trimmed it from app_state. Don't keep useless state!
for e in uid:
text_ID += '' + str(e)
print(text_ID)
async def play_audio(app_state):
print("s", end="")
if self.now_audio != '':
audio.play(audiocore.WaveFile(open(app_state.now_audio, "rb")))
async def toggle_colour(app_state):
print("t", end="")
if app_state.now_colour is BLACK:
pixels[0] = BLUE
pixels.show()
app_state.now_colour = BLUE
elif app_state.now_colour is BLUE:
pixels[0] = BLACK
pixels.show()
app_state.now_colour = BLACK
def run():
app_state = AppState()
asynccp.schedule(frequency=1, coroutine_function=app.scan_NFC, app_state)
asynccp.schedule(frequency=0.2, coroutine_function=app.play_audio, app_state)
asynccp.schedule(frequency=0.1, coroutine_function=app.toggle_colour, app_state)
asynccp.run()
if __name__ == '__main__':
run() I definitely echo @jepler's advice about looking to the CircuitPython discord or Adafruit Forums for help writing circuitpython code! |
@jepler, @WarriorOfWire, thanks for your help. I shall go to discord, but since asynccp is not part of basic circuitpython I thought i'd risk a question here. I struggle to get my head around class/objects - in this instance I don't understand how the state variables work. I have got the code up and running through, and the different functions 'communicate' with each other by means og global variables. I'm sure that is the bad way to do it, but so far as I do not understand better, that is the way I have to do it. Agree about the pn532.read_passive_target(timeout=0.1) behaving as a blocker. I have squeezed the time down to 0.05s though. To get a start_read & is_read option I will have to figure out commands directly to the NFC chip. Apologize for misusing git for solving my problems. |
This change:
__await__()
support as the backing implementation hook for CircuitPython async/await noun/verb pair.if sys.implementation.name in ('micropython', 'circuitpython'):
from micropython async tests. These tests now behave like CPython in CircuitPython.By using
__await__()
rather than__iter__()
(uPython uses iter), CircuitPython is more amenable to library interoperation with CPython. Users (and crucially, library developers!) can implement non-blocking APIs for complex devices in pure Python without writing state machines and saddling their application code with polling machines.This is syntax-only. Without a task scheduler, one can use this language infrastructure via the
send()
method on the coroutine-generators though that is not the intended usage pattern.Today there is an example task scheduler implementation based on a standard python
asyncio
usage pattern. It is different from asyncio in material ways but it provides an easy way to schedule recurring tasks and asynchronously drive disparate tasks without aloop()
method. https://github.com/WarriorOfWire/taskoThis has used practically on my personal projects for months but has not achieved wide adoption through my personal repository. The error text is typically close to what CPython outputs and most cases of invalid syntax are reported similarly. It is expected that additional errata exist but the discovery and prioritization of resolution of the same should be shared going forward.