Skip to content

Commit c2f7f46

Browse files
committed
Move V2 resources to v2 directory.
1 parent 78d22a4 commit c2f7f46

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

87 files changed

+870
-3
lines changed
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

v2/README.md

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
# 1. uasyncio V2
2+
3+
This repo also contains an optional `fast_io` variant of `uasyncio` V2. This
4+
variant offers high I/O performance and also includes workrounds for many of
5+
the bugs in V2. (Bugs properly fixed in V3.)
6+
7+
## Reasons for running V2
8+
9+
In general I recommend V3, especially for new projects. It is better in every
10+
respect bar one: the `fast_io` variant of V2 currently offers superior I/O
11+
performance, relative both to V2 and V3.
12+
13+
The main reason for running official V2 is that many existing libraries have
14+
not yet been ported to V3. Some will run without change, but those using more
15+
advanced features of `uasyncio` may not.
16+
17+
## 1.1 Resources
18+
19+
* [A tutorial](./TUTORIAL.md) An introductory tutorial on asynchronous
20+
programming and the use of the `uasyncio` library.
21+
* [Asynchronous device drivers](./DRIVERS.md). A module providing drivers for
22+
devices such as switches and pushbuttons.
23+
* [Synchronisation primitives](./PRIMITIVES.md). Provides commonly used
24+
synchronisation primitives plus an API for task cancellation and monitoring.
25+
* [A driver for an IR remote control](./nec_ir/README.md) This is intended as
26+
an example of an asynchronous device driver. It decodes signals received from
27+
infra red remote controls using the popular NEC protocol.
28+
* [A driver for the HTU21D](./htu21d/README.md) temperature and humidity
29+
sensor. This is intended to be portable across platforms and is another
30+
example of an asynchronous device driver.
31+
* [A driver for character LCD displays](./HD44780/README.md). A simple
32+
asynchronous interface to displays based on the Hitachi HD44780 chip.
33+
* [A driver for GPS modules](./gps/README.md) Runs a background task to read
34+
and decode NMEA sentences, providing constantly updated position, course,
35+
altitude and time/date information.
36+
* [Communication using I2C slave mode.](./i2c/README.md) Enables a Pyboard to
37+
to communicate with another MicroPython device using stream I/O. The Pyboard
38+
achieves bidirectional communication with targets such as an ESP8266.
39+
* [Communication between devices](./syncom_as/README.md) Enables MicroPython
40+
boards to communicate without using a UART. This is hardware agnostic but
41+
slower than the I2C version.
42+
43+
## 1.2 The fast_io variant
44+
45+
This comprises two parts.
46+
1. The [fast_io](./FASTPOLL.md) version of `uasyncio` is a "drop in"
47+
replacement for the official version 2 providing bug fixes, additional
48+
functionality and, in certain respects, higher performance.
49+
2. An optional extension module enabling the [fast_io](./FASTPOLL.md) version
50+
to run with very low power draw. This is Pyboard-only including Pyboard D.
51+
52+
Official `uasyncio` suffers from high levels of latency when scheduling I/O in
53+
typical applications. It also has an issue which can cause bidirectional
54+
devices such as UART's to block. The `fast_io` version fixes the bug. It also
55+
provides a facility for reducing I/O latency which can substantially improve
56+
the performance of stream I/O drivers. It provides other features aimed at
57+
providing greater control over scheduling behaviour.
58+
59+
To take advantage of the reduced latency device drivers should be written to
60+
employ stream I/O. To operate at low latency they are simply run under the
61+
`fast_io` version. The [tutorial](./TUTORIAL.md#64-writing-streaming-device-drivers)
62+
has details of how to write streaming drivers.
63+
64+
The current `fast_io` version 0.24 fixes an issue with task cancellation and
65+
timeouts. In `uasyncio` version 2.0, where a coroutine is waiting on a
66+
`sleep()` or on I/O, a timeout or cancellation is deferred until the coroutine
67+
is next scheduled. This introduces uncertainty into when the coroutine is
68+
stopped.
69+
70+
## 1.2.1 A Pyboard-only low power module
71+
72+
This is documented [here](./lowpower/README.md). In essence a Python file is
73+
placed on the device which configures the `fast_io` version of `uasyncio` to
74+
reduce power consumption at times when it is not busy. This provides a means of
75+
using `uasyncio` in battery powered projects. This is decidedly experimental:
76+
hopefully `uasyncio` V3 will introduce power saving in a less hacky manner.
77+
78+
## 1.3 Under the hood
79+
80+
[Under the hood](./UNDER_THE_HOOD.md) A guide to help understand the V2
81+
`uasyncio` code. For scheduler geeks and those wishing to modify `uasyncio`.
82+
83+
## 1.4 Synchronisation Primitives
84+
85+
All solutions listed below work with stock `uasyncio` V2 or `fast_io`.
86+
87+
The CPython `asyncio` library supports these synchronisation primitives:
88+
* `Lock`
89+
* `Event`
90+
* `gather`
91+
* `Semaphore` and `BoundedSemaphore`.
92+
* `Condition`.
93+
* `Queue`. This was implemented by Paul Sokolvsky in `uasyncio.queues`.
94+
95+
See [CPython docs](https://docs.python.org/3/library/asyncio-sync.html).
96+
97+
The file `asyn.py` contains implementations of these, also
98+
* `Barrier` An additional synchronisation primitive.
99+
* Cancellation decorators and classes: these are workrounds for the bug where
100+
in V2 cancellation does not occur promptly.
101+
* Support for `gather`.
102+
103+
The `Event` class in `asyn.py` provides a nonstandard option to supply a data
104+
value to the `.set` method and to retrieve this with `.value`. It is also an
105+
awaitable class.
106+
107+
#### These are documented [here](./PRIMITIVES.md)
108+
109+
## 1.5 Switches, Pushbuttons and Timeouts
110+
111+
The file `aswitch.py` provides support for:
112+
* `Delay_ms` A software retriggerable monostable or watchdog.
113+
* `Switch` Debounced switch and pushbutton classes with callbacks.
114+
* `Pushbutton`
115+
116+
#### It is documented [here](./DRIVERS.md)
117+
118+
# 2. Version 2.0 usage notes
119+
120+
These notes are intended for users familiar with `asyncio` under CPython.
121+
122+
The MicroPython language is based on CPython 3.4. The `uasyncio` library
123+
supports a subset of the CPython 3.4 `asyncio` library with some V3.5
124+
extensions. In addition there are non-standard extensions to optimise services
125+
such as millisecond level timing and task cancellation. Its design focus is on
126+
high performance and scheduling is performed without RAM allocation.
127+
128+
The `uasyncio` library supports the following Python 3.5 features:
129+
130+
* `async def` and `await` syntax.
131+
* Awaitable classes (using `__iter__` rather than `__await__`).
132+
* Asynchronous context managers.
133+
* Asynchronous iterators.
134+
* Event loop methods `call_soon` and `call_later`.
135+
* `sleep(seconds)`.
136+
137+
It supports millisecond level timing with the following:
138+
139+
* Event loop method `call_later_ms`
140+
* uasyncio `sleep_ms(time)`
141+
142+
`uasyncio` V2 supports coroutine timeouts and cancellation.
143+
144+
* `wait_for(coro, t_secs)` runs `coro` with a timeout.
145+
* `cancel(coro)` tags `coro` for cancellation when it is next scheduled.
146+
147+
Classes `Task` and `Future` are not supported.
148+
149+
## 2.1 Asynchronous I/O
150+
151+
Asynchronous I/O (`StreamReader` and `StreamWriter` classes) support devices
152+
with streaming drivers, such as UARTs and sockets. It is now possible to write
153+
streaming device drivers in Python.
154+
155+
## 2.2 Time values
156+
157+
For timing asyncio uses floating point values of seconds. The `uasyncio.sleep`
158+
method accepts floats (including sub-second values) or integers. Note that in
159+
MicroPython the use of floats implies RAM allocation which incurs a performance
160+
penalty. The design of `uasyncio` enables allocation-free scheduling. In
161+
applications where performance is an issue, integers should be used and the
162+
millisecond level functions (with integer arguments) employed where necessary.
163+
164+
The `loop.time` method returns an integer number of milliseconds whereas
165+
CPython returns a floating point number of seconds. `call_at` follows the
166+
same convention.
File renamed without changes.
File renamed without changes.
File renamed without changes.

apoll.py renamed to v2/apoll.py

File renamed without changes.

aqtest.py renamed to v2/aqtest.py

File renamed without changes.
File renamed without changes.

v2/aswitch.py

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
# aswitch.py Switch and pushbutton classes for asyncio
2+
# Delay_ms A retriggerable delay class. Can schedule a coro on timeout.
3+
# Switch Simple debounced switch class for normally open grounded switch.
4+
# Pushbutton extend the above to support logical state, long press and
5+
# double-click events
6+
# Tested on Pyboard but should run on other microcontroller platforms
7+
# running MicroPython and uasyncio.
8+
9+
# The MIT License (MIT)
10+
#
11+
# Copyright (c) 2017 Peter Hinch
12+
#
13+
# Permission is hereby granted, free of charge, to any person obtaining a copy
14+
# of this software and associated documentation files (the "Software"), to deal
15+
# in the Software without restriction, including without limitation the rights
16+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17+
# copies of the Software, and to permit persons to whom the Software is
18+
# furnished to do so, subject to the following conditions:
19+
#
20+
# The above copyright notice and this permission notice shall be included in
21+
# all copies or substantial portions of the Software.
22+
#
23+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29+
# THE SOFTWARE.
30+
31+
import uasyncio as asyncio
32+
import utime as time
33+
# Remove dependency on asyn to save RAM:
34+
# launch: run a callback or initiate a coroutine depending on which is passed.
35+
async def _g():
36+
pass
37+
type_coro = type(_g())
38+
39+
# If a callback is passed, run it and return.
40+
# If a coro is passed initiate it and return.
41+
# coros are passed by name i.e. not using function call syntax.
42+
def launch(func, tup_args):
43+
res = func(*tup_args)
44+
if isinstance(res, type_coro):
45+
loop = asyncio.get_event_loop()
46+
loop.create_task(res)
47+
48+
49+
class Delay_ms:
50+
verbose = False
51+
def __init__(self, func=None, args=(), can_alloc=True, duration=1000):
52+
self.func = func
53+
self.args = args
54+
self.can_alloc = can_alloc
55+
self.duration = duration # Default duration
56+
self._tstop = None # Killer not running
57+
self._running = False # Timer not running
58+
self.loop = asyncio.get_event_loop()
59+
if not can_alloc:
60+
self.loop.create_task(self._run())
61+
62+
async def _run(self):
63+
while True:
64+
if not self._running: # timer not running
65+
await asyncio.sleep_ms(0)
66+
else:
67+
await self._killer()
68+
69+
def stop(self):
70+
self._running = False
71+
# If uasyncio is ever fixed we should cancel .killer
72+
73+
def trigger(self, duration=0): # Update end time
74+
self._running = True
75+
if duration <= 0:
76+
duration = self.duration
77+
tn = time.ticks_add(time.ticks_ms(), duration) # new end time
78+
self.verbose and self._tstop is not None and self._tstop > tn \
79+
and print("Warning: can't reduce Delay_ms time.")
80+
# Start killer if can allocate and killer is not running
81+
sk = self.can_alloc and self._tstop is None
82+
# The following indicates ._killer is running: it will be
83+
# started either here or in ._run
84+
self._tstop = tn
85+
if sk: # ._killer stops the delay when its period has elapsed
86+
self.loop.create_task(self._killer())
87+
88+
def running(self):
89+
return self._running
90+
91+
__call__ = running
92+
93+
async def _killer(self):
94+
twait = time.ticks_diff(self._tstop, time.ticks_ms())
95+
while twait > 0: # Must loop here: might be retriggered
96+
await asyncio.sleep_ms(twait)
97+
if self._tstop is None:
98+
break # Return if stop() called during wait
99+
twait = time.ticks_diff(self._tstop, time.ticks_ms())
100+
if self._running and self.func is not None:
101+
launch(self.func, self.args) # Timed out: execute callback
102+
self._tstop = None # killer not running
103+
self._running = False # timer is stopped
104+
105+
class Switch:
106+
debounce_ms = 50
107+
def __init__(self, pin):
108+
self.pin = pin # Should be initialised for input with pullup
109+
self._open_func = False
110+
self._close_func = False
111+
self.switchstate = self.pin.value() # Get initial state
112+
loop = asyncio.get_event_loop()
113+
loop.create_task(self.switchcheck()) # Thread runs forever
114+
115+
def open_func(self, func, args=()):
116+
self._open_func = func
117+
self._open_args = args
118+
119+
def close_func(self, func, args=()):
120+
self._close_func = func
121+
self._close_args = args
122+
123+
# Return current state of switch (0 = pressed)
124+
def __call__(self):
125+
return self.switchstate
126+
127+
async def switchcheck(self):
128+
while True:
129+
state = self.pin.value()
130+
if state != self.switchstate:
131+
# State has changed: act on it now.
132+
self.switchstate = state
133+
if state == 0 and self._close_func:
134+
launch(self._close_func, self._close_args)
135+
elif state == 1 and self._open_func:
136+
launch(self._open_func, self._open_args)
137+
# Ignore further state changes until switch has settled
138+
await asyncio.sleep_ms(Switch.debounce_ms)
139+
140+
# An alternative Pushbutton solution with lower RAM use is available here
141+
# https://github.com/kevinkk525/pysmartnode/blob/dev/pysmartnode/utils/abutton.py
142+
class Pushbutton:
143+
debounce_ms = 50
144+
long_press_ms = 1000
145+
double_click_ms = 400
146+
def __init__(self, pin, suppress=False):
147+
self.pin = pin # Initialise for input
148+
self._supp = suppress
149+
self._dblpend = False # Doubleclick waiting for 2nd click
150+
self._dblran = False # Doubleclick executed user function
151+
self._tf = False
152+
self._ff = False
153+
self._df = False
154+
self._lf = False
155+
self._ld = False # Delay_ms instance for long press
156+
self._dd = False # Ditto for doubleclick
157+
self.sense = pin.value() # Convert from electrical to logical value
158+
self.state = self.rawstate() # Initial state
159+
loop = asyncio.get_event_loop()
160+
loop.create_task(self.buttoncheck()) # Thread runs forever
161+
162+
def press_func(self, func, args=()):
163+
self._tf = func
164+
self._ta = args
165+
166+
def release_func(self, func, args=()):
167+
self._ff = func
168+
self._fa = args
169+
170+
def double_func(self, func, args=()):
171+
self._df = func
172+
self._da = args
173+
174+
def long_func(self, func, args=()):
175+
self._lf = func
176+
self._la = args
177+
178+
# Current non-debounced logical button state: True == pressed
179+
def rawstate(self):
180+
return bool(self.pin.value() ^ self.sense)
181+
182+
# Current debounced state of button (True == pressed)
183+
def __call__(self):
184+
return self.state
185+
186+
def _ddto(self): # Doubleclick timeout: no doubleclick occurred
187+
self._dblpend = False
188+
if self._supp and not self.state:
189+
if not self._ld or (self._ld and not self._ld()):
190+
launch(self._ff, self._fa)
191+
192+
async def buttoncheck(self):
193+
if self._lf: # Instantiate timers if funcs exist
194+
self._ld = Delay_ms(self._lf, self._la)
195+
if self._df:
196+
self._dd = Delay_ms(self._ddto)
197+
while True:
198+
state = self.rawstate()
199+
# State has changed: act on it now.
200+
if state != self.state:
201+
self.state = state
202+
if state: # Button pressed: launch pressed func
203+
if self._tf:
204+
launch(self._tf, self._ta)
205+
if self._lf: # There's a long func: start long press delay
206+
self._ld.trigger(Pushbutton.long_press_ms)
207+
if self._df:
208+
if self._dd(): # Second click: timer running
209+
self._dd.stop()
210+
self._dblpend = False
211+
self._dblran = True # Prevent suppressed launch on release
212+
launch(self._df, self._da)
213+
else:
214+
# First click: start doubleclick timer
215+
self._dd.trigger(Pushbutton.double_click_ms)
216+
self._dblpend = True # Prevent suppressed launch on release
217+
else: # Button release. Is there a release func?
218+
if self._ff:
219+
if self._supp:
220+
d = self._ld
221+
# If long delay exists, is running and doubleclick status is OK
222+
if not self._dblpend and not self._dblran:
223+
if (d and d()) or not d:
224+
launch(self._ff, self._fa)
225+
else:
226+
launch(self._ff, self._fa)
227+
if self._ld:
228+
self._ld.stop() # Avoid interpreting a second click as a long push
229+
self._dblran = False
230+
# Ignore state changes until switch has settled
231+
await asyncio.sleep_ms(Pushbutton.debounce_ms)

0 commit comments

Comments
 (0)