Skip to content

Commit

Permalink
Bringing back prior (and new) button long-press actions (+extended do…
Browse files Browse the repository at this point in the history
…cumentation) (MiczFlor#1406)

* Added an optional countermeasure against button bouncing effects
(can be switched on via the config);
Enhanced verbosity of Button status on console output.

* Updated ShutdownButton:
- Optimized control logic (iteration_time was formerly forced to 200ms ignoring the config value)
- Renamed time_pressed to hold_time to match the corresponding name in SimpleButton base class
- Pass-through of antibounce feature
- Enhanced verbosity of ShutdownButton status on console output.

* Introduced support for different modes on button hold:
Changed hold_repeat flag (True/False) to hold_mode (textual). "hold_repeat = True" is "hold_mode = Repeat" now.
Added new hold_mode "Postpone" (as possible in earlier PhonieBox versions)

* Added new hold_mode "SecondFunc" for a different action after hold_time (as possible in earlier PhonieBox versions)

* Added new hold_mode "SecondFuncRepeated" for repeated executions of a different action after hold_time

* Updated TwoButtonControl:
- Pass-through of all relevant SimpleButton base parameters
- Enhanced verbosity of TwoButtonControl status on console output
- Updated VolumeControl.py (though this file is completely superfluous / redundant IMHO)

* Added more detailed GPIO component documentation to README.MD
Fixed several typos and issues in several ini files

* Added more functions that can be called by GPIO controls like buttons.
Extended documentation, e.g. more example configurations.

* Added auto-conversion of old/deprecated syntax within gpiocontrol.ini entries.
Removed special (and redundant) handling for controls if (and only if) the corresponding config sections is named "VolumeControl"

* Added the __init__.py I just forgot...
  • Loading branch information
T0bi79 authored May 15, 2021
1 parent 5eba635 commit 88931b1
Show file tree
Hide file tree
Showing 17 changed files with 558 additions and 176 deletions.
27 changes: 0 additions & 27 deletions components/gpio_control/GPIODevices/VolumeControl.py

This file was deleted.

1 change: 0 additions & 1 deletion components/gpio_control/GPIODevices/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@
from .shutdown_button import ShutdownButton
from .simple_button import SimpleButton
from .two_button_control import TwoButtonControl
from .VolumeControl import VolumeControl
from .led import *
40 changes: 20 additions & 20 deletions components/gpio_control/GPIODevices/shutdown_button.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
import math
import time
from RPi import GPIO
import logging
try:
from simple_button import SimpleButton
from simple_button import SimpleButton, print_edge_key, print_pull_up_down
except ImportError:
from .simple_button import SimpleButton
from .simple_button import SimpleButton, print_edge_key, print_pull_up_down

logger = logging.getLogger(__name__)


class ShutdownButton(SimpleButton):

def __init__(self, pin, action=lambda *args: None, name=None, bouncetime=500, edge=GPIO.FALLING,
led_pin=None, time_pressed=2, pull_up_down=GPIO.PUD_UP, iteration_time=.2):
def __init__(self, pin, action=lambda *args: None, name=None, bouncetime=500, antibouncehack=False, edge='falling',
led_pin=None, hold_time=3.0, pull_up_down='pull_up', iteration_time=.2):
self.led_pin = led_pin
self.time_pressed = time_pressed
self.iteration_time = iteration_time
if self.led_pin is not None:
GPIO.setup(self.led_pin, GPIO.OUT)
super(ShutdownButton, self).__init__(pin=pin, action=action, name=name, bouncetime=bouncetime, edge=edge,
super(ShutdownButton, self).__init__(pin=pin, action=action, name=name, bouncetime=bouncetime,
antibouncehack=antibouncehack, edge=edge, hold_time=hold_time,
pull_up_down=pull_up_down
)
pass
Expand All @@ -35,28 +34,29 @@ def set_led(self, status):
logger.debug('cannot set LED to {}: no LED pin defined'.format(status))

def callbackFunctionHandler(self, *args):
cancelled = False
n_checks = math.ceil(self.time_pressed / self.iteration_time)
logger.debug('ShutdownButton pressed, ensuring long press for {} seconds, checking each {}s: {}'.format(
self.time_pressed, self.iteration_time, n_checks
logger.debug('ShutdownButton pressed, ensuring long press for {} seconds, checking each {}s'.format(
self.hold_time, self.iteration_time
))
for x in range(n_checks):
self.set_led(x & 1)
time.sleep(.2)
cancelled = not self.is_pressed
if cancelled:
t_passed = 0
led_state = True
while t_passed < self.hold_time:
self.set_led(led_state)
time.sleep(self.iteration_time)
t_passed += self.iteration_time
led_state = not led_state
if not self.is_pressed:
break
if not cancelled:
if t_passed >= self.hold_time:
# trippel off period to indicate command accepted
time.sleep(.6)
self.set_led(GPIO.HIGH)
time.sleep(.6)
# leave it on for the moment, it will be off when the system is down
self.when_pressed(*args)
else:
# switch off LED if pressing was cancelled early (during flashing)
self.set_led(GPIO.LOW)

def __repr__(self):
return '<ShutdownButton-{}(pin {},time_pressed={},iteration_time={},led_pin={})>'.format(
self.name, self.pin, self.time_pressed, self.iteration_time, self.led_pin
return '<ShutdownButton-{}(pin={},hold_time={},iteration_time={},led_pin={},edge={},bouncetime={},antibouncehack={},pull_up_down={})>'.format(
self.name, self.pin, self.hold_time, self.iteration_time, self.led_pin, print_edge_key(self.edge), self.bouncetime,self.antibouncehack, print_pull_up_down(self.pull_up_down)
)
123 changes: 85 additions & 38 deletions components/gpio_control/GPIODevices/simple_button.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,44 @@

logger = logging.getLogger(__name__)

map_edge_parse = {'falling':GPIO.FALLING, 'rising':GPIO.RISING, 'both':GPIO.BOTH}
map_pull_parse = {'pull_up':GPIO.PUD_UP, 'pull_down':GPIO.PUD_DOWN, 'pull_off':GPIO.PUD_OFF}
map_edge_print = {GPIO.FALLING: 'falling', GPIO.RISING: 'rising', GPIO.BOTH: 'both'}
map_pull_print = {GPIO.PUD_UP:'pull_up', GPIO.PUD_DOWN: 'pull_down', GPIO.PUD_OFF: 'pull_off'}

def parse_edge_key(edge):
if edge in [GPIO.FALLING, GPIO.RISING, GPIO.BOTH]:
edge
elif edge.lower() == 'falling':
edge = GPIO.FALLING
elif edge.lower() == 'raising':
edge = GPIO.RISING
elif edge.lower() == 'both':
edge = GPIO.BOTH
else:
return edge
try:
result = map_edge_parse[edge.lower()]
except KeyError:
result = edge
raise KeyError('Unknown Edge type {edge}'.format(edge=edge))
return edge

return result

def parse_pull_up_down(pull_up_down):
if pull_up_down in [GPIO.PUD_UP, GPIO.PUD_DOWN, GPIO.PUD_OFF]:
pull_up_down
elif pull_up_down.lower() == 'pull_up':
pull_up_down = GPIO.PUD_UP
elif pull_up_down.lower() == 'pull_down':
pull_up_down = GPIO.PUD_DOWN
elif pull_up_down.lower() == 'pull_off':
pull_up_down = GPIO.PUD_OFF
else:
return pull_up_down
try:
result = map_pull_parse[pull_up_down]
except KeyError:
result = pull_up_down
raise KeyError('Unknown Pull Up/Down type {pull_up_down}'.format(pull_up_down=pull_up_down))
return pull_up_down

return result

def print_edge_key(edge):
try:
result = map_edge_print[edge]
except KeyError:
result = edge
return result

def print_pull_up_down(pull_up_down):
try:
result = map_pull_print[pull_up_down]
except KeyError:
result = pull_up_down
return result

# This function takes a holding time (fractional seconds), a channel, a GPIO state and an action reference (function).
# It checks if the GPIO is in the state since the function was called. If the state
Expand All @@ -43,6 +53,7 @@ def checkGpioStaysInState(holdingTime, gpioChannel, gpioHoldingState):
startTime = time.perf_counter()
# Continously check if time is not over
while True:
time.sleep(0.1)
currentState = GPIO.input(gpioChannel)
if holdingTime < (time.perf_counter() - startTime):
break
Expand All @@ -57,19 +68,21 @@ def checkGpioStaysInState(holdingTime, gpioChannel, gpioHoldingState):


class SimpleButton:
def __init__(self, pin, action=lambda *args: None, name=None, bouncetime=500, edge=GPIO.FALLING,
hold_time=.1, hold_repeat=False, pull_up_down=GPIO.PUD_UP):
def __init__(self, pin, action=lambda *args: None, action2=lambda *args: None, name=None,
bouncetime=500, antibouncehack=False, edge='falling', hold_time=.3, hold_mode=None, pull_up_down='pull_up'):
self.edge = parse_edge_key(edge)
self.hold_time = hold_time
self.hold_repeat = hold_repeat
self.hold_mode = hold_mode
self.pull_up = True
self.pull_up_down = parse_pull_up_down(pull_up_down)

self.pin = pin
self.name = name
self.bouncetime = bouncetime
self.antibouncehack = antibouncehack
GPIO.setup(self.pin, GPIO.IN, pull_up_down=self.pull_up_down)
self._action = action
self._action2 = action2
GPIO.add_event_detect(self.pin, edge=self.edge, callback=self.callbackFunctionHandler,
bouncetime=self.bouncetime)
self.callback_with_pin_argument = False
Expand All @@ -80,37 +93,71 @@ def callbackFunctionHandler(self, *args):
args = args[1:]
logger.debug('args after: {}'.format(args))

if self.hold_repeat:
return self.holdAndRepeatHandler(*args)
logger.info('{}: execute callback'.format(self.name))
return self.when_pressed(*args)
if self.antibouncehack:
time.sleep(0.1)
inval = GPIO.input(self.pin)
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)

@property
def when_pressed(self):
logger.info('{}: action'.format(self.name))
return self._action

@property
def when_held(self):
logger.info('{}: action2'.format(self.name))
return self._action2

@when_pressed.setter
def when_pressed(self, func):
logger.info('{}: set when_pressed')
self._action = func

GPIO.remove_event_detect(self.pin)
self._action = func
logger.info('add new action')
GPIO.add_event_detect(self.pin, edge=self.edge, callback=self.callbackFunctionHandler, bouncetime=self.bouncetime)

def set_callbackFunction(self, callbackFunction):
self.when_pressed = callbackFunction

def holdAndRepeatHandler(self, *args):
logger.info('{}: holdAndRepeatHandler'.format(self.name))
# Rise volume as requested
self.when_pressed(*args)
# Detect holding of button
while checkGpioStaysInState(self.hold_time, self.pin, GPIO.LOW):
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)


# action(s) after hold_time
if self.hold_mode == "Repeat":
# Repeated call of main action (multiple times 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)
if checkGpioStaysInState(self.hold_time, self.pin, GPIO.LOW):
self.when_pressed(*args)
while checkGpioStaysInState(self.hold_time, self.pin, GPIO.LOW):
pass

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

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):
self.when_held(*args)

def __del__(self):
logger.debug('remove event detection')
GPIO.remove_event_detect(self.pin)
Expand All @@ -122,14 +169,14 @@ def is_pressed(self):
return GPIO.input(self.pin)

def __repr__(self):
return '<SimpleButton-{}(pin {},hold_repeat={},hold_time={})>'.format(
self.name, self.pin, self.hold_repeat, self.hold_time
return '<SimpleButton-{}(pin={},edge={},hold_mode={},hold_time={},bouncetime={},antibouncehack={},pull_up_down={})>'.format(
self.name, self.pin, print_edge_key(self.edge), self.hold_mode, self.hold_time, self.bouncetime,self.antibouncehack,print_pull_up_down(self.pull_up_down)
)


if __name__ == "__main__":
print('please enter pin no to test')
pin = int(input())
func = lambda *args: print('FunctionCall with {}'.format(args))
btn = SimpleButton(pin=pin, action=func, hold_repeat=True)
btn = SimpleButton(pin=pin, action=func, hold_mode='Repeat')
pause()
51 changes: 31 additions & 20 deletions components/gpio_control/GPIODevices/two_button_control.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
try:
from simple_button import SimpleButton
from simple_button import SimpleButton, print_edge_key, print_pull_up_down
except ImportError:
from .simple_button import SimpleButton
from .simple_button import SimpleButton, print_edge_key, print_pull_up_down
from RPi import GPIO
import logging
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -59,32 +59,43 @@ def __init__(self,
functionCallBtn1,
functionCallBtn2,
functionCallTwoBtns=None,
pull_up=True,
hold_repeat=True,
pull_up_down='pull_up',
hold_mode=None,
hold_time=0.3,
bouncetime=500,
antibouncehack=False,
edge='falling',
name='TwoButtonControl'):
self.bcmPin1 = bcmPin1
self.bcmPin2 = bcmPin2
self.functionCallBtn1 = functionCallBtn1
self.functionCallBtn2 = functionCallBtn2
self.functionCallTwoBtns = functionCallTwoBtns
self.bcmPin1 = bcmPin1
self.bcmPin2 = bcmPin2
self.pull_up_down=pull_up_down
self.hold_mode=hold_mode
self.hold_time=hold_time
self.bouncetime=bouncetime
self.antibouncehack=antibouncehack
self.edge=edge
self.btn1 = SimpleButton(
pin=bcmPin1,
action=lambda *args: None,
name=name + 'Btn1',
bouncetime=500,
edge=GPIO.FALLING,
bouncetime=bouncetime,
antibouncehack=antibouncehack,
edge=edge,
hold_time=hold_time,
hold_repeat=hold_repeat)
hold_mode=hold_mode,
pull_up_down=pull_up_down)
self.btn1.callback_with_pin_argument = True

self.btn2 = SimpleButton(pin=bcmPin2,
action=lambda *args: None,
hold_time=hold_time,
hold_repeat=hold_repeat,
name=name + 'Btn2',
bouncetime=500,
edge=GPIO.FALLING)
bouncetime=bouncetime,
antibouncehack=antibouncehack,
edge=edge,
hold_time=hold_time,
hold_mode=hold_mode,
pull_up_down=pull_up_down)
self.btn2.callback_with_pin_argument = True
generatedTwoButtonFunctionCall = functionCallTwoButtons(self.btn1,
self.btn2,
Expand All @@ -100,11 +111,11 @@ def __init__(self,

def __repr__(self):
two_btns_action = self.functionCallTwoBtns is not None
return '<TwoBtnControl-{name}({bcmPin1}, {bcmPin2},two_buttons_action={two_btns_action})>'.format(
name=self.name,
bcmPin1=self.bcmPin1,
bcmPin2=self.bcmPin2,
two_btns_action=two_btns_action
return '<TwoBtnControl-{}({}, {},two_buttons_action={},hold_mode={},hold_time={},edge={},bouncetime={},antibouncehack={},pull_up_down={})>'.format(
self.name, self.bcmPin1, self.bcmPin2, two_btns_action,
self.hold_mode, self.hold_time, print_edge_key(self.edge),
self.bouncetime, self.antibouncehack,
print_pull_up_down(self.pull_up_down)
)


Expand Down
Loading

0 comments on commit 88931b1

Please sign in to comment.