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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy