Skip to content

Commit 3fa515f

Browse files
committed
First pass at revised docs.
1 parent 36d10c2 commit 3fa515f

File tree

3 files changed

+183
-589
lines changed

3 files changed

+183
-589
lines changed

v3/docs/DRIVERS.md

Lines changed: 161 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,25 @@ MicroPython's `asyncio` when used in a microcontroller context.
1010
3. [Interfacing switches](./DRIVERS.md#3-interfacing-switches)
1111
3.1 [ESwitch class](./DRIVERS.md#31-eswitch-class) Switch debouncer with event interface.
1212
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)
1818
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.
2121
4.6 [Suppress mode](./DRIVERS.md#46-suppress-mode) Reduce the number of events/callbacks.
2222
5. [ADC monitoring](./DRIVERS.md#5-adc-monitoring) Pause until an ADC goes out of bounds
2323
5.1 [AADC class](./DRIVERS.md#51-aadc-class)
2424
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.
2626
6.1 [Encoder class](./DRIVERS.md#61-encoder-class)
2727
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.
3232

3333
###### [Tutorial](./TUTORIAL.md#contents)
3434

@@ -43,15 +43,21 @@ to the existing CPython-compatible primitives.
4343

4444
## 1.1 API design
4545

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.
5352

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
5561
following. In this case the device is an asynchronous iterator:
5662
```python
5763
async def run_callback(device, callback, *args):
@@ -66,10 +72,6 @@ async def run_callback(device, callback, *args):
6672
device.clear() # Clear it down
6773
callback(*args)
6874
```
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.
7375

7476
## 1.2 Switches
7577

@@ -131,13 +133,14 @@ To prevent this it is wise to add physical resistors between the input pins and
131133

132134
# 2. Installation and usage
133135

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.
135137
To install the library, connect the target hardware to WiFi and issue:
136138
```python
137139
import mip
138140
mip.install("github:peterhinch/micropython-async/v3/primitives")
139141
```
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):
141144
```bash
142145
$ mpremote mip install "github:peterhinch/micropython-async/v3/primitives"
143146
```
@@ -171,7 +174,7 @@ minimal driver providing an `Event` interface. The latter supports callbacks and
171174
## 3.1 ESwitch class
172175

173176
```python
174-
from primitives import ESwitch # evennts.py
177+
from primitives import ESwitch # events.py
175178
```
176179
This provides a debounced interface to a switch connected to gnd or to 3V3. A
177180
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)
185188
```
186189
Constructor arguments:
187190

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
189192
down as appropriate.
190193
2. `lopen=1` Electrical level when switch is open circuit i.e. 1 is 3.3V, 0 is
191194
gnd.
@@ -298,7 +301,7 @@ The `primitives` module provides the following classes for interfacing
298301
pushbuttons. The following support normally open or normally closed buttons
299302
connected to gnd or to 3V3:
300303
* `EButton` Provides an `Event` based interface.
301-
* `Pushbutton` Offers `Event`s and/or callbacks.
304+
* `Pushbutton` Offers `Event`s and/or callbacks.
302305
The following support normally open pushbuttons connected in a crosspoint array.
303306
* `Keyboard` An asynchronous iterator responding to button presses.
304307
* `SwArray` As above, but also supporting open, double and long events.
@@ -328,7 +331,7 @@ Constructor arguments:
328331
1. `pin` Mandatory. The initialised Pin instance.
329332
2. `suppress=False`. See [Suppress mode](./DRIVERS.md#46-suppress-mode).
330333
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).
332335

333336
Methods:
334337

@@ -395,7 +398,8 @@ Please see the note on timing in [section 3](./DRIVERS.md#3-interfacing-switches
395398
Constructor arguments:
396399

397400
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).
399403
3. `sense` Default `None`. Option to define electrical connection. See
400404
[section 4.2.1](./DRIVERS.md#421-the-sense-constructor-argument).
401405

@@ -619,7 +623,7 @@ async def receiver(uart):
619623
print('Received', res)
620624

621625
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)]
623627
colpins = [Pin(p, Pin.IN, Pin.PULL_UP) for p in range(16, 20)]
624628
uart = UART(0, 9600, tx=0, rx=1)
625629
asyncio.create_task(receiver(uart))
@@ -635,12 +639,14 @@ asyncio.run(main())
635639
## 4.5 SwArray class
636640

637641
```python
638-
from primitives import SwArray # sw_array.py
642+
from primitives.sw_array import SwArray, CLOSE, OPEN, LONG, DOUBLE, SUPPRESS
639643
```
640644
An `SwArray` is similar to a `Keyboard` except that single, double and long
641645
presses are supported. Items in the array may be switches or pushbuttons,
642646
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.
644650

645651
Constructor mandatory args:
646652
* `rowpins` A list or tuple of initialised open drain output pins.
@@ -668,8 +674,8 @@ Constructor optional keyword only args:
668674
* `double_click_ms = 400` Threshold for double-click detection.
669675

670676
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
673679
the `CLOSE` bit is specified, switch closures will be reported. An omitted event
674680
will be ignored. Where the array comprises switches it is usual to specify only
675681
`CLOSE` and/or `OPEN`. This invokes a more efficient mode of operation because
@@ -678,11 +684,17 @@ timing is not required.
678684
* `OPEN` Contact opening.
679685
* `LONG` Contact closure longer than `long_press_ms`.
680686
* `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.
682691

683692
The `SwArray` class is subclassed from [Ringbuf Queue](./DRIVERS.md#7-ringbuf-queue).
684693
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:
686698
```python
687699
import asyncio
688700
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
948960
circular buffer which may be of any mutable type supporting the buffer protocol
949961
e.g. `list`, `array` or `bytearray`.
950962

963+
It should be noted that `Queue`, `RingbufQueue` (and CPython's `Queue`) are not
964+
thread safe. See [Threading](./THREADING.md).
965+
951966
Attributes of `RingbufQueue`:
952967
1. It is of fixed size, `Queue` can grow to arbitrary size.
953968
2. It uses pre-allocated buffers of various types (`Queue` uses a `list`).
@@ -1003,9 +1018,114 @@ def add_item(q, data):
10031018
```
10041019
###### [Contents](./DRIVERS.md#0-contents)
10051020

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
10071127

1008-
## 8.1 Launch
1128+
## 9.1 Launch
10091129

10101130
Import as follows:
10111131
```python
@@ -1017,7 +1137,7 @@ runs it and returns the callback's return value. If a coro is passed, it is
10171137
converted to a `task` and run asynchronously. The return value is the `task`
10181138
instance. A usage example is in `primitives/switch.py`.
10191139

1020-
## 8.2 set_global_exception
1140+
## 9.2 set_global_exception
10211141

10221142
Import as follows:
10231143
```python
@@ -1047,57 +1167,3 @@ events can be hard to deduce. A global handler ensures that the entire
10471167
application stops allowing the traceback and other debug prints to be studied.
10481168

10491169
###### [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

Comments
 (0)