Skip to content

Commit 9a1939c

Browse files
committed
aswitch.py: fix bug where Delay_ms spawned needless coros.
1 parent a6a102d commit 9a1939c

File tree

3 files changed

+75
-21
lines changed

3 files changed

+75
-21
lines changed

DRIVERS.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,9 @@ loop = asyncio.get_event_loop()
156156
loop.run_until_complete(my_app()) # Run main application code
157157
```
158158

159+
An alternative Pushbutton class with lower RAM usage is available
160+
[here](https://github.com/kevinkk525/pysmartnode/blob/dev/pysmartnode/utils/abutton.py).
161+
159162
### 3.2.1 The suppress constructor argument
160163

161164
When the button is pressed `press_func` runs immediately. This minimal latency
@@ -207,12 +210,18 @@ Methods:
207210

208211
1. `trigger` optional argument `duration=0`. A timeout will occur after
209212
`duration` ms unless retriggered. If no arg is passed the period will be that
210-
of the `duration` passed to the constructor.
213+
of the `duration` passed to the constructor. See Class variable below.
211214
2. `stop` No argument. Cancels the timeout, setting the `running` status
212215
`False`. The timer can be restarted by issuing `trigger` again.
213216
3. `running` No argument. Returns the running status of the object.
214217
4. `__call__` Alias for running.
215218

219+
Class variable:
220+
221+
1. `verbose=False` If `True` a warning will be printed if a running timer is
222+
retriggered with a time value shorter than the time currently outstanding.
223+
Such an operation has no effect owing to the design of `uasyncio`.
224+
216225
If the `trigger` method is to be called from an interrupt service routine the
217226
`can_alloc` constructor arg should be `False`. This causes the delay object
218227
to use a slightly less efficient mode which avoids RAM allocation when
@@ -229,7 +238,7 @@ import uasyncio as asyncio
229238
from aswitch import Pushbutton, Delay_ms
230239

231240
async def my_app():
232-
await asyncio.sleep(60) # Dummy
241+
await asyncio.sleep(60) # Run for 1 minute
233242

234243
pin = Pin('X1', Pin.IN, Pin.PULL_UP) # Pushbutton to gnd
235244
red = LED(1)

astests.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,16 @@
1313
Test using switch or pushbutton between X1 and gnd.
1414
Ground pin X2 to terminate test.
1515
Soft reset (ctrl-D) after each test.
16+
17+
'''
18+
tests = '''
19+
Available tests:
20+
test_sw Switch test
21+
test_swcb Switch with callback
22+
test_btn Pushutton launching coros
23+
test_btncb Pushbutton launching callbacks
1624
'''
25+
print(tests)
1726

1827
# Pulse an LED (coroutine)
1928
async def pulse(led, ms):
@@ -33,8 +42,13 @@ async def killer():
3342

3443
# Test for the Switch class passing coros
3544
def test_sw():
45+
s = '''
46+
close pulses green
47+
open pulses red
48+
'''
3649
print('Test of switch scheduling coroutines.')
3750
print(helptext)
51+
print(s)
3852
pin = Pin('X1', Pin.IN, Pin.PULL_UP)
3953
red = LED(1)
4054
green = LED(2)
@@ -47,8 +61,13 @@ def test_sw():
4761

4862
# Test for the switch class with a callback
4963
def test_swcb():
64+
s = '''
65+
close toggles red
66+
open toggles green
67+
'''
5068
print('Test of switch executing callbacks.')
5169
print(helptext)
70+
print(s)
5271
pin = Pin('X1', Pin.IN, Pin.PULL_UP)
5372
red = LED(1)
5473
green = LED(2)
@@ -62,8 +81,15 @@ def test_swcb():
6281
# Test for the Pushbutton class (coroutines)
6382
# Pass True to test suppress
6483
def test_btn(suppress=False, lf=True, df=True):
84+
s = '''
85+
press pulses red
86+
release pulses green
87+
double click pulses yellow
88+
long press pulses blue
89+
'''
6590
print('Test of pushbutton scheduling coroutines.')
6691
print(helptext)
92+
print(s)
6793
pin = Pin('X1', Pin.IN, Pin.PULL_UP)
6894
red = LED(1)
6995
green = LED(2)
@@ -83,8 +109,15 @@ def test_btn(suppress=False, lf=True, df=True):
83109

84110
# Test for the Pushbutton class (callbacks)
85111
def test_btncb():
112+
s = '''
113+
press toggles red
114+
release toggles green
115+
double click toggles yellow
116+
long press toggles blue
117+
'''
86118
print('Test of pushbutton executing callbacks.')
87119
print(helptext)
120+
print(s)
88121
pin = Pin('X1', Pin.IN, Pin.PULL_UP)
89122
red = LED(1)
90123
green = LED(2)

aswitch.py

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -46,53 +46,63 @@ def launch(func, tup_args):
4646
loop.create_task(res)
4747

4848

49-
class Delay_ms(object):
49+
class Delay_ms:
50+
verbose = False
5051
def __init__(self, func=None, args=(), can_alloc=True, duration=1000):
5152
self.func = func
5253
self.args = args
5354
self.can_alloc = can_alloc
5455
self.duration = duration # Default duration
55-
self.tstop = None # Not running
56+
self._tstop = None # Killer not running
57+
self._running = False # Timer not running
5658
self.loop = asyncio.get_event_loop()
5759
if not can_alloc:
5860
self.loop.create_task(self._run())
5961

6062
async def _run(self):
6163
while True:
62-
if self.tstop is None: # Not running
64+
if not self._running: # timer not running
6365
await asyncio.sleep_ms(0)
6466
else:
65-
await self.killer()
67+
await self._killer()
6668

6769
def stop(self):
68-
self.tstop = None
70+
self._running = False
71+
# If uasyncio is ever fixed we should cancel .killer
6972

7073
def trigger(self, duration=0): # Update end time
74+
self._running = True
7175
if duration <= 0:
7276
duration = self.duration
73-
if self.can_alloc and self.tstop is None: # No killer task is running
74-
self.tstop = time.ticks_add(time.ticks_ms(), duration)
75-
# Start a task which stops the delay after its period has elapsed
76-
self.loop.create_task(self.killer())
77-
self.tstop = time.ticks_add(time.ticks_ms(), 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())
7887

7988
def running(self):
80-
return self.tstop is not None
89+
return self._running
8190

8291
__call__ = running
8392

84-
async def killer(self):
85-
twait = time.ticks_diff(self.tstop, time.ticks_ms())
93+
async def _killer(self):
94+
twait = time.ticks_diff(self._tstop, time.ticks_ms())
8695
while twait > 0: # Must loop here: might be retriggered
8796
await asyncio.sleep_ms(twait)
88-
if self.tstop is None:
97+
if self._tstop is None:
8998
break # Return if stop() called during wait
90-
twait = time.ticks_diff(self.tstop, time.ticks_ms())
91-
if self.tstop is not None and self.func is not None:
99+
twait = time.ticks_diff(self._tstop, time.ticks_ms())
100+
if self._running and self.func is not None:
92101
launch(self.func, self.args) # Timed out: execute callback
93-
self.tstop = None # Not running
102+
self._tstop = None # killer not running
103+
self._running = False # timer is stopped
94104

95-
class Switch(object):
105+
class Switch:
96106
debounce_ms = 50
97107
def __init__(self, pin):
98108
self.pin = pin # Should be initialised for input with pullup
@@ -127,7 +137,9 @@ async def switchcheck(self):
127137
# Ignore further state changes until switch has settled
128138
await asyncio.sleep_ms(Switch.debounce_ms)
129139

130-
class Pushbutton(object):
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:
131143
debounce_ms = 50
132144
long_press_ms = 1000
133145
double_click_ms = 400

0 commit comments

Comments
 (0)