Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bringing back prior (and new) button long-press actions (+extended documentation) #1406

Merged
merged 10 commits into from
May 15, 2021
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