Skip to content

Commit

Permalink
Fix button SecondFunc trigger (#2367)
Browse files Browse the repository at this point in the history
* fix instant execution of main action on holding

* simplify holdMode callback handling

* Update test_SimpleButton.py

* update docs

* fix: only go into while loop if further held

* docs: update comments

* docs: add alert for behavior change

* docs: fix typo
  • Loading branch information
AlvinSchiller authored May 8, 2024
1 parent cffa96c commit f3b4b20
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 26 deletions.
41 changes: 23 additions & 18 deletions components/gpio_control/GPIODevices/simple_button.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,7 @@ def callbackFunctionHandler(self, *args):
if inval != GPIO.LOW:
return None

if self.hold_mode in ('Repeat', 'Postpone', 'SecondFunc', 'SecondFuncRepeat'):
return self.longPressHandler(*args)
else:
logger.info('{}: execute callback'.format(self.name))
return self.when_pressed(*args)
return self._handleCallbackFunction(*args)

@property
def when_pressed(self):
Expand All @@ -136,36 +132,45 @@ def when_pressed(self, func):
def set_callbackFunction(self, callbackFunction):
self.when_pressed = callbackFunction

def longPressHandler(self, *args):
logger.info('{}: longPressHandler, mode: {}'.format(self.name, self.hold_mode))
# instant action (except Postpone mode)
if self.hold_mode != "Postpone":
self.when_pressed(*args)
def _handleCallbackFunction(self, *args):
logger.info('{}: handleCallbackFunction, mode: {}'.format(self.name, self.hold_mode))

# action(s) after hold_time
if self.hold_mode == "Repeat":
# Repeated call of main action (multiple times if button is held long enough)
# Instantly call primary action
self.when_pressed(*args)
# Repeated call of primary action if button is held long enough
while checkGpioStaysInState(self.hold_time, self.pin, GPIO.LOW):
self.when_pressed(*args)

elif self.hold_mode == "Postpone":
# Postponed call of main action (once)
# Postponed call of primary action (once)
if checkGpioStaysInState(self.hold_time, self.pin, GPIO.LOW):
self.when_pressed(*args)
while checkGpioStaysInState(self.hold_time, self.pin, GPIO.LOW):
pass
while checkGpioStaysInState(self.hold_time, self.pin, GPIO.LOW):
pass

elif self.hold_mode == "SecondFunc":
# Call of secondary action (once)
# execute primary action if not held past hold_time
if checkGpioStaysInState(self.hold_time, self.pin, GPIO.LOW):
self.when_held(*args)
while checkGpioStaysInState(self.hold_time, self.pin, GPIO.LOW):
pass
while checkGpioStaysInState(self.hold_time, self.pin, GPIO.LOW):
pass
else:
self.when_pressed(*args)

elif self.hold_mode == "SecondFuncRepeat":
# Repeated call of secondary action (multiple times if button is held long enough)
while checkGpioStaysInState(self.hold_time, self.pin, GPIO.LOW):
# execute primary action if not held past hold_time
if checkGpioStaysInState(self.hold_time, self.pin, GPIO.LOW):
self.when_held(*args)
while checkGpioStaysInState(self.hold_time, self.pin, GPIO.LOW):
self.when_held(*args)
else:
self.when_pressed(*args)

else:
self.when_pressed(*args)

def __del__(self):
logger.debug('remove event detection')
Expand Down
15 changes: 9 additions & 6 deletions components/gpio_control/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,18 @@ functionCall: functionCallPlayerPause
However, a button has more parameters than these. In the following comprehensive list you can also find the default values which are used automatically if you leave out these settings:

* **functionCallArgs**: Arguments for primary function, defaults to `None`. Arguments are ignored, if `functionCall` does not take any.

> [!IMPORTANT]
> Since v2.8.0 the behavior of `hold_mode` `SecondFunc` and `SecondFuncRepeat` has changed. The secondary function is no longer triggered additionally to the primary function.
> Now its called exclusively if `hold_time` is reached. The primary function will only be triggered if the button is pressed shorter then `hold_time`!
> Existing configurations may need to adapt to this.
* **hold_mode**: Specifies what shall happen if the button is held pressed for longer than `hold_time`:
* `None` (Default): Nothing special will happen.
* `Repeat`: The configured `functionCall` is repeated after each `hold_time` interval.
* `Repeat`: The configured `functionCall` is instantly called and repeatedly after each `hold_time` interval.
* `Postpone`: The function will not be called before `hold_time`, i.e. the button needs to be pressed this long to activate the function
* `SecondFunc`: Holding the button for at least `hold_time` will additionally execute the function `functionCall2` with `functionCall2Args`.
* `SecondFunc`: Pressing the button (shorter than `hold_time`) will execute the function `functionCall` with `functionCallArgs`. Holding the button for at least `hold_time` will execute the function `functionCall2` with `functionCall2Args`.
* `SecondFuncRepeat`: Like SecondFunc, but `functionCall2` is repeated after each `hold_time` interval.

In every `hold_mode` except `Postpone`, the main action `functionCall` gets executed instantly.

Holding the button even longer than `hold_time` will cause no further action unless you are in the `Repeat` or `SecondFuncRepeat` mode.

* **hold_time**: Reference time for this buttons `hold_mode` feature in seconds. Default is `0.3`. This setting is ignored if `hold_mode` is unset or `None`
Expand All @@ -79,10 +82,10 @@ However, a button has more parameters than these. In the following comprehensive
* `pull_off`. Use this to deactivate internal pull-up/pulldown resistors. This is useful if your wiring includes your own (external) pull up / down resistors.
* **edge**: Configures the events in which the GPIO library shall trigger the callback function. Valid settings:
* `falling` (Default). Triggers if the GPIO voltage goes down.
* `rising`. Trigegrs only if the GPIO voltage goes up.
* `rising`. Triggers only if the GPIO voltage goes up.
* `both`. Triggers in both cases.
* **bouncetime**: This is a setting of the GPIO library to limit bouncing effects during button usage. Default is `500` ms.
* **antibouncehack**: Despite the integrated bounce reduction of the GPIO library some users may notice false triggers of their buttons (e.g. unrequested / double actions when releasing the button). If you encounter such problems, try setting this setting to `True` to activate an additional countermeasure.
* **antibouncehack**: Despite the integrated bounce reduction of the GPIO library some users may notice false triggers of their buttons (e.g. unrequested / double actions when releasing the button). If you encounter such problems, try setting this to `True` to activate an additional countermeasure.

Note: If you prefer, you may also use `Type: SimpleButton` instead of `Type: Button` - this makes no difference.

Expand Down
4 changes: 2 additions & 2 deletions components/gpio_control/test/test_SimpleButton.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def test_hold_SecondFunc_longer_holdtime(self, simple_button):
simple_button.hold_mode = 'SecondFunc'
calls = mockedSecAction.call_count
simple_button.callbackFunctionHandler(simple_button.pin)
mockedAction.assert_called_once()
mockedAction.assert_not_called()
assert mockedSecAction.call_count - calls == 1

def test_hold_SecondFunc_shorter_holdtime(self, simple_button):
Expand All @@ -170,7 +170,7 @@ def test_hold_SecondFuncRepeat_longer_holdtime(self, simple_button):
simple_button.hold_mode = 'SecondFuncRepeat'
calls = mockedSecAction.call_count
simple_button.callbackFunctionHandler(simple_button.pin)
mockedAction.assert_called_once()
mockedAction.assert_not_called()
assert mockedSecAction.call_count - calls == 3

def test_hold_SecondFuncRepeat_shorter_holdtime(self, simple_button):
Expand Down

0 comments on commit f3b4b20

Please sign in to comment.