From e5a18757baa8d68d3cfbedd8c8ae637ebe944aea Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 25 Mar 2020 10:47:49 +0000 Subject: [PATCH 1/6] Add support for additional per pin event options --- mopidy_raspberry_gpio/pinconfig.py | 19 ++++++++++++++----- tests/test_config.py | 18 ++++++++++++++++++ 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/mopidy_raspberry_gpio/pinconfig.py b/mopidy_raspberry_gpio/pinconfig.py index f6c7270..112e50b 100644 --- a/mopidy_raspberry_gpio/pinconfig.py +++ b/mopidy_raspberry_gpio/pinconfig.py @@ -12,7 +12,7 @@ class ValidList(list): class PinConfig(config.ConfigValue): - tuple_pinconfig = namedtuple("PinConfig", ("event", "active", "bouncetime")) + tuple_pinconfig = namedtuple("PinConfig", ("event", "active", "bouncetime", "options")) valid_events = ValidList( ["play_pause", "prev", "next", "volume_up", "volume_down"] @@ -29,11 +29,13 @@ class PinConfig(config.ConfigValue): value = types.decode(value).strip() - try: - event, active, bouncetime = value.split(",") - except ValueError: + value = value.split(",") + + if len(value) < 3: # At least Event, Active and Bouncetime settings required return None + event, active, bouncetime = value[0:3] + if event not in self.valid_events: raise ValueError( f"invalid event for pin config {event} (Must be {self.valid_events})" @@ -51,10 +53,17 @@ class PinConfig(config.ConfigValue): f"invalid bouncetime value for pin config {bouncetime}" ) - return self.tuple_pinconfig(event, active, bouncetime) + options = {} + + for option in value[3:]: + key, value = option.split("=") + options[key] = value + + return self.tuple_pinconfig(event, active, bouncetime, options) def serialize(self, value, display=False): if value is None: return "" + options = ",".join({f'{k}={v}' for k, v in value.options.items()}) value = f"{value.event},{value.active},{value.bouncetime}" return types.encode(value) diff --git a/tests/test_config.py b/tests/test_config.py index 0ca5765..0eb966d 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -59,3 +59,21 @@ def test_pinconfig_invalid_bouncetime_raises_valueerror(): with pytest.raises(ValueError): bcm1 = schema["bcm1"].deserialize("play_pause,active_low,tomato") del bcm1 + + +def test_pinconfig_additional_options(): + ext = Extension() + + schema = ext.get_config_schema() + + bcm1 = schema["bcm1"].deserialize("volume_up,active_low,30,steps=1") + del bcm1 + + +def test_pinconfig_serialize(): + ext = Extension() + + schema = ext.get_config_schema() + + bcm1 = schema["bcm1"].deserialize("volume_up,active_low,30,steps=1") + assert bcm1.serialize() == "volume_up,active_low,30,steps=1" From 9499f2e6eed55db6e054dfb46236c09b999e48e8 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 25 Mar 2020 11:08:10 +0000 Subject: [PATCH 2/6] Correct linting errors and tests --- mopidy_raspberry_gpio/pinconfig.py | 11 +++++++---- tests/test_config.py | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/mopidy_raspberry_gpio/pinconfig.py b/mopidy_raspberry_gpio/pinconfig.py index 112e50b..e55a152 100644 --- a/mopidy_raspberry_gpio/pinconfig.py +++ b/mopidy_raspberry_gpio/pinconfig.py @@ -12,7 +12,9 @@ class ValidList(list): class PinConfig(config.ConfigValue): - tuple_pinconfig = namedtuple("PinConfig", ("event", "active", "bouncetime", "options")) + tuple_pinconfig = namedtuple( + "PinConfig", ("event", "active", "bouncetime", "options") + ) valid_events = ValidList( ["play_pause", "prev", "next", "volume_up", "volume_down"] @@ -31,7 +33,8 @@ class PinConfig(config.ConfigValue): value = value.split(",") - if len(value) < 3: # At least Event, Active and Bouncetime settings required + # At least Event, Active and Bouncetime settings required + if len(value) < 3: return None event, active, bouncetime = value[0:3] @@ -64,6 +67,6 @@ class PinConfig(config.ConfigValue): def serialize(self, value, display=False): if value is None: return "" - options = ",".join({f'{k}={v}' for k, v in value.options.items()}) - value = f"{value.event},{value.active},{value.bouncetime}" + options = ",".join({f"{k}={v}" for k, v in value.options.items()}) + value = f"{value.event},{value.active},{value.bouncetime},{options}" return types.encode(value) diff --git a/tests/test_config.py b/tests/test_config.py index 0eb966d..5a61ee3 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -76,4 +76,4 @@ def test_pinconfig_serialize(): schema = ext.get_config_schema() bcm1 = schema["bcm1"].deserialize("volume_up,active_low,30,steps=1") - assert bcm1.serialize() == "volume_up,active_low,30,steps=1" + assert schema["bcm1"].serialize(bcm1) == "volume_up,active_low,30,steps=1" From ecd9b14a243249706fcb4e5ab84714ce892d201f Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 25 Mar 2020 11:42:57 +0000 Subject: [PATCH 3/6] Pass options into event handlers --- mopidy_raspberry_gpio/frontend.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/mopidy_raspberry_gpio/frontend.py b/mopidy_raspberry_gpio/frontend.py index 1bbdf42..ed81d97 100644 --- a/mopidy_raspberry_gpio/frontend.py +++ b/mopidy_raspberry_gpio/frontend.py @@ -46,37 +46,39 @@ class RaspberryGPIOFrontend(pykka.ThreadingActor, core.CoreListener): def gpio_event(self, pin): settings = self.pin_settings[pin] - self.dispatch_input(settings.event) + self.dispatch_input(settings) - def dispatch_input(self, event): - handler_name = f"handle_{event}" + def dispatch_input(self, settings): + handler_name = f"handle_{settings.event}" try: - getattr(self, handler_name)() + getattr(self, handler_name)(settings.config) except AttributeError: raise RuntimeError( - f"Could not find input handler for event: {event}" + f"Could not find input handler for event: {settings.event}" ) - def handle_play_pause(self): + def handle_play_pause(self, config): if self.core.playback.get_state().get() == core.PlaybackState.PLAYING: self.core.playback.pause() else: self.core.playback.play() - def handle_next(self): + def handle_next(self, config): self.core.playback.next() - def handle_prev(self): + def handle_prev(self, config): self.core.playback.previous() - def handle_volume_up(self): + def handle_volume_up(self, config): + step = int(config.get("step", 5)) volume = self.core.mixer.get_volume().get() - volume += 5 + volume += step volume = min(volume, 100) self.core.mixer.set_volume(volume) - def handle_volume_down(self): + def handle_volume_down(self, config): + step = int(config.get("step", 5)) volume = self.core.mixer.get_volume().get() - volume -= 5 + volume -= step volume = max(volume, 0) self.core.mixer.set_volume(volume) From 4cbbe2dbd1c75a24fcdaa7501745a6c6c4a65cb3 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 25 Mar 2020 12:16:18 +0000 Subject: [PATCH 4/6] Update handler tests to pass settings --- tests/test_frontend.py | 57 +++++++++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/tests/test_frontend.py b/tests/test_frontend.py index 393c69e..56e2455 100644 --- a/tests/test_frontend.py +++ b/tests/test_frontend.py @@ -4,7 +4,6 @@ from unittest import mock import pykka from mopidy import core -import pytest from mopidy_raspberry_gpio import Extension from mopidy_raspberry_gpio import frontend as frontend_lib from mopidy_raspberry_gpio import pinconfig @@ -58,7 +57,11 @@ def test_frontend_handler_dispatch_play_pause(): dummy_config, dummy_mopidy_core() ) - frontend.dispatch_input("play_pause") + ext = Extension() + schema = ext.get_config_schema() + settings = schema["bcm1"].deserialize("play_pause,active_low,30") + + frontend.dispatch_input(settings) stop_mopidy_core() @@ -71,7 +74,11 @@ def test_frontend_handler_dispatch_next(): dummy_config, dummy_mopidy_core() ) - frontend.dispatch_input("next") + ext = Extension() + schema = ext.get_config_schema() + settings = schema["bcm1"].deserialize("next,active_low,30") + + frontend.dispatch_input(settings) stop_mopidy_core() @@ -84,7 +91,11 @@ def test_frontend_handler_dispatch_prev(): dummy_config, dummy_mopidy_core() ) - frontend.dispatch_input("prev") + ext = Extension() + schema = ext.get_config_schema() + settings = schema["bcm1"].deserialize("prev,active_low,30") + + frontend.dispatch_input(settings) stop_mopidy_core() @@ -97,7 +108,11 @@ def test_frontend_handler_dispatch_volume_up(): dummy_config, dummy_mopidy_core() ) - frontend.dispatch_input("volume_up") + ext = Extension() + schema = ext.get_config_schema() + settings = schema["bcm1"].deserialize("volume_up,active_low,30") + + frontend.dispatch_input(settings) stop_mopidy_core() @@ -110,12 +125,16 @@ def test_frontend_handler_dispatch_volume_down(): dummy_config, dummy_mopidy_core() ) - frontend.dispatch_input("volume_down") + ext = Extension() + schema = ext.get_config_schema() + settings = schema["bcm1"].deserialize("volume_down,active_low,30") + + frontend.dispatch_input(settings) stop_mopidy_core() -def test_frontend_handler_dispatch_invalid_event(): +def test_frontend_handler_dispatch_volume_up_custom_step(): sys.modules["RPi"] = mock.Mock() sys.modules["RPi.GPIO"] = mock.Mock() @@ -123,8 +142,28 @@ def test_frontend_handler_dispatch_invalid_event(): dummy_config, dummy_mopidy_core() ) - with pytest.raises(RuntimeError): - frontend.dispatch_input("tomato") + ext = Extension() + schema = ext.get_config_schema() + settings = schema["bcm1"].deserialize("volume_up,active_low,30,step=1") + + frontend.dispatch_input(settings) + + stop_mopidy_core() + + +def test_frontend_handler_dispatch_volume_down_custom_step(): + sys.modules["RPi"] = mock.Mock() + sys.modules["RPi.GPIO"] = mock.Mock() + + frontend = frontend_lib.RaspberryGPIOFrontend( + dummy_config, dummy_mopidy_core() + ) + + ext = Extension() + schema = ext.get_config_schema() + settings = schema["bcm1"].deserialize("volume_down,active_low,30,step=1") + + frontend.dispatch_input(settings) stop_mopidy_core() From 279ddb4f39e38d776f4ec8ffa8a394328bd569de Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 25 Mar 2020 12:31:22 +0000 Subject: [PATCH 5/6] Change config to options --- mopidy_raspberry_gpio/frontend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mopidy_raspberry_gpio/frontend.py b/mopidy_raspberry_gpio/frontend.py index ed81d97..097d5e2 100644 --- a/mopidy_raspberry_gpio/frontend.py +++ b/mopidy_raspberry_gpio/frontend.py @@ -51,7 +51,7 @@ class RaspberryGPIOFrontend(pykka.ThreadingActor, core.CoreListener): def dispatch_input(self, settings): handler_name = f"handle_{settings.event}" try: - getattr(self, handler_name)(settings.config) + getattr(self, handler_name)(settings.options) except AttributeError: raise RuntimeError( f"Could not find input handler for event: {settings.event}" From f44bbf59ab995cc141bad6a36e41e325a39028b2 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 25 Mar 2020 13:40:15 +0000 Subject: [PATCH 6/6] Document new step option --- README.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.rst b/README.rst index a8bb1ab..a63220e 100644 --- a/README.rst +++ b/README.rst @@ -58,6 +58,17 @@ Supported modes: - active_low - configures the pin with a pull-up and triggers when it reads 0/low (RECOMMENDED) - active_high - configures the pin as a pull-down and triggers when it reads 1/high +Events volume_up and volume_down both support an (optional) "step" option, which controls the amount (in percent) that the volume is adjusted with each button press. + +Eg:: + + [raspberry-gpio] + enabled = true + bcm5 = play_pause,active_low,250 + bcm6 = volume_down,active_low,250,step=1 + bcm16 = next,active_low,250 + bcm20 = volume_up,active_low,250,step=1 + Project resources =================