@@ -10,25 +10,25 @@ MicroPython's `asyncio` when used in a microcontroller context.
10
10
3 . [ Interfacing switches] ( ./DRIVERS.md#3-interfacing-switches )
11
11
3.1 [ ESwitch class] ( ./DRIVERS.md#31-eswitch-class ) Switch debouncer with event interface.
12
12
3.2 [ Switch class] ( ./DRIVERS.md#32-switch-class ) Switch debouncer with callbacks.
13
- 4 . [ Interfacing pushbuttons] ( ./DRIVERS.md#4-interfacing-pushbuttons ) Extends Switch for long and double-click events
14
- 4.1 [ EButton class] ( ./DRIVERS.md#41-ebutton-class ) Pushbutton with Event-based interface.
15
- 4.2 [ Pushbutton class] ( ./DRIVERS.md#42-pushbutton-class )
16
-   ;  ;  ;  ;  ; 4.2.1 [ The suppress constructor argument] ( ./DRIVERS.md#431 -the-suppress-constructor-argument )
17
-   ;  ;  ;  ;  ; 4.2.2 [ The sense constructor argument] ( ./DRIVERS.md#432 -the-sense-constructor-argument )
13
+ 4 . [ Interfacing pushbuttons] ( ./DRIVERS.md#4-interfacing-pushbuttons ) Access short, long and double-click events.
14
+ 4.1 [ EButton class] ( ./DRIVERS.md#41-ebutton-class ) Debounced pushbutton with Event-based interface.
15
+ 4.2 [ Pushbutton class] ( ./DRIVERS.md#42-pushbutton-class ) Debounced pushbutton with callback interface.
16
+   ;  ;  ;  ;  ; 4.2.1 [ The suppress constructor argument] ( ./DRIVERS.md#421 -the-suppress-constructor-argument )
17
+   ;  ;  ;  ;  ; 4.2.2 [ The sense constructor argument] ( ./DRIVERS.md#422 -the-sense-constructor-argument )
18
18
4.3 [ ESP32Touch class] ( ./DRIVERS.md#43-esp32touch-class )
19
- 4.4 [ keyboard class] ( ./DRIVERS.md#44-keyboard-class )
20
- 4.5 [ SwArray class] ( ./DRIVERS.md#45-swarray-class )
19
+ 4.4 [ Keyboard class] ( ./DRIVERS.md#44-keyboard-class ) Retrieve characters from a keypad.
20
+ 4.5 [ SwArray class] ( ./DRIVERS.md#45-swarray-class ) Interface a crosspoint array of switches or buttons.
21
21
4.6 [ Suppress mode] ( ./DRIVERS.md#46-suppress-mode ) Reduce the number of events/callbacks.
22
22
5 . [ ADC monitoring] ( ./DRIVERS.md#5-adc-monitoring ) Pause until an ADC goes out of bounds
23
23
5.1 [ AADC class] ( ./DRIVERS.md#51-aadc-class )
24
24
5.2 [ Design note] ( ./DRIVERS.md#52-design-note )
25
- 6 . [ Quadrature encoders] ( ./DRIVERS.md#6-quadrature-encoders )
25
+ 6 . [ Quadrature encoders] ( ./DRIVERS.md#6-quadrature-encoders ) Asynchronous interface for rotary encoders.
26
26
6.1 [ Encoder class] ( ./DRIVERS.md#61-encoder-class )
27
27
7 . [ Ringbuf Queue] ( ./DRIVERS.md#7-ringbuf-queue ) A MicroPython optimised queue primitive.
28
- 8 . [ Additional functions ] ( ./DRIVERS.md#8-additional-functions )
29
- 8.1 [ launch ] ( ./DRIVERS.md#81-launch ) Run a coro or callback interchangeably
30
- 8.2 [ set_global_exception ] ( ./DRIVERS.md#82-set_global_exception ) Simplify debugging with a global exception handler .
31
- 9 . [ Event based interface ] ( ./DRIVERS.md#9-event-based-interface ) An alternative interface to Switch and Pushbutton objects .
28
+ 8 . [ Delay_ms class ] (./DRIVERS.md#8-delay_ms class) A flexible retriggerable delay with callback or Event interface.
29
+ 9 . [ Additional functions ] ( ./DRIVERS.md#9-additional-functions )
30
+ 9.1 [ launch ] ( ./DRIVERS.md#91-launch ) Run a coro or callback interchangeably .
31
+ 9.2 [ set_global_exception ] ( ./DRIVERS.md#92-set_global_exception ) Simplify debugging with a global exception handler .
32
32
33
33
###### [ Tutorial] ( ./TUTORIAL.md#contents )
34
34
@@ -43,15 +43,21 @@ to the existing CPython-compatible primitives.
43
43
44
44
## 1.1 API design
45
45
46
- The traditional interface to asynchronous external events is a callback. When
47
- the event occurs, the device driver runs a user-specified callback. Some classes
48
- described here offer a callback interface; newer designs have abandoned this in
49
- favour of asynchronous interfaces by exposing ` Event ` or asynchronous iterator
50
- interfaces. Note that where callbacks are used the term ` callable ` implies a
51
- Python ` callable ` : namely a function, bound method, coroutine or bound
52
- coroutine. Any of these may be supplied as a callback function.
46
+ The traditional interface to asynchronous external events is via a callback.
47
+ When the event occurs, the device driver runs a user-specified callback. Some
48
+ classes described here offer a callback interface. Where callbacks are used the
49
+ term ` callable ` implies a Python ` callable ` : namely a function, bound method,
50
+ coroutine or bound coroutine. Any of these may be supplied as a callback
51
+ function.
53
52
54
- Asynchronous interfaces allow the use of callbacks using patterns like the
53
+
54
+ Newer class designs abandon callbacks in favour of asynchronous interfaces. This
55
+ is done by exposing ` Event ` or asynchronous iterator interfaces. It is arguable
56
+ that callbacks are outdated. Handling of arguments and return values is
57
+ inelegant and there are usually better ways using asynchronous coding. In
58
+ particular MicroPython's ` asyncio ` implements asynchronous interfaces in an
59
+ efficient manner. A task waiting on an ` Event ` consumes minimal resources. If a
60
+ user wishes to use a callback it may readily be achieved using patterns like the
55
61
following. In this case the device is an asynchronous iterator:
56
62
``` python
57
63
async def run_callback (device , callback , * args ):
@@ -66,10 +72,6 @@ async def run_callback(device, callback, *args):
66
72
device.clear() # Clear it down
67
73
callback(* args)
68
74
```
69
- It is arguable that callbacks are outdated. Handling of arguments and return
70
- values is messy and there are usually better ways using asynchronous coding. In
71
- particular MicroPython's ` asyncio ` implements asynchronous interfaces in an
72
- efficient manner. A task waiting on an ` Event ` consumes minimal resources.
73
75
74
76
## 1.2 Switches
75
77
@@ -131,13 +133,14 @@ To prevent this it is wise to add physical resistors between the input pins and
131
133
132
134
# 2. Installation and usage
133
135
134
- The latest release build of firmware or a newer nightly build is recommended.
136
+ The latest release build of firmware or a newer preview build is recommended.
135
137
To install the library, connect the target hardware to WiFi and issue:
136
138
``` python
137
139
import mip
138
140
mip.install(" github:peterhinch/micropython-async/v3/primitives" )
139
141
```
140
- For any target including non-networked ones use ` mpremote ` :
142
+ For any target including non-networked ones use
143
+ [ mpremote] ( https://docs.micropython.org/en/latest/reference/mpremote.html ) :
141
144
``` bash
142
145
$ mpremote mip install " github:peterhinch/micropython-async/v3/primitives"
143
146
```
@@ -171,7 +174,7 @@ minimal driver providing an `Event` interface. The latter supports callbacks and
171
174
## 3.1 ESwitch class
172
175
173
176
``` python
174
- from primitives import ESwitch # evennts .py
177
+ from primitives import ESwitch # events .py
175
178
```
176
179
This provides a debounced interface to a switch connected to gnd or to 3V3. A
177
180
pullup or pull down resistor should be supplied to ensure a valid logic level
@@ -185,7 +188,7 @@ pin = Pin(pin_id, Pin.IN, Pin.PULL_UP)
185
188
```
186
189
Constructor arguments:
187
190
188
- 1 . ` pin ` The Pin instance: should be initialised as an input with a pullup or
191
+ 1 . ` pin ` The ` Pin ` instance: should be initialised as an input with a pullup or
189
192
down as appropriate.
190
193
2 . ` lopen=1 ` Electrical level when switch is open circuit i.e. 1 is 3.3V, 0 is
191
194
gnd.
@@ -298,7 +301,7 @@ The `primitives` module provides the following classes for interfacing
298
301
pushbuttons. The following support normally open or normally closed buttons
299
302
connected to gnd or to 3V3:
300
303
* ` EButton ` Provides an ` Event ` based interface.
301
- * ` Pushbutton ` Offers ` Event ` s and/or callbacks.
304
+ * ` Pushbutton ` Offers ` Event ` s and/or callbacks.
302
305
The following support normally open pushbuttons connected in a crosspoint array.
303
306
* ` Keyboard ` An asynchronous iterator responding to button presses.
304
307
* ` SwArray ` As above, but also supporting open, double and long events.
@@ -328,7 +331,7 @@ Constructor arguments:
328
331
1 . ` pin ` Mandatory. The initialised Pin instance.
329
332
2 . ` suppress=False ` . See [ Suppress mode] ( ./DRIVERS.md#46-suppress-mode ) .
330
333
3 . ` sense=None ` . Optionally define the electrical connection: see
331
- [ section 4.2.1] ( ./EVENTS .md#421 -the-sense-constructor-argument ) .
334
+ [ section 4.2.1] ( ./DRIVERS .md#411 -the-sense-constructor-argument ) .
332
335
333
336
Methods:
334
337
@@ -395,7 +398,8 @@ Please see the note on timing in [section 3](./DRIVERS.md#3-interfacing-switches
395
398
Constructor arguments:
396
399
397
400
1 . ` pin ` Mandatory. The initialised Pin instance.
398
- 2 . ` suppress ` Default ` False ` . See [ Suppress mode] ( ./DRIVERS.md#46-suppress-mode ) .
401
+ 2 . ` suppress ` Default ` False ` . See
402
+ [ section 4.2.2] ( ./DRIVERS.md#422-the-suppress-constructor-argument ) .
399
403
3 . ` sense ` Default ` None ` . Option to define electrical connection. See
400
404
[ section 4.2.1] ( ./DRIVERS.md#421-the-sense-constructor-argument ) .
401
405
@@ -619,7 +623,7 @@ async def receiver(uart):
619
623
print (' Received' , res)
620
624
621
625
async def main (): # Run forever
622
- rowpins = [Pin(p, Pin.OPEN_DRAIN ) for p in range (10 , 14 )]
626
+ rowpins = [Pin(p, Pin.OPEN_DRAIN ) for p in range (10 , 13 )]
623
627
colpins = [Pin(p, Pin.IN , Pin.PULL_UP ) for p in range (16 , 20 )]
624
628
uart = UART(0 , 9600 , tx = 0 , rx = 1 )
625
629
asyncio.create_task(receiver(uart))
@@ -635,12 +639,14 @@ asyncio.run(main())
635
639
## 4.5 SwArray class
636
640
637
641
``` python
638
- from primitives import SwArray # sw_array.py
642
+ from primitives.sw_array import SwArray, CLOSE , OPEN , LONG , DOUBLE , SUPPRESS
639
643
```
640
644
An ` SwArray ` is similar to a ` Keyboard ` except that single, double and long
641
645
presses are supported. Items in the array may be switches or pushbuttons,
642
646
however if switches are used they must be diode-isolated. For the reason see
643
- [ Switches] ( ./DRIVERS.md#12-switches ) .
647
+ [ Switches] ( ./DRIVERS.md#12-switches ) . It is an asynchronous iterator with events
648
+ being retrieved with ` async for ` : this returns a pair of integers being the scan
649
+ code and a bit representing the event which occurred.
644
650
645
651
Constructor mandatory args:
646
652
* ` rowpins ` A list or tuple of initialised open drain output pins.
@@ -668,8 +674,8 @@ Constructor optional keyword only args:
668
674
* ` double_click_ms = 400 ` Threshold for double-click detection.
669
675
670
676
Module constants.
671
- The folowing constants are provided to simplify defining the ` cfg ` constructor
672
- arg. This may be defined as a bitwise or of selected constants. For example if
677
+ The following constants are provided to simplify defining the ` cfg ` constructor
678
+ arg. This may be defined as a bitwise ` or ` of selected constants. For example if
673
679
the ` CLOSE ` bit is specified, switch closures will be reported. An omitted event
674
680
will be ignored. Where the array comprises switches it is usual to specify only
675
681
` CLOSE ` and/or ` OPEN ` . This invokes a more efficient mode of operation because
@@ -678,11 +684,17 @@ timing is not required.
678
684
* ` OPEN ` Contact opening.
679
685
* ` LONG ` Contact closure longer than ` long_press_ms ` .
680
686
* ` DOUBLE ` Two closures in less than ` double_click_ms ` .
681
- * ` SUPPRESS ` Disambiguate. For explanation see ` EButton ` .
687
+ * ` SUPPRESS ` Disambiguate. For explanation see
688
+ [ Suppress mode] ( ./DRIVERS.md#46-suppress-mode ) . If all the above bits are set,
689
+ a double click will result in ` DOUBLE ` and ` OPEN ` responses. If the ` OPEN ` bit
690
+ were clear, only ` DOUBLE ` would occur.
682
691
683
692
The ` SwArray ` class is subclassed from [ Ringbuf Queue] ( ./DRIVERS.md#7-ringbuf-queue ) .
684
693
This is an asynchronous iterator, enabling scan codes and event types to be
685
- retrieved as state changes occur with ` async for ` :
694
+ retrieved as state changes occur. The event type is a single bit corresponding
695
+ to the above constants.
696
+
697
+ Usage example:
686
698
``` python
687
699
import asyncio
688
700
from primitives.sw_array import SwArray, CLOSE , OPEN , LONG , DOUBLE , SUPPRESS
@@ -948,6 +960,9 @@ efficiency. As the name suggests, the `RingbufQueue` class uses a pre-allocated
948
960
circular buffer which may be of any mutable type supporting the buffer protocol
949
961
e.g. ` list ` , ` array ` or ` bytearray ` .
950
962
963
+ It should be noted that ` Queue ` , ` RingbufQueue ` (and CPython's ` Queue ` ) are not
964
+ thread safe. See [ Threading] ( ./THREADING.md ) .
965
+
951
966
Attributes of ` RingbufQueue ` :
952
967
1 . It is of fixed size, ` Queue ` can grow to arbitrary size.
953
968
2 . It uses pre-allocated buffers of various types (` Queue ` uses a ` list ` ).
@@ -1003,9 +1018,114 @@ def add_item(q, data):
1003
1018
```
1004
1019
###### [ Contents] ( ./DRIVERS.md#0-contents )
1005
1020
1006
- # 8. Additional functions
1021
+ ## 3.8 Delay_ms class
1022
+
1023
+ This implements the software equivalent of a retriggerable monostable or a
1024
+ watchdog timer. It has an internal boolean ` running ` state. When instantiated
1025
+ the ` Delay_ms ` instance does nothing, with ` running ` ` False ` until triggered.
1026
+ Then ` running ` becomes ` True ` and a timer is initiated. This can be prevented
1027
+ from timing out by triggering it again (with a new timeout duration). So long
1028
+ as it is triggered before the time specified in the preceding trigger it will
1029
+ never time out.
1030
+
1031
+ If it does time out the ` running ` state will revert to ` False ` . This can be
1032
+ interrogated by the object's ` running() ` method. In addition a ` callable ` can
1033
+ be specified to the constructor. A ` callable ` can be a callback or a coroutine.
1034
+ A callback will execute when a timeout occurs; where the ` callable ` is a
1035
+ coroutine it will be converted to a ` Task ` and run asynchronously.
1036
+
1037
+ Constructor arguments (defaults in brackets):
1038
+
1039
+ 1 . ` func ` The ` callable ` to call on timeout (default ` None ` ).
1040
+ 2 . ` args ` A tuple of arguments for the ` callable ` (default ` () ` ).
1041
+ 3 . ` can_alloc ` Unused arg, retained to avoid breaking code.
1042
+ 4 . ` duration ` Integer, default 1000 ms. The default timer period where no value
1043
+ is passed to the ` trigger ` method.
1044
+
1045
+ Synchronous methods:
1046
+
1047
+ 1 . ` trigger ` optional argument ` duration=0 ` . A timeout will occur after
1048
+ ` duration ` ms unless retriggered. If no arg is passed the period will be that
1049
+ of the ` duration ` passed to the constructor. The method can be called from a
1050
+ hard or soft ISR. It is now valid for ` duration ` to be less than the current
1051
+ time outstanding.
1052
+ 2 . ` stop ` No argument. Cancels the timeout, setting the ` running ` status
1053
+ ` False ` . The timer can be restarted by issuing ` trigger ` again. Also clears
1054
+ the ` Event ` described in ` wait ` below.
1055
+ 3 . ` running ` No argument. Returns the running status of the object.
1056
+ 4 . ` __call__ ` Alias for running.
1057
+ 5 . ` rvalue ` No argument. If a timeout has occurred and a callback has run,
1058
+ returns the return value of the callback. If a coroutine was passed, returns
1059
+ the ` Task ` instance. This allows the ` Task ` to be cancelled or awaited.
1060
+ 6 . ` callback ` args ` func=None ` , ` args=() ` . Allows the callable and its args to
1061
+ be assigned, reassigned or disabled at run time.
1062
+ 7 . ` deinit ` No args. Cancels the running task. See [ Object scope] ( ./TUTORIAL.md#44-object-scope ) .
1063
+ 8 . ` clear ` No args. Clears the ` Event ` described in ` wait ` below.
1064
+ 9 . ` set ` No args. Sets the ` Event ` described in ` wait ` below.
1065
+
1066
+ Asynchronous method:
1067
+ 1 . ` wait ` One or more tasks may wait on a ` Delay_ms ` instance. Pause until the
1068
+ delay instance has timed out.
1069
+
1070
+ In this example a ` Delay_ms ` instance is created with the default duration of
1071
+ 1 sec. It is repeatedly triggered for 5 secs, preventing the callback from
1072
+ running. One second after the triggering ceases, the callback runs.
1073
+
1074
+ ``` python
1075
+ import asyncio
1076
+ from primitives import Delay_ms
1077
+
1078
+ async def my_app ():
1079
+ d = Delay_ms(callback, (' Callback running' ,))
1080
+ print (' Holding off callback' )
1081
+ for _ in range (10 ): # Hold off for 5 secs
1082
+ await asyncio.sleep_ms(500 )
1083
+ d.trigger()
1084
+ print (' Callback will run in 1s' )
1085
+ await asyncio.sleep(2 )
1086
+ print (' Done' )
1087
+
1088
+ def callback (v ):
1089
+ print (v)
1090
+
1091
+ try :
1092
+ asyncio.run(my_app())
1093
+ finally :
1094
+ asyncio.new_event_loop() # Clear retained state
1095
+ ```
1096
+ This example illustrates multiple tasks waiting on a ` Delay_ms ` . No callback is
1097
+ used.
1098
+ ``` python
1099
+ import asyncio
1100
+ from primitives import Delay_ms
1101
+
1102
+ async def foo (n , d ):
1103
+ await d.wait()
1104
+ d.clear() # Task waiting on the Event must clear it
1105
+ print (' Done in foo no.' , n)
1106
+
1107
+ async def my_app ():
1108
+ d = Delay_ms()
1109
+ tasks = [None ] * 4 # For CPython compaibility must store a reference see Note
1110
+ for n in range (4 ):
1111
+ tasks[n] = asyncio.create_task(foo(n, d))
1112
+ d.trigger(3000 )
1113
+ print (' Waiting on d' )
1114
+ await d.wait()
1115
+ print (' Done in my_app.' )
1116
+ await asyncio.sleep(1 )
1117
+ print (' Test complete.' )
1118
+
1119
+ try :
1120
+ asyncio.run(my_app())
1121
+ finally :
1122
+ _ = asyncio.new_event_loop() # Clear retained state
1123
+ ```
1124
+ ###### [ Contents] ( ./DRIVERS.md#0-contents )
1125
+
1126
+ # 9. Additional functions
1007
1127
1008
- ## 8 .1 Launch
1128
+ ## 9 .1 Launch
1009
1129
1010
1130
Import as follows:
1011
1131
``` python
@@ -1017,7 +1137,7 @@ runs it and returns the callback's return value. If a coro is passed, it is
1017
1137
converted to a ` task ` and run asynchronously. The return value is the ` task `
1018
1138
instance. A usage example is in ` primitives/switch.py ` .
1019
1139
1020
- ## 8 .2 set_global_exception
1140
+ ## 9 .2 set_global_exception
1021
1141
1022
1142
Import as follows:
1023
1143
``` python
@@ -1047,57 +1167,3 @@ events can be hard to deduce. A global handler ensures that the entire
1047
1167
application stops allowing the traceback and other debug prints to be studied.
1048
1168
1049
1169
###### [ Contents] ( ./DRIVERS.md#0-contents )
1050
-
1051
- # 9. Event based interface
1052
-
1053
- The ` Switch ` and ` Pushbutton ` classes offer a traditional callback-based
1054
- interface. While familiar, it has drawbacks and requires extra code to perform
1055
- tasks like retrieving the result of a callback or, where a task is launched,
1056
- cancelling that task. The reason for this API is historical; an efficient
1057
- ` Event ` class only materialised with ` uasyncio ` V3. The class ensures that a
1058
- task waiting on an ` Event ` consumes minimal processor time.
1059
-
1060
- It is suggested that this API is used in new projects.
1061
-
1062
- The event based interface to ` Switch ` and ` Pushbutton ` classes is engaged by
1063
- passing ` None ` to the methods used to register callbacks. This causes a bound
1064
- ` Event ` to be instantiated, which may be accessed by user code.
1065
-
1066
- The following shows the name of the bound ` Event ` created when ` None ` is passed
1067
- to a method:
1068
-
1069
- | Class | method | Event |
1070
- | :-----------| :-------------| :--------|
1071
- | Switch | close_func | close |
1072
- | Switch | open_func | open |
1073
- | Pushbutton | press_func | press |
1074
- | Pushbutton | release_func | release |
1075
- | Pushbutton | long_func | long |
1076
- | Pushbutton | double_func | double |
1077
-
1078
- Typical usage is as follows:
1079
- ``` python
1080
- import asyncio
1081
- from primitives import Switch
1082
- from pyb import Pin
1083
-
1084
- async def foo (evt ):
1085
- while True :
1086
- evt.clear() # re-enable the event
1087
- await evt.wait() # minimal resources used while paused
1088
- print (" Switch closed." )
1089
- # Omitted code runs each time the switch closes
1090
-
1091
- async def main ():
1092
- sw = Switch(Pin(" X1" , Pin.IN , Pin.PULL_UP ))
1093
- sw.close_func(None ) # Use event based interface
1094
- await foo(sw.close) # Pass the bound event to foo
1095
-
1096
- asyncio.run(main())
1097
- ```
1098
- With appropriate code the behaviour of the callback based interface may be
1099
- replicated, but with added benefits. For example the omitted code in ` foo `
1100
- could run a callback-style synchronous method, retrieving its value.
1101
- Alternatively the code could create a task which could be cancelled.
1102
-
1103
- ###### [ Contents] ( ./DRIVERS.md#0-contents )
0 commit comments