@@ -20,9 +20,10 @@ This document assumes familiarity with `asyncio`. See [official docs](http://doc
2020 5.1 [ Use of Delay_ms] ( ./EVENTS.md#51-use-of-delay_ms ) A retriggerable delay
2121 5.2 [ Long and very long button press] ( ./EVENTS.md#52-long-and-very-long-button-press )
2222 5.3 [ Application example] ( ./EVENTS.md#53-application-example )
23- 6 . [ Drivers] ( ./EVENTS.md#6-drivers ) Minimal Event-based drivers
24- 6.1 [ ESwitch] ( ./EVENTS.md#61-eswitch ) Debounced switch
25- 6.2 [ EButton] ( ./EVENTS.md#62-ebutton ) Debounced pushbutton with double and long press events
23+ 6 . [ ELO class] ( ./EVENTS.md#6-elo-class ) Convert a coroutine or task to an event-like object.
24+ 7 . [ Drivers] ( ./EVENTS.md#7-drivers ) Minimal Event-based drivers
25+ 7.1 [ ESwitch] ( ./EVENTS.md#71-eswitch ) Debounced switch
26+ 7.2 [ EButton] ( ./EVENTS.md#72-ebutton ) Debounced pushbutton with double and long press events
2627
2728[ Appendix 1 Polling] ( ./EVENTS.md#100-appendix-1-polling )
2829
@@ -61,6 +62,11 @@ Users only need to know the names of the bound `Event` instances. By contast
6162there is no standard way to specify callbacks, to define the passing of
6263callback arguments or to define how to retrieve their return values.
6364
65+ There are other ways to define an API without callbacks, notably the stream
66+ mechanism and the use of asynchronous iterators with ` async for ` . This doc
67+ discusses the ` Event ` based approach which is ideal for sporadic occurrences
68+ such as responding to user input.
69+
6470###### [ Contents] ( ./EVENTS.md#0-contents )
6571
6672# 2. Rationale
@@ -135,6 +141,10 @@ ELO examples are:
135141| [ Delay_ms] [ 2m ] | Y | Y | Y | Self-setting |
136142| [ WaitAll] ( ./EVENTS.md#42-waitall ) | Y | Y | N | See below |
137143| [ WaitAny] ( ./EVENTS.md#41-waitany ) | Y | Y | N | |
144+ | [ ELO instances] ( ./EVENTS.md#44-elo-class ) | Y | N | N | |
145+
146+ The ` ELO ` class converts coroutines or ` Task ` instances to event-like objects,
147+ allowing them to be included in the arguments of event based primitives.
138148
139149Drivers exposing ` Event ` instances include:
140150
@@ -316,19 +326,118 @@ async def foo():
316326 else :
317327 # Normal outcome, process readings
318328```
329+ ###### [ Contents] ( ./EVENTS.md#0-contents )
330+
331+ # 6. ELO class
332+
333+ This converts a task to an "event-like object", enabling tasks to be included in
334+ ` WaitAll ` and ` WaitAny ` arguments. An ` ELO ` instance is a wrapper for a ` Task `
335+ instance and its lifetime is that of its ` Task ` . The constructor can take a
336+ coroutine or a task as its first argument; in the former case the coro is
337+ converted to a ` Task ` .
338+
339+ #### Constructor args
340+
341+ 1 . ` coro ` This may be a coroutine or a ` Task ` instance.
342+ 2 . ` *args ` Positional args for a coroutine (ignored if a ` Task ` is passed).
343+ 3 . ` **kwargs ` Keyword args for a coroutine (ignored if a ` Task ` is passed).
344+
345+ If a coro is passed it is immediately converted to a ` Task ` and scheduled for
346+ execution.
347+
348+ #### Asynchronous method
349+
350+ 1 . ` wait ` Pauses until the ` Task ` is complete or is cancelled. In the latter
351+ case no exception is thrown.
352+
353+ #### Synchronous method
354+
355+ 1 . ` __call__ ` Returns the instance's ` Task ` . If the instance's ` Task ` was
356+ cancelled the ` CancelledError ` exception is returned. The function call operator
357+ allows a running task to be accessed, e.g. for cancellation. It also enables return values to be
358+ retrieved.
359+
360+ #### Usage example
361+
362+ In most use cases an ` ELO ` instance is a throw-away object which allows a coro
363+ to participate in an event-based primitive:
364+ ``` python
365+ evt = asyncio.Event()
366+ async def my_coro (t ):
367+ await asyncio.wait(t)
368+
369+ async def foo (): # Puase until the event has been triggered and coro has completed
370+ await WaitAll((evt, ELO(my_coro, 5 ))).wait() # Note argument passing
371+ ```
372+ #### Retrieving results
373+
374+ A task may return a result on completion. This may be accessed by awaiting the
375+ ` ELO ` instance's ` Task ` . A reference to the ` Task ` may be acquired with function
376+ call syntax. The following code fragment illustrates usage. It assumes that
377+ ` task ` has already been created, and that ` my_coro ` is a coroutine taking an
378+ integer arg. There is an ` EButton ` instance ` ebutton ` and execution pauses until
379+ tasks have run to completion and the button has been pressed.
380+ ``` python
381+ async def foo ():
382+ elos = (ELO(my_coro, 5 ), ELO(task))
383+ events = (ebutton.press,)
384+ await WaitAll(elos + events).wait()
385+ for e in elos: # Retrieve results from each task
386+ r = await e() # Works even though task has already completed
387+ print (r)
388+ ```
389+ This works because it is valid to ` await ` a task which has already completed.
390+ The ` await ` returns immediately with the result. If ` WaitAny ` were used an ` ELO `
391+ instance might contain a running task. In this case the line
392+ ``` python
393+ r = await e()
394+ ```
395+ would pause before returning the result.
396+
397+ #### Cancellation
398+
399+ The ` Task ` in ` ELO ` instance ` elo ` may be retrieved by issuing ` elo() ` . For
400+ example the following will subject an ` ELO ` instance to a timeout:
401+ ``` python
402+ async def elo_timeout (elo , t ):
403+ await asyncio.sleep(t)
404+ elo().cancel() # Retrieve the Task and cancel it
405+
406+ async def foo ():
407+ elo = ELO(my_coro, 5 )
408+ asyncio.create_task(elo_timeout(2 ))
409+ await WaitAll((elo, ebutton.press)).wait() # Until button press and ELO either finished or timed out
410+ ```
411+ If the ` ELO ` task is cancelled, ` .wait ` terminates; the exception is retained.
412+ Thus ` WaitAll ` or ` WaitAny ` behaves as if the task had terminated normally. A
413+ subsequent call to ` elo() ` will return the exception. In an application
414+ where the task might return a result or be cancelled, the following may be used:
415+ ``` python
416+ async def foo ():
417+ elos = (ELO(my_coro, 5 ), ELO(task))
418+ events = (ebutton.press,)
419+ await WaitAll(elos + events).wait()
420+ for e in elos: # Check each task
421+ t = e()
422+ if isinstance (t, asyncio.CancelledError):
423+ # Handle exception
424+ else : # Retrieve results
425+ r = await t # Works even though task has already completed
426+ print (r)
427+ ```
319428
320429###### [ Contents] ( ./EVENTS.md#0-contents )
321430
322- # 6 . Drivers
431+ # 7 . Drivers
323432
324433The following device drivers provide an ` Event ` based interface for switches and
325434pushbuttons.
326435
327- ## 6 .1 ESwitch
436+ ## 7 .1 ESwitch
328437
329438This is now documented [ here] ( ./DRIVERS.md#31-eswitch-class ) .
330439
331- ## 6 .2 EButton
440+ ## 7 .2 EButton
332441
333442This is now documented [ here] ( ./DRIVERS.md#41-ebutton-class ) .
334443
0 commit comments