Skip to content

Commit 0d8312a

Browse files
committed
Pushbutton: suppress release on long and double clicks.
1 parent b0dcc78 commit 0d8312a

File tree

3 files changed

+99
-58
lines changed

3 files changed

+99
-58
lines changed

DRIVERS.md

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ events.
1616
and also a software retriggerable delay object. Pushbuttons are a
1717
generalisation of switches providing logical rather than physical status along
1818
with double-clicked and long pressed events.
19-
3. `asyn.py` Provides synchronisation primitives. Required by `aswitch.py`.
20-
4. `astests.py` Test/demonstration programs for `aswitch.py`.
19+
3. `astests.py` Test/demonstration programs for `aswitch.py`.
2120

2221
# 3. Module aswitch.py
2322

@@ -104,7 +103,7 @@ scheduled for execution and will run asynchronously.
104103
Constructor arguments:
105104

106105
1. `pin` Mandatory. The initialised Pin instance.
107-
2. `lpmode` Default `False`. See below.
106+
2. `suppress` Default `False`. See 3.2.1 below.
108107

109108
Methods:
110109

@@ -148,17 +147,28 @@ loop = asyncio.get_event_loop()
148147
loop.run_until_complete(my_app()) # Run main application code
149148
```
150149

151-
The `lpmode` constructor argument modifies the behaviour of `press_func` in the
152-
event that a `long_func` is specified.
153-
154-
If `lpmode` is `False`, if a button press occurs `press_func` runs immediately;
155-
`long_func` runs if the button is still pressed when the timeout has elapsed.
156-
If `lpmode` is `True`, `press_func` is delayed until button release. If, at the
157-
time of release, `long_func` has run, `press_func` will be suppressed.
158-
159-
The default provides for low latency but a long button press will trigger
160-
`press_func` and `long_func`. `lpmode` = `True` prevents `press_func` from
161-
running.
150+
### 3.2.1 The suppress constructor argument
151+
152+
When the button is pressed `press_func` runs immediately. This minimal latency
153+
is ideal for applications such as games, but does imply that in the event of a
154+
long press, both `press_func` and `long_func` run: `press_func` immediately and
155+
`long_func` if the button is still pressed when the timer has elapsed. Similar
156+
reasoning applies to the double click function.
157+
158+
There can be a need for a **function** which runs if a button is pressed but
159+
only if a doubleclick or long press function does not run. The soonest that the
160+
absence of a long press can be detected is on button release. The absence of a
161+
double click can only be detected when the double click timer times out without
162+
a second press occurring.
163+
164+
This **function** is the `release_func`. If the `suppress` constructor arg is
165+
set, `release_func` will be launched as follows:
166+
1. If `double_func` does not exist on rapid button release.
167+
2. If `double_func` exists, after the expiration of the doubleclick timer.
168+
3. If `long_func` exists and the press duration causes `long_func` to be
169+
launched, `release_func` will not be launched.
170+
4. If `double_func` exists and a double click occurs, `release_func` will not
171+
be launched.
162172

163173
## 3.3 Delay_ms class
164174

@@ -192,6 +202,7 @@ Methods:
192202
2. `stop` No argument. Cancels the timeout, setting the `running` status
193203
`False`. The timer can be restarted by issuing `trigger` again.
194204
3. `running` No argument. Returns the running status of the object.
205+
4. `__call__` Alias for running.
195206

196207
If the `trigger` method is to be called from an interrupt service routine the
197208
`can_alloc` constructor arg should be `False`. This causes the delay object
@@ -236,11 +247,18 @@ of its behaviour if the switch is toggled rapidly.
236247

237248
Demonstrates the use of callbacks to toggle the red and green LED's.
238249

239-
## 4.3 Function test_btn()
250+
## 4.3 Function test_btn(lpmode=False)
240251

241252
This will flash the red LED on button push, and the green LED on release. A
242253
long press will flash the blue LED and a double-press the yellow one.
243254

255+
Test the launching of coroutines and also the `suppress` constructor arg.
256+
257+
It takes three optional positional boolean args:
258+
1. `Suppresss=False` If `True` sets the `suppress` constructor arg.
259+
2. `lf=True` Declare a long press coro.
260+
3. `df=true` Declare a double click coro.
261+
244262
The note below on race conditions applies.
245263

246264
## 4.4 Function test_btncb()
@@ -268,3 +286,6 @@ In the case of this test program it might be to ignore events while a similar
268286
one is running, or to extend the timer to prolong the LED illumination.
269287
Alternatively a subsequent button press might be required to terminate the
270288
illumination. The "right" behaviour is application dependent.
289+
290+
A further consequence of scheduling new coroutine instances when one or more
291+
are already running is that the `uasyncio` queue can fill causing an exception.

astests.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Tested on Pyboard but should run on other microcontroller platforms
33
# running MicroPython with uasyncio library.
44
# Author: Peter Hinch.
5-
# Copyright Peter Hinch 2017 Released under the MIT license.
5+
# Copyright Peter Hinch 2017-2018 Released under the MIT license.
66

77
from machine import Pin
88
from pyb import LED
@@ -60,20 +60,22 @@ def test_swcb():
6060
loop.run_until_complete(killer())
6161

6262
# Test for the Pushbutton class (coroutines)
63-
# Pass True to test lpmode
64-
def test_btn(lpmode=False):
63+
# Pass True to test suppress
64+
def test_btn(suppress=False, lf=True, df=True):
6565
print('Test of pushbutton scheduling coroutines.')
6666
print(helptext)
6767
pin = Pin('X1', Pin.IN, Pin.PULL_UP)
6868
red = LED(1)
6969
green = LED(2)
7070
yellow = LED(3)
7171
blue = LED(4)
72-
pb = Pushbutton(pin, lpmode)
72+
pb = Pushbutton(pin, suppress)
7373
pb.press_func(pulse, (red, 1000))
7474
pb.release_func(pulse, (green, 1000))
75-
pb.double_func(pulse, (yellow, 1000))
76-
pb.long_func(pulse, (blue, 1000))
75+
if df:
76+
pb.double_func(pulse, (yellow, 1000))
77+
if lf:
78+
pb.long_func(pulse, (blue, 1000))
7779
loop = asyncio.get_event_loop()
7880
loop.run_until_complete(killer())
7981

aswitch.py

Lines changed: 55 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ def trigger(self, duration=0): # Update end time
6767
def running(self):
6868
return self.tstop is not None
6969

70+
__call__ = running
71+
7072
async def killer(self):
7173
twait = time.ticks_diff(self.tstop, time.ticks_ms())
7274
while twait > 0: # Must loop here: might be retriggered
@@ -118,34 +120,37 @@ class Pushbutton(object):
118120
debounce_ms = 50
119121
long_press_ms = 1000
120122
double_click_ms = 400
121-
def __init__(self, pin, lpmode=False):
123+
def __init__(self, pin, suppress=False):
122124
self.pin = pin # Initialise for input
123-
self._lpmode = lpmode
124-
self._press_pend = False
125-
self._true_func = False
126-
self._false_func = False
127-
self._double_func = False
128-
self._long_func = False
125+
self._supp = suppress
126+
self._dblpend = False # Doubleclick waiting for 2nd click
127+
self._dblran = False # Doubleclick executed user function
128+
self._tf = False
129+
self._ff = False
130+
self._df = False
131+
self._lf = False
132+
self._ld = False # Delay_ms instance for long press
133+
self._dd = False # Ditto for doubleclick
129134
self.sense = pin.value() # Convert from electrical to logical value
130135
self.buttonstate = self.rawstate() # Initial state
131136
loop = asyncio.get_event_loop()
132137
loop.create_task(self.buttoncheck()) # Thread runs forever
133138

134139
def press_func(self, func, args=()):
135-
self._true_func = func
136-
self._true_args = args
140+
self._tf = func
141+
self._ta = args
137142

138143
def release_func(self, func, args=()):
139-
self._false_func = func
140-
self._false_args = args
144+
self._ff = func
145+
self._fa = args
141146

142147
def double_func(self, func, args=()):
143-
self._double_func = func
144-
self._double_args = args
148+
self._df = func
149+
self._da = args
145150

146151
def long_func(self, func, args=()):
147-
self._long_func = func
148-
self._long_args = args
152+
self._lf = func
153+
self._la = args
149154

150155
# Current non-debounced logical button state: True == pressed
151156
def rawstate(self):
@@ -155,42 +160,55 @@ def rawstate(self):
155160
def __call__(self):
156161
return self.buttonstate
157162

163+
def _ddto(self): # Doubleclick timeout: no doubleclick occurred
164+
self._dblpend = False
165+
if self._supp:
166+
if not self._ld or (self._ld and not self._ld()):
167+
launch(self._ff, self._fa)
168+
169+
def _ldip(self): # True if a long delay exists and is running
170+
d = self._ld
171+
return d and d()
172+
158173
async def buttoncheck(self):
159174
loop = asyncio.get_event_loop()
160-
if self._long_func:
161-
longdelay = Delay_ms(self._long_func, self._long_args)
162-
if self._double_func:
163-
doubledelay = Delay_ms()
175+
if self._lf:
176+
self._ld = Delay_ms(self._lf, self._la)
177+
if self._df:
178+
self._dd = Delay_ms(self._ddto)
164179
while True:
165180
state = self.rawstate()
166181
# State has changed: act on it now.
167182
if state != self.buttonstate:
168183
self.buttonstate = state
169184
if state:
170185
# Button is pressed
171-
if self._long_func and not longdelay.running():
186+
if self._lf:
172187
# Start long press delay
173-
longdelay.trigger(Pushbutton.long_press_ms)
174-
if self._double_func:
175-
if doubledelay.running():
176-
launch(self._double_func, self._double_args)
188+
self._ld.trigger(Pushbutton.long_press_ms)
189+
if self._df:
190+
if self._dd():
191+
self._dd.stop()
192+
self._dblpend = False
193+
self._dblran = True # Prevent suppressed launch on release
194+
launch(self._df, self._da)
177195
else:
178196
# First click: start doubleclick timer
179-
doubledelay.trigger(Pushbutton.double_click_ms)
180-
if self._true_func:
181-
if self._long_func and self._lpmode:
182-
self._press_pend = True # Delay launch until release
183-
else:
184-
launch(self._true_func, self._true_args)
197+
self._dd.trigger(Pushbutton.double_click_ms)
198+
self._dblpend = True # Prevent suppressed launch on release
199+
if self._tf:
200+
launch(self._tf, self._ta)
185201
else:
186202
# Button release
187-
if longdelay.running():
203+
if self._ff:
204+
if self._supp:
205+
if self._ldip() and not self._dblpend and not self._dblran:
206+
launch(self._ff, self._fa)
207+
else:
208+
launch(self._ff, self._fa)
209+
if self._ldip():
188210
# Avoid interpreting a second click as a long push
189-
longdelay.stop()
190-
if self._press_pend:
191-
launch(self._true_func, self._true_args)
192-
if self._false_func:
193-
launch(self._false_func, self._false_args)
194-
self._press_pend = False
211+
self._ld.stop()
212+
self._dblran = False
195213
# Ignore state changes until switch has settled
196214
await asyncio.sleep_ms(Pushbutton.debounce_ms)

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