Skip to content

Commit db9977d

Browse files
committed
Minor changes to switch and pushbutton drivers.
1 parent b8f7fa2 commit db9977d

File tree

2 files changed

+139
-54
lines changed

2 files changed

+139
-54
lines changed

v3/primitives/pushbutton.py

Lines changed: 59 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,20 @@
66
import uasyncio as asyncio
77
import utime as time
88
from . import launch, Delay_ms
9+
910
try:
1011
from machine import TouchPad
1112
except ImportError:
1213
pass
1314

15+
1416
class Pushbutton:
1517
debounce_ms = 50
1618
long_press_ms = 1000
1719
double_click_ms = 400
20+
1821
def __init__(self, pin, suppress=False, sense=None):
19-
self.pin = pin # Initialise for input
22+
self._pin = pin # Initialise for input
2023
self._supp = suppress
2124
self._dblpend = False # Doubleclick waiting for 2nd click
2225
self._dblran = False # Doubleclick executed user function
@@ -25,10 +28,59 @@ def __init__(self, pin, suppress=False, sense=None):
2528
self._df = False
2629
self._ld = False # Delay_ms instance for long press
2730
self._dd = False # Ditto for doubleclick
28-
self.sense = pin.value() if sense is None else sense # Convert from electrical to logical value
29-
self.state = self.rawstate() # Initial state
30-
self._run = asyncio.create_task(self.buttoncheck()) # Thread runs forever
31+
# Convert from electrical to logical value
32+
self._sense = pin.value() if sense is None else sense
33+
self._state = self.rawstate() # Initial state
34+
self._run = asyncio.create_task(self._go()) # Thread runs forever
35+
36+
async def _go(self):
37+
while True:
38+
self._check(self.rawstate())
39+
# Ignore state changes until switch has settled. Also avoid hogging CPU.
40+
# See https://github.com/peterhinch/micropython-async/issues/69
41+
await asyncio.sleep_ms(Pushbutton.debounce_ms)
42+
43+
def _check(self, state):
44+
if state == self._state:
45+
return
46+
# State has changed: act on it now.
47+
self._state = state
48+
if state: # Button pressed: launch pressed func
49+
if self._tf:
50+
launch(self._tf, self._ta)
51+
if self._ld: # There's a long func: start long press delay
52+
self._ld.trigger(Pushbutton.long_press_ms)
53+
if self._df:
54+
if self._dd(): # Second click: timer running
55+
self._dd.stop()
56+
self._dblpend = False
57+
self._dblran = True # Prevent suppressed launch on release
58+
launch(self._df, self._da)
59+
else:
60+
# First click: start doubleclick timer
61+
self._dd.trigger(Pushbutton.double_click_ms)
62+
self._dblpend = True # Prevent suppressed launch on release
63+
else: # Button release. Is there a release func?
64+
if self._ff:
65+
if self._supp:
66+
d = self._ld
67+
# If long delay exists, is running and doubleclick status is OK
68+
if not self._dblpend and not self._dblran:
69+
if (d and d()) or not d:
70+
launch(self._ff, self._fa)
71+
else:
72+
launch(self._ff, self._fa)
73+
if self._ld:
74+
self._ld.stop() # Avoid interpreting a second click as a long push
75+
self._dblran = False
76+
77+
def _ddto(self): # Doubleclick timeout: no doubleclick occurred
78+
self._dblpend = False
79+
if self._supp and not self._state:
80+
if not self._ld or (self._ld and not self._ld()):
81+
launch(self._ff, self._fa)
3182

83+
# ****** API ******
3284
def press_func(self, func=False, args=()):
3385
if func is None:
3486
self.press = asyncio.Event()
@@ -67,62 +119,19 @@ def long_func(self, func=False, args=()):
67119

68120
# Current non-debounced logical button state: True == pressed
69121
def rawstate(self):
70-
return bool(self.pin.value() ^ self.sense)
122+
return bool(self._pin() ^ self._sense)
71123

72124
# Current debounced state of button (True == pressed)
73125
def __call__(self):
74-
return self.state
75-
76-
def _ddto(self): # Doubleclick timeout: no doubleclick occurred
77-
self._dblpend = False
78-
if self._supp and not self.state:
79-
if not self._ld or (self._ld and not self._ld()):
80-
launch(self._ff, self._fa)
81-
82-
async def buttoncheck(self):
83-
while True:
84-
state = self.rawstate()
85-
# State has changed: act on it now.
86-
if state != self.state:
87-
self.state = state
88-
if state: # Button pressed: launch pressed func
89-
if self._tf:
90-
launch(self._tf, self._ta)
91-
if self._ld: # There's a long func: start long press delay
92-
self._ld.trigger(Pushbutton.long_press_ms)
93-
if self._df:
94-
if self._dd(): # Second click: timer running
95-
self._dd.stop()
96-
self._dblpend = False
97-
self._dblran = True # Prevent suppressed launch on release
98-
launch(self._df, self._da)
99-
else:
100-
# First click: start doubleclick timer
101-
self._dd.trigger(Pushbutton.double_click_ms)
102-
self._dblpend = True # Prevent suppressed launch on release
103-
else: # Button release. Is there a release func?
104-
if self._ff:
105-
if self._supp:
106-
d = self._ld
107-
# If long delay exists, is running and doubleclick status is OK
108-
if not self._dblpend and not self._dblran:
109-
if (d and d()) or not d:
110-
launch(self._ff, self._fa)
111-
else:
112-
launch(self._ff, self._fa)
113-
if self._ld:
114-
self._ld.stop() # Avoid interpreting a second click as a long push
115-
self._dblran = False
116-
# Ignore state changes until switch has settled
117-
# See https://github.com/peterhinch/micropython-async/issues/69
118-
await asyncio.sleep_ms(Pushbutton.debounce_ms)
126+
return self._state
119127

120128
def deinit(self):
121129
self._run.cancel()
122130

123131

124132
class ESP32Touch(Pushbutton):
125133
thresh = (80 << 8) // 100
134+
126135
@classmethod
127136
def threshold(cls, val):
128137
if not (isinstance(val, int) and 0 < val < 100):

v3/primitives/tests/switches.py

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,16 @@
2121
2222
'''
2323
tests = '''
24+
\x1b[32m
2425
Available tests:
25-
test_sw Switch test
26-
test_swcb Switch with callback
27-
test_btn Pushutton launching coros
28-
test_btncb Pushbutton launching callbacks
26+
test_sw Switch test.
27+
test_swcb Switch with callback.
28+
test_sw_event Switch with event.
29+
test_btn Pushutton launching coros.
30+
test_btncb Pushbutton launching callbacks.
2931
btn_dynamic Change coros launched at runtime.
32+
btn_event Pushbutton event interface.
33+
\x1b[39m
3034
'''
3135
print(tests)
3236

@@ -36,6 +40,15 @@ async def pulse(led, ms):
3640
await asyncio.sleep_ms(ms)
3741
led.off()
3842

43+
# Pulse an LED when an event triggered
44+
async def evt_pulse(event, led):
45+
while True:
46+
event.clear()
47+
await event.wait()
48+
led.on()
49+
await asyncio.sleep_ms(500)
50+
led.off()
51+
3952
# Toggle an LED (callback)
4053
def toggle(led):
4154
led.toggle()
@@ -94,6 +107,35 @@ def test_swcb():
94107
sw.open_func(toggle, (green,))
95108
run(sw)
96109

110+
# Test for the Switch class (events)
111+
async def do_sw_event():
112+
pin = Pin('X1', Pin.IN, Pin.PULL_UP)
113+
sw = Switch(pin)
114+
sw.open_func(None)
115+
sw.close_func(None)
116+
tasks = []
117+
for event, led in ((sw.close, 1), (sw.open, 2)):
118+
tasks.append(asyncio.create_task(evt_pulse(event, LED(led))))
119+
await killer(sw)
120+
for task in tasks:
121+
task.cancel()
122+
123+
def test_sw_event():
124+
s = '''
125+
close pulse red
126+
open pulses green
127+
'''
128+
print('Test of switch triggering events.')
129+
print(helptext)
130+
print(s)
131+
try:
132+
asyncio.run(do_sw_event())
133+
except KeyboardInterrupt:
134+
print('Interrupted')
135+
finally:
136+
asyncio.new_event_loop()
137+
print(tests)
138+
97139
# Test for the Pushbutton class (coroutines)
98140
# Pass True to test suppress
99141
def test_btn(suppress=False, lf=True, df=True):
@@ -181,3 +223,37 @@ def btn_dynamic():
181223
setup(pb, red, green, yellow, None)
182224
pb.long_func(setup, (pb, blue, red, green, yellow, 2000))
183225
run(pb)
226+
227+
# Test for the Pushbutton class (events)
228+
async def do_btn_event():
229+
pin = Pin('X1', Pin.IN, Pin.PULL_UP)
230+
pb = Pushbutton(pin)
231+
pb.press_func(None)
232+
pb.release_func(None)
233+
pb.double_func(None)
234+
pb.long_func(None)
235+
tasks = []
236+
for event, led in ((pb.press, 1), (pb.release, 2), (pb.double, 3), (pb.long, 4)):
237+
tasks.append(asyncio.create_task(evt_pulse(event, LED(led))))
238+
await killer(pb)
239+
for task in tasks:
240+
task.cancel()
241+
242+
def btn_event():
243+
s = '''
244+
press pulse red
245+
release pulses green
246+
double click pulses yellow
247+
long press pulses blue
248+
'''
249+
print('Test of pushbutton triggering events.')
250+
print(helptext)
251+
print(s)
252+
try:
253+
asyncio.run(do_btn_event())
254+
except KeyboardInterrupt:
255+
print('Interrupted')
256+
finally:
257+
asyncio.new_event_loop()
258+
print(tests)
259+

0 commit comments

Comments
 (0)