From f37d50f9243ea50c1022b60cba88d32af7ed4f34 Mon Sep 17 00:00:00 2001 From: T0bi79 Date: Mon, 10 May 2021 21:46:54 +0200 Subject: [PATCH 01/10] Added an optional countermeasure against button bouncing effects (can be switched on via the config); Enhanced verbosity of Button status on console output. --- .../gpio_control/GPIODevices/simple_button.py | 65 ++++++++++++------- .../example_configs/gpio_settings.ini | 2 + components/gpio_control/gpio_control.py | 1 + 3 files changed, 44 insertions(+), 24 deletions(-) diff --git a/components/gpio_control/GPIODevices/simple_button.py b/components/gpio_control/GPIODevices/simple_button.py index f2e43c87d..61e241e64 100644 --- a/components/gpio_control/GPIODevices/simple_button.py +++ b/components/gpio_control/GPIODevices/simple_button.py @@ -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 @@ -57,8 +67,8 @@ 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, name=None, bouncetime=500, antibouncehack=False, + edge=GPIO.FALLING, hold_time=.1, hold_repeat=False, pull_up_down=GPIO.PUD_UP): self.edge = parse_edge_key(edge) self.hold_time = hold_time self.hold_repeat = hold_repeat @@ -68,6 +78,7 @@ def __init__(self, pin, action=lambda *args: None, name=None, bouncetime=500, ed 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 GPIO.add_event_detect(self.pin, edge=self.edge, callback=self.callbackFunctionHandler, @@ -80,6 +91,12 @@ def callbackFunctionHandler(self, *args): args = args[1:] logger.debug('args after: {}'.format(args)) + if self.antibouncehack: + time.sleep(0.1) + inval = GPIO.input(self.pin) + if inval != GPIO.LOW: + return None + if self.hold_repeat: return self.holdAndRepeatHandler(*args) logger.info('{}: execute callback'.format(self.name)) @@ -122,8 +139,8 @@ def is_pressed(self): return GPIO.input(self.pin) def __repr__(self): - return ''.format( - self.name, self.pin, self.hold_repeat, self.hold_time + return ''.format( + self.name, self.pin, print_edge_key(self.edge), self.hold_repeat, self.hold_time, self.bouncetime,self.antibouncehack,print_pull_up_down(self.pull_up_down) ) diff --git a/components/gpio_control/example_configs/gpio_settings.ini b/components/gpio_control/example_configs/gpio_settings.ini index c7e50485b..34400ad30 100755 --- a/components/gpio_control/example_configs/gpio_settings.ini +++ b/components/gpio_control/example_configs/gpio_settings.ini @@ -1,5 +1,7 @@ [DEFAULT] enabled: True +antibouncehack: False + [VolumeControl] enabled: True Type: TwoButtonControl ;or RotaryEncoder diff --git a/components/gpio_control/gpio_control.py b/components/gpio_control/gpio_control.py index ddd8634bf..dbaf6f05e 100755 --- a/components/gpio_control/gpio_control.py +++ b/components/gpio_control/gpio_control.py @@ -53,6 +53,7 @@ def generate_device(self, config, deviceName): action=self.getFunctionCall(config.get('functionCall')), name=deviceName, bouncetime=config.getint('bouncetime', fallback=500), + antibouncehack=config.getboolean('antibouncehack', fallback=False), edge=config.get('edge', fallback='FALLING'), hold_repeat=config.getboolean('hold_repeat', False), hold_time=config.getfloat('hold_time', fallback=0.3), From 443964de83aa807ddead21999ac00268cda34a43 Mon Sep 17 00:00:00 2001 From: T0bi79 Date: Mon, 10 May 2021 21:47:59 +0200 Subject: [PATCH 02/10] 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. --- .../GPIODevices/shutdown_button.py | 40 +++++++++---------- .../example_configs/gpio_settings_test.ini | 2 +- components/gpio_control/gpio_control.py | 3 +- .../gpio_control/test/gpio_settings_test.ini | 2 +- 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/components/gpio_control/GPIODevices/shutdown_button.py b/components/gpio_control/GPIODevices/shutdown_button.py index d9ef7218a..bc0560a2b 100644 --- a/components/gpio_control/GPIODevices/shutdown_button.py +++ b/components/gpio_control/GPIODevices/shutdown_button.py @@ -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=GPIO.FALLING, + led_pin=None, hold_time=3.0, pull_up_down=GPIO.PUD_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 @@ -35,21 +34,22 @@ 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: @@ -57,6 +57,6 @@ def callbackFunctionHandler(self, *args): self.set_led(GPIO.LOW) def __repr__(self): - return ''.format( - self.name, self.pin, self.time_pressed, self.iteration_time, self.led_pin + return ''.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) ) diff --git a/components/gpio_control/example_configs/gpio_settings_test.ini b/components/gpio_control/example_configs/gpio_settings_test.ini index 8d9c9adfc..7cd17b8c5 100644 --- a/components/gpio_control/example_configs/gpio_settings_test.ini +++ b/components/gpio_control/example_configs/gpio_settings_test.ini @@ -37,7 +37,7 @@ hold_repeat: False enabled: True Type: ShutdownButton Pin: 3 -time_pressed: 2 +hold_time: 2 iteration_time: 0.2 ; led_pin: , if LED used functionCall: functionCallShutdown diff --git a/components/gpio_control/gpio_control.py b/components/gpio_control/gpio_control.py index dbaf6f05e..79773d5d4 100755 --- a/components/gpio_control/gpio_control.py +++ b/components/gpio_control/gpio_control.py @@ -76,8 +76,9 @@ def generate_device(self, config, deviceName): action=self.getFunctionCall(config.get('functionCall', fallback='functionCallShutdown')), name=deviceName, bouncetime=config.getint('bouncetime', fallback=500), + antibouncehack=config.getboolean('antibouncehack', fallback=False), edge=config.get('edge', fallback='FALLING'), - time_pressed=config.getfloat('time_pressed', fallback=2.0), + hold_time=config.getfloat('hold_time', fallback=3.0), iteration_time=config.getfloat('iteration_time', fallback=0.2), led_pin=config.getint('led_pin', fallback=None), pull_up_down=config.get('pull_up_down', fallback=GPIO.PUD_UP)) diff --git a/components/gpio_control/test/gpio_settings_test.ini b/components/gpio_control/test/gpio_settings_test.ini index a2795f057..d83c6b71a 100644 --- a/components/gpio_control/test/gpio_settings_test.ini +++ b/components/gpio_control/test/gpio_settings_test.ini @@ -38,7 +38,7 @@ hold_repeat: False enabled: True Type: ShutdownButton Pin: 3 -time_pressed: 2 +hold_time: 2 iteration_time: 0.2 ; led_pin: , if LED used functionCall: functionCallShutdown From 0ca374ef38e54694e5c44fce487bbc416971985c Mon Sep 17 00:00:00 2001 From: T0bi79 Date: Mon, 10 May 2021 21:48:59 +0200 Subject: [PATCH 03/10] 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) --- .../gpio_control/GPIODevices/simple_button.py | 44 ++++++++++++------- .../GPIODevices/two_button_control.py | 5 ++- .../example_configs/gpio_settings.ini | 6 ++- .../example_configs/gpio_settings_test.ini | 4 +- components/gpio_control/gpio_control.py | 2 +- .../gpio_control/test/gpio_settings_test.ini | 16 +++---- .../gpio_control/test/test_SimpleButton.py | 2 +- 7 files changed, 47 insertions(+), 32 deletions(-) diff --git a/components/gpio_control/GPIODevices/simple_button.py b/components/gpio_control/GPIODevices/simple_button.py index 61e241e64..4ab0dce5b 100644 --- a/components/gpio_control/GPIODevices/simple_button.py +++ b/components/gpio_control/GPIODevices/simple_button.py @@ -53,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 @@ -68,10 +69,10 @@ def checkGpioStaysInState(holdingTime, gpioChannel, gpioHoldingState): class SimpleButton: def __init__(self, pin, action=lambda *args: None, name=None, bouncetime=500, antibouncehack=False, - edge=GPIO.FALLING, hold_time=.1, hold_repeat=False, pull_up_down=GPIO.PUD_UP): + edge=GPIO.FALLING, hold_time=.3, hold_mode=None, pull_up_down=GPIO.PUD_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) @@ -97,10 +98,11 @@ def callbackFunctionHandler(self, *args): if inval != GPIO.LOW: return None - if self.hold_repeat: - return self.holdAndRepeatHandler(*args) - logger.info('{}: execute callback'.format(self.name)) - return self.when_pressed(*args) + if self.hold_mode in ('Repeat', 'Postpone'): + return self.longPressHandler(*args) + else: + logger.info('{}: execute callback'.format(self.name)) + return self.when_pressed(*args) @property def when_pressed(self): @@ -113,20 +115,30 @@ def when_pressed(self, func): 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 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 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 def __del__(self): logger.debug('remove event detection') @@ -139,8 +151,8 @@ def is_pressed(self): return GPIO.input(self.pin) def __repr__(self): - return ''.format( - self.name, self.pin, print_edge_key(self.edge), self.hold_repeat, self.hold_time, self.bouncetime,self.antibouncehack,print_pull_up_down(self.pull_up_down) + return ''.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) ) @@ -148,5 +160,5 @@ def __repr__(self): 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() diff --git a/components/gpio_control/GPIODevices/two_button_control.py b/components/gpio_control/GPIODevices/two_button_control.py index 702b6a79d..9de101f69 100644 --- a/components/gpio_control/GPIODevices/two_button_control.py +++ b/components/gpio_control/GPIODevices/two_button_control.py @@ -68,6 +68,7 @@ def __init__(self, self.functionCallTwoBtns = functionCallTwoBtns self.bcmPin1 = bcmPin1 self.bcmPin2 = bcmPin2 + hold_mode = None if hold_repeat else 'Repeat' self.btn1 = SimpleButton( pin=bcmPin1, action=lambda *args: None, @@ -75,13 +76,13 @@ def __init__(self, bouncetime=500, edge=GPIO.FALLING, hold_time=hold_time, - hold_repeat=hold_repeat) + hold_mode=hold_mode) self.btn1.callback_with_pin_argument = True self.btn2 = SimpleButton(pin=bcmPin2, action=lambda *args: None, hold_time=hold_time, - hold_repeat=hold_repeat, + hold_mode=hold_mode, name=name + 'Btn2', bouncetime=500, edge=GPIO.FALLING) diff --git a/components/gpio_control/example_configs/gpio_settings.ini b/components/gpio_control/example_configs/gpio_settings.ini index 34400ad30..1cf4cfb46 100755 --- a/components/gpio_control/example_configs/gpio_settings.ini +++ b/components/gpio_control/example_configs/gpio_settings.ini @@ -39,6 +39,8 @@ enabled: False Type: Button Pin: 3 pull_up_down: pull_up +hold_mode: Postpone +hold_time: 2 functionCall: functionCallShutdown [Volume0] @@ -54,7 +56,7 @@ Type: Button Pin: 16 pull_up_down: pull_up hold_time: 0.3 -hold_repeat: True +hold_mode: Repeat functionCall: functionCallVolU [VolumeDown] @@ -63,7 +65,7 @@ Type: Button Pin: 19 pull_up_down: pull_up hold_time: 0.3 -hold_repeat: True +hold_mode: Repeat functionCall: functionCallVolD [NextSong] diff --git a/components/gpio_control/example_configs/gpio_settings_test.ini b/components/gpio_control/example_configs/gpio_settings_test.ini index 7cd17b8c5..3c7c87811 100644 --- a/components/gpio_control/example_configs/gpio_settings_test.ini +++ b/components/gpio_control/example_configs/gpio_settings_test.ini @@ -55,7 +55,7 @@ Type: Button Pin: 16 pull_up_down: pull_up hold_time: 0.3 -hold_repeat: True +hold_mode: Repeat functionCall: functionCallVolU [VolumeDown] @@ -64,7 +64,7 @@ Type: Button Pin: 19 pull_up_down: pull_up hold_time: 0.3 -hold_repeat: True +hold_mode: Repeat [NextSong] enabled: False diff --git a/components/gpio_control/gpio_control.py b/components/gpio_control/gpio_control.py index 79773d5d4..6d308c531 100755 --- a/components/gpio_control/gpio_control.py +++ b/components/gpio_control/gpio_control.py @@ -55,7 +55,7 @@ def generate_device(self, config, deviceName): bouncetime=config.getint('bouncetime', fallback=500), antibouncehack=config.getboolean('antibouncehack', fallback=False), edge=config.get('edge', fallback='FALLING'), - hold_repeat=config.getboolean('hold_repeat', False), + hold_mode=config.get('hold_mode', fallback=None), hold_time=config.getfloat('hold_time', fallback=0.3), pull_up_down=config.get('pull_up_down', fallback=GPIO.PUD_UP)) elif device_type == 'LED': diff --git a/components/gpio_control/test/gpio_settings_test.ini b/components/gpio_control/test/gpio_settings_test.ini index d83c6b71a..f6b876935 100644 --- a/components/gpio_control/test/gpio_settings_test.ini +++ b/components/gpio_control/test/gpio_settings_test.ini @@ -47,43 +47,43 @@ functionCall: functionCallShutdown enabled: False Type: Button Pin: 13 -pull_up: True +pull_up_down: pull_up functionCall: functionCallVol0 [VolumeUp] enabled: False Type: Button Pin: 16 -pull_up: True +pull_up_down: pull_up hold_time: 0.3 -hold_repeat: True +hold_mode: Repeat functionCall: functionCallVolU [VolumeDown] enabled: False Type: Button Pin: 19 -pull_up: True +pull_up_down: pull_up hold_time: 0.3 -hold_repeat: True +hold_mode: Repeat [NextSong] enabled: False Type: Button Pin: 26 -pull_up: True +pull_up_down: pull_up [PrevSong] enabled: False Type: Button Pin: 20 -pull_up: True +pull_up_down: pull_up [Halt] enabled: False Type: Button Pin: 21 -pull_up: True +pull_up_down: pull_up [StatusLED] enable: True diff --git a/components/gpio_control/test/test_SimpleButton.py b/components/gpio_control/test/test_SimpleButton.py index 87a3afe6f..7f118ebb4 100644 --- a/components/gpio_control/test/test_SimpleButton.py +++ b/components/gpio_control/test/test_SimpleButton.py @@ -53,7 +53,7 @@ def test_hold(self, simple_button): GPIO.LOW = 0 GPIO.input.side_effect = [False, False, False, True] simple_button.hold_time = 0 - simple_button.hold_repeat = True + simple_button.hold_mode = 'Repeat' calls = mockedAction.call_count simple_button.callbackFunctionHandler(simple_button.pin) assert mockedAction.call_count - calls == 4 From d8dc112a14bbbb87ccb8304fde12a5de8688123c Mon Sep 17 00:00:00 2001 From: T0bi79 Date: Mon, 10 May 2021 21:50:16 +0200 Subject: [PATCH 04/10] Added new hold_mode "SecondFunc" for a different action after hold_time (as possible in earlier PhonieBox versions) --- .../gpio_control/GPIODevices/simple_button.py | 23 +++++++++++++++---- .../example_configs/gpio_settings.ini | 10 ++++++++ components/gpio_control/gpio_control.py | 1 + 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/components/gpio_control/GPIODevices/simple_button.py b/components/gpio_control/GPIODevices/simple_button.py index 4ab0dce5b..a80ef5e9e 100644 --- a/components/gpio_control/GPIODevices/simple_button.py +++ b/components/gpio_control/GPIODevices/simple_button.py @@ -68,8 +68,8 @@ def checkGpioStaysInState(holdingTime, gpioChannel, gpioHoldingState): class SimpleButton: - def __init__(self, pin, action=lambda *args: None, name=None, bouncetime=500, antibouncehack=False, - edge=GPIO.FALLING, hold_time=.3, hold_mode=None, 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=GPIO.FALLING, hold_time=.3, hold_mode=None, pull_up_down=GPIO.PUD_UP): self.edge = parse_edge_key(edge) self.hold_time = hold_time self.hold_mode = hold_mode @@ -82,6 +82,7 @@ def __init__(self, pin, action=lambda *args: None, name=None, bouncetime=500, an 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 @@ -98,7 +99,7 @@ def callbackFunctionHandler(self, *args): if inval != GPIO.LOW: return None - if self.hold_mode in ('Repeat', 'Postpone'): + if self.hold_mode in ('Repeat', 'Postpone', 'SecondFunc'): return self.longPressHandler(*args) else: logger.info('{}: execute callback'.format(self.name)) @@ -109,6 +110,11 @@ 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') @@ -129,16 +135,23 @@ def longPressHandler(self, *args): # action(s) after hold_time if self.hold_mode == "Repeat": - # Repeated call of action (multiple times if button is held long enough) + # 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 action (once) + # 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 def __del__(self): logger.debug('remove event detection') diff --git a/components/gpio_control/example_configs/gpio_settings.ini b/components/gpio_control/example_configs/gpio_settings.ini index 1cf4cfb46..152b579b1 100755 --- a/components/gpio_control/example_configs/gpio_settings.ini +++ b/components/gpio_control/example_configs/gpio_settings.ini @@ -43,6 +43,16 @@ hold_mode: Postpone hold_time: 2 functionCall: functionCallShutdown +[PauseShutdown] +enabled: False +Type: Button +Pin: 3 +pull_up_down: pull_up +hold_time: 2.0 +hold_mode: SecondFunc +functionCall: functionCallPlayerPause +functionCall2: functionCallShutdown + [Volume0] enabled: False Type: Button diff --git a/components/gpio_control/gpio_control.py b/components/gpio_control/gpio_control.py index 6d308c531..f2fde3cef 100755 --- a/components/gpio_control/gpio_control.py +++ b/components/gpio_control/gpio_control.py @@ -51,6 +51,7 @@ def generate_device(self, config, deviceName): elif device_type in ('Button', 'SimpleButton'): return SimpleButton(config.getint('Pin'), action=self.getFunctionCall(config.get('functionCall')), + action2=self.getFunctionCall(config.get('functionCall2', fallback='None')), name=deviceName, bouncetime=config.getint('bouncetime', fallback=500), antibouncehack=config.getboolean('antibouncehack', fallback=False), From 0839ddbed57450ac7b176fffe6dabc3783cd15fe Mon Sep 17 00:00:00 2001 From: T0bi79 Date: Mon, 10 May 2021 21:51:08 +0200 Subject: [PATCH 05/10] Added new hold_mode "SecondFuncRepeated" for repeated executions of a different action after hold_time --- .../gpio_control/GPIODevices/simple_button.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/components/gpio_control/GPIODevices/simple_button.py b/components/gpio_control/GPIODevices/simple_button.py index a80ef5e9e..ebdd525f7 100644 --- a/components/gpio_control/GPIODevices/simple_button.py +++ b/components/gpio_control/GPIODevices/simple_button.py @@ -99,7 +99,7 @@ def callbackFunctionHandler(self, *args): if inval != GPIO.LOW: return None - if self.hold_mode in ('Repeat', 'Postpone', 'SecondFunc'): + if self.hold_mode in ('Repeat', 'Postpone', 'SecondFunc', 'SecondFuncRepeat'): return self.longPressHandler(*args) else: logger.info('{}: execute callback'.format(self.name)) @@ -132,7 +132,7 @@ def longPressHandler(self, *args): # 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) @@ -145,14 +145,19 @@ def longPressHandler(self, *args): 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) From 9c583e2081ac94ed9f014ea730efda1846fc644e Mon Sep 17 00:00:00 2001 From: T0bi79 Date: Mon, 10 May 2021 21:52:06 +0200 Subject: [PATCH 06/10] 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) --- .../gpio_control/GPIODevices/VolumeControl.py | 15 +++--- .../GPIODevices/shutdown_button.py | 4 +- .../gpio_control/GPIODevices/simple_button.py | 2 +- .../GPIODevices/two_button_control.py | 50 +++++++++++-------- .../example_configs/gpio_settings.ini | 16 +++--- .../gpio_settings_rotary_and_led.ini | 4 +- .../example_configs/gpio_settings_test.ini | 4 +- components/gpio_control/gpio_control.py | 15 +++--- .../gpio_control/test/gpio_settings_test.ini | 4 +- .../test/test_TwoButtonControl.py | 10 ++-- 10 files changed, 70 insertions(+), 54 deletions(-) diff --git a/components/gpio_control/GPIODevices/VolumeControl.py b/components/gpio_control/GPIODevices/VolumeControl.py index 47997a2b4..e27adc632 100644 --- a/components/gpio_control/GPIODevices/VolumeControl.py +++ b/components/gpio_control/GPIODevices/VolumeControl.py @@ -7,14 +7,17 @@ def __new__(self, config, getFunctionCall, logger): if config.get('Type') == 'TwoButtonControl': logger.info('VolumeControl as TwoButtonControl') return TwoButtonControl( - config.getint('pinUp'), - config.getint('pinDown'), - getFunctionCall(config.get('functionCallUp')), - getFunctionCall(config.get('functionCallDown')), + config.getint('Pin1'), + config.getint('Pin2'), + getFunctionCall(config.get('functionCall1')), + getFunctionCall(config.get('functionCall2')), functionCallTwoBtns=getFunctionCall(config.get('functionCallTwoButtons')), - pull_up=config.getboolean('pull_up', fallback=True), - hold_repeat=config.getboolean('hold_repeat', fallback=True), + pull_up_down=config.get('pull_up_down', fallback='pull_up'), + hold_mode=config.get('hold_mode', fallback='Repeat'), hold_time=config.getfloat('hold_time', fallback=0.3), + bouncetime=config.getint('bouncetime', fallback=500), + edge=config.get('edge', fallback='falling'), + antibouncehack=config.getboolean('antibouncehack', fallback=False), name='VolumeControl' ) elif config.get('Type') == 'RotaryEncoder': diff --git a/components/gpio_control/GPIODevices/shutdown_button.py b/components/gpio_control/GPIODevices/shutdown_button.py index bc0560a2b..30ba26b6f 100644 --- a/components/gpio_control/GPIODevices/shutdown_button.py +++ b/components/gpio_control/GPIODevices/shutdown_button.py @@ -11,8 +11,8 @@ class ShutdownButton(SimpleButton): - def __init__(self, pin, action=lambda *args: None, name=None, bouncetime=500, antibouncehack=False, edge=GPIO.FALLING, - led_pin=None, hold_time=3.0, 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.iteration_time = iteration_time if self.led_pin is not None: diff --git a/components/gpio_control/GPIODevices/simple_button.py b/components/gpio_control/GPIODevices/simple_button.py index ebdd525f7..69b6e3407 100644 --- a/components/gpio_control/GPIODevices/simple_button.py +++ b/components/gpio_control/GPIODevices/simple_button.py @@ -69,7 +69,7 @@ def checkGpioStaysInState(holdingTime, gpioChannel, gpioHoldingState): class SimpleButton: def __init__(self, pin, action=lambda *args: None, action2=lambda *args: None, name=None, - bouncetime=500, antibouncehack=False, edge=GPIO.FALLING, hold_time=.3, hold_mode=None, pull_up_down=GPIO.PUD_UP): + 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_mode = hold_mode diff --git a/components/gpio_control/GPIODevices/two_button_control.py b/components/gpio_control/GPIODevices/two_button_control.py index 9de101f69..d4ceb0a11 100644 --- a/components/gpio_control/GPIODevices/two_button_control.py +++ b/components/gpio_control/GPIODevices/two_button_control.py @@ -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__) @@ -59,33 +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 - hold_mode = None if hold_repeat else 'Repeat' + 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_mode=hold_mode) + 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, + name=name + 'Btn2', + bouncetime=bouncetime, + antibouncehack=antibouncehack, + edge=edge, hold_time=hold_time, hold_mode=hold_mode, - name=name + 'Btn2', - bouncetime=500, - edge=GPIO.FALLING) + pull_up_down=pull_up_down) self.btn2.callback_with_pin_argument = True generatedTwoButtonFunctionCall = functionCallTwoButtons(self.btn1, self.btn2, @@ -101,11 +111,11 @@ def __init__(self, def __repr__(self): two_btns_action = self.functionCallTwoBtns is not None - return ''.format( - name=self.name, - bcmPin1=self.bcmPin1, - bcmPin2=self.bcmPin2, - two_btns_action=two_btns_action + return ''.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) ) diff --git a/components/gpio_control/example_configs/gpio_settings.ini b/components/gpio_control/example_configs/gpio_settings.ini index 152b579b1..817c13bf4 100755 --- a/components/gpio_control/example_configs/gpio_settings.ini +++ b/components/gpio_control/example_configs/gpio_settings.ini @@ -5,14 +5,14 @@ antibouncehack: False [VolumeControl] enabled: True Type: TwoButtonControl ;or RotaryEncoder -PinUp: 5 -PinDown: 6 -pull_up: True +Pin1: 5 +Pin2: 6 +pull_up_down: pull_up hold_time: 0.3 -hold_repeat: True +hold_mode: Repeat timeBase: 0.1 ;only for RotaryEncoder -functionCallDown: functionCallVolD -functionCallUp: functionCallVolU +functionCall1: functionCallVolU +functionCall2: functionCallVolD functionCallTwoButtons: functionCallVol0 ;only for TwoButtonControl [PrevNextControl] @@ -23,9 +23,9 @@ Pin2: 23 functionCall1: functionCallPlayerPrev functionCall2: functionCallPlayerNext functionCallTwoButtons: None -pull_up: True +pull_up_down: pull_up hold_time: 0.3 -hold_repeat: False +hold_mode: None [PlayPause] enabled: True diff --git a/components/gpio_control/example_configs/gpio_settings_rotary_and_led.ini b/components/gpio_control/example_configs/gpio_settings_rotary_and_led.ini index 3e89195c8..a8d8cfcae 100644 --- a/components/gpio_control/example_configs/gpio_settings_rotary_and_led.ini +++ b/components/gpio_control/example_configs/gpio_settings_rotary_and_led.ini @@ -27,9 +27,9 @@ Pin2: 12 functionCall1: functionCallPlayerPrev functionCall2: functionCallPlayerNext functionCallTwoButtons: functionCallPlayerStop -pull_up: True +pull_up_down: pull_up hold_time: 0.3 -hold_repeat: False +hold_mode: None [Shutdown] diff --git a/components/gpio_control/example_configs/gpio_settings_test.ini b/components/gpio_control/example_configs/gpio_settings_test.ini index 3c7c87811..79e365f61 100644 --- a/components/gpio_control/example_configs/gpio_settings_test.ini +++ b/components/gpio_control/example_configs/gpio_settings_test.ini @@ -28,9 +28,9 @@ Pin2: 3 functionCall1: functionCallPlayerPrev functionCall2: functionCallPlayerNext functionCallTwoButtons: None -pull_up: True +pull_up_down: pull_up hold_time: 0.3 -hold_repeat: False +hold_mode: None [Shutdown] diff --git a/components/gpio_control/gpio_control.py b/components/gpio_control/gpio_control.py index f2fde3cef..23c7748ec 100755 --- a/components/gpio_control/gpio_control.py +++ b/components/gpio_control/gpio_control.py @@ -44,9 +44,12 @@ def generate_device(self, config, deviceName): self.getFunctionCall(config.get('functionCall1')), self.getFunctionCall(config.get('functionCall2')), functionCallTwoBtns=self.getFunctionCall(config.get('functionCallTwoButtons')), - pull_up=config.getboolean('pull_up', fallback=True), - hold_repeat=config.getboolean('hold_repeat', False), + pull_up_down=config.get('pull_up_down', fallback='pull_up'), + hold_mode=config.get('hold_mode', fallback=None), hold_time=config.getfloat('hold_time', fallback=0.3), + bouncetime=config.getint('bouncetime', fallback=500), + edge=config.get('edge', fallback='falling'), + antibouncehack=config.getboolean('antibouncehack', fallback=False), name=deviceName) elif device_type in ('Button', 'SimpleButton'): return SimpleButton(config.getint('Pin'), @@ -55,10 +58,10 @@ def generate_device(self, config, deviceName): name=deviceName, bouncetime=config.getint('bouncetime', fallback=500), antibouncehack=config.getboolean('antibouncehack', fallback=False), - edge=config.get('edge', fallback='FALLING'), + edge=config.get('edge', fallback='falling'), hold_mode=config.get('hold_mode', fallback=None), hold_time=config.getfloat('hold_time', fallback=0.3), - pull_up_down=config.get('pull_up_down', fallback=GPIO.PUD_UP)) + pull_up_down=config.get('pull_up_down', fallback='pull_up')) elif device_type == 'LED': return LED(config.getint('Pin'), name=deviceName, @@ -78,11 +81,11 @@ def generate_device(self, config, deviceName): name=deviceName, bouncetime=config.getint('bouncetime', fallback=500), antibouncehack=config.getboolean('antibouncehack', fallback=False), - edge=config.get('edge', fallback='FALLING'), + edge=config.get('edge', fallback='falling'), hold_time=config.getfloat('hold_time', fallback=3.0), iteration_time=config.getfloat('iteration_time', fallback=0.2), led_pin=config.getint('led_pin', fallback=None), - pull_up_down=config.get('pull_up_down', fallback=GPIO.PUD_UP)) + pull_up_down=config.get('pull_up_down', fallback='pull_up')) self.logger.warning('cannot find {}'.format(deviceName)) return None diff --git a/components/gpio_control/test/gpio_settings_test.ini b/components/gpio_control/test/gpio_settings_test.ini index f6b876935..cf7e45120 100644 --- a/components/gpio_control/test/gpio_settings_test.ini +++ b/components/gpio_control/test/gpio_settings_test.ini @@ -29,9 +29,9 @@ Pin2: 3 functionCall1: functionCallPlayerPrev functionCall2: functionCallPlayerNext functionCallTwoButtons: None -pull_up: True +pull_up_down: pull_up hold_time: 0.3 -hold_repeat: False +hold_mode: None [Shutdown] diff --git a/components/gpio_control/test/test_TwoButtonControl.py b/components/gpio_control/test/test_TwoButtonControl.py index 619ea4dd2..2741430d1 100644 --- a/components/gpio_control/test/test_TwoButtonControl.py +++ b/components/gpio_control/test/test_TwoButtonControl.py @@ -92,8 +92,8 @@ def two_button_controller(): functionCallBtn1=mockedFunction1, functionCallBtn2=mockedFunction2, functionCallTwoBtns=mockedFunction3, - pull_up=True, - hold_repeat=False, + pull_up_down='pull_up', + hold_mode=None, hold_time=0.3, name='TwoButtonControl') @@ -105,8 +105,8 @@ def test_init(self): functionCallBtn1=mockedFunction1, functionCallBtn2=mockedFunction2, functionCallTwoBtns=mockedFunction3, - pull_up=True, - hold_repeat=False, + pull_up_down='pull_up', + hold_mode=None, hold_time=0.3, name='TwoButtonControl') @@ -152,5 +152,5 @@ def test_btn1_and_btn2_pressed(self, two_button_controller): assert mockedFunction3.call_count == 2 def test_repr(self, two_button_controller): - expected = "" + expected = "" assert repr(two_button_controller) == expected From f74ed809d4ffe5912a2d6aa5d995953fe791dd3e Mon Sep 17 00:00:00 2001 From: T0bi79 Date: Mon, 10 May 2021 21:53:01 +0200 Subject: [PATCH 07/10] Added more detailed GPIO component documentation to README.MD Fixed several typos and issues in several ini files --- components/gpio_control/README.md | 198 +++++++++++++++++- .../gpio_setting_rotary_vol_prevnext.ini | 2 +- .../example_configs/gpio_settings.ini | 2 +- .../gpio_settings_rotary_and_led.ini | 8 +- .../gpio_settings_status_led.ini | 2 +- .../gpio_control/test/gpio_settings_test.ini | 5 +- 6 files changed, 198 insertions(+), 19 deletions(-) diff --git a/components/gpio_control/README.md b/components/gpio_control/README.md index 5851b9baa..9baca0d6f 100644 --- a/components/gpio_control/README.md +++ b/components/gpio_control/README.md @@ -15,17 +15,201 @@ In the following the different devices are described. Each device can have actions which correspond to function calls. Up to now the following input devices are implemented: * **Button**: - A simple button which has a hold and repeat functionality as well as a delayed action. - It can be configured using the keywords: Pin (**use GPIO number here**), hold_time, functionCall + A simple button with optional long-press actions like hold and repeat functionality or delayed action. + Its main parameters are: `Pin` (use GPIO number here) and `functionCall`. For additional options, see [extended documentation below](#doc_button). + +* **ShutdownButton**: + A specialized implementation for a shutdown button with integrated (but optional) LED support. It initializes a shutdown if the button is pressed more than `time_pressed` seconds and a (optional) LED on GPIO `led_pin` is flashing until that time is reached. For additional information, see [extended documentation below](#doc_sdbutton). * **RotaryEncoder**: - Control of a rotary encoder, for example KY040, see also in - [Wiki](https://github.com/MiczFlor/RPi-Jukebox-RFID/wiki/Audio-RotaryKnobVolume) - it can be configured using pinA (**use GPIO number here**), pinB (**use GPIO number here**), functionCallIncr, functionCallDecr, timeBase=0.1 + Control of a rotary encoder, for example KY040, see also in [Wiki](https://github.com/MiczFlor/RPi-Jukebox-RFID/wiki/Audio-RotaryKnobVolume). + It can be configured using `pinUp` and `PiNDown` (use GPIO numbers here), `functionCallUp`, `functionCallDown`, and `timeBase` see [extended documentation below](#doc_rotary). * **TwoButtonControl**: - This Device uses two Buttons and implements a third action if both buttons are pressed together. - + This Device uses two Buttons and implements a third action if both buttons are pressed together. See [extended documentation below](#doc_twobutton). + +* **StatusLED**: + A LED which will light up once the Phoniebox has fully booted up and is ready to be used. For additional information, see [extended documentation below](#doc_sled). + Each section needs to be activated by setting `enabled: True`. Many example files are located in `~/RPi-Jukebox-RFID/components/gpio_control/example_configs/`. + +# Extended documentation +This section provides some extended documentation and guideline. Especially some exemplary configurations are introduced showing how these controls can be set up in the configuration file `~/RPi-Jukebox-RFID/settings/gpio_settings.ini`. + +## Button +At the most basic level, a button can be created using an `ini` entry like this: +``` +[PlayPause] +enabled: True +Type: Button +Pin: 27 +functionCall: functionCallPlayerPause +``` +* **enabled**: This needs to be `True` for the button to work. +* **Pin**: GPIO number +* **functionCall**: The function that you want to be called on a button press. See [function documentation below](#doc_funcs). + +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: +* **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. + * `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`. + * `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` +* **functionCall2**: Secondary function; default is `None`. This setting is ignored unless `hold_mode` is set to `SecondFunc` or `SecondFuncRepeat`. +* **pull_up_down**: Configures the internal Pull up/down resistors. Valid settings: + * `pull_up` (Default). Internal pull-up resistors are activated. Use this if you attached a button to `GND` to the GPIO pin without any external pull-up resistor. + * `pull_down`. Use this if you need the internal pull-down resistor activated. + * `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. + * `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. + +Note: If you prefer, you may also use `Type: SimpleButton` instead of `Type: Button` - this makes no difference. + +## ShutdownButton +An extended ShutdownButton can be created using an `ini` entry like these: +``` +[Shutdown_without_LED] +enabled: True +Type: ShutdownButton +Pin: 3 + +[Shutdown_with_LED] +enabled: True +Type: ShutdownButton +Pin: 3 +led_pin: 17 +``` +* **enabled**: This needs to be `True` for the extended shutdown button to work. +* **Pin**: GPIO number of the button +* **led_pin**: GPIO number of the LED (Default is `None`). Note that you should not attach LEDs to GPIO ports without a matching resistor in line. + +Again, there are 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: +* **hold_time**: This parameter controls how many seconds (default: `3.0`) the button has to be hold until shutdown will be initiated. +* **iteration_time**: This parameter determines the flashing speed of the LED indicator. Default value is `0.2` seconds. +* **functionCall**: While the default action is `functionCallShutdown`, you might use this button type even with other functions than system shutdown (again, see [function documentation below](#doc_funcs) for a list of available functions). + +Furthermore, the following settings can be used as described for the [regular buttons](#doc_button): **pull_up_down**, **edge**, **bouncetime**, **antibouncehack** + +Note that using a ShutdownButton without a LED can also be implemented with a normal button like this: + +``` +[Shutdown] +enabled: True +Type: Button +Pin: 3 +hold_mode: Postpone +hold_time: 3.0 +functionCall: functionCallShutdown +``` + +## TwoButtonControl +A TwoButtonControl can be created using an `ini` entry like this: + +``` +[PrevNextStop] +enabled: True +Type: TwoButtonControl +Pin1: 24 +Pin2: 25 +functionCall1: functionCallPlayerNext +functionCall2: functionCallPlayerPrev +functionCallTwoButtons: functionCallPlayerStop +``` +In this example, you can navigate to the previous or, respectively next track by pushing the respective button. If you push both buttons simultaneously, the player stops. + +It is possible to combine the TwoButtonControl with the Repeat mode, e.g. to increment the volume further while the button keeps getting held: +``` +[VolumeControl] +enabled: True +Type: TwoButtonControl +Pin1: 5 +Pin2: 6 +hold_time: 0.3 +hold_mode: Repeat +functionCall1: functionCallVolD +functionCall2: functionCallVolU +functionCallTwoButtons: functionCallVol0 +``` +In this example, the volume will be in-/decreased step-wise using intervals of 0.3 seconds while the respective button is held. If both buttons are pushed simultaneously, the player is muted (volume 0). + +Furthermore, the following settings can be used as described for the [regular buttons](#doc_button): **pull_up_down**, **edge**, **bouncetime**, **antibouncehack** + + +## RotaryEncoder +A RotaryEncoder can be created using an `ini` entry like this: +``` +enabled: True +Type: RotaryEncoder +PinUp: 7 +PinDown: 8 +timeBase: 0.02 +functionCallDown: functionCallVolD +functionCallUp: functionCallVolU +``` + +TODO: details ... + +## StatusLED +A StatusLED can be created using an `ini` entry like this: +``` +[StatusLED] +enable: True +Type: StatusLED +Pin: 14 +``` +* **Pin**: GPIO number of the LED (mandatory option). Note that you should not attach LEDs to GPIO ports without a matching resistor in line. + +Note: If you prefer, you may also use `Type: MPDStatusLED` instead of `Type: StatusLED` - this makes no difference. + +## Functions +The available functions are defined/implemented in `components/gpio_control/function_calls.py`: +* **functionCallShutdown**: System shutdown +* **functionCallVolU**: Volupe up +* **functionCallVolD**: Volume down +* **functionCallVol0**: Mute +* **functionCallPlayerNext**: Next track +* **functionCallPlayerPrev**: Previous track +* **functionCallPlayerPauseForce**: Pause (forced) +* **functionCallPlayerPause**: Pause +* **functionCallRecordStart**: Start recording +* **functionCallRecordStop**: Stop recording +* **functionCallRecordPlayLatest**: Play latest recording +* **functionCallToggleWifi**: Toggle WIFI +* **functionCallPlayerStop**: Stop Player +* **functionCallPlayerSeekFwd**: Seek 10 seconds forward +* **functionCallPlayerSeekBack**: Seek 10 seconds backward + + +## Troubleshooting +If you encounter bouncing effects with your buttons like unrequested/double actions after releasing a button, you can try to set `antibouncehack` to True: + +``` +[NextSong] +enabled: True +Type: Button +Pin: 26 +functionCall: functionCallPlayerNext +antibouncehack: True +``` + +Instead of adding this to each button, you can also define it as default for all elements, by inserting the statement into the `Default` section which can be found at the beginning of the `~/RPi-Jukebox-RFID/settings/gpio_settings.ini` file: + + +``` +[DEFAULT] +enabled: True +antibouncehack: True +``` diff --git a/components/gpio_control/example_configs/gpio_setting_rotary_vol_prevnext.ini b/components/gpio_control/example_configs/gpio_setting_rotary_vol_prevnext.ini index efc2f057b..31d2df340 100755 --- a/components/gpio_control/example_configs/gpio_setting_rotary_vol_prevnext.ini +++ b/components/gpio_control/example_configs/gpio_setting_rotary_vol_prevnext.ini @@ -27,7 +27,7 @@ edge: raising functionCall: functionCallShutdown [PlayPause] -enable: True +enabled: True Type: Button Pin: 25 pull_up_down: pull_up diff --git a/components/gpio_control/example_configs/gpio_settings.ini b/components/gpio_control/example_configs/gpio_settings.ini index 817c13bf4..b05004c7a 100755 --- a/components/gpio_control/example_configs/gpio_settings.ini +++ b/components/gpio_control/example_configs/gpio_settings.ini @@ -114,7 +114,7 @@ pull_up_down: pull_up functionCall: functionCallPlayerPauseForce [RFIDDevice] -enable: True +enabled: True Type: Button Pin: 21 pull_up_down: pull_up diff --git a/components/gpio_control/example_configs/gpio_settings_rotary_and_led.ini b/components/gpio_control/example_configs/gpio_settings_rotary_and_led.ini index a8d8cfcae..303d0888d 100644 --- a/components/gpio_control/example_configs/gpio_settings_rotary_and_led.ini +++ b/components/gpio_control/example_configs/gpio_settings_rotary_and_led.ini @@ -7,9 +7,7 @@ enabled: True Type: RotaryEncoder PinUp: 17 PinDown: 22 -pull_up: True -hold_time: 0.3 -hold_repeat: True +pull_up_down: pull_up timeBase: 0.2 ; only for rotary encoder functionCallDown: functionCallVolD @@ -40,13 +38,13 @@ functionCall: functionCallShutdown [PlayPause] -enable: True +enabled: True Type: Button Pin: 27 pull_up_down: pull_up functionCall: functionCallPlayerPause [StatusLED] -enable: True +enabled: True Type: MPDStatusLED Pin: 16 diff --git a/components/gpio_control/example_configs/gpio_settings_status_led.ini b/components/gpio_control/example_configs/gpio_settings_status_led.ini index 00b9d1d7f..68e6461b2 100644 --- a/components/gpio_control/example_configs/gpio_settings_status_led.ini +++ b/components/gpio_control/example_configs/gpio_settings_status_led.ini @@ -2,6 +2,6 @@ enabled: True [StatusLED] -enable: True +enabled: True Type: StatusLED Pin: 14 diff --git a/components/gpio_control/test/gpio_settings_test.ini b/components/gpio_control/test/gpio_settings_test.ini index cf7e45120..a0e318244 100644 --- a/components/gpio_control/test/gpio_settings_test.ini +++ b/components/gpio_control/test/gpio_settings_test.ini @@ -11,9 +11,6 @@ enabled: True Type: RotaryEncoder PinUp: 16 PinDown: 19 -pull_up: True -hold_time: 0.3 -hold_repeat: True timeBase: 0.1 ; timeBase only for rotary encoder functionCallDown: functionCallVolD @@ -86,6 +83,6 @@ Pin: 21 pull_up_down: pull_up [StatusLED] -enable: True +enabled: True Type: StatusLED Pin: 14 From 60492791ca23eaea3771af18b2ef2e85953392f6 Mon Sep 17 00:00:00 2001 From: T0bi79 Date: Mon, 10 May 2021 23:12:24 +0200 Subject: [PATCH 08/10] Added more functions that can be called by GPIO controls like buttons. Extended documentation, e.g. more example configurations. --- components/gpio_control/README.md | 55 +++++++++++++++++++++-- components/gpio_control/function_calls.py | 15 +++++++ 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/components/gpio_control/README.md b/components/gpio_control/README.md index 9baca0d6f..0bb29bcc7 100644 --- a/components/gpio_control/README.md +++ b/components/gpio_control/README.md @@ -160,8 +160,6 @@ functionCallDown: functionCallVolD functionCallUp: functionCallVolU ``` -TODO: details ... - ## StatusLED A StatusLED can be created using an `ini` entry like this: ``` @@ -174,10 +172,55 @@ Pin: 14 Note: If you prefer, you may also use `Type: MPDStatusLED` instead of `Type: StatusLED` - this makes no difference. +## Further examples +By tapping the potential of the features presented above, you can create buttons like this: + +### Play random tracks or folders +If you have buttons to navigate to the next/previous track it might be a good idea to define that holding these buttons for a certain time (e.g. 2 seconds) will activate a random (surpise!) track or even folder/card. This might look like this + +``` +[NextOrRand] +enabled: True +Type: Button +Pin: 24 +pull_up_down: pull_up +hold_time: 2.0 +hold_mode: SecondFunc +functionCall: functionCallPlayerNext +functionCall2: functionCallPlayerRandomTrack + +[PrevOrRand] +enabled: True +Type: Button +Pin: 25 +pull_up_down: pull_up +hold_time: 2.0 +hold_mode: SecondFunc +functionCall: functionCallPlayerPrev +functionCall2: functionCallPlayerRandomFolder +``` + +### Short and long jumps +If you are using two buttons to jump backwards or forwards within the current track, you can use the repeated hold action to allow larger jumps: +``` +[SkipForward] +enabled: True +Type: Button +Pin: 24 +pull_up_down: pull_up +hold_time: 5.0 +hold_mode: SecondFuncRepeat +functionCall: functionCallPlayerSeekFwd +functionCall2: functionCallPlayerSeekFarFwd +``` +In this example, a short press initiates a short jump forward by 10 seconds (functionCallPlayerSeekFwd) while holding the button will cause further, longer jumps. In this case it will cause a jump of 1 minute forward (functionCallPlayerSeekFarFwd) every 5 seconds. If you wish, you can adjust these values in `components/gpio_control/function_calls.py`. +For jumping backwards, this can be done equivalently (see [function list below](#doc_funcs)). + + ## Functions The available functions are defined/implemented in `components/gpio_control/function_calls.py`: * **functionCallShutdown**: System shutdown -* **functionCallVolU**: Volupe up +* **functionCallVolU**: Volume up * **functionCallVolD**: Volume down * **functionCallVol0**: Mute * **functionCallPlayerNext**: Next track @@ -191,7 +234,11 @@ The available functions are defined/implemented in `components/gpio_control/func * **functionCallPlayerStop**: Stop Player * **functionCallPlayerSeekFwd**: Seek 10 seconds forward * **functionCallPlayerSeekBack**: Seek 10 seconds backward - +* **functionCallPlayerSeekFarFwd**: Seek 1 minute forward +* **functionCallPlayerSeekFarBack**: Seek 1 minute backward +* **functionCallPlayerRandomTrack**: Jumps to random track (within current playlist) +* **functionCallPlayerRandomCard**: Activate a random card +* **functionCallPlayerRandomFolder**: Play a random folder ## Troubleshooting If you encounter bouncing effects with your buttons like unrequested/double actions after releasing a button, you can try to set `antibouncehack` to True: diff --git a/components/gpio_control/function_calls.py b/components/gpio_control/function_calls.py index e1f1eb2c8..5f6deb1ff 100644 --- a/components/gpio_control/function_calls.py +++ b/components/gpio_control/function_calls.py @@ -69,6 +69,21 @@ def functionCallPlayerSeekFwd(self, *args): def functionCallPlayerSeekBack(self, *args): function_call("{command} -c=playerseek -v=-10".format(command=self.playout_control), shell=True) + def functionCallPlayerSeekFarFwd(self, *args): + function_call("{command} -c=playerseek -v=+60".format(command=self.playout_control), shell=True) + + def functionCallPlayerSeekFarBack(self, *args): + function_call("{command} -c=playerseek -v=-60".format(command=self.playout_control), shell=True) + + def functionCallPlayerRandomTrack(self, *args): + function_call("{command} -c=randomtrack".format(command=self.playout_control), shell=True) + + def functionCallPlayerRandomCard(self, *args): + function_call("{command} -c=randomcard".format(command=self.playout_control), shell=True) + + def functionCallPlayerRandomFolder(self, *args): + function_call("{command} -c=randomfolder".format(command=self.playout_control), shell=True) + def functionCallBluetoothToggle(self, *args): function_call("{command} -c=bluetoothtoggle -v=toggle".format(command=self.playout_control), shell=True) From 194ba0f8aa6277256e00ca1449de8ce9b0021053 Mon Sep 17 00:00:00 2001 From: T0bi79 Date: Fri, 14 May 2021 22:59:20 +0200 Subject: [PATCH 09/10] 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" --- .../gpio_control/GPIODevices/VolumeControl.py | 30 ------- components/gpio_control/README.md | 22 +++-- .../gpio_control/config_compatibility.py | 87 +++++++++++++++++++ components/gpio_control/gpio_control.py | 18 ++-- 4 files changed, 109 insertions(+), 48 deletions(-) delete mode 100644 components/gpio_control/GPIODevices/VolumeControl.py create mode 100644 components/gpio_control/config_compatibility.py diff --git a/components/gpio_control/GPIODevices/VolumeControl.py b/components/gpio_control/GPIODevices/VolumeControl.py deleted file mode 100644 index e27adc632..000000000 --- a/components/gpio_control/GPIODevices/VolumeControl.py +++ /dev/null @@ -1,30 +0,0 @@ -from GPIODevices import TwoButtonControl, RotaryEncoder -# from gpio_control import logger, getFunctionCall - - -class VolumeControl: - def __new__(self, config, getFunctionCall, logger): - if config.get('Type') == 'TwoButtonControl': - logger.info('VolumeControl as TwoButtonControl') - return TwoButtonControl( - config.getint('Pin1'), - config.getint('Pin2'), - getFunctionCall(config.get('functionCall1')), - getFunctionCall(config.get('functionCall2')), - functionCallTwoBtns=getFunctionCall(config.get('functionCallTwoButtons')), - pull_up_down=config.get('pull_up_down', fallback='pull_up'), - hold_mode=config.get('hold_mode', fallback='Repeat'), - hold_time=config.getfloat('hold_time', fallback=0.3), - bouncetime=config.getint('bouncetime', fallback=500), - edge=config.get('edge', fallback='falling'), - antibouncehack=config.getboolean('antibouncehack', fallback=False), - name='VolumeControl' - ) - elif config.get('Type') == 'RotaryEncoder': - return RotaryEncoder( - config.getint('pinUp'), - config.getint('pinDown'), - getFunctionCall(config.get('functionCallUp')), - getFunctionCall(config.get('functionCallDown')), - config.getfloat('timeBase', fallback=0.1), - name='RotaryVolumeControl') diff --git a/components/gpio_control/README.md b/components/gpio_control/README.md index 0bb29bcc7..9e4477cbe 100644 --- a/components/gpio_control/README.md +++ b/components/gpio_control/README.md @@ -137,13 +137,13 @@ enabled: True Type: TwoButtonControl Pin1: 5 Pin2: 6 -hold_time: 0.3 hold_mode: Repeat -functionCall1: functionCallVolD -functionCall2: functionCallVolU +hold_time: 0.3 +functionCall1: functionCallVolU +functionCall2: functionCallVolD functionCallTwoButtons: functionCallVol0 ``` -In this example, the volume will be in-/decreased step-wise using intervals of 0.3 seconds while the respective button is held. If both buttons are pushed simultaneously, the player is muted (volume 0). +In this example, the volume will be in-/decreased step-wise using intervals of 0.3 seconds while the respective button is held. If both buttons are pushed simultaneously, the player is muted (volume 0). In this example, Pin1 is used for increasing the volume, while Pin2 decreases it. Furthermore, the following settings can be used as described for the [regular buttons](#doc_button): **pull_up_down**, **edge**, **bouncetime**, **antibouncehack** @@ -151,20 +151,24 @@ Furthermore, the following settings can be used as described for the [regular bu ## RotaryEncoder A RotaryEncoder can be created using an `ini` entry like this: ``` +[VolumeControl] enabled: True Type: RotaryEncoder -PinUp: 7 -PinDown: 8 +Pin1: 7 +Pin2: 8 timeBase: 0.02 -functionCallDown: functionCallVolD -functionCallUp: functionCallVolU +functionCall1: functionCallVolU +functionCall2: functionCallVolD ``` +Pin1 and FunctionCall1 correspond to rotary direction "up", while Pin2 and FunctionCall2 correspond to "down". +Note that the old configuration entries PinUp/PinDown and functionCallUp/functionCallDown are deprecated and might stop working in future. + ## StatusLED A StatusLED can be created using an `ini` entry like this: ``` [StatusLED] -enable: True +enabled: True Type: StatusLED Pin: 14 ``` diff --git a/components/gpio_control/config_compatibility.py b/components/gpio_control/config_compatibility.py new file mode 100644 index 000000000..62922c299 --- /dev/null +++ b/components/gpio_control/config_compatibility.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +import configparser +import os +from shutil import copyfile + +def Ini_CheckAndUpgrade(config): + has_changed = False + for section in config.sections(): + # enable: True --> enabled: True + # enable: False --> enabled: False + if config.has_option(section, 'enable'): + v = config.getboolean(section, 'enable', fallback=False) + config.remove_option(section, 'enable') + has_changed = True + if not config.has_option(section, 'enabled'): + config.set(section, 'enabled', 'True' if v else 'False') + # pull_up: True --> pull_up_down: pull_up + # pull_up: False --> pull_up_down: pull_off + if config.has_option(section, 'pull_up'): + v = config.getboolean(section, 'pull_up', fallback=True) + config.remove_option(section, 'pull_up') + has_changed = True + if not config.has_option(section, 'pull_up_down'): + config.set(section, 'pull_up_down', 'pull_up' if v else 'pull_off') + # hold_repeat: True --> hold_mode: Repeat + # hold_repeat: False --> hold_mode: None + if config.has_option(section, 'hold_repeat'): + v = config.getboolean(section, 'hold_repeat', fallback=False) + config.remove_option(section, 'hold_repeat') + has_changed = True + if not config.has_option(section, 'hold_mode'): + config.set(section, 'hold_mode', 'Repeat' if v else 'None') + # time_pressed --> hold_time + if config.has_option(section, 'time_pressed'): + v = config.getfloat(section, 'time_pressed') + config.remove_option(section, 'time_pressed') + has_changed = True + if not config.has_option(section, 'hold_time'): + config.set(section, 'hold_time', str(v)) + #PinUp: --> Pin1 + #PinDown: --> Pin2 + if config.has_option(section, 'PinUp'): + v = config.getint(section, 'PinUp') + config.remove_option(section, 'PinUp') + has_changed = True + if not config.has_option(section, 'Pin1'): + config.set(section, 'Pin1', str(v)) + if config.has_option(section, 'PinDown'): + v = config.getint(section, 'PinDown') + config.remove_option(section, 'PinDown') + has_changed = True + if not config.has_option(section, 'Pin2'): + config.set(section, 'Pin2', str(v)) + # functionCallUp --> functionCall1 + # functionCallDown --> functionCall2 + if config.has_option(section, 'functionCallUp'): + v = config.get(section, 'functionCallUp') + config.remove_option(section, 'functionCallUp') + has_changed = True + if not config.has_option(section, 'functionCall1'): + config.set(section, 'functionCall1', v) + if config.has_option(section, 'functionCallDown'): + v = config.get(section, 'functionCallDown') + config.remove_option(section, 'functionCallDown') + has_changed = True + if not config.has_option(section, 'functionCall2'): + config.set(section, 'functionCall2', v) + + return has_changed + + +def ConfigCompatibilityChecks(config, config_path): + # Check for deprecated settings in gpio_settings.ini + if not Ini_CheckAndUpgrade(config): + return + + # If we reach here, gpio_settings.ini needed some patching... + + # Try creating a backup of the previous ini file + backup_path = config_path+'.bak' + if os.path.isfile(backup_path): + return + copyfile(config_path, backup_path) + + # Save fixed gpio_settings.ini + with open(config_path, 'w') as inifile: + config.write(inifile) diff --git a/components/gpio_control/gpio_control.py b/components/gpio_control/gpio_control.py index 23c7748ec..44ad68df1 100755 --- a/components/gpio_control/gpio_control.py +++ b/components/gpio_control/gpio_control.py @@ -7,7 +7,7 @@ import function_calls from signal import pause from RPi import GPIO -# from GPIODevices.VolumeControl import VolumeControl +from config_compatibility import ConfigCompatibilityChecks class gpio_control(): @@ -34,9 +34,7 @@ def getFunctionCall(self, function_name): def generate_device(self, config, deviceName): print(deviceName) device_type = config.get('Type') - if deviceName.lower() == 'VolumeControl'.lower(): - return VolumeControl(config, self.getFunctionCall, logger) - elif device_type == 'TwoButtonControl': + if device_type == 'TwoButtonControl': self.logger.info('adding TwoButtonControl') return TwoButtonControl( config.getint('Pin1'), @@ -69,10 +67,10 @@ def generate_device(self, config, deviceName): elif device_type in ('StatusLED', 'MPDStatusLED'): return StatusLED(config.getint('Pin'), name=deviceName) elif device_type == 'RotaryEncoder': - return RotaryEncoder(config.getint('pinUp'), - config.getint('pinDown'), - self.getFunctionCall(config.get('functionCallUp')), - self.getFunctionCall(config.get('functionCallDown')), + return RotaryEncoder(config.getint('Pin1'), + config.getint('Pin2'), + self.getFunctionCall(config.get('functionCall1')), + self.getFunctionCall(config.get('functionCall2')), config.getfloat('timeBase', fallback=0.1), name=deviceName) elif device_type == 'ShutdownButton': @@ -117,9 +115,11 @@ def gpio_loop(self): if __name__ == "__main__": - config = configparser.ConfigParser(inline_comment_prefixes=";") + config = configparser.ConfigParser(inline_comment_prefixes=";", delimiters=(':', '=')) config_path = os.path.expanduser('/home/pi/RPi-Jukebox-RFID/settings/gpio_settings.ini') config.read(config_path) + + ConfigCompatibilityChecks(config, config_path) phoniebox_function_calls = function_calls.phoniebox_function_calls() gpio_controler = gpio_control(phoniebox_function_calls) From 2ff84131c6102f0e5b5ac877b83434050c3f0e20 Mon Sep 17 00:00:00 2001 From: T0bi79 Date: Fri, 14 May 2021 23:03:54 +0200 Subject: [PATCH 10/10] Added the __init__.py I just forgot... --- components/gpio_control/GPIODevices/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/components/gpio_control/GPIODevices/__init__.py b/components/gpio_control/GPIODevices/__init__.py index 445ac80c7..5eaad3009 100644 --- a/components/gpio_control/GPIODevices/__init__.py +++ b/components/gpio_control/GPIODevices/__init__.py @@ -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 *