Skip to content

Commit 603b624

Browse files
authored
Merge branch 'master' into default-pin-state
2 parents 6da7633 + 1738720 commit 603b624

File tree

5 files changed

+215
-65
lines changed

5 files changed

+215
-65
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ ensure your firmware build is official MicroPython V1.12 and follow the
1515
`uasyncio` installation instructions in [the V2 tutorial](./TUTORIAL.md). For
1616
V3, install the latest daily build which includes `uasyncio`.
1717

18+
I strongly recommend V3 unless you need the `fast_io` variant of V2. When V3
19+
acquires this ability (it is planned) and appears in a release build I expect
20+
to obsolete all V2 material in this repo.
21+
1822
Resources for V3 and an updated tutorial may be found in the v3 directory.
1923

2024
### [Go to V3 docs](./v3/README.md)

v3/README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,32 @@ MicroPython and CPython 3.8. This is discussed
103103
Classes based on `uio.IOBase` will need changes to the `write` method. See
104104
[tutorial](./docs/TUTORIAL.md#64-writing-streaming-device-drivers).
105105

106+
### 3.1.3 Early task creation
107+
108+
It is [bad practice](https://github.com/micropython/micropython/issues/6174)
109+
to create tasks before issuing `asyncio.run()`. CPython 3.8 throws if you do.
110+
Such code can be ported by wrapping functions that create tasks in a
111+
coroutine as below.
112+
113+
There is a subtlety affecting code that creates tasks early:
114+
`loop.run_forever()` did just that, never returning and scheduling all created
115+
tasks. By contrast `asyncio.run(coro())` terminates when the coro does. Typical
116+
firmware applications run forever so the coroutine started by `.run()` must
117+
`await` a continuously running task. This may imply exposing an asynchronous
118+
method which runs forever:
119+
120+
```python
121+
async def main():
122+
obj = MyObject() # Constructor creates tasks
123+
await obj.run_forever() # Never terminates
124+
125+
def run(): # Entry point
126+
try:
127+
asyncio.run(main())
128+
finally:
129+
asyncio.new_event_loop()
130+
```
131+
106132
## 3.2 Modules from this repository
107133

108134
Modules `asyn.py` and `aswitch.py` are deprecated for V3 applications. See

v3/docs/DRIVERS.md

Lines changed: 89 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# 1. Introduction
1+
# 0. Introduction
22

33
Drivers for switches and pushbuttons are provided, plus a retriggerable delay
44
class. The switch and button drivers support debouncing. The switch driver
@@ -11,6 +11,22 @@ events.
1111
The asynchronous ADC supports pausing a task until the value read from an ADC
1212
goes outside defined bounds.
1313

14+
# 1. Contents
15+
16+
1. [Contents](./DRIVERS.md#1-contents)
17+
2. [Installation and usage](./DRIVERS.md#2-installation-and-usage)
18+
3. [Interfacing switches](./DRIVERS.md#3-interfacing-switches) Switch debouncer with callbacks.
19+
3.1 [Switch class](./DRIVERS.md#31-switch-class)
20+
4. [Interfacing pushbuttons](./DRIVERS.md#4-interfacing-pushbuttons) Extends Switch for long and double click events
21+
4.1 [Pushbutton class](./DRIVERS.md#41-pushbutton-class)
22+
     4.1.1 [The suppress constructor argument](./DRIVERS.md#411-the-suppress-constructor-argument)
23+
5. [ADC monitoring](./DRIVERS.md#5-adc-monitoring) Pause until an ADC goes out of bounds
24+
5.1 [AADC class](./DRIVERS.md#51-aadc-class)
25+
5.2 [Design note](./DRIVERS.md#52-design-note)
26+
6. [Additional functions](./DRIVERS.md#6-additional-functions)
27+
6.1 [launch](./DRIVERS.md#61-launch) Run a coro or callback interchangeably
28+
6.2 [set_global_exception](./DRIVERS.md#62-set_global_exception) Simplify debugging with a global exception handler
29+
1430
###### [Tutorial](./TUTORIAL.md#contents)
1531

1632
# 2. Installation and usage
@@ -38,11 +54,13 @@ from primitives.tests.adctest import test
3854
test()
3955
```
4056

41-
# 3. primitives.switch
57+
###### [Contents](./DRIVERS.md#1-contents)
58+
59+
# 3. Interfacing switches
4260

43-
This module provides the `Switch` class. This supports debouncing a normally
44-
open switch connected between a pin and ground. Can run callbacks or schedule
45-
coros on contact closure and/or opening.
61+
The `primitives.switch` module provides the `Switch` class. This supports
62+
debouncing a normally open switch connected between a pin and ground. Can run
63+
callbacks or schedule coros on contact closure and/or opening.
4664

4765
In the following text the term `callable` implies a Python `callable`: namely a
4866
function, bound method, coroutine or bound coroutine. The term implies that any
@@ -102,11 +120,14 @@ sw.close_func(pulse, (red, 1000)) # Note how coro and args are passed
102120
asyncio.run(my_app()) # Run main application code
103121
```
104122

105-
# 4. primitives.pushbutton
123+
###### [Contents](./DRIVERS.md#1-contents)
124+
125+
# 4. Interfacing pushbuttons
106126

107-
The `Pushbutton` class is generalisation of `Switch` to support normally open
108-
or normally closed switches connected to ground or 3V3. Can run a `callable` on
109-
on press, release, double-click or long press events.
127+
The `primitives.pushbutton` module provides the `Pushbutton` class. This is a
128+
generalisation of `Switch` to support normally open or normally closed switches
129+
connected to ground or 3V3. Can run a `callable` on on press, release,
130+
double-click or long press events.
110131

111132
## 4.1 Pushbutton class
112133

@@ -175,8 +196,10 @@ pb.press_func(toggle, (red,)) # Note how function and args are passed
175196
asyncio.run(my_app()) # Run main application code
176197
```
177198

178-
An alternative Pushbutton class with lower RAM usage is available
179-
[here](https://github.com/kevinkk525/pysmartnode/blob/dev/pysmartnode/utils/abutton.py).
199+
An alternative, compatible `Pushbutton` implementation is available
200+
[here](https://github.com/kevinkk525/pysmartnode/blob/dev/pysmartnode/utils/abutton.py):
201+
this implementation avoids the use of the `Delay_ms` class to minimise the
202+
number of coroutines.
180203

181204
### 4.1.1 The suppress constructor argument
182205

@@ -209,15 +232,16 @@ the `closed` state of the button is active `high` or active `low`.
209232

210233
This parameter will default to the current value of `pin` for convienence.
211234

235+
###### [Contents](./DRIVERS.md#1-contents)
212236

213-
# 5. primitives.aadc
237+
# 5. ADC monitoring
214238

215-
The `AADC` (asynchronous ADC) class provides for coroutines which pause until
216-
the value returned by an ADC goes outside predefined bounds. The bounds can be
217-
absolute or relative to the current value. The data from ADC's is usually
218-
noisy. Relative bounds provide a simple (if crude) means of eliminating this.
219-
Absolute bounds can be used to raise an alarm, or log data, if the value goes
220-
out of range. Typical usage:
239+
The `primitives.aadc` module provides the `AADC` (asynchronous ADC) class. This
240+
provides for coroutines which pause until the value returned by an ADC goes
241+
outside predefined bounds. Bounds may be absolute or relative to the current
242+
value. Data from ADC's is usually noisy. Relative bounds provide a simple (if
243+
crude) means of eliminating this. Absolute bounds can be used to raise an alarm
244+
or log data, if the value goes out of range. Typical usage:
221245
```python
222246
import uasyncio as asyncio
223247
from machine import ADC
@@ -282,3 +306,50 @@ The `AADC` class uses the `uasyncio` stream I/O mechanism. This is not the most
282306
obvious design. It was chosen because the plan for `uasyncio` is that it will
283307
include an option for prioritising I/O. I wanted this class to be able to use
284308
this for applications requiring rapid response.
309+
310+
###### [Contents](./DRIVERS.md#1-contents)
311+
312+
# 6. Additional functions
313+
314+
## 6.1 Launch
315+
316+
Importe as follows:
317+
```python
318+
from primitives import launch
319+
```
320+
`launch` enables a function to accept a coro or a callback interchangeably. It
321+
accepts the callable plus a tuple of args. If a callback is passed, `launch`
322+
runs it and returns the callback's return value. If a coro is passed, it is
323+
converted to a `task` and run asynchronously. The return value is the `task`
324+
instance. A usage example is in `primitives/switch.py`.
325+
326+
## 6.2 set_global_exception
327+
328+
Import as follows:
329+
```python
330+
from primitives import set_global_exception
331+
```
332+
`set_global_exception` is a convenience funtion to enable a global exception
333+
handler to simplify debugging. The function takes no args. It is called as
334+
follows:
335+
336+
```python
337+
import uasyncio as asyncio
338+
from primitives import set_global_exception
339+
340+
async def main():
341+
set_global_exception()
342+
# Main body of application code omitted
343+
344+
try:
345+
asyncio.run(main())
346+
finally:
347+
asyncio.new_event_loop() # Clear retained state
348+
```
349+
This is explained in the tutorial. In essence if an exception occurs in a task,
350+
the default behaviour is for the task to stop but for the rest of the code to
351+
continue to run. This means that the failure can be missed and the sequence of
352+
events can be hard to deduce. A global handler ensures that the entire
353+
application stops allowing the traceback and other debug prints to be studied.
354+
355+
###### [Contents](./DRIVERS.md#1-contents)

0 commit comments

Comments
 (0)